koishi-plugin-ll-douyin 0.1.0 → 0.1.2
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/dist/index.js +1 -1
- package/dist/style.css +1 -1
- package/lib/index.js +98 -16
- package/lib/store.js +30 -1
- package/package.json +36 -36
- package/readme.md +3 -0
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineComponent as ee,ref as i,onMounted as le,resolveComponent as d,openBlock as
|
|
1
|
+
import{defineComponent as ee,ref as i,onMounted as le,resolveComponent as d,openBlock as c,createBlock as $,withCtx as a,createVNode as s,createElementVNode as n,withKeys as te,createTextVNode as u,createElementBlock as _,Fragment as E,renderList as I,toDisplayString as o,createCommentVNode as K}from"vue";import{send as ae}from"@koishijs/client";const se={class:"head"},ne={class:"row"},oe={key:0,class:"results"},ue={class:"meta"},ie={class:"name"},de={class:"sub"},ce={key:1,class:"hint pad"},re={class:"row"},ve={class:"hint"},me={key:0,class:"hint pad"},fe={class:"card-head"},_e={class:"hint"},pe={key:0,class:"notice"},ye=ee({__name:"page",setup(g){const w=i(""),p=i(false),C=i(false),y=i([]),U=i([]),h=i(""),V=i([]),m=i(""),B=i([]),N=i(false),x=i(""),v=i({accounts:0,subs:0,checks:0,pushes:0,lastCheck:"",lastError:"",cookie:false,cookieOk:false}),f=(l,...e)=>ae(l,...e);function T(l){return l?l>=1e4?(l/1e4).toFixed(1)+" 万":String(l):"0"}function r(l){x.value=l,setTimeout(()=>{x.value===l&&(x.value="")},4e3)}async function O(){const l=w.value.trim();if(l){p.value=true,C.value=false;try{y.value=await f("ll-douyin/search",l)}catch(e){y.value=[],r("搜索失败:"+((e==null?void 0:e.message)||e))}finally{p.value=false,C.value=true}}}async function j(){try{U.value=await f("ll-douyin/bots"),!h.value&&U.value.length&&(h.value=U.value[0].key,await P())}catch(l){r("读取机器人失败:"+((l==null?void 0:l.message)||l))}}async function P(){const l=U.value.find(e=>e.key===h.value);if(l)try{V.value=await f("ll-douyin/groups",l.platform,l.selfId),V.value.some(e=>e.umo===m.value)||(m.value="")}catch(e){V.value=[],r("拉群列表失败:"+((e==null?void 0:e.message)||e))}}async function S(){try{B.value=await f("ll-douyin/list")}catch(l){r("读取订阅失败:"+((l==null?void 0:l.message)||l))}}async function q(){try{v.value=await f("ll-douyin/state")}catch{}}async function F(){N.value=true;try{await Promise.all([j(),S(),q()])}finally{N.value=false}}async function D(l){if(!m.value)return r("先在②里选一个群");try{const e=await f("ll-douyin/subscribe",{umo:m.value,secUid:l.secUid});r(e!=null&&e.added?`已订阅:${l.nickname||l.secUid}`:"这个群已经订过该账号"),await S()}catch(e){r("订阅失败:"+((e==null?void 0:e.message)||e))}}async function G(l){if(confirm(`取消订阅「${l.nickname||l.secUid}」?`))try{await f("ll-douyin/unsubscribe",{umo:l.umo,secUid:l.secUid}),await S()}catch(e){r("取消失败:"+((e==null?void 0:e.message)||e))}}async function H(l){l._testing=true;try{const e=await f("ll-douyin/test",{umo:l.umo,secUid:l.secUid});r((e==null?void 0:e.message)||"测试推送已发送"),await q()}catch(e){r("测试推送失败:"+((e==null?void 0:e.message)||e))}finally{l._testing=false}}return le(F),(l,e)=>{const z=d("el-button"),J=d("el-input"),R=d("el-avatar"),A=d("el-card"),L=d("el-option"),M=d("el-select"),b=d("el-table-column"),W=d("el-tag"),X=d("el-table"),k=d("el-descriptions-item"),Y=d("el-descriptions"),Z=d("k-layout");return c(),$(Z,null,{header:a(()=>[n("div",se,[e[4]||(e[4]=n("span",{class:"title"},"抖音订阅推送",-1)),s(z,{size:"small",loading:N.value,onClick:F},{default:a(()=>[...e[3]||(e[3]=[u("刷新",-1)])]),_:1},8,["loading"])])]),default:a(()=>[s(A,{class:"card",shadow:"never"},{header:a(()=>[...e[5]||(e[5]=[n("div",{class:"card-head"},[n("b",null,"① 搜索账号"),n("span",{class:"hint"},"主页链接 / 抖音号 / 昵称都可以,链接成功率最高")],-1)])]),default:a(()=>[n("div",ne,[s(J,{modelValue:w.value,"onUpdate:modelValue":e[0]||(e[0]=t=>w.value=t),placeholder:"https://www.douyin.com/user/MS4wLjABAAAA... 或 抖音号",clearable:"",onKeyup:te(O,["enter"])},null,8,["modelValue"]),s(z,{type:"primary",loading:p.value,onClick:O},{default:a(()=>[...e[6]||(e[6]=[u("搜索账号",-1)])]),_:1},8,["loading"])]),y.value.length?(c(),_("div",oe,[(c(true),_(E,null,I(y.value,t=>(c(),_("div",{key:t.secUid,class:"item"},[s(R,{src:t.avatar,size:42},{default:a(()=>[u(o((t.nickname||"?").slice(0,1)),1)]),_:2},1032,["src"]),n("div",ue,[n("div",ie,o(t.nickname||"(未命名)"),1),n("div",de,o(t.uniqueId||t.secUid)+" · 粉丝 "+o(T(t.followerCount)),1)]),s(z,{size:"small",type:"primary",disabled:!m.value,onClick:Q=>D(t)},{default:a(()=>[...e[7]||(e[7]=[u(" 订阅到此目标 ",-1)])]),_:1},8,["disabled","onClick"])]))),128))])):C.value?(c(),_("div",ce,"没搜到。抖音站内搜索对高频请求会返回空结果,建议直接粘主页链接。")):K("v-if",true)]),_:1}),s(A,{class:"card",shadow:"never"},{header:a(()=>[...e[8]||(e[8]=[n("div",{class:"card-head"},[n("b",null,"② 推送目标"),n("span",{class:"hint"},"先选机器人,再选群")],-1)])]),default:a(()=>[n("div",re,[s(M,{modelValue:h.value,"onUpdate:modelValue":e[1]||(e[1]=t=>h.value=t),placeholder:"选择机器人",style:{width:"200px"},onChange:P},{default:a(()=>[(c(true),_(E,null,I(U.value,t=>(c(),$(L,{key:t.key,label:t.label,value:t.key},null,8,["label","value"]))),128))]),_:1},8,["modelValue"]),s(M,{modelValue:m.value,"onUpdate:modelValue":e[2]||(e[2]=t=>m.value=t),placeholder:"选择群 / 频道",filterable:"",style:{width:"280px"}},{default:a(()=>[(c(true),_(E,null,I(V.value,t=>(c(),$(L,{key:t.umo,label:t.name,value:t.umo},null,8,["label","value"]))),128))]),_:1},8,["modelValue"]),n("span",ve,"当前:"+o(m.value||"未选择"),1)]),h.value&&!V.value.length?(c(),_("div",me," 官方 QQ 没有「列出群」的接口,群列表只能靠机器人收到过消息来积累:在群里 @ 一下机器人(或发条指令), 再点右上角刷新,那个群就会出现。 ")):K("v-if",true)]),_:1}),s(A,{class:"card",shadow:"never"},{header:a(()=>[n("div",fe,[e[9]||(e[9]=n("b",null,"③ 已订阅",-1)),n("span",_e,"共 "+o(B.value.length)+" 条",1)])]),default:a(()=>[s(X,{data:B.value,size:"small","empty-text":"还没有订阅"},{default:a(()=>[s(b,{prop:"targetName",label:"推送目标","min-width":"170","show-overflow-tooltip":""}),s(b,{label:"账号","min-width":"160","show-overflow-tooltip":""},{default:a(({row:t})=>[u(o(t.nickname||t.secUid),1)]),_:1}),s(b,{prop:"uniqueId",label:"抖音号",width:"140","show-overflow-tooltip":""}),s(b,{prop:"seen",label:"已记录",width:"86"}),s(b,{label:"状态",width:"92"},{default:a(({row:t})=>[s(W,{type:t.failCount?"danger":"success",size:"small"},{default:a(()=>[u(o(t.failCount?"失败 "+t.failCount:"正常"),1)]),_:2},1032,["type"])]),_:1}),s(b,{label:"操作",width:"190"},{default:a(({row:t})=>[s(z,{size:"small",loading:t._testing,onClick:Q=>H(t)},{default:a(()=>[...e[10]||(e[10]=[u("测试推送",-1)])]),_:1},8,["loading","onClick"]),s(z,{size:"small",type:"danger",plain:"",onClick:Q=>G(t)},{default:a(()=>[...e[11]||(e[11]=[u("取消",-1)])]),_:1},8,["onClick"])]),_:1})]),_:1},8,["data"])]),_:1}),s(A,{class:"card",shadow:"never"},{header:a(()=>[...e[12]||(e[12]=[n("div",{class:"card-head"},[n("b",null,"④ 运行状态")],-1)])]),default:a(()=>[s(Y,{column:3,size:"small",border:""},{default:a(()=>[s(k,{label:"Cookie"},{default:a(()=>[u(o(v.value.cookieOk?"有效":v.value.cookie?"未验证":"未配置"),1)]),_:1}),s(k,{label:"账号 / 订阅"},{default:a(()=>[u(o(v.value.accounts)+" / "+o(v.value.subs),1)]),_:1}),s(k,{label:"最近检查"},{default:a(()=>[u(o(v.value.lastCheck||"—"),1)]),_:1}),s(k,{label:"累计检查"},{default:a(()=>[u(o(v.value.checks),1)]),_:1}),s(k,{label:"累计推送"},{default:a(()=>[u(o(v.value.pushes),1)]),_:1}),s(k,{label:"最近错误"},{default:a(()=>[u(o(v.value.lastError||"—"),1)]),_:1})]),_:1})]),_:1}),x.value?(c(),_("div",pe,o(x.value),1)):K("v-if",true)]),_:1})}}}),he=(g,w)=>{const p=g.__vccOpts||g;for(const[C,y]of w)p[C]=y;return p},be=he(ye,[["__scopeId","data-v-75e9c86b"]]),we=g=>{g.page({id:"ll-douyin",path:"/ll-douyin",name:"抖音订阅",desc:"订阅抖音账号,新作品自动推送",icon:"activity:default",order:200,component:be})};export{we as default};
|
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.head[data-v-
|
|
1
|
+
.head[data-v-75e9c86b]{display:flex;align-items:center;gap:12px}.title[data-v-75e9c86b]{font-size:18px;font-weight:600}.card[data-v-75e9c86b]{margin-bottom:14px}.card-head[data-v-75e9c86b]{display:flex;align-items:baseline;gap:10px}.hint[data-v-75e9c86b]{color:var(--k-text-secondary, #8a9099);font-size:12px}.hint.pad[data-v-75e9c86b]{padding:8px 0 2px}.row[data-v-75e9c86b]{display:flex;gap:10px;align-items:center;flex-wrap:wrap}.results[data-v-75e9c86b]{margin-top:12px;display:flex;flex-direction:column;gap:8px}.item[data-v-75e9c86b]{display:flex;align-items:center;gap:12px;padding:8px 10px;border:1px solid var(--k-border-color, #e3e5e9);border-radius:8px}.meta[data-v-75e9c86b]{flex:1;min-width:0}.name[data-v-75e9c86b]{font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sub[data-v-75e9c86b]{color:var(--k-text-secondary, #8a9099);font-size:12px}.notice[data-v-75e9c86b]{position:sticky;bottom:0;margin-top:4px;padding:8px 12px;background:var(--k-color-primary-2, #eaf0ff);border-radius:6px;font-size:13px}
|
package/lib/index.js
CHANGED
|
@@ -42,7 +42,9 @@ exports.Config = Schema.object({
|
|
|
42
42
|
}).description('推送内容'),
|
|
43
43
|
|
|
44
44
|
video: Schema.object({
|
|
45
|
-
mode: Schema.union(['video', 'file'
|
|
45
|
+
mode: Schema.union(['video', 'file', 'link'])
|
|
46
|
+
.description('视频投递方式:video = 视频消息,file = 群文件,link = 只发封面 + 链接(最省流量,不会被视频审核卡)')
|
|
47
|
+
.default('video'),
|
|
46
48
|
delivery: Schema.union(['auto', 'direct', 'download']).description('直链投递 / 先下载再投递').default('auto'),
|
|
47
49
|
sizeLimitMb: Schema.number().description('体积上限(MB),超过则改发链接').min(0).default(100),
|
|
48
50
|
coverOnFallback: Schema.boolean().description('降级发链接时附带封面').default(true),
|
|
@@ -171,6 +173,17 @@ function apply(ctx, config) {
|
|
|
171
173
|
return `data:${mime};base64,${body.toString('base64')}`
|
|
172
174
|
}
|
|
173
175
|
|
|
176
|
+
/** 封面图元素,需要本地化时转 data URI,失败退回直链。 */
|
|
177
|
+
async function coverEl(url, localize) {
|
|
178
|
+
if (!url) return null
|
|
179
|
+
if (!localize) return h.image(url)
|
|
180
|
+
try {
|
|
181
|
+
return h.image(await asDataUri(url, 20 * MB, 'image/jpeg'))
|
|
182
|
+
} catch (e) {
|
|
183
|
+
return h.image(url)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
174
187
|
async function buildPlan(post, platform) {
|
|
175
188
|
const opt = config.content
|
|
176
189
|
const lines = []
|
|
@@ -205,12 +218,17 @@ function apply(ctx, config) {
|
|
|
205
218
|
const url = config.video.preferNoWatermark
|
|
206
219
|
? postNoWatermarkUrl(post) || post.videoUrl
|
|
207
220
|
: post.videoUrl || postNoWatermarkUrl(post)
|
|
221
|
+
const limit = Math.max(config.video.sizeLimitMb, 0) * MB
|
|
208
222
|
|
|
209
|
-
|
|
223
|
+
// mode=link:不发视频本体,只发「封面 + 链接」
|
|
224
|
+
if (config.video.mode === 'link') {
|
|
225
|
+
lines.push(`🔗 ${link}`)
|
|
226
|
+
if (config.video.coverOnFallback && post.coverUrl) media.push(await coverEl(post.coverUrl, localize))
|
|
227
|
+
kind = 'link'
|
|
228
|
+
} else if (!url) {
|
|
210
229
|
lines.push(`🔗 ${link}`)
|
|
211
230
|
kind = 'link'
|
|
212
231
|
} else {
|
|
213
|
-
const limit = Math.max(config.video.sizeLimitMb, 0) * MB
|
|
214
232
|
let size = 0
|
|
215
233
|
if (limit > 0) {
|
|
216
234
|
try {
|
|
@@ -224,7 +242,7 @@ function apply(ctx, config) {
|
|
|
224
242
|
degraded = true
|
|
225
243
|
lines.push(`🎬 视频约 ${(size / MB).toFixed(1)}MB,超过 ${config.video.sizeLimitMb}MB 上限,改为发送链接:`)
|
|
226
244
|
lines.push(`🔗 ${link}`)
|
|
227
|
-
if (config.video.coverOnFallback && post.coverUrl) media.push(
|
|
245
|
+
if (config.video.coverOnFallback && post.coverUrl) media.push(await coverEl(post.coverUrl, localize))
|
|
228
246
|
kind = 'link'
|
|
229
247
|
} else {
|
|
230
248
|
kind = config.video.mode === 'file' ? 'file' : 'video'
|
|
@@ -237,7 +255,7 @@ function apply(ctx, config) {
|
|
|
237
255
|
degraded = true
|
|
238
256
|
lines.push(`🎬 视频下载失败(${e.message}),改为发送链接:`)
|
|
239
257
|
lines.push(`🔗 ${link}`)
|
|
240
|
-
if (config.video.coverOnFallback && post.coverUrl) media.push(
|
|
258
|
+
if (config.video.coverOnFallback && post.coverUrl) media.push(await coverEl(post.coverUrl, localize))
|
|
241
259
|
kind = 'link'
|
|
242
260
|
}
|
|
243
261
|
}
|
|
@@ -256,7 +274,7 @@ function apply(ctx, config) {
|
|
|
256
274
|
// 媒体发不出去时的兜底:文本 + 链接 + 封面
|
|
257
275
|
fallback: {
|
|
258
276
|
text: [text, `🔗 ${link}`].filter(Boolean).join('\n'),
|
|
259
|
-
media: post.coverUrl ? [
|
|
277
|
+
media: post.coverUrl ? [await coverEl(post.coverUrl, localize)] : [],
|
|
260
278
|
},
|
|
261
279
|
}
|
|
262
280
|
}
|
|
@@ -522,6 +540,38 @@ function apply(ctx, config) {
|
|
|
522
540
|
return lines.join('\n')
|
|
523
541
|
})
|
|
524
542
|
|
|
543
|
+
// -------------------------------------------------------------------------
|
|
544
|
+
// 见过的群
|
|
545
|
+
//
|
|
546
|
+
// 官方 QQ 没有「列出机器人所在的群」这个接口:适配器的 getGuildList() 走的是
|
|
547
|
+
// 频道 API,机器人只在群里时它永远是空的。群 openid 只能从群消息事件里拿,
|
|
548
|
+
// 所以这里把收到过消息的会话记下来,后台的群列表才有东西可选。
|
|
549
|
+
// -------------------------------------------------------------------------
|
|
550
|
+
|
|
551
|
+
let lastGroupSave = 0
|
|
552
|
+
ctx.middleware((session, next) => {
|
|
553
|
+
try {
|
|
554
|
+
const channelId = session.channelId || session.userId
|
|
555
|
+
if (channelId && session.platform) {
|
|
556
|
+
store.touchGroup({
|
|
557
|
+
umo: `${session.platform}:${channelId}`,
|
|
558
|
+
platform: session.platform,
|
|
559
|
+
selfId: session.selfId,
|
|
560
|
+
channelId,
|
|
561
|
+
name: (session.guild && session.guild.name) || (session.channel && session.channel.name) || '',
|
|
562
|
+
})
|
|
563
|
+
// 每条消息都落盘太重,最多一分钟写一次
|
|
564
|
+
if (Date.now() - lastGroupSave > 60000) {
|
|
565
|
+
lastGroupSave = Date.now()
|
|
566
|
+
store.save()
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
} catch (e) {
|
|
570
|
+
// 记录群失败不能影响正常消息
|
|
571
|
+
}
|
|
572
|
+
return next()
|
|
573
|
+
})
|
|
574
|
+
|
|
525
575
|
// -------------------------------------------------------------------------
|
|
526
576
|
// 控制台页面 + 接口
|
|
527
577
|
// -------------------------------------------------------------------------
|
|
@@ -554,20 +604,52 @@ function apply(ctx, config) {
|
|
|
554
604
|
ctx.console.addListener('ll-douyin/state', () => snapshot())
|
|
555
605
|
|
|
556
606
|
ctx.console.addListener('ll-douyin/bots', () =>
|
|
557
|
-
ctx.bots
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
607
|
+
ctx.bots
|
|
608
|
+
.map((b) => ({
|
|
609
|
+
key: `${b.platform}|${b.selfId}`,
|
|
610
|
+
label: `${b.platform} · ${b.selfId}${b.user && b.user.name ? ' · ' + b.user.name : ''}`,
|
|
611
|
+
platform: b.platform,
|
|
612
|
+
selfId: b.selfId,
|
|
613
|
+
}))
|
|
614
|
+
// 同一个号通常有两个适配器:qq(群聊/单聊)和 qqguild(频道)。
|
|
615
|
+
// 群推送要用前者,排到前面,默认就选它。
|
|
616
|
+
.sort((a, b) => (a.platform === 'qq' ? -1 : b.platform === 'qq' ? 1 : 0))
|
|
563
617
|
)
|
|
564
618
|
|
|
565
619
|
ctx.console.addListener('ll-douyin/groups', async (platform, selfId) => {
|
|
620
|
+
const out = new Map()
|
|
621
|
+
const add = (umo, name) => {
|
|
622
|
+
if (umo && !out.has(umo)) out.set(umo, { umo, name: name || umo })
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// 1) 适配器能列的(只有频道场景才有内容)
|
|
566
626
|
const bot = ctx.bots.find((b) => b.platform === platform && String(b.selfId) === String(selfId))
|
|
567
|
-
if (
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
627
|
+
if (bot && typeof bot.getGuildList === 'function') {
|
|
628
|
+
try {
|
|
629
|
+
const res = await bot.getGuildList()
|
|
630
|
+
const list = Array.isArray(res) ? res : (res && res.data) || []
|
|
631
|
+
for (const g of list) add(`${bot.platform}:${g.id}`, g.name || String(g.id))
|
|
632
|
+
} catch (e) {
|
|
633
|
+
// 群场景没有频道,正常
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// 2) 数据库里见过的频道/群
|
|
638
|
+
if (ctx.database) {
|
|
639
|
+
try {
|
|
640
|
+
const rows = await ctx.database.get('channel', { platform })
|
|
641
|
+
for (const row of rows) add(`${row.platform}:${row.id}`, row.name || '')
|
|
642
|
+
} catch (e) {
|
|
643
|
+
// 没装数据库就算了
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// 3) 本插件自己记录的(官方 QQ 群只能靠这个)
|
|
648
|
+
for (const g of store.groupsOf(platform, selfId)) {
|
|
649
|
+
add(g.umo, g.name || `群 ${String(g.channelId).slice(0, 6)}…`)
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return Array.from(out.values())
|
|
571
653
|
})
|
|
572
654
|
|
|
573
655
|
ctx.console.addListener('ll-douyin/search', async (keyword) => {
|
package/lib/store.js
CHANGED
|
@@ -17,7 +17,7 @@ const path = require('path')
|
|
|
17
17
|
const SCHEMA_VERSION = 1
|
|
18
18
|
|
|
19
19
|
function emptyData() {
|
|
20
|
-
return { version: SCHEMA_VERSION, accounts: {}, sessions: {} }
|
|
20
|
+
return { version: SCHEMA_VERSION, accounts: {}, sessions: {}, groups: {} }
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
class Store {
|
|
@@ -40,6 +40,7 @@ class Store {
|
|
|
40
40
|
version: raw.version || SCHEMA_VERSION,
|
|
41
41
|
accounts: raw.accounts || {},
|
|
42
42
|
sessions: raw.sessions || {},
|
|
43
|
+
groups: raw.groups || {},
|
|
43
44
|
}
|
|
44
45
|
} catch (e) {
|
|
45
46
|
const backup = this.file.replace(/\.json$/, '') + `.bad.${Math.floor(Date.now() / 1000)}.json`
|
|
@@ -58,6 +59,7 @@ class Store {
|
|
|
58
59
|
s.subs = s.subs || []
|
|
59
60
|
s.latestSynced = s.latestSynced || []
|
|
60
61
|
}
|
|
62
|
+
this.data.groups = this.data.groups || {}
|
|
61
63
|
this._loaded = true
|
|
62
64
|
}
|
|
63
65
|
|
|
@@ -235,6 +237,33 @@ class Store {
|
|
|
235
237
|
return Object.values(this.data.sessions).filter((s) => s.subs.length).length
|
|
236
238
|
}
|
|
237
239
|
|
|
240
|
+
// -- 见过的群(官方 QQ 没有群列表接口,只能靠消息事件积累) --------------
|
|
241
|
+
|
|
242
|
+
touchGroup(info) {
|
|
243
|
+
const g = this.data.groups || (this.data.groups = {})
|
|
244
|
+
const prev = g[info.umo] || {}
|
|
245
|
+
g[info.umo] = {
|
|
246
|
+
umo: info.umo,
|
|
247
|
+
platform: info.platform,
|
|
248
|
+
selfId: info.selfId || prev.selfId || '',
|
|
249
|
+
channelId: info.channelId,
|
|
250
|
+
name: info.name || prev.name || '',
|
|
251
|
+
lastSeen: Date.now(),
|
|
252
|
+
}
|
|
253
|
+
const keys = Object.keys(g)
|
|
254
|
+
if (keys.length > 300) {
|
|
255
|
+
keys.sort((a, b) => (g[a].lastSeen || 0) - (g[b].lastSeen || 0))
|
|
256
|
+
for (const k of keys.slice(0, keys.length - 300)) delete g[k]
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
groupsOf(platform, selfId) {
|
|
261
|
+
const g = this.data.groups || {}
|
|
262
|
+
return Object.values(g)
|
|
263
|
+
.filter((x) => (!platform || x.platform === platform) && (!selfId || String(x.selfId) === String(selfId)))
|
|
264
|
+
.sort((a, b) => (b.lastSeen || 0) - (a.lastSeen || 0))
|
|
265
|
+
}
|
|
266
|
+
|
|
238
267
|
dump() {
|
|
239
268
|
return this.data
|
|
240
269
|
}
|
package/package.json
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "koishi-plugin-ll-douyin",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Koishi plugin: 抖音订阅推送 —— 订阅抖音账号,新作品自动推送(图文发图、视频发视频),带控制台侧边栏管理页。",
|
|
5
|
-
"main": "lib/index.js",
|
|
6
|
-
"files": [
|
|
7
|
-
"lib",
|
|
8
|
-
"dist",
|
|
9
|
-
"readme.md"
|
|
10
|
-
],
|
|
11
|
-
"scripts": {
|
|
12
|
-
"build": "node build.js"
|
|
13
|
-
},
|
|
14
|
-
"keywords": [
|
|
15
|
-
"koishi",
|
|
16
|
-
"plugin",
|
|
17
|
-
"douyin",
|
|
18
|
-
"subscribe",
|
|
19
|
-
"qq",
|
|
20
|
-
"push"
|
|
21
|
-
],
|
|
22
|
-
"license": "MIT",
|
|
23
|
-
"peerDependencies": {
|
|
24
|
-
"koishi": "^4.18.0",
|
|
25
|
-
"@koishijs/plugin-console": "^5.11.0"
|
|
26
|
-
},
|
|
27
|
-
"devDependencies": {
|
|
28
|
-
"@koishijs/client": "^5.30.0"
|
|
29
|
-
},
|
|
30
|
-
"koishi": {
|
|
31
|
-
"description": {
|
|
32
|
-
"zh": "抖音订阅推送:轮询订阅账号的作品列表,图文发全部图片、视频发视频;支持体积上限降级、三级兜底(直链→下载重试→链接+封面)。自带控制台侧边栏管理页(搜索账号、选群订阅、测试推送、预览)。签名纯 JS 实现,无第三方依赖。",
|
|
33
|
-
"en": "Subscribe to Douyin accounts and push new posts: carousels as images, videos as videos, with size-limit degradation and a 3-stage fallback. Ships a console sidebar page for searching accounts, binding groups and testing pushes."
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "koishi-plugin-ll-douyin",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Koishi plugin: 抖音订阅推送 —— 订阅抖音账号,新作品自动推送(图文发图、视频发视频),带控制台侧边栏管理页。",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"lib",
|
|
8
|
+
"dist",
|
|
9
|
+
"readme.md"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "node build.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"koishi",
|
|
16
|
+
"plugin",
|
|
17
|
+
"douyin",
|
|
18
|
+
"subscribe",
|
|
19
|
+
"qq",
|
|
20
|
+
"push"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"koishi": "^4.18.0",
|
|
25
|
+
"@koishijs/plugin-console": "^5.11.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@koishijs/client": "^5.30.0"
|
|
29
|
+
},
|
|
30
|
+
"koishi": {
|
|
31
|
+
"description": {
|
|
32
|
+
"zh": "抖音订阅推送:轮询订阅账号的作品列表,图文发全部图片、视频发视频;支持体积上限降级、三级兜底(直链→下载重试→链接+封面)。自带控制台侧边栏管理页(搜索账号、选群订阅、测试推送、预览)。签名纯 JS 实现,无第三方依赖。",
|
|
33
|
+
"en": "Subscribe to Douyin accounts and push new posts: carousels as images, videos as videos, with size-limit degradation and a 3-stage fallback. Ships a console sidebar page for searching accounts, binding groups and testing pushes."
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
package/readme.md
CHANGED