@luoyi-dsh/instagram-scroll 1.0.1 → 1.0.6
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/AGENTS.md +92 -0
- package/README.md +77 -172
- package/README.zh-Hant.md +73 -174
- package/client.js +261 -33
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +156 -9
- 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 +563 -74
- package/dist/core/scrollDuty.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/ledger.d.ts +73 -10
- package/dist/lib/ledger.d.ts.map +1 -1
- package/dist/lib/ledger.js +402 -22
- package/dist/lib/ledger.js.map +1 -1
- package/package.json +4 -3
package/client.js
CHANGED
|
@@ -539,16 +539,40 @@ window.__ModuleLoader__.load({
|
|
|
539
539
|
|
|
540
540
|
const normalizeScrollConfig = (raw) => {
|
|
541
541
|
const c = (raw && typeof raw === 'object' && !Array.isArray(raw)) ? raw : {}
|
|
542
|
+
const baseTags = cleanArr(c.interestTags, ['art', 'deepseek', 'coding', 'kemono', 'tech'])
|
|
542
543
|
return {
|
|
543
544
|
masterUsername: cleanStr(c.masterUsername, ''),
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
545
|
+
fuzzySearchEnabled: cleanBool(c.fuzzySearchEnabled, true),
|
|
546
|
+
fuzzyKeywords: cleanArr(c.fuzzyKeywords, baseTags),
|
|
547
|
+
tagSearchEnabled: cleanBool(c.tagSearchEnabled, false),
|
|
548
|
+
interestTags: baseTags,
|
|
549
|
+
userInterests: cleanStr(
|
|
550
|
+
c.userInterests,
|
|
551
|
+
'專注於前沿 AI 技術 (LLM, Agent, 開源模型)、全端與前端架構、優質開源專案、高效率開發工具、優質 UI/UX 設計或深度乾貨技術解析。偏好乾貨分享、實踐案例與原創見解,不喜歡空洞無物、純蹭熱度、打廣告或純娛樂搞笑的內容。'
|
|
552
|
+
),
|
|
553
|
+
positiveKeywords: cleanArr(c.positiveKeywords, [
|
|
554
|
+
'ai', 'deepseek', 'agent', 'coding', '技術', '前端', '後端', '架構', '開源', '教學', '程式', '開發', '模型', 'github', 'prompt', '演算法', '工具', '實戰', '乾貨',
|
|
555
|
+
]),
|
|
556
|
+
negativeKeywords: cleanArr(c.negativeKeywords, [
|
|
557
|
+
'互粉', '代購', '博彩', '運彩', '兼職', '抽獎', '免費領', '私訊', '點擊連結', '主頁連結', '減肥', '微商', '外匯', '虛擬幣', '心靈雞湯', '穿搭', '搞笑', '自拍', '八卦', '暴富', '躺平', '加盟', 'AI生成', 'AI影片', 'AI動畫', 'AI繪圖', 'AI文章', 'AIGC', 'Midjourney', 'Stable Diffusion', 'Sora', 'Runway', 'Kling', '可靈', '即夢', 'AI配音', 'AI美女',
|
|
558
|
+
]),
|
|
559
|
+
minCaptionLength: cleanNum(c.minCaptionLength, 30),
|
|
560
|
+
requireBodyMatch: cleanBool(c.requireBodyMatch, true),
|
|
561
|
+
strictContentCheck: cleanBool(c.strictContentCheck, true),
|
|
562
|
+
personaPrompt: cleanStr(
|
|
563
|
+
c.personaPrompt,
|
|
564
|
+
'你是一位洞察力敏銳、品味高雅的社群特助。請以極高標準評估這篇 Instagram 貼文是否深度符合主人的興趣偏好。若是具備前沿乾貨、創意見解或高實用價值的精選內容,請給出高分評價並撰寫簡短自然、熱情切題的推坑一句話。'
|
|
565
|
+
),
|
|
566
|
+
minScore: cleanNum(c.minScore, 80),
|
|
547
567
|
dailyRecommendCap: cleanNum(c.dailyRecommendCap, 5),
|
|
548
568
|
scrollIntervalMinutes: cleanNum(c.scrollIntervalMinutes, 60),
|
|
549
569
|
autoScrollEnabled: cleanBool(c.autoScrollEnabled, true),
|
|
550
570
|
likeOnRecommend: cleanBool(c.likeOnRecommend, true),
|
|
551
571
|
batchSize: cleanNum(c.batchSize, 15),
|
|
572
|
+
maxRecommendsPerRun: cleanNum(c.maxRecommendsPerRun, 1),
|
|
573
|
+
minPostReadDelaySeconds: cleanNum(c.minPostReadDelaySeconds, 3),
|
|
574
|
+
maxPostReadDelaySeconds: cleanNum(c.maxPostReadDelaySeconds, 7),
|
|
575
|
+
cooldownOnBlockMinutes: cleanNum(c.cooldownOnBlockMinutes, 30),
|
|
552
576
|
}
|
|
553
577
|
}
|
|
554
578
|
|
|
@@ -563,6 +587,21 @@ window.__ModuleLoader__.load({
|
|
|
563
587
|
const [runningRound, setRunningRound] = useState(false)
|
|
564
588
|
const [msg, setMsg] = useState(null)
|
|
565
589
|
|
|
590
|
+
// 獨立字串狀態:解決 input 鍵入逗號與空格被 React 重新格式化吃掉的問題
|
|
591
|
+
const [fuzzyWordsText, setFuzzyWordsText] = useState('')
|
|
592
|
+
const [tagsInputText, setTagsInputText] = useState('')
|
|
593
|
+
const [positiveWordsText, setPositiveWordsText] = useState('')
|
|
594
|
+
const [negativeWordsText, setNegativeWordsText] = useState('')
|
|
595
|
+
|
|
596
|
+
const parseListFromText = (text, isTag = false) => {
|
|
597
|
+
if (typeof text !== 'string') return []
|
|
598
|
+
return text
|
|
599
|
+
.split(/[,,\n]+/)
|
|
600
|
+
.map((s) => s.trim())
|
|
601
|
+
.map((s) => (isTag ? s.replace(/^#/, '') : s))
|
|
602
|
+
.filter(Boolean)
|
|
603
|
+
}
|
|
604
|
+
|
|
566
605
|
const refresh = async () => {
|
|
567
606
|
try {
|
|
568
607
|
const [cfgRes, statusRes, reportRes, mcpRes] = await Promise.all([
|
|
@@ -573,7 +612,14 @@ window.__ModuleLoader__.load({
|
|
|
573
612
|
.then((r) => r.json())
|
|
574
613
|
.catch(() => null),
|
|
575
614
|
])
|
|
576
|
-
if (cfgRes?.ok)
|
|
615
|
+
if (cfgRes?.ok) {
|
|
616
|
+
const loaded = normalizeScrollConfig(cfgRes.config)
|
|
617
|
+
setCfg(loaded)
|
|
618
|
+
setFuzzyWordsText(loaded.fuzzyKeywords.join(', '))
|
|
619
|
+
setTagsInputText(loaded.interestTags.join(', '))
|
|
620
|
+
setPositiveWordsText(loaded.positiveKeywords.join(', '))
|
|
621
|
+
setNegativeWordsText(loaded.negativeKeywords.join(', '))
|
|
622
|
+
}
|
|
577
623
|
if (statusRes?.ok) setStatus(statusRes)
|
|
578
624
|
if (reportRes?.ok) setReport(reportRes.report || '')
|
|
579
625
|
if (mcpRes?.ok) setMcpStatus(mcpRes)
|
|
@@ -594,10 +640,21 @@ window.__ModuleLoader__.load({
|
|
|
594
640
|
setSaving(true)
|
|
595
641
|
setMsg(null)
|
|
596
642
|
try {
|
|
597
|
-
const cleanPayload =
|
|
643
|
+
const cleanPayload = {
|
|
644
|
+
...normalizeScrollConfig(cfg),
|
|
645
|
+
fuzzyKeywords: parseListFromText(fuzzyWordsText, true),
|
|
646
|
+
interestTags: parseListFromText(tagsInputText, true),
|
|
647
|
+
positiveKeywords: parseListFromText(positiveWordsText, false),
|
|
648
|
+
negativeKeywords: parseListFromText(negativeWordsText, false),
|
|
649
|
+
}
|
|
598
650
|
const res = await jpost('/config', cleanPayload)
|
|
599
651
|
if (res?.ok) {
|
|
600
|
-
|
|
652
|
+
const saved = normalizeScrollConfig(res.config)
|
|
653
|
+
setCfg(saved)
|
|
654
|
+
setFuzzyWordsText(saved.fuzzyKeywords.join(', '))
|
|
655
|
+
setTagsInputText(saved.interestTags.join(', '))
|
|
656
|
+
setPositiveWordsText(saved.positiveKeywords.join(', '))
|
|
657
|
+
setNegativeWordsText(saved.negativeKeywords.join(', '))
|
|
601
658
|
setMsg('✅ 滾動推坑設定已成功保存!')
|
|
602
659
|
}
|
|
603
660
|
} catch (e) {
|
|
@@ -640,11 +697,23 @@ window.__ModuleLoader__.load({
|
|
|
640
697
|
const rawAccount =
|
|
641
698
|
status?.mcpAccount ||
|
|
642
699
|
status?.loginStatus?.username ||
|
|
700
|
+
mcpStatus?.botUsername ||
|
|
643
701
|
mcpStatus?.loginStatus?.username ||
|
|
644
702
|
(mcpStatus?.loginStatus?.message?.match(/@([^\s)]+)/)?.[1]) ||
|
|
645
703
|
(isMcpConnected ? '已連線' : '')
|
|
646
704
|
|
|
647
|
-
|
|
705
|
+
let cachedMcpAccounts = {}
|
|
706
|
+
try {
|
|
707
|
+
cachedMcpAccounts = JSON.parse(localStorage.getItem('dsh_ig_mcp_accounts') || '{}') || {}
|
|
708
|
+
} catch {}
|
|
709
|
+
const displayAccount = (cachedMcpAccounts.username || rawAccount || '').replace(/^@/, '')
|
|
710
|
+
const displayMaster = (
|
|
711
|
+
cachedMcpAccounts.masterUsername ||
|
|
712
|
+
mcpStatus?.masterUsername ||
|
|
713
|
+
status?.masterUsername ||
|
|
714
|
+
cfg.masterUsername ||
|
|
715
|
+
''
|
|
716
|
+
).replace(/^@/, '')
|
|
648
717
|
|
|
649
718
|
return h(
|
|
650
719
|
'div',
|
|
@@ -665,7 +734,7 @@ window.__ModuleLoader__.load({
|
|
|
665
734
|
h(
|
|
666
735
|
'div',
|
|
667
736
|
{ className: 'ig-scr-brand-sub', style: { display: 'flex', alignItems: 'center', gap: '8px' } },
|
|
668
|
-
|
|
737
|
+
displayMaster ? `目標主人 (繼承自 MCP): @${displayMaster}` : '尚未於 MCP 設定主人帳號',
|
|
669
738
|
h('span', { style: { color: 'rgba(255, 255, 255, 0.2)' } }, '|'),
|
|
670
739
|
isMcpConnected
|
|
671
740
|
? h(
|
|
@@ -856,7 +925,7 @@ window.__ModuleLoader__.load({
|
|
|
856
925
|
h('div', { className: 'ig-scr-section-desc' }, '設定主人的 Instagram 帳號名與感興趣的標籤領域,AI 將依此進行嚴格內容篩選。')
|
|
857
926
|
),
|
|
858
927
|
|
|
859
|
-
// MCP Session
|
|
928
|
+
// MCP Session 與主人帳號來源提示 (直接繼承自 MCP,無需重複輸入)
|
|
860
929
|
h(
|
|
861
930
|
'div',
|
|
862
931
|
{
|
|
@@ -864,52 +933,211 @@ window.__ModuleLoader__.load({
|
|
|
864
933
|
background: 'rgba(59, 130, 246, 0.06)',
|
|
865
934
|
border: '1px solid rgba(59, 130, 246, 0.2)',
|
|
866
935
|
borderRadius: '8px',
|
|
867
|
-
padding: '
|
|
936
|
+
padding: '12px 14px',
|
|
868
937
|
fontSize: '12px',
|
|
869
938
|
color: '#93c5fd',
|
|
870
939
|
display: 'flex',
|
|
871
|
-
|
|
872
|
-
gap: '
|
|
940
|
+
flexDirection: 'column',
|
|
941
|
+
gap: '6px',
|
|
873
942
|
},
|
|
874
943
|
},
|
|
875
|
-
h(Icon.Sparkles, { size: 14 }),
|
|
876
944
|
h(
|
|
877
|
-
'
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
945
|
+
'div',
|
|
946
|
+
{ style: { display: 'flex', alignItems: 'center', gap: '8px', fontWeight: 600 } },
|
|
947
|
+
h(Icon.Sparkles, { size: 14 }),
|
|
948
|
+
h(
|
|
949
|
+
'span',
|
|
950
|
+
null,
|
|
951
|
+
'機器人與主人帳號已統一由 dsh-instagram-mcp 管理,本外掛無需重複輸入'
|
|
952
|
+
)
|
|
953
|
+
),
|
|
954
|
+
h(
|
|
955
|
+
'div',
|
|
956
|
+
{ style: { display: 'flex', flexWrap: 'wrap', gap: '16px', color: '#e2e8f0', paddingLeft: '22px' } },
|
|
957
|
+
h('span', null, `🤖 機器人帳號: ${displayAccount ? `@${displayAccount}` : '尚未於 MCP 設定'}`),
|
|
958
|
+
h('span', null, `🎯 主人帳號 (推坑 @ 對象): ${displayMaster ? `@${displayMaster}` : '尚未於 MCP 設定'}`)
|
|
959
|
+
)
|
|
960
|
+
),
|
|
961
|
+
|
|
962
|
+
// 正文實質內容審查開關
|
|
963
|
+
h(
|
|
964
|
+
'div',
|
|
965
|
+
{ className: 'ig-scr-switch' },
|
|
966
|
+
h(
|
|
967
|
+
'div',
|
|
968
|
+
{ className: 'ig-scr-switch-text' },
|
|
969
|
+
h('div', { className: 'ig-scr-switch-name' }, '🛡️ 啟用 @ 前正文內容深度審查'),
|
|
970
|
+
h('div', { className: 'ig-scr-hint' }, '拒絕隨便推薦!留言 @ 主人前深度檢查正文實質乾貨與品質,剔除純圖廢文與空洞標籤')
|
|
971
|
+
),
|
|
972
|
+
h(
|
|
973
|
+
'button',
|
|
974
|
+
{
|
|
975
|
+
className: `ig-scr-toggle ${cfg.strictContentCheck ? 'on' : ''}`,
|
|
976
|
+
onClick: () => updateField('strictContentCheck', !cfg.strictContentCheck),
|
|
977
|
+
},
|
|
978
|
+
h('span', { className: 'ig-scr-toggle-thumb' })
|
|
979
|
+
)
|
|
980
|
+
),
|
|
981
|
+
|
|
982
|
+
// 正文實質命中開關
|
|
983
|
+
h(
|
|
984
|
+
'div',
|
|
985
|
+
{ className: 'ig-scr-switch' },
|
|
986
|
+
h(
|
|
987
|
+
'div',
|
|
988
|
+
{ className: 'ig-scr-switch-text' },
|
|
989
|
+
h('div', { className: 'ig-scr-switch-name' }, '🔍 正文必須實質討論偏好主題 (拒絕僅在 tag 蹭熱度)'),
|
|
990
|
+
h('div', { className: 'ig-scr-hint' }, '關鍵詞必須在正文中真正出現;若僅在文末堆砌 #標籤 則視為標籤農場直接過濾')
|
|
991
|
+
),
|
|
992
|
+
h(
|
|
993
|
+
'button',
|
|
994
|
+
{
|
|
995
|
+
className: `ig-scr-toggle ${cfg.requireBodyMatch ? 'on' : ''}`,
|
|
996
|
+
onClick: () => updateField('requireBodyMatch', !cfg.requireBodyMatch),
|
|
997
|
+
},
|
|
998
|
+
h('span', { className: 'ig-scr-toggle-thumb' })
|
|
882
999
|
)
|
|
883
1000
|
),
|
|
884
1001
|
|
|
1002
|
+
// 主人喜歡的內容風格與偏好詳述
|
|
1003
|
+
h(
|
|
1004
|
+
'div',
|
|
1005
|
+
{ className: 'ig-scr-field' },
|
|
1006
|
+
h('label', { className: 'ig-scr-label' }, '💎 主人喜歡的內容風格與領域詳述 (User Interests)'),
|
|
1007
|
+
h('textarea', {
|
|
1008
|
+
className: 'ig-scr-textarea',
|
|
1009
|
+
style: { minHeight: '75px' },
|
|
1010
|
+
value: cleanStr(cfg.userInterests),
|
|
1011
|
+
placeholder: '詳細描述您喜歡的貼文類型、技術領域、內容風格與乾貨要求...',
|
|
1012
|
+
onChange: (e) => updateField('userInterests', e.target.value),
|
|
1013
|
+
}),
|
|
1014
|
+
h('div', { className: 'ig-scr-hint' }, 'AI 將依據此描述深度比對正文語義,確保推薦完全符合您的個人品味與需求')
|
|
1015
|
+
),
|
|
1016
|
+
|
|
1017
|
+
// 正向關鍵字
|
|
885
1018
|
h(
|
|
886
1019
|
'div',
|
|
887
1020
|
{ className: 'ig-scr-field' },
|
|
888
|
-
h('label', { className: 'ig-scr-label' }, '
|
|
1021
|
+
h('label', { className: 'ig-scr-label' }, '✨ 正文正向領域關鍵字 (Positive Keywords)'),
|
|
889
1022
|
h('input', {
|
|
890
1023
|
className: 'ig-scr-input',
|
|
891
|
-
value:
|
|
892
|
-
placeholder: '
|
|
893
|
-
onChange: (e) =>
|
|
1024
|
+
value: positiveWordsText,
|
|
1025
|
+
placeholder: 'ai, deepseek, coding, 前端, 架構, 開源, 教學, 工具',
|
|
1026
|
+
onChange: (e) => {
|
|
1027
|
+
const raw = e.target.value
|
|
1028
|
+
setPositiveWordsText(raw)
|
|
1029
|
+
updateField('positiveKeywords', parseListFromText(raw, false))
|
|
1030
|
+
},
|
|
894
1031
|
}),
|
|
895
|
-
h('div', { className: 'ig-scr-hint' }, '
|
|
1032
|
+
h('div', { className: 'ig-scr-hint' }, '正文實質提及這些關鍵詞或相關領域時給予高權重加分')
|
|
896
1033
|
),
|
|
897
1034
|
|
|
1035
|
+
// 負向排除詞
|
|
898
1036
|
h(
|
|
899
1037
|
'div',
|
|
900
1038
|
{ className: 'ig-scr-field' },
|
|
901
|
-
h('label', { className: 'ig-scr-label' }, '
|
|
1039
|
+
h('label', { className: 'ig-scr-label' }, '🚫 負向排除關鍵字 (Negative Keywords / Anti-Spam)'),
|
|
902
1040
|
h('input', {
|
|
903
1041
|
className: 'ig-scr-input',
|
|
904
|
-
value:
|
|
905
|
-
placeholder: '
|
|
906
|
-
onChange: (e) =>
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
1042
|
+
value: negativeWordsText,
|
|
1043
|
+
placeholder: '互粉, 代購, 博彩, 抽獎, 微商, 減肥, 穿搭, 搞笑, 自拍',
|
|
1044
|
+
onChange: (e) => {
|
|
1045
|
+
const raw = e.target.value
|
|
1046
|
+
setNegativeWordsText(raw)
|
|
1047
|
+
updateField('negativeKeywords', parseListFromText(raw, false))
|
|
1048
|
+
},
|
|
911
1049
|
}),
|
|
912
|
-
h('div', { className: 'ig-scr-hint' }, '
|
|
1050
|
+
h('div', { className: 'ig-scr-hint' }, '命中任一排除詞直接判定不合格,杜絕廣告宣傳、抽獎促銷與無意義廢文')
|
|
1051
|
+
),
|
|
1052
|
+
|
|
1053
|
+
// 模糊搜尋探索開關 (預設開啟)
|
|
1054
|
+
h(
|
|
1055
|
+
'div',
|
|
1056
|
+
{ className: 'ig-scr-switch' },
|
|
1057
|
+
h(
|
|
1058
|
+
'div',
|
|
1059
|
+
{ className: 'ig-scr-switch-text' },
|
|
1060
|
+
h('div', { className: 'ig-scr-switch-name' }, '🔎 啟用模糊搜尋抓取貼文 (Fuzzy Search — 推薦)'),
|
|
1061
|
+
h('div', { className: 'ig-scr-hint' }, '結合 Instagram 綜合關鍵字 SERP、Reels 搜尋與相關創作者貼文,解決單純 Tags 搜尋一直抓到 0 則貼文的問題')
|
|
1062
|
+
),
|
|
1063
|
+
h(
|
|
1064
|
+
'button',
|
|
1065
|
+
{
|
|
1066
|
+
className: `ig-scr-toggle ${cfg.fuzzySearchEnabled !== false ? 'on' : ''}`,
|
|
1067
|
+
onClick: () => updateField('fuzzySearchEnabled', !(cfg.fuzzySearchEnabled !== false)),
|
|
1068
|
+
},
|
|
1069
|
+
h('span', { className: 'ig-scr-toggle-thumb' })
|
|
1070
|
+
)
|
|
1071
|
+
),
|
|
1072
|
+
|
|
1073
|
+
h(
|
|
1074
|
+
'div',
|
|
1075
|
+
{ className: 'ig-scr-field', style: { opacity: cfg.fuzzySearchEnabled !== false ? 1 : 0.6 } },
|
|
1076
|
+
h('label', { className: 'ig-scr-label' }, '🔎 模糊搜尋關鍵字 (Fuzzy Search Keywords)'),
|
|
1077
|
+
h('input', {
|
|
1078
|
+
className: 'ig-scr-input',
|
|
1079
|
+
value: fuzzyWordsText,
|
|
1080
|
+
placeholder: 'ai, deepseek, coding, tech, gadgets',
|
|
1081
|
+
onChange: (e) => {
|
|
1082
|
+
const raw = e.target.value
|
|
1083
|
+
setFuzzyWordsText(raw)
|
|
1084
|
+
updateField('fuzzyKeywords', parseListFromText(raw, true))
|
|
1085
|
+
},
|
|
1086
|
+
}),
|
|
1087
|
+
h('div', { className: 'ig-scr-hint' }, '每輪滾動時隨機挑選關鍵字進行綜合模糊搜尋抓取貼文 (支援逗號分隔)')
|
|
1088
|
+
),
|
|
1089
|
+
|
|
1090
|
+
// 主題標籤搜尋開關 (保留功能,用按鈕開關,預設關閉)
|
|
1091
|
+
h(
|
|
1092
|
+
'div',
|
|
1093
|
+
{ className: 'ig-scr-switch' },
|
|
1094
|
+
h(
|
|
1095
|
+
'div',
|
|
1096
|
+
{ className: 'ig-scr-switch-text' },
|
|
1097
|
+
h('div', { className: 'ig-scr-switch-name' }, '🏷️ 啟用主題標籤搜尋 (Tags Search — 保留備用)'),
|
|
1098
|
+
h('div', { className: 'ig-scr-hint' }, '預設關閉(因 IG 官方常限制公開 Hashtag API 返回 0 則貼文);保留此設定,可隨時點擊按鈕開關')
|
|
1099
|
+
),
|
|
1100
|
+
h(
|
|
1101
|
+
'button',
|
|
1102
|
+
{
|
|
1103
|
+
className: `ig-scr-toggle ${cfg.tagSearchEnabled ? 'on' : ''}`,
|
|
1104
|
+
onClick: () => updateField('tagSearchEnabled', !cfg.tagSearchEnabled),
|
|
1105
|
+
},
|
|
1106
|
+
h('span', { className: 'ig-scr-toggle-thumb' })
|
|
1107
|
+
)
|
|
1108
|
+
),
|
|
1109
|
+
|
|
1110
|
+
h(
|
|
1111
|
+
'div',
|
|
1112
|
+
{ className: 'ig-scr-grid-2' },
|
|
1113
|
+
h(
|
|
1114
|
+
'div',
|
|
1115
|
+
{ className: 'ig-scr-field', style: { opacity: cfg.tagSearchEnabled ? 1 : 0.6 } },
|
|
1116
|
+
h('label', { className: 'ig-scr-label' }, '🏷️ 探索主題標籤 (Interest Tags — 開啟上方按鈕時生效)'),
|
|
1117
|
+
h('input', {
|
|
1118
|
+
className: 'ig-scr-input',
|
|
1119
|
+
value: tagsInputText,
|
|
1120
|
+
placeholder: 'ai, deepseek, coding, tech, gadgets',
|
|
1121
|
+
onChange: (e) => {
|
|
1122
|
+
const raw = e.target.value
|
|
1123
|
+
setTagsInputText(raw)
|
|
1124
|
+
updateField('interestTags', parseListFromText(raw, true))
|
|
1125
|
+
},
|
|
1126
|
+
}),
|
|
1127
|
+
h('div', { className: 'ig-scr-hint' }, '保留之主題標籤清單 (支援空格與中英文逗號)')
|
|
1128
|
+
),
|
|
1129
|
+
h(
|
|
1130
|
+
'div',
|
|
1131
|
+
{ className: 'ig-scr-field' },
|
|
1132
|
+
h('label', { className: 'ig-scr-label' }, '📏 正文有效文字最小長度 (字數)'),
|
|
1133
|
+
h('input', {
|
|
1134
|
+
type: 'number',
|
|
1135
|
+
className: 'ig-scr-input',
|
|
1136
|
+
value: cfg.minCaptionLength ?? 30,
|
|
1137
|
+
onChange: (e) => updateField('minCaptionLength', Number(e.target.value)),
|
|
1138
|
+
}),
|
|
1139
|
+
h('div', { className: 'ig-scr-hint' }, '扣除 #標籤 後的正文字數門檻')
|
|
1140
|
+
)
|
|
913
1141
|
),
|
|
914
1142
|
|
|
915
1143
|
h(
|
|
@@ -1017,7 +1245,7 @@ window.__ModuleLoader__.load({
|
|
|
1017
1245
|
h('input', {
|
|
1018
1246
|
type: 'number',
|
|
1019
1247
|
className: 'ig-scr-input',
|
|
1020
|
-
value: cfg.minScore ??
|
|
1248
|
+
value: cfg.minScore ?? 80,
|
|
1021
1249
|
onChange: (e) => updateField('minScore', Number(e.target.value)),
|
|
1022
1250
|
}),
|
|
1023
1251
|
h('div', { className: 'ig-scr-hint' }, '高於此分數的貼文才會在留言區 @主人')
|
package/dist/config.d.ts
CHANGED
|
@@ -3,7 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export interface InstagramScrollConfig {
|
|
5
5
|
masterUsername?: string;
|
|
6
|
+
fuzzySearchEnabled?: boolean;
|
|
7
|
+
fuzzyKeywords?: string[];
|
|
8
|
+
tagSearchEnabled?: boolean;
|
|
6
9
|
interestTags?: string[];
|
|
10
|
+
userInterests?: string;
|
|
11
|
+
positiveKeywords?: string[];
|
|
12
|
+
negativeKeywords?: string[];
|
|
13
|
+
minCaptionLength?: number;
|
|
14
|
+
requireBodyMatch?: boolean;
|
|
15
|
+
strictContentCheck?: boolean;
|
|
7
16
|
personaPrompt?: string;
|
|
8
17
|
minScore?: number;
|
|
9
18
|
dailyRecommendCap?: number;
|
|
@@ -11,6 +20,10 @@ export interface InstagramScrollConfig {
|
|
|
11
20
|
autoScrollEnabled?: boolean;
|
|
12
21
|
likeOnRecommend?: boolean;
|
|
13
22
|
batchSize?: number;
|
|
23
|
+
maxRecommendsPerRun?: number;
|
|
24
|
+
minPostReadDelaySeconds?: number;
|
|
25
|
+
maxPostReadDelaySeconds?: number;
|
|
26
|
+
cooldownOnBlockMinutes?: number;
|
|
14
27
|
}
|
|
15
28
|
export declare const defaultScrollConfig: InstagramScrollConfig;
|
|
16
29
|
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,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,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,qBAoFjC,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,CA2BpE;AAED,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,GAAG,OA2F7C"}
|
package/dist/config.js
CHANGED
|
@@ -3,14 +3,86 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const defaultScrollConfig = {
|
|
5
5
|
masterUsername: '',
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
fuzzySearchEnabled: true,
|
|
7
|
+
fuzzyKeywords: ['art', 'deepseek', 'coding', 'kemono', 'tech'],
|
|
8
|
+
tagSearchEnabled: false,
|
|
9
|
+
interestTags: ['art', 'deepseek', 'coding', 'kemono', 'tech'],
|
|
10
|
+
userInterests: '專注於真實人類原創手繪藝術 (插畫、Kemono、角色設計)、前沿軟體技術 (LLM 架構、Agent、開源專案)、全端與前端開發、高效率工具或深度技術解析。偏好真實人類原創作品與實踐乾貨,嚴格排除任何 AI 生成的影片、AI 動畫、AI 繪圖/算圖或 AI 生成的農場文章!',
|
|
11
|
+
positiveKeywords: [
|
|
12
|
+
'art',
|
|
13
|
+
'kemono',
|
|
14
|
+
'deepseek',
|
|
15
|
+
'agent',
|
|
16
|
+
'coding',
|
|
17
|
+
'技術',
|
|
18
|
+
'前端',
|
|
19
|
+
'後端',
|
|
20
|
+
'架構',
|
|
21
|
+
'開源',
|
|
22
|
+
'教學',
|
|
23
|
+
'程式',
|
|
24
|
+
'開發',
|
|
25
|
+
'模型',
|
|
26
|
+
'github',
|
|
27
|
+
'prompt',
|
|
28
|
+
'演算法',
|
|
29
|
+
'工具',
|
|
30
|
+
'實戰',
|
|
31
|
+
'乾貨',
|
|
32
|
+
],
|
|
33
|
+
negativeKeywords: [
|
|
34
|
+
'互粉',
|
|
35
|
+
'代購',
|
|
36
|
+
'博彩',
|
|
37
|
+
'運彩',
|
|
38
|
+
'兼職',
|
|
39
|
+
'抽獎',
|
|
40
|
+
'免費領',
|
|
41
|
+
'私訊',
|
|
42
|
+
'點擊連結',
|
|
43
|
+
'主頁連結',
|
|
44
|
+
'減肥',
|
|
45
|
+
'微商',
|
|
46
|
+
'外匯',
|
|
47
|
+
'虛擬幣',
|
|
48
|
+
'心靈雞湯',
|
|
49
|
+
'穿搭',
|
|
50
|
+
'搞笑',
|
|
51
|
+
'自拍',
|
|
52
|
+
'八卦',
|
|
53
|
+
'暴富',
|
|
54
|
+
'躺平',
|
|
55
|
+
'加盟',
|
|
56
|
+
'AI生成',
|
|
57
|
+
'AI影片',
|
|
58
|
+
'AI動畫',
|
|
59
|
+
'AI繪圖',
|
|
60
|
+
'AI文章',
|
|
61
|
+
'AIGC',
|
|
62
|
+
'Midjourney',
|
|
63
|
+
'Stable Diffusion',
|
|
64
|
+
'Sora',
|
|
65
|
+
'Runway',
|
|
66
|
+
'Kling',
|
|
67
|
+
'可靈',
|
|
68
|
+
'即夢',
|
|
69
|
+
'AI配音',
|
|
70
|
+
'AI美女',
|
|
71
|
+
],
|
|
72
|
+
minCaptionLength: 30,
|
|
73
|
+
requireBodyMatch: true,
|
|
74
|
+
strictContentCheck: true,
|
|
75
|
+
personaPrompt: '你是一位洞察力敏銳、品味高雅的社群特助。請以極高標準評估這篇 Instagram 貼文是否深度符合主人的興趣偏好。若是具備前沿乾貨、創意見解或高實用價值的精選內容,請給出高分評價並撰寫簡短自然、熱情切題的推坑一句話。',
|
|
76
|
+
minScore: 80,
|
|
9
77
|
dailyRecommendCap: 5,
|
|
10
78
|
scrollIntervalMinutes: 60,
|
|
11
79
|
autoScrollEnabled: true,
|
|
12
80
|
likeOnRecommend: true,
|
|
13
81
|
batchSize: 15,
|
|
82
|
+
maxRecommendsPerRun: 1,
|
|
83
|
+
minPostReadDelaySeconds: 3,
|
|
84
|
+
maxPostReadDelaySeconds: 7,
|
|
85
|
+
cooldownOnBlockMinutes: 30,
|
|
14
86
|
};
|
|
15
87
|
export function unwrapScrollValue(val, fallback) {
|
|
16
88
|
if (val === null || val === undefined)
|
|
@@ -26,18 +98,41 @@ export function unwrapScrollValue(val, fallback) {
|
|
|
26
98
|
}
|
|
27
99
|
return val;
|
|
28
100
|
}
|
|
101
|
+
function parseListValue(val, fallback = []) {
|
|
102
|
+
if (Array.isArray(val)) {
|
|
103
|
+
return val.map((s) => String(s || '').trim()).filter(Boolean);
|
|
104
|
+
}
|
|
105
|
+
if (typeof val === 'string') {
|
|
106
|
+
return val.split(/[,,\n]+/).map((s) => s.trim().replace(/^#/, '')).filter(Boolean);
|
|
107
|
+
}
|
|
108
|
+
return fallback;
|
|
109
|
+
}
|
|
29
110
|
export function sanitizeScrollConfig(raw) {
|
|
30
111
|
const c = raw || {};
|
|
112
|
+
const parsedTags = parseListValue(c.interestTags, defaultScrollConfig.interestTags || []);
|
|
31
113
|
return {
|
|
32
114
|
masterUsername: unwrapScrollValue(c.masterUsername, defaultScrollConfig.masterUsername || ''),
|
|
33
|
-
|
|
115
|
+
fuzzySearchEnabled: unwrapScrollValue(c.fuzzySearchEnabled, defaultScrollConfig.fuzzySearchEnabled ?? true),
|
|
116
|
+
fuzzyKeywords: parseListValue(c.fuzzyKeywords, parsedTags.length > 0 ? parsedTags : (defaultScrollConfig.fuzzyKeywords || [])),
|
|
117
|
+
tagSearchEnabled: unwrapScrollValue(c.tagSearchEnabled, defaultScrollConfig.tagSearchEnabled ?? false),
|
|
118
|
+
interestTags: parsedTags,
|
|
119
|
+
userInterests: unwrapScrollValue(c.userInterests, defaultScrollConfig.userInterests || ''),
|
|
120
|
+
positiveKeywords: parseListValue(c.positiveKeywords, defaultScrollConfig.positiveKeywords || []),
|
|
121
|
+
negativeKeywords: parseListValue(c.negativeKeywords, defaultScrollConfig.negativeKeywords || []),
|
|
122
|
+
minCaptionLength: unwrapScrollValue(c.minCaptionLength, defaultScrollConfig.minCaptionLength || 30),
|
|
123
|
+
requireBodyMatch: unwrapScrollValue(c.requireBodyMatch, defaultScrollConfig.requireBodyMatch ?? true),
|
|
124
|
+
strictContentCheck: unwrapScrollValue(c.strictContentCheck, defaultScrollConfig.strictContentCheck ?? true),
|
|
34
125
|
personaPrompt: unwrapScrollValue(c.personaPrompt, defaultScrollConfig.personaPrompt || ''),
|
|
35
|
-
minScore: unwrapScrollValue(c.minScore, defaultScrollConfig.minScore ||
|
|
126
|
+
minScore: unwrapScrollValue(c.minScore, defaultScrollConfig.minScore || 80),
|
|
36
127
|
dailyRecommendCap: unwrapScrollValue(c.dailyRecommendCap, defaultScrollConfig.dailyRecommendCap || 5),
|
|
37
128
|
scrollIntervalMinutes: unwrapScrollValue(c.scrollIntervalMinutes, defaultScrollConfig.scrollIntervalMinutes || 60),
|
|
38
129
|
autoScrollEnabled: unwrapScrollValue(c.autoScrollEnabled, defaultScrollConfig.autoScrollEnabled ?? true),
|
|
39
130
|
likeOnRecommend: unwrapScrollValue(c.likeOnRecommend, defaultScrollConfig.likeOnRecommend ?? true),
|
|
40
131
|
batchSize: unwrapScrollValue(c.batchSize, defaultScrollConfig.batchSize || 15),
|
|
132
|
+
maxRecommendsPerRun: unwrapScrollValue(c.maxRecommendsPerRun, defaultScrollConfig.maxRecommendsPerRun || 1),
|
|
133
|
+
minPostReadDelaySeconds: unwrapScrollValue(c.minPostReadDelaySeconds, defaultScrollConfig.minPostReadDelaySeconds || 3),
|
|
134
|
+
maxPostReadDelaySeconds: unwrapScrollValue(c.maxPostReadDelaySeconds, defaultScrollConfig.maxPostReadDelaySeconds || 7),
|
|
135
|
+
cooldownOnBlockMinutes: unwrapScrollValue(c.cooldownOnBlockMinutes, defaultScrollConfig.cooldownOnBlockMinutes || 30),
|
|
41
136
|
};
|
|
42
137
|
}
|
|
43
138
|
export function buildScrollConfigSchema(z) {
|
|
@@ -46,18 +141,54 @@ export function buildScrollConfigSchema(z) {
|
|
|
46
141
|
.string()
|
|
47
142
|
.default('')
|
|
48
143
|
.description('主人的 Instagram 使用者名稱 (發現好文時將在留言區 @主人推薦)'),
|
|
144
|
+
fuzzySearchEnabled: z
|
|
145
|
+
.boolean()
|
|
146
|
+
.default(true)
|
|
147
|
+
.description('是否啟用模糊搜尋探索貼文 (預設開啟,解決單純 Tags 搜尋抓到 0 則貼文的問題)'),
|
|
148
|
+
fuzzyKeywords: z
|
|
149
|
+
.array(z.string())
|
|
150
|
+
.default(['ai', 'deepseek', 'coding', 'tech', 'gadgets'])
|
|
151
|
+
.description('模糊搜尋關鍵字清單 (結合綜合 SERP、Reels 搜尋與相關創作者貼文)'),
|
|
152
|
+
tagSearchEnabled: z
|
|
153
|
+
.boolean()
|
|
154
|
+
.default(false)
|
|
155
|
+
.description('是否啟用主題標籤 #Tag 搜尋 (預設關閉,保留並可用按鈕隨時開關)'),
|
|
49
156
|
interestTags: z
|
|
50
157
|
.array(z.string())
|
|
51
|
-
.default(['ai', 'deepseek', 'coding', 'tech'])
|
|
52
|
-
.description('
|
|
158
|
+
.default(['ai', 'deepseek', 'coding', 'tech', 'gadgets'])
|
|
159
|
+
.description('探索的主題標籤清單 (保留設定,當開啟 Tags 搜尋按鈕時使用)'),
|
|
160
|
+
userInterests: z
|
|
161
|
+
.string()
|
|
162
|
+
.default(defaultScrollConfig.userInterests)
|
|
163
|
+
.description('主人喜歡的內容風格與領域詳細描述 (AI 將依此深度審查貼文實質內容)'),
|
|
164
|
+
positiveKeywords: z
|
|
165
|
+
.array(z.string())
|
|
166
|
+
.default(defaultScrollConfig.positiveKeywords)
|
|
167
|
+
.description('正文必須討論或偏好的正面核心關鍵字清單'),
|
|
168
|
+
negativeKeywords: z
|
|
169
|
+
.array(z.string())
|
|
170
|
+
.default(defaultScrollConfig.negativeKeywords)
|
|
171
|
+
.description('負向排除關鍵字 (命中時直接拒絕推薦,避免廣告、抽獎或廢文)'),
|
|
172
|
+
minCaptionLength: z
|
|
173
|
+
.number()
|
|
174
|
+
.default(30)
|
|
175
|
+
.description('正文有效文字最小字數要求 (扣除 #標籤 後,過短或無實質內容者直接過濾)'),
|
|
176
|
+
requireBodyMatch: z
|
|
177
|
+
.boolean()
|
|
178
|
+
.default(true)
|
|
179
|
+
.description('正文實質內容必須命中主人偏好主題 (拒絕僅在文末蹭 tag 的貼文)'),
|
|
180
|
+
strictContentCheck: z
|
|
181
|
+
.boolean()
|
|
182
|
+
.default(true)
|
|
183
|
+
.description('啟用嚴格內容審查機制 (結合 LLM / 正文語義深度質檢)'),
|
|
53
184
|
personaPrompt: z
|
|
54
185
|
.string()
|
|
55
186
|
.default(defaultScrollConfig.personaPrompt)
|
|
56
187
|
.description('評估貼文趣味度與撰寫 @主人推薦語的人設提示詞'),
|
|
57
188
|
minScore: z
|
|
58
189
|
.number()
|
|
59
|
-
.default(
|
|
60
|
-
.description('推坑門檻分數 (0-100,達標才在貼文下留言
|
|
190
|
+
.default(80)
|
|
191
|
+
.description('推坑門檻分數 (0-100,達標才在貼文下留言 @主人,避免隨便推薦)'),
|
|
61
192
|
dailyRecommendCap: z
|
|
62
193
|
.number()
|
|
63
194
|
.default(5)
|
|
@@ -78,6 +209,22 @@ export function buildScrollConfigSchema(z) {
|
|
|
78
209
|
.number()
|
|
79
210
|
.default(15)
|
|
80
211
|
.description('每輪滾動瀏覽抓取的貼文筆數 (10-30)'),
|
|
212
|
+
maxRecommendsPerRun: z
|
|
213
|
+
.number()
|
|
214
|
+
.default(1)
|
|
215
|
+
.description('單次滾動瀏覽最多推坑留言則數 (避免短時間連發觸發官方風控)'),
|
|
216
|
+
minPostReadDelaySeconds: z
|
|
217
|
+
.number()
|
|
218
|
+
.default(3)
|
|
219
|
+
.description('每掃描一則貼文前人造擬真閱讀微延遲最小秒數'),
|
|
220
|
+
maxPostReadDelaySeconds: z
|
|
221
|
+
.number()
|
|
222
|
+
.default(7)
|
|
223
|
+
.description('每掃描一則貼文前人造擬真閱讀微延遲最大秒數'),
|
|
224
|
+
cooldownOnBlockMinutes: z
|
|
225
|
+
.number()
|
|
226
|
+
.default(30)
|
|
227
|
+
.description('遇到 Action Block / 頻率限制時自動熔斷暫停分鐘數'),
|
|
81
228
|
});
|
|
82
229
|
}
|
|
83
230
|
//# sourceMappingURL=config.js.map
|