@luoyi-dsh/instagram-scroll 1.0.0 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +202 -158
- package/README.zh-Hant.md +204 -0
- package/client.js +282 -40
- package/cordis.patch.yml +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +120 -8
- package/dist/config.js.map +1 -1
- package/dist/core/scrollDuty.d.ts +30 -1
- package/dist/core/scrollDuty.d.ts.map +1 -1
- package/dist/core/scrollDuty.js +353 -63
- package/dist/core/scrollDuty.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -9
- package/dist/index.js.map +1 -1
- package/dist/lib/ledger.d.ts +7 -0
- package/dist/lib/ledger.d.ts.map +1 -1
- package/dist/lib/ledger.js +29 -6
- package/dist/lib/ledger.js.map +1 -1
- package/package.json +2 -1
package/client.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
window.__ModuleLoader__.load({
|
|
16
|
-
id: 'dsh
|
|
16
|
+
id: '@luoyi-dsh/instagram-scroll',
|
|
17
17
|
factory: (require) => {
|
|
18
18
|
const React = require('react')
|
|
19
19
|
const h = React.createElement
|
|
@@ -542,13 +542,33 @@ window.__ModuleLoader__.load({
|
|
|
542
542
|
return {
|
|
543
543
|
masterUsername: cleanStr(c.masterUsername, ''),
|
|
544
544
|
interestTags: cleanArr(c.interestTags, ['ai', 'deepseek', 'coding', 'tech', 'gadgets']),
|
|
545
|
-
|
|
546
|
-
|
|
545
|
+
userInterests: cleanStr(
|
|
546
|
+
c.userInterests,
|
|
547
|
+
'專注於前沿 AI 技術 (LLM, Agent, 開源模型)、全端與前端架構、優質開源專案、高效率開發工具、優質 UI/UX 設計或深度乾貨技術解析。偏好乾貨分享、實踐案例與原創見解,不喜歡空洞無物、純蹭熱度、打廣告或純娛樂搞笑的內容。'
|
|
548
|
+
),
|
|
549
|
+
positiveKeywords: cleanArr(c.positiveKeywords, [
|
|
550
|
+
'ai', 'deepseek', 'agent', 'coding', '技術', '前端', '後端', '架構', '開源', '教學', '程式', '開發', '模型', 'github', 'prompt', '演算法', '工具', '實戰', '乾貨',
|
|
551
|
+
]),
|
|
552
|
+
negativeKeywords: cleanArr(c.negativeKeywords, [
|
|
553
|
+
'互粉', '代購', '博彩', '運彩', '兼職', '抽獎', '免費領', '私訊', '點擊連結', '主頁連結', '減肥', '微商', '外匯', '虛擬幣', '心靈雞湯', '穿搭', '搞笑', '自拍', '八卦', '暴富', '躺平', '加盟',
|
|
554
|
+
]),
|
|
555
|
+
minCaptionLength: cleanNum(c.minCaptionLength, 30),
|
|
556
|
+
requireBodyMatch: cleanBool(c.requireBodyMatch, true),
|
|
557
|
+
strictContentCheck: cleanBool(c.strictContentCheck, true),
|
|
558
|
+
personaPrompt: cleanStr(
|
|
559
|
+
c.personaPrompt,
|
|
560
|
+
'你是一位洞察力敏銳、品味高雅的社群特助。請以極高標準評估這篇 Instagram 貼文是否深度符合主人的興趣偏好。若是具備前沿乾貨、創意見解或高實用價值的精選內容,請給出高分評價並撰寫簡短自然、熱情切題的推坑一句話。'
|
|
561
|
+
),
|
|
562
|
+
minScore: cleanNum(c.minScore, 80),
|
|
547
563
|
dailyRecommendCap: cleanNum(c.dailyRecommendCap, 5),
|
|
548
564
|
scrollIntervalMinutes: cleanNum(c.scrollIntervalMinutes, 60),
|
|
549
565
|
autoScrollEnabled: cleanBool(c.autoScrollEnabled, true),
|
|
550
566
|
likeOnRecommend: cleanBool(c.likeOnRecommend, true),
|
|
551
567
|
batchSize: cleanNum(c.batchSize, 15),
|
|
568
|
+
maxRecommendsPerRun: cleanNum(c.maxRecommendsPerRun, 1),
|
|
569
|
+
minPostReadDelaySeconds: cleanNum(c.minPostReadDelaySeconds, 3),
|
|
570
|
+
maxPostReadDelaySeconds: cleanNum(c.maxPostReadDelaySeconds, 7),
|
|
571
|
+
cooldownOnBlockMinutes: cleanNum(c.cooldownOnBlockMinutes, 30),
|
|
552
572
|
}
|
|
553
573
|
}
|
|
554
574
|
|
|
@@ -556,22 +576,47 @@ window.__ModuleLoader__.load({
|
|
|
556
576
|
const [tab, setTab] = useState('feed')
|
|
557
577
|
const [cfg, setCfg] = useState(normalizeScrollConfig({}))
|
|
558
578
|
const [status, setStatus] = useState(null)
|
|
579
|
+
const [mcpStatus, setMcpStatus] = useState(null)
|
|
559
580
|
const [report, setReport] = useState('')
|
|
560
581
|
const [loading, setLoading] = useState(true)
|
|
561
582
|
const [saving, setSaving] = useState(false)
|
|
562
583
|
const [runningRound, setRunningRound] = useState(false)
|
|
563
584
|
const [msg, setMsg] = useState(null)
|
|
564
585
|
|
|
586
|
+
// 獨立字串狀態:解決 input 鍵入逗號與空格被 React 重新格式化吃掉的問題
|
|
587
|
+
const [tagsInputText, setTagsInputText] = useState('')
|
|
588
|
+
const [positiveWordsText, setPositiveWordsText] = useState('')
|
|
589
|
+
const [negativeWordsText, setNegativeWordsText] = useState('')
|
|
590
|
+
|
|
591
|
+
const parseListFromText = (text, isTag = false) => {
|
|
592
|
+
if (typeof text !== 'string') return []
|
|
593
|
+
return text
|
|
594
|
+
.split(/[,,\n]+/)
|
|
595
|
+
.map((s) => s.trim())
|
|
596
|
+
.map((s) => (isTag ? s.replace(/^#/, '') : s))
|
|
597
|
+
.filter(Boolean)
|
|
598
|
+
}
|
|
599
|
+
|
|
565
600
|
const refresh = async () => {
|
|
566
601
|
try {
|
|
567
|
-
const [cfgRes, statusRes, reportRes] = await Promise.all([
|
|
568
|
-
jget('/config'),
|
|
569
|
-
jget('/status'),
|
|
570
|
-
jget('/report'),
|
|
602
|
+
const [cfgRes, statusRes, reportRes, mcpRes] = await Promise.all([
|
|
603
|
+
jget('/config').catch(() => null),
|
|
604
|
+
jget('/status').catch(() => null),
|
|
605
|
+
jget('/report').catch(() => null),
|
|
606
|
+
fetch('/api/instagram/mcp/status', { credentials: 'same-origin' })
|
|
607
|
+
.then((r) => r.json())
|
|
608
|
+
.catch(() => null),
|
|
571
609
|
])
|
|
572
|
-
if (cfgRes?.ok)
|
|
610
|
+
if (cfgRes?.ok) {
|
|
611
|
+
const loaded = normalizeScrollConfig(cfgRes.config)
|
|
612
|
+
setCfg(loaded)
|
|
613
|
+
setTagsInputText(loaded.interestTags.join(', '))
|
|
614
|
+
setPositiveWordsText(loaded.positiveKeywords.join(', '))
|
|
615
|
+
setNegativeWordsText(loaded.negativeKeywords.join(', '))
|
|
616
|
+
}
|
|
573
617
|
if (statusRes?.ok) setStatus(statusRes)
|
|
574
618
|
if (reportRes?.ok) setReport(reportRes.report || '')
|
|
619
|
+
if (mcpRes?.ok) setMcpStatus(mcpRes)
|
|
575
620
|
} catch (e) {
|
|
576
621
|
setMsg(`讀取狀態失敗: ${e.message}`)
|
|
577
622
|
} finally {
|
|
@@ -589,10 +634,19 @@ window.__ModuleLoader__.load({
|
|
|
589
634
|
setSaving(true)
|
|
590
635
|
setMsg(null)
|
|
591
636
|
try {
|
|
592
|
-
const cleanPayload =
|
|
637
|
+
const cleanPayload = {
|
|
638
|
+
...normalizeScrollConfig(cfg),
|
|
639
|
+
interestTags: parseListFromText(tagsInputText, true),
|
|
640
|
+
positiveKeywords: parseListFromText(positiveWordsText, false),
|
|
641
|
+
negativeKeywords: parseListFromText(negativeWordsText, false),
|
|
642
|
+
}
|
|
593
643
|
const res = await jpost('/config', cleanPayload)
|
|
594
644
|
if (res?.ok) {
|
|
595
|
-
|
|
645
|
+
const saved = normalizeScrollConfig(res.config)
|
|
646
|
+
setCfg(saved)
|
|
647
|
+
setTagsInputText(saved.interestTags.join(', '))
|
|
648
|
+
setPositiveWordsText(saved.positiveKeywords.join(', '))
|
|
649
|
+
setNegativeWordsText(saved.negativeKeywords.join(', '))
|
|
596
650
|
setMsg('✅ 滾動推坑設定已成功保存!')
|
|
597
651
|
}
|
|
598
652
|
} catch (e) {
|
|
@@ -625,6 +679,22 @@ window.__ModuleLoader__.load({
|
|
|
625
679
|
const daily = status?.dailyStats || {}
|
|
626
680
|
const recommendedItems = daily.recommendedItems || []
|
|
627
681
|
|
|
682
|
+
const isMcpConnected = Boolean(
|
|
683
|
+
status?.mcpReady ||
|
|
684
|
+
status?.loginStatus?.ok ||
|
|
685
|
+
mcpStatus?.mcpReady ||
|
|
686
|
+
mcpStatus?.loginStatus?.ok
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
const rawAccount =
|
|
690
|
+
status?.mcpAccount ||
|
|
691
|
+
status?.loginStatus?.username ||
|
|
692
|
+
mcpStatus?.loginStatus?.username ||
|
|
693
|
+
(mcpStatus?.loginStatus?.message?.match(/@([^\s)]+)/)?.[1]) ||
|
|
694
|
+
(isMcpConnected ? '已連線' : '')
|
|
695
|
+
|
|
696
|
+
const displayAccount = rawAccount ? rawAccount.replace(/^@/, '') : ''
|
|
697
|
+
|
|
628
698
|
return h(
|
|
629
699
|
'div',
|
|
630
700
|
{ className: 'ig-scr-root' },
|
|
@@ -643,8 +713,22 @@ window.__ModuleLoader__.load({
|
|
|
643
713
|
h('div', { className: 'ig-scr-brand-title' }, 'Instagram 滾動探索與主人推坑'),
|
|
644
714
|
h(
|
|
645
715
|
'div',
|
|
646
|
-
{ className: 'ig-scr-brand-sub' },
|
|
647
|
-
cfg.masterUsername ?
|
|
716
|
+
{ className: 'ig-scr-brand-sub', style: { display: 'flex', alignItems: 'center', gap: '8px' } },
|
|
717
|
+
cfg.masterUsername ? `目標主人: @${cfg.masterUsername.replace(/^@/, '')}` : '尚未綁定主人帳號',
|
|
718
|
+
h('span', { style: { color: 'rgba(255, 255, 255, 0.2)' } }, '|'),
|
|
719
|
+
isMcpConnected
|
|
720
|
+
? h(
|
|
721
|
+
'span',
|
|
722
|
+
{ style: { color: '#34d399', fontSize: '11px', display: 'inline-flex', alignItems: 'center', gap: '4px' } },
|
|
723
|
+
h('span', { style: { width: '6px', height: '6px', borderRadius: '50%', background: '#10b981' } }),
|
|
724
|
+
`MCP 會話: @${displayAccount || '已連線'}`
|
|
725
|
+
)
|
|
726
|
+
: h(
|
|
727
|
+
'span',
|
|
728
|
+
{ style: { color: '#fbbf24', fontSize: '11px', display: 'inline-flex', alignItems: 'center', gap: '4px' } },
|
|
729
|
+
h('span', { style: { width: '6px', height: '6px', borderRadius: '50%', background: '#f59e0b' } }),
|
|
730
|
+
'等待 MCP 登入'
|
|
731
|
+
)
|
|
648
732
|
)
|
|
649
733
|
)
|
|
650
734
|
),
|
|
@@ -652,7 +736,8 @@ window.__ModuleLoader__.load({
|
|
|
652
736
|
'button',
|
|
653
737
|
{
|
|
654
738
|
className: 'ig-scr-btn primary',
|
|
655
|
-
disabled: runningRound,
|
|
739
|
+
disabled: runningRound || !isMcpConnected,
|
|
740
|
+
title: !isMcpConnected ? '等待 FastMCP 連線就緒後即可刷動態' : '立即替主人刷一輪動態',
|
|
656
741
|
onClick: runNow,
|
|
657
742
|
},
|
|
658
743
|
h(Icon.Play, { size: 12 }),
|
|
@@ -820,6 +905,32 @@ window.__ModuleLoader__.load({
|
|
|
820
905
|
h('div', { className: 'ig-scr-section-desc' }, '設定主人的 Instagram 帳號名與感興趣的標籤領域,AI 將依此進行嚴格內容篩選。')
|
|
821
906
|
),
|
|
822
907
|
|
|
908
|
+
// MCP Session 來源提示
|
|
909
|
+
h(
|
|
910
|
+
'div',
|
|
911
|
+
{
|
|
912
|
+
style: {
|
|
913
|
+
background: 'rgba(59, 130, 246, 0.06)',
|
|
914
|
+
border: '1px solid rgba(59, 130, 246, 0.2)',
|
|
915
|
+
borderRadius: '8px',
|
|
916
|
+
padding: '10px 14px',
|
|
917
|
+
fontSize: '12px',
|
|
918
|
+
color: '#93c5fd',
|
|
919
|
+
display: 'flex',
|
|
920
|
+
alignItems: 'center',
|
|
921
|
+
gap: '8px',
|
|
922
|
+
},
|
|
923
|
+
},
|
|
924
|
+
h(Icon.Sparkles, { size: 14 }),
|
|
925
|
+
h(
|
|
926
|
+
'span',
|
|
927
|
+
null,
|
|
928
|
+
isMcpConnected
|
|
929
|
+
? `已沿用 @luoyi-dsh/instagram-mcp 的登入會話 (@${displayAccount || '已授權'}),本外掛無需填寫帳密。`
|
|
930
|
+
: '本外掛無需輸入帳密,直接沿用依賴的 dsh-instagram-mcp 獲取之 Session。目前 MCP 尚未登入,請先至 MCP 面板完成登入。'
|
|
931
|
+
)
|
|
932
|
+
),
|
|
933
|
+
|
|
823
934
|
h(
|
|
824
935
|
'div',
|
|
825
936
|
{ className: 'ig-scr-field' },
|
|
@@ -833,21 +944,128 @@ window.__ModuleLoader__.load({
|
|
|
833
944
|
h('div', { className: 'ig-scr-hint' }, '發現值得推薦的貼文/影片時,會在留言區 @ 此帳號進行推薦')
|
|
834
945
|
),
|
|
835
946
|
|
|
947
|
+
// 正文實質內容審查開關
|
|
948
|
+
h(
|
|
949
|
+
'div',
|
|
950
|
+
{ className: 'ig-scr-switch' },
|
|
951
|
+
h(
|
|
952
|
+
'div',
|
|
953
|
+
{ className: 'ig-scr-switch-text' },
|
|
954
|
+
h('div', { className: 'ig-scr-switch-name' }, '🛡️ 啟用 @ 前正文內容深度審查'),
|
|
955
|
+
h('div', { className: 'ig-scr-hint' }, '拒絕隨便推薦!留言 @ 主人前深度檢查正文實質乾貨與品質,剔除純圖廢文與空洞標籤')
|
|
956
|
+
),
|
|
957
|
+
h(
|
|
958
|
+
'button',
|
|
959
|
+
{
|
|
960
|
+
className: `ig-scr-toggle ${cfg.strictContentCheck ? 'on' : ''}`,
|
|
961
|
+
onClick: () => updateField('strictContentCheck', !cfg.strictContentCheck),
|
|
962
|
+
},
|
|
963
|
+
h('span', { className: 'ig-scr-toggle-thumb' })
|
|
964
|
+
)
|
|
965
|
+
),
|
|
966
|
+
|
|
967
|
+
// 正文實質命中開關
|
|
968
|
+
h(
|
|
969
|
+
'div',
|
|
970
|
+
{ className: 'ig-scr-switch' },
|
|
971
|
+
h(
|
|
972
|
+
'div',
|
|
973
|
+
{ className: 'ig-scr-switch-text' },
|
|
974
|
+
h('div', { className: 'ig-scr-switch-name' }, '🔍 正文必須實質討論偏好主題 (拒絕僅在 tag 蹭熱度)'),
|
|
975
|
+
h('div', { className: 'ig-scr-hint' }, '關鍵詞必須在正文中真正出現;若僅在文末堆砌 #標籤 則視為標籤農場直接過濾')
|
|
976
|
+
),
|
|
977
|
+
h(
|
|
978
|
+
'button',
|
|
979
|
+
{
|
|
980
|
+
className: `ig-scr-toggle ${cfg.requireBodyMatch ? 'on' : ''}`,
|
|
981
|
+
onClick: () => updateField('requireBodyMatch', !cfg.requireBodyMatch),
|
|
982
|
+
},
|
|
983
|
+
h('span', { className: 'ig-scr-toggle-thumb' })
|
|
984
|
+
)
|
|
985
|
+
),
|
|
986
|
+
|
|
987
|
+
// 主人喜歡的內容風格與偏好詳述
|
|
988
|
+
h(
|
|
989
|
+
'div',
|
|
990
|
+
{ className: 'ig-scr-field' },
|
|
991
|
+
h('label', { className: 'ig-scr-label' }, '💎 主人喜歡的內容風格與領域詳述 (User Interests)'),
|
|
992
|
+
h('textarea', {
|
|
993
|
+
className: 'ig-scr-textarea',
|
|
994
|
+
style: { minHeight: '75px' },
|
|
995
|
+
value: cleanStr(cfg.userInterests),
|
|
996
|
+
placeholder: '詳細描述您喜歡的貼文類型、技術領域、內容風格與乾貨要求...',
|
|
997
|
+
onChange: (e) => updateField('userInterests', e.target.value),
|
|
998
|
+
}),
|
|
999
|
+
h('div', { className: 'ig-scr-hint' }, 'AI 將依據此描述深度比對正文語義,確保推薦完全符合您的個人品味與需求')
|
|
1000
|
+
),
|
|
1001
|
+
|
|
1002
|
+
// 正向關鍵字
|
|
836
1003
|
h(
|
|
837
1004
|
'div',
|
|
838
1005
|
{ className: 'ig-scr-field' },
|
|
839
|
-
h('label', { className: 'ig-scr-label' }, '
|
|
1006
|
+
h('label', { className: 'ig-scr-label' }, '✨ 正文正向領域關鍵字 (Positive Keywords)'),
|
|
840
1007
|
h('input', {
|
|
841
1008
|
className: 'ig-scr-input',
|
|
842
|
-
value:
|
|
843
|
-
placeholder: 'ai, deepseek, coding,
|
|
844
|
-
onChange: (e) =>
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1009
|
+
value: positiveWordsText,
|
|
1010
|
+
placeholder: 'ai, deepseek, coding, 前端, 架構, 開源, 教學, 工具',
|
|
1011
|
+
onChange: (e) => {
|
|
1012
|
+
const raw = e.target.value
|
|
1013
|
+
setPositiveWordsText(raw)
|
|
1014
|
+
updateField('positiveKeywords', parseListFromText(raw, false))
|
|
1015
|
+
},
|
|
849
1016
|
}),
|
|
850
|
-
h('div', { className: 'ig-scr-hint' }, '
|
|
1017
|
+
h('div', { className: 'ig-scr-hint' }, '正文實質提及這些關鍵詞或相關領域時給予高權重加分')
|
|
1018
|
+
),
|
|
1019
|
+
|
|
1020
|
+
// 負向排除詞
|
|
1021
|
+
h(
|
|
1022
|
+
'div',
|
|
1023
|
+
{ className: 'ig-scr-field' },
|
|
1024
|
+
h('label', { className: 'ig-scr-label' }, '🚫 負向排除關鍵字 (Negative Keywords / Anti-Spam)'),
|
|
1025
|
+
h('input', {
|
|
1026
|
+
className: 'ig-scr-input',
|
|
1027
|
+
value: negativeWordsText,
|
|
1028
|
+
placeholder: '互粉, 代購, 博彩, 抽獎, 微商, 減肥, 穿搭, 搞笑, 自拍',
|
|
1029
|
+
onChange: (e) => {
|
|
1030
|
+
const raw = e.target.value
|
|
1031
|
+
setNegativeWordsText(raw)
|
|
1032
|
+
updateField('negativeKeywords', parseListFromText(raw, false))
|
|
1033
|
+
},
|
|
1034
|
+
}),
|
|
1035
|
+
h('div', { className: 'ig-scr-hint' }, '命中任一排除詞直接判定不合格,杜絕廣告宣傳、抽獎促銷與無意義廢文')
|
|
1036
|
+
),
|
|
1037
|
+
|
|
1038
|
+
h(
|
|
1039
|
+
'div',
|
|
1040
|
+
{ className: 'ig-scr-grid-2' },
|
|
1041
|
+
h(
|
|
1042
|
+
'div',
|
|
1043
|
+
{ className: 'ig-scr-field' },
|
|
1044
|
+
h('label', { className: 'ig-scr-label' }, '🏷️ 探索主題標籤 (Interest Tags)'),
|
|
1045
|
+
h('input', {
|
|
1046
|
+
className: 'ig-scr-input',
|
|
1047
|
+
value: tagsInputText,
|
|
1048
|
+
placeholder: 'ai, deepseek, coding, tech, gadgets',
|
|
1049
|
+
onChange: (e) => {
|
|
1050
|
+
const raw = e.target.value
|
|
1051
|
+
setTagsInputText(raw)
|
|
1052
|
+
updateField('interestTags', parseListFromText(raw, true))
|
|
1053
|
+
},
|
|
1054
|
+
}),
|
|
1055
|
+
h('div', { className: 'ig-scr-hint' }, '探索抓取起點標籤 (非推薦唯一標準,支援空格與中英文逗號)')
|
|
1056
|
+
),
|
|
1057
|
+
h(
|
|
1058
|
+
'div',
|
|
1059
|
+
{ className: 'ig-scr-field' },
|
|
1060
|
+
h('label', { className: 'ig-scr-label' }, '📏 正文有效文字最小長度 (字數)'),
|
|
1061
|
+
h('input', {
|
|
1062
|
+
type: 'number',
|
|
1063
|
+
className: 'ig-scr-input',
|
|
1064
|
+
value: cfg.minCaptionLength ?? 30,
|
|
1065
|
+
onChange: (e) => updateField('minCaptionLength', Number(e.target.value)),
|
|
1066
|
+
}),
|
|
1067
|
+
h('div', { className: 'ig-scr-hint' }, '扣除 #標籤 後的正文字數門檻')
|
|
1068
|
+
)
|
|
851
1069
|
),
|
|
852
1070
|
|
|
853
1071
|
h(
|
|
@@ -955,7 +1173,7 @@ window.__ModuleLoader__.load({
|
|
|
955
1173
|
h('input', {
|
|
956
1174
|
type: 'number',
|
|
957
1175
|
className: 'ig-scr-input',
|
|
958
|
-
value: cfg.minScore ??
|
|
1176
|
+
value: cfg.minScore ?? 80,
|
|
959
1177
|
onChange: (e) => updateField('minScore', Number(e.target.value)),
|
|
960
1178
|
}),
|
|
961
1179
|
h('div', { className: 'ig-scr-hint' }, '高於此分數的貼文才會在留言區 @主人')
|
|
@@ -1013,47 +1231,71 @@ window.__ModuleLoader__.load({
|
|
|
1013
1231
|
const slots = (typeof ctx.get === 'function' ? ctx.get('slots') : undefined) || ctx.slots
|
|
1014
1232
|
if (!slots) return
|
|
1015
1233
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1234
|
+
// 1. 左側一級專屬導航槽位 (settings.section)
|
|
1235
|
+
const registerSection = () => {
|
|
1236
|
+
try {
|
|
1018
1237
|
slots.register(
|
|
1019
1238
|
{
|
|
1020
1239
|
name: 'settings.section',
|
|
1021
1240
|
id: 'instagram-scroll',
|
|
1022
|
-
order:
|
|
1023
|
-
label: 'Instagram
|
|
1241
|
+
order: 36,
|
|
1242
|
+
label: 'Instagram 滾動',
|
|
1024
1243
|
},
|
|
1025
1244
|
() => h(ScrollPanel)
|
|
1026
1245
|
)
|
|
1027
|
-
)
|
|
1028
|
-
|
|
1246
|
+
} catch (e) {
|
|
1247
|
+
console.error('[@luoyi-dsh/instagram-scroll] 註冊 settings.section 異常:', e)
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
if (typeof slots.inject === 'function') {
|
|
1251
|
+
slots.inject('settings.section', registerSection)
|
|
1252
|
+
} else if (typeof slots.register === 'function') {
|
|
1253
|
+
registerSection()
|
|
1254
|
+
}
|
|
1029
1255
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1256
|
+
// 2. 外掛程式頁面頂部頁籤 (settings.plugins.tab)
|
|
1257
|
+
const registerTab = () => {
|
|
1258
|
+
try {
|
|
1032
1259
|
slots.register(
|
|
1033
1260
|
{
|
|
1034
1261
|
name: 'settings.plugins.tab',
|
|
1035
1262
|
id: 'instagram-scroll',
|
|
1036
|
-
order:
|
|
1037
|
-
label: 'Instagram
|
|
1263
|
+
order: 36,
|
|
1264
|
+
label: 'Instagram 滾動',
|
|
1038
1265
|
},
|
|
1039
1266
|
() => h(ScrollPanel)
|
|
1040
1267
|
)
|
|
1041
|
-
)
|
|
1042
|
-
|
|
1268
|
+
} catch (e) {
|
|
1269
|
+
console.error('[@luoyi-dsh/instagram-scroll] 註冊 settings.plugins.tab 異常:', e)
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
if (typeof slots.inject === 'function') {
|
|
1273
|
+
slots.inject('settings.plugins.tab', registerTab)
|
|
1274
|
+
} else if (typeof slots.register === 'function') {
|
|
1275
|
+
registerTab()
|
|
1276
|
+
}
|
|
1043
1277
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1278
|
+
// 3. 外掛程式卡片列表槽位 (plugins.item)
|
|
1279
|
+
const registerItem = () => {
|
|
1280
|
+
try {
|
|
1046
1281
|
slots.register(
|
|
1047
1282
|
{
|
|
1048
1283
|
name: 'plugins.item',
|
|
1049
1284
|
id: 'instagram-scroll',
|
|
1050
|
-
order:
|
|
1285
|
+
order: 36,
|
|
1051
1286
|
label: 'Instagram 滾動探索與推坑',
|
|
1052
1287
|
},
|
|
1053
1288
|
() => h(ScrollPanel)
|
|
1054
1289
|
)
|
|
1055
|
-
)
|
|
1056
|
-
|
|
1290
|
+
} catch (e) {
|
|
1291
|
+
console.error('[@luoyi-dsh/instagram-scroll] 註冊 plugins.item 異常:', e)
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
if (typeof slots.inject === 'function') {
|
|
1295
|
+
slots.inject('plugins.item', registerItem)
|
|
1296
|
+
} else if (typeof slots.register === 'function') {
|
|
1297
|
+
registerItem()
|
|
1298
|
+
}
|
|
1057
1299
|
},
|
|
1058
1300
|
}
|
|
1059
1301
|
},
|
package/cordis.patch.yml
CHANGED
package/dist/config.d.ts
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
export interface InstagramScrollConfig {
|
|
5
5
|
masterUsername?: string;
|
|
6
6
|
interestTags?: string[];
|
|
7
|
+
userInterests?: string;
|
|
8
|
+
positiveKeywords?: string[];
|
|
9
|
+
negativeKeywords?: string[];
|
|
10
|
+
minCaptionLength?: number;
|
|
11
|
+
requireBodyMatch?: boolean;
|
|
12
|
+
strictContentCheck?: boolean;
|
|
7
13
|
personaPrompt?: string;
|
|
8
14
|
minScore?: number;
|
|
9
15
|
dailyRecommendCap?: number;
|
|
@@ -11,6 +17,10 @@ export interface InstagramScrollConfig {
|
|
|
11
17
|
autoScrollEnabled?: boolean;
|
|
12
18
|
likeOnRecommend?: boolean;
|
|
13
19
|
batchSize?: number;
|
|
20
|
+
maxRecommendsPerRun?: number;
|
|
21
|
+
minPostReadDelaySeconds?: number;
|
|
22
|
+
maxPostReadDelaySeconds?: number;
|
|
23
|
+
cooldownOnBlockMinutes?: number;
|
|
14
24
|
}
|
|
15
25
|
export declare const defaultScrollConfig: InstagramScrollConfig;
|
|
16
26
|
export declare function unwrapScrollValue<T>(val: any, fallback: T): T;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,qBAAqB;IACpC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,qBAAqB;IACpC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IAEvB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAE5B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,sBAAsB,CAAC,EAAE,MAAM,CAAA;CAChC;AAED,eAAO,MAAM,mBAAmB,EAAE,qBAiEjC,CAAA;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,CAW7D;AAYD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,GAAG,qBAAqB,CAuBpE;AAED,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,GAAG,OA+E7C"}
|
package/dist/config.js
CHANGED
|
@@ -4,13 +4,66 @@
|
|
|
4
4
|
export const defaultScrollConfig = {
|
|
5
5
|
masterUsername: '',
|
|
6
6
|
interestTags: ['ai', 'deepseek', 'coding', 'tech', 'gadgets'],
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
userInterests: '專注於前沿 AI 技術 (LLM, Agent, 開源模型)、全端與前端架構、優質開源專案、高效率開發工具、優質 UI/UX 設計或深度乾貨技術解析。偏好乾貨分享、實踐案例與原創見解,不喜歡空洞無物、純蹭熱度、打廣告或純娛樂搞笑的內容。',
|
|
8
|
+
positiveKeywords: [
|
|
9
|
+
'ai',
|
|
10
|
+
'deepseek',
|
|
11
|
+
'agent',
|
|
12
|
+
'coding',
|
|
13
|
+
'技術',
|
|
14
|
+
'前端',
|
|
15
|
+
'後端',
|
|
16
|
+
'架構',
|
|
17
|
+
'開源',
|
|
18
|
+
'教學',
|
|
19
|
+
'程式',
|
|
20
|
+
'開發',
|
|
21
|
+
'模型',
|
|
22
|
+
'github',
|
|
23
|
+
'prompt',
|
|
24
|
+
'演算法',
|
|
25
|
+
'工具',
|
|
26
|
+
'實戰',
|
|
27
|
+
'乾貨',
|
|
28
|
+
],
|
|
29
|
+
negativeKeywords: [
|
|
30
|
+
'互粉',
|
|
31
|
+
'代購',
|
|
32
|
+
'博彩',
|
|
33
|
+
'運彩',
|
|
34
|
+
'兼職',
|
|
35
|
+
'抽獎',
|
|
36
|
+
'免費領',
|
|
37
|
+
'私訊',
|
|
38
|
+
'點擊連結',
|
|
39
|
+
'主頁連結',
|
|
40
|
+
'減肥',
|
|
41
|
+
'微商',
|
|
42
|
+
'外匯',
|
|
43
|
+
'虛擬幣',
|
|
44
|
+
'心靈雞湯',
|
|
45
|
+
'穿搭',
|
|
46
|
+
'搞笑',
|
|
47
|
+
'自拍',
|
|
48
|
+
'八卦',
|
|
49
|
+
'暴富',
|
|
50
|
+
'躺平',
|
|
51
|
+
'加盟',
|
|
52
|
+
],
|
|
53
|
+
minCaptionLength: 30,
|
|
54
|
+
requireBodyMatch: true,
|
|
55
|
+
strictContentCheck: true,
|
|
56
|
+
personaPrompt: '你是一位洞察力敏銳、品味高雅的社群特助。請以極高標準評估這篇 Instagram 貼文是否深度符合主人的興趣偏好。若是具備前沿乾貨、創意見解或高實用價值的精選內容,請給出高分評價並撰寫簡短自然、熱情切題的推坑一句話。',
|
|
57
|
+
minScore: 80,
|
|
9
58
|
dailyRecommendCap: 5,
|
|
10
59
|
scrollIntervalMinutes: 60,
|
|
11
60
|
autoScrollEnabled: true,
|
|
12
61
|
likeOnRecommend: true,
|
|
13
62
|
batchSize: 15,
|
|
63
|
+
maxRecommendsPerRun: 1,
|
|
64
|
+
minPostReadDelaySeconds: 3,
|
|
65
|
+
maxPostReadDelaySeconds: 7,
|
|
66
|
+
cooldownOnBlockMinutes: 30,
|
|
14
67
|
};
|
|
15
68
|
export function unwrapScrollValue(val, fallback) {
|
|
16
69
|
if (val === null || val === undefined)
|
|
@@ -26,18 +79,37 @@ export function unwrapScrollValue(val, fallback) {
|
|
|
26
79
|
}
|
|
27
80
|
return val;
|
|
28
81
|
}
|
|
82
|
+
function parseListValue(val, fallback = []) {
|
|
83
|
+
if (Array.isArray(val)) {
|
|
84
|
+
return val.map((s) => String(s || '').trim()).filter(Boolean);
|
|
85
|
+
}
|
|
86
|
+
if (typeof val === 'string') {
|
|
87
|
+
return val.split(/[,,\n]+/).map((s) => s.trim().replace(/^#/, '')).filter(Boolean);
|
|
88
|
+
}
|
|
89
|
+
return fallback;
|
|
90
|
+
}
|
|
29
91
|
export function sanitizeScrollConfig(raw) {
|
|
30
92
|
const c = raw || {};
|
|
31
93
|
return {
|
|
32
94
|
masterUsername: unwrapScrollValue(c.masterUsername, defaultScrollConfig.masterUsername || ''),
|
|
33
|
-
interestTags:
|
|
95
|
+
interestTags: parseListValue(c.interestTags, defaultScrollConfig.interestTags || []),
|
|
96
|
+
userInterests: unwrapScrollValue(c.userInterests, defaultScrollConfig.userInterests || ''),
|
|
97
|
+
positiveKeywords: parseListValue(c.positiveKeywords, defaultScrollConfig.positiveKeywords || []),
|
|
98
|
+
negativeKeywords: parseListValue(c.negativeKeywords, defaultScrollConfig.negativeKeywords || []),
|
|
99
|
+
minCaptionLength: unwrapScrollValue(c.minCaptionLength, defaultScrollConfig.minCaptionLength || 30),
|
|
100
|
+
requireBodyMatch: unwrapScrollValue(c.requireBodyMatch, defaultScrollConfig.requireBodyMatch ?? true),
|
|
101
|
+
strictContentCheck: unwrapScrollValue(c.strictContentCheck, defaultScrollConfig.strictContentCheck ?? true),
|
|
34
102
|
personaPrompt: unwrapScrollValue(c.personaPrompt, defaultScrollConfig.personaPrompt || ''),
|
|
35
|
-
minScore: unwrapScrollValue(c.minScore, defaultScrollConfig.minScore ||
|
|
103
|
+
minScore: unwrapScrollValue(c.minScore, defaultScrollConfig.minScore || 80),
|
|
36
104
|
dailyRecommendCap: unwrapScrollValue(c.dailyRecommendCap, defaultScrollConfig.dailyRecommendCap || 5),
|
|
37
105
|
scrollIntervalMinutes: unwrapScrollValue(c.scrollIntervalMinutes, defaultScrollConfig.scrollIntervalMinutes || 60),
|
|
38
106
|
autoScrollEnabled: unwrapScrollValue(c.autoScrollEnabled, defaultScrollConfig.autoScrollEnabled ?? true),
|
|
39
107
|
likeOnRecommend: unwrapScrollValue(c.likeOnRecommend, defaultScrollConfig.likeOnRecommend ?? true),
|
|
40
108
|
batchSize: unwrapScrollValue(c.batchSize, defaultScrollConfig.batchSize || 15),
|
|
109
|
+
maxRecommendsPerRun: unwrapScrollValue(c.maxRecommendsPerRun, defaultScrollConfig.maxRecommendsPerRun || 1),
|
|
110
|
+
minPostReadDelaySeconds: unwrapScrollValue(c.minPostReadDelaySeconds, defaultScrollConfig.minPostReadDelaySeconds || 3),
|
|
111
|
+
maxPostReadDelaySeconds: unwrapScrollValue(c.maxPostReadDelaySeconds, defaultScrollConfig.maxPostReadDelaySeconds || 7),
|
|
112
|
+
cooldownOnBlockMinutes: unwrapScrollValue(c.cooldownOnBlockMinutes, defaultScrollConfig.cooldownOnBlockMinutes || 30),
|
|
41
113
|
};
|
|
42
114
|
}
|
|
43
115
|
export function buildScrollConfigSchema(z) {
|
|
@@ -48,16 +120,40 @@ export function buildScrollConfigSchema(z) {
|
|
|
48
120
|
.description('主人的 Instagram 使用者名稱 (發現好文時將在留言區 @主人推薦)'),
|
|
49
121
|
interestTags: z
|
|
50
122
|
.array(z.string())
|
|
51
|
-
.default(['ai', 'deepseek', 'coding', 'tech'])
|
|
52
|
-
.description('
|
|
123
|
+
.default(['ai', 'deepseek', 'coding', 'tech', 'gadgets'])
|
|
124
|
+
.description('探索的主題標籤清單 (僅用於初步探索抓取,不作為推薦唯一判斷依據)'),
|
|
125
|
+
userInterests: z
|
|
126
|
+
.string()
|
|
127
|
+
.default(defaultScrollConfig.userInterests)
|
|
128
|
+
.description('主人喜歡的內容風格與領域詳細描述 (AI 將依此深度審查貼文實質內容)'),
|
|
129
|
+
positiveKeywords: z
|
|
130
|
+
.array(z.string())
|
|
131
|
+
.default(defaultScrollConfig.positiveKeywords)
|
|
132
|
+
.description('正文必須討論或偏好的正面核心關鍵字清單'),
|
|
133
|
+
negativeKeywords: z
|
|
134
|
+
.array(z.string())
|
|
135
|
+
.default(defaultScrollConfig.negativeKeywords)
|
|
136
|
+
.description('負向排除關鍵字 (命中時直接拒絕推薦,避免廣告、抽獎或廢文)'),
|
|
137
|
+
minCaptionLength: z
|
|
138
|
+
.number()
|
|
139
|
+
.default(30)
|
|
140
|
+
.description('正文有效文字最小字數要求 (扣除 #標籤 後,過短或無實質內容者直接過濾)'),
|
|
141
|
+
requireBodyMatch: z
|
|
142
|
+
.boolean()
|
|
143
|
+
.default(true)
|
|
144
|
+
.description('正文實質內容必須命中主人偏好主題 (拒絕僅在文末蹭 tag 的貼文)'),
|
|
145
|
+
strictContentCheck: z
|
|
146
|
+
.boolean()
|
|
147
|
+
.default(true)
|
|
148
|
+
.description('啟用嚴格內容審查機制 (結合 LLM / 正文語義深度質檢)'),
|
|
53
149
|
personaPrompt: z
|
|
54
150
|
.string()
|
|
55
151
|
.default(defaultScrollConfig.personaPrompt)
|
|
56
152
|
.description('評估貼文趣味度與撰寫 @主人推薦語的人設提示詞'),
|
|
57
153
|
minScore: z
|
|
58
154
|
.number()
|
|
59
|
-
.default(
|
|
60
|
-
.description('推坑門檻分數 (0-100,達標才在貼文下留言
|
|
155
|
+
.default(80)
|
|
156
|
+
.description('推坑門檻分數 (0-100,達標才在貼文下留言 @主人,避免隨便推薦)'),
|
|
61
157
|
dailyRecommendCap: z
|
|
62
158
|
.number()
|
|
63
159
|
.default(5)
|
|
@@ -78,6 +174,22 @@ export function buildScrollConfigSchema(z) {
|
|
|
78
174
|
.number()
|
|
79
175
|
.default(15)
|
|
80
176
|
.description('每輪滾動瀏覽抓取的貼文筆數 (10-30)'),
|
|
177
|
+
maxRecommendsPerRun: z
|
|
178
|
+
.number()
|
|
179
|
+
.default(1)
|
|
180
|
+
.description('單次滾動瀏覽最多推坑留言則數 (避免短時間連發觸發官方風控)'),
|
|
181
|
+
minPostReadDelaySeconds: z
|
|
182
|
+
.number()
|
|
183
|
+
.default(3)
|
|
184
|
+
.description('每掃描一則貼文前人造擬真閱讀微延遲最小秒數'),
|
|
185
|
+
maxPostReadDelaySeconds: z
|
|
186
|
+
.number()
|
|
187
|
+
.default(7)
|
|
188
|
+
.description('每掃描一則貼文前人造擬真閱讀微延遲最大秒數'),
|
|
189
|
+
cooldownOnBlockMinutes: z
|
|
190
|
+
.number()
|
|
191
|
+
.default(30)
|
|
192
|
+
.description('遇到 Action Block / 頻率限制時自動熔斷暫停分鐘數'),
|
|
81
193
|
});
|
|
82
194
|
}
|
|
83
195
|
//# sourceMappingURL=config.js.map
|