"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.query = exports.resendLast = exports.view = exports.unsubAll = exports.unsub = exports.list = exports.sub = exports.parseCmd = void 0; const fs = require("fs"); const path = require("path"); const datetime_1 = require("./datetime"); const loggers_1 = require("./loggers"); const twitter_1 = require("./twitter"); const utils_1 = require("./utils"); const logger = (0, loggers_1.getLogger)('command'); function parseCmd(message) { 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 === null || strs === void 0 ? void 0 : strs.length) ? strs[0].length ? strs[0].substring(0, 1) === '/' ? strs[0].substring(1) : '' : '' : ''; const args = (strs !== null && strs !== void 0 ? 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, }; } exports.parseCmd = parseCmd; function linkFinder(checkedMatch, chat, lock) { var _a; const normalizedLink = (0, twitter_1.linkBuilder)(twitter_1.ScreenNameNormalizer.normalize(checkedMatch[0]), (_a = checkedMatch[1]) === null || _a === void 0 ? void 0 : _a.toLowerCase()); 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.toString() === chatID.toString() && chat.chatType === chatType); return [link, index]; } function sub(chat, args, reply, lock, lockfile) { if (chat.chatType === 'temp') { return reply('请先添加机器人为好友。'); } if (args.length === 0) { return reply('找不到要订阅媒体推文的链接。'); } const match = (0, twitter_1.parseLink)(args[0]); if (!match) { return reply(`订阅链接格式错误: 示例: https://twitter.com/Saito_Shuka https://twitter.com/_satou_riko/lists/lovelive https://twitter.com/TomoyoKurosawa/status/1294613494860361729`); } let offset = '0'; if (match[1]) { const matchStatus = /\/status\/(\d+)/.exec(match[1]); if (matchStatus) { offset = utils_1.BigNumOps.plus(matchStatus[1], '-1'); delete match[1]; } } const subscribeTo = (link, config = {}) => { const { addNew = false, msg = `已为此聊天订阅 ${link} 的媒体推文` } = config; if (addNew) { lock.feed.push(link); lock.threads[link] = { offset, 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 [realLink, index] = linkFinder(match, chat, lock); if (index > -1) return reply('此聊天已订阅此链接。'); if (realLink) return subscribeTo(realLink); const [rawUserName, more] = match; if (rawUserName.toLowerCase() === 'i' && /lists\/(\d+)/.exec(more)) { return subscribeTo((0, twitter_1.linkBuilder)('i', more), { addNew: true }); } twitter_1.ScreenNameNormalizer.normalizeLive(rawUserName).then(userName => { if (!userName) return reply(`找不到用户 ${rawUserName.replace(/^@?(.*)$/, '@$1')}。`); const link = (0, twitter_1.linkBuilder)(userName, more); const msg = (offset === '0') ? undefined : `已为此聊天订阅 ${link} 的媒体动态并回溯到此动态 ID(含)之后的第一条媒体动态。 (参见:https://blog.twitter.com/engineering/en_us/a/2010/announcing-snowflake.html)`; subscribeTo(link, { addNew: true, msg }); }); } exports.sub = sub; function unsubAll(chat, args, reply, lock, lockfile) { if (chat.chatType === 'temp') { return reply('请先添加机器人为好友。'); } Object.entries(lock.threads).forEach(([link, { subscribers }]) => { const index = subscribers.findIndex(({ chatID, chatType }) => chat.chatID.toString() === chatID.toString() && chat.chatType === chatType); 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(`已为此聊天退订所有媒体推文链接。`); } exports.unsubAll = unsubAll; function unsub(chat, args, reply, lock, lockfile) { if (chat.chatType === 'temp') { return reply('请先添加机器人为好友。'); } if (args.length === 0) { return reply('找不到要退订媒体推文的链接。'); } const match = (0, twitter_1.parseLink)(args[0]); if (!match) { return reply('链接格式有误。'); } const [link, index] = linkFinder(match, chat, lock); if (index === -1) return list(chat, args, msg => reply('您没有订阅此链接的媒体推文。\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} 的媒体推文`); } } exports.unsub = unsub; function list(chat, _, reply, lock) { if (chat.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} ${(0, datetime_1.relativeDate)(lock.threads[key].updatedAt)}`); }); return reply('此聊天中订阅媒体推文的链接:\n' + links.join('\n')); } exports.list = list; function view(chat, args, reply) { if (args.length === 0 || !args[0]) { return reply('找不到要查看的链接或表达式。'); } const match = /^(last(?:|-(\d+))@[^\/?#]+)$/.exec(args[0]) || /^(?:.*twitter.com\/[^\/?#]+\/status\/)?(\d+)/.exec(args[0]); if (!match) { return reply(`链接或表达式格式有误。 示例: https://twitter.com/TomoyoKurosawa/status/1486136914864345092 1486136914864345092 (注:直接查看指定推文时并不会区分其媒体属性) last@TomoyoKurosawa last-1@sunflower930316,noreps=off,norts=on (表达式筛选参数详见 /help twitterpic_query)`); } if (Math.abs(Number(match[2])) >= 50) { return reply('表达式中指定的回溯数量超出取值范围。'); } let forceRefresh; for (const arg of args.slice(1)) { const optMatch = /^(force|refresh)=(.*)/.exec(arg); if (!optMatch) return reply(`未定义的查看参数:${arg}。`); forceRefresh = { on: true, off: false }[optMatch[2]]; } try { (0, twitter_1.sendTweet)(match[1], chat, forceRefresh); } catch (e) { reply('推特机器人尚未加载完毕,请稍后重试。'); } } exports.view = view; function resendLast(chat, args, reply) { view(chat, [(args[0] || '').replace(/^@?(.+)$/, 'last@$1'), 'refresh=on'], reply); } exports.resendLast = resendLast; function query(chat, args, reply) { if (args.length === 0 || !args[0]) { return reply('找不到要查询的用户。'); } const match = /twitter.com\/([^\/?#]+)/.exec(args[0]) || /^([^\/?#]+)$/.exec(args[0]); if (!match) { return reply('链接或用户名格式有误。'); } const conf = { username: match[1], noreps: 'on', norts: 'off' }; const confZH = { count: '数量上限', since: '起始点', until: '结束点', noreps: '忽略回复推文(on/off)', norts: '忽略原生转推(on/off)', }; for (const arg of args.slice(1)) { const optMatch = /^(count|since|until|noreps|norts)=(.*)/.exec(arg); if (!optMatch) return reply(`未定义的查询参数:${arg}。`); const optKey = optMatch[1]; if (optMatch.length === 1) return reply(`查询${confZH[optKey]}参数格式有误。`); conf[optKey] = optMatch[2]; if (optMatch[2] === '') return reply(`查询${confZH[optKey]}参数值不可为空。`); } if (conf.count !== undefined && !Number(conf.count) || Math.abs(Number(conf.count)) > 50) { return reply('查询数量上限参数为零、非数值或超出取值范围。'); } try { (0, twitter_1.sendTimeline)(conf, chat); } catch (e) { logger.error(`error querying timeline, error: ${e}`); reply('推特机器人尚未加载完毕,请稍后重试。'); } } exports.query = query;