123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- /* 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 = <T>(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<keyof typeof conf, string> = {
- 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 };
|