/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/member-delimiter-style */ /* eslint-disable prefer-arrow/prefer-arrow-functions */ import * as fs from 'fs'; import * as path from 'path'; import { relativeDate } from './datetime'; import { getLogger } from './loggers'; import { sendAllStories, sendStory, sendTimeline, ScreenNameNormalizer as normalizer, linkBuilder, parseLink } from './twitter'; const logger = getLogger('command'); function parseCmd(message: string): { cmd: string; args: string[]; } { message = message.trim(); message = message.replace('\\\\', '\\0x5c'); message = message.replace('\\\"', '\\0x22'); message = message.replace('\\\'', '\\0x27'); const strs = message.match(/'[\s\S]*?'|(?:\S+=)?"[\s\S]*?"|\S+/mg); const cmd = strs?.length ? strs[0].length ? strs[0].substring(0, 1) === '/' ? strs[0].substring(1) : '' : '' : ''; const args = (strs ?? []).slice(1).map(arg => { arg = arg.replace(/^(\S+=)?["']+(?!.*=)|["']+$/g, '$1'); arg = arg.replace('\\0x27', '\\\''); arg = arg.replace('\\0x22', '\\\"'); arg = arg.replace('\\0x5c', '\\\\'); return arg; }); return { cmd, args, }; } function linkFinder(userName: string, chat: IChat, lock: ILock): [string, number] { const normalizedLink = linkBuilder({userName}); const link = Object.keys(lock.threads).find(realLink => normalizedLink === realLink.replace(/\/@/, '/').toLowerCase() ); if (!link) return [null, -1]; const index = lock.threads[link].subscribers.findIndex(({chatID, chatType}) => chat.chatID === chatID && chat.chatType === chatType ); return [link, index]; } function sub(chat: IChat, args: string[], reply: (msg: string) => any, lock: ILock, lockfile: string ): void { if (chat.chatType === ChatType.Temp) { return reply('请先添加机器人为好友。'); } if (args.length === 0) { return reply('找不到要订阅 Instagram 限时动态的链接。'); } const matched = parseLink(args[0]); if (!matched) { return reply(`订阅链接格式错误: 示例: https://www.instagram.com/tomoyo_kurosawa_/`); } const subscribeTo = (link: string, config: {id?: number, msg?: string} = {}) => { const {id, msg = `已为此聊天订阅 ${link} 的 Instagram 限时动态`} = config; if (id) { lock.feed.push(link); lock.threads[link] = { id, offset: '0', subscribers: [], updatedAt: '', }; } lock.threads[link].subscribers.push(chat); logger.warn(`chat ${JSON.stringify(chat)} has subscribed ${link}`); fs.writeFileSync(path.resolve(lockfile), JSON.stringify(lock)); reply(msg); }; const tryFindSub = (userName: string) => { const [realLink, index] = linkFinder(userName, chat, lock); if (index > -1) { reply('此聊天已订阅此链接的 Instagram 限时动态。'); return true; } if (realLink) { subscribeTo(realLink); return true; } return false; }; const newSub = (userName: string) => { const link = linkBuilder(matched); subscribeTo(link, {id: Number(userName.split(':')[1])}); }; if (!tryFindSub(matched.userName)) { normalizer.normalizeLive(matched.userName).then(userName => { if (!userName) return reply(`找不到用户 ${matched.userName.replace(/^@?(.*)$/, '@$1')}。`); else newSub(userName); }); } } function unsubAll(chat: IChat, args: string[], reply: (msg: string) => any, lock: ILock, lockfile: string ): void { if (chat.chatType === ChatType.Temp) { return reply('请先添加机器人为好友。'); } Object.entries(lock.threads).forEach(([link, {subscribers}]) => { const index = subscribers.indexOf(chat); if (index === -1) return; subscribers.splice(index, 1); fs.writeFileSync(path.resolve(lockfile), JSON.stringify(lock)); logger.warn(`chat ${JSON.stringify(chat)} has unsubscribed ${link}`); }); return reply(`已为此聊天退订所有 Instagram 限时动态链接。`); } function unsub(chat: IChat, args: string[], reply: (msg: string) => any, lock: ILock, lockfile: string ): void { if (chat.chatType === ChatType.Temp) { return reply('请先添加机器人为好友。'); } if (args.length === 0) { return reply('找不到要退订 Instagram 限时动态的链接。'); } const match = parseLink(args[0])?.userName; if (!match) { return reply('链接格式有误。'); } const [link, index] = linkFinder(match, chat, lock); if (index === -1) return list(chat, args, msg => reply('您没有订阅此链接的 Instagram 限时动态。\n' + msg), lock); else { lock.threads[link].subscribers.splice(index, 1); fs.writeFileSync(path.resolve(lockfile), JSON.stringify(lock)); logger.warn(`chat ${JSON.stringify(chat)} has unsubscribed ${link}`); return reply(`已为此聊天退订 ${link} 的 Instagram 限时动态`); } } function list(chat: IChat, _: string[], reply: (msg: string) => any, lock: ILock): void { if (chat.chatType === ChatType.Temp) { return reply('请先添加机器人为好友。'); } const links = []; Object.keys(lock.threads).forEach(key => { if (lock.threads[key].subscribers.find(({chatID, chatType}) => chat.chatID === chatID && chat.chatType === chatType )) links.push(`${key} ${relativeDate(lock.threads[key].updatedAt)}`); }); return reply('此聊天中订阅的 Instagram 限时动态动态链接:\n' + links.join('\n')); } function view(chat: IChat, args: string[], reply: (msg: string) => any): void { const promptOnError = (func: (...args: T[]) => void) => (...args: T[]): void => { try { func(...args); } catch (e) { reply('机器人尚未加载完毕,请稍后重试。'); } } if (args.length === 0) { return reply('找不到要查看 Instagram 限时动态的链接。'); } const match = parseLink(args[0]); if (!match?.userName) { return reply('链接格式有误。'); } if (match?.storyId) { return promptOnError(sendStory)(match.userName, match.storyId, chat); } const conf: { skip?: number, count?: number, } = {}; const confZH: Record = { count: '最大查看数量', skip: '跳过数量', }; for (const arg of args.slice(1)) { const optMatch = /^(count|skip)=(.*)/.exec(arg); if (!optMatch) return reply(`未定义的查看参数:${arg}。`); const optKey = optMatch[1] as keyof typeof confZH; if (optMatch.length === 1 || !/^-?\d*$/.test(optMatch[2])) return reply(`${confZH[optKey]}参数应为数值。`); if (optMatch[2] === '') return reply(`${confZH[optKey]}参数值不可为空。`); conf[optKey] = Number(optMatch[2]); } promptOnError(sendAllStories)(match.userName, chat, conf.skip, conf.count); } function query(chat: IChat, args: string[], reply: (msg: string) => any): void { if (args.length === 0) { return reply('找不到要查询 Instagram 限时动态的用户。'); } const match = parseLink(args[0])?.userName; if (!match) { return reply('链接格式有误。'); } try { sendTimeline(match, chat); } catch (e) { logger.error(`error querying timeline, error: ${e}`); reply('机器人尚未加载完毕,请稍后重试。'); } } export { parseCmd, sub, list, unsub, unsubAll, view, query };