command.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.query = exports.view = exports.unsub = exports.list = exports.sub = exports.parseCmd = void 0;
  4. const fs = require("fs");
  5. const path = require("path");
  6. const datetime_1 = require("./datetime");
  7. const loggers_1 = require("./loggers");
  8. const twitter_1 = require("./twitter");
  9. const utils_1 = require("./utils");
  10. const logger = loggers_1.getLogger('command');
  11. function parseCmd(message) {
  12. message = message.trim();
  13. message = message.replace('\\\\', '\\0x5c');
  14. message = message.replace('\\\"', '\\0x22');
  15. message = message.replace('\\\'', '\\0x27');
  16. const strs = message.match(/'[\s\S]*?'|(?:\S+=)?"[\s\S]*?"|\S+/mg);
  17. const cmd = (strs === null || strs === void 0 ? void 0 : strs.length) ? strs[0].length ? strs[0].substring(0, 1) === '/' ? strs[0].substring(1) : '' : '' : '';
  18. const args = (strs !== null && strs !== void 0 ? strs : []).slice(1).map(arg => {
  19. arg = arg.replace(/^(\S+=)?["']+(?!.*=)|["']+$/g, '$1');
  20. arg = arg.replace('\\0x27', '\\\'');
  21. arg = arg.replace('\\0x22', '\\\"');
  22. arg = arg.replace('\\0x5c', '\\\\');
  23. return arg;
  24. });
  25. return {
  26. cmd,
  27. args,
  28. };
  29. }
  30. exports.parseCmd = parseCmd;
  31. function parseLink(link) {
  32. let match = /twitter.com\/([^\/?#]+)\/lists\/([^\/?#]+)/.exec(link) ||
  33. /^([^\/?#]+)\/([^\/?#]+)$/.exec(link);
  34. if (match)
  35. return [match[1], `/lists/${match[2]}`];
  36. match =
  37. /twitter.com\/([^\/?#]+)\/status\/(\d+)/.exec(link);
  38. if (match)
  39. return [match[1], `/status/${match[2]}`];
  40. match =
  41. /twitter.com\/([^\/?#]+)/.exec(link) ||
  42. /^([^\/?#]+)$/.exec(link);
  43. if (match)
  44. return [match[1]];
  45. return;
  46. }
  47. function linkBuilder(userName, more = '') {
  48. if (!userName)
  49. return;
  50. return `https://twitter.com/${userName}${more}`;
  51. }
  52. function linkFinder(checkedMatch, chat, lock) {
  53. var _a;
  54. const normalizedLink = linkBuilder(twitter_1.ScreenNameNormalizer.normalize(checkedMatch[0]), (_a = checkedMatch[1]) === null || _a === void 0 ? void 0 : _a.toLowerCase());
  55. const link = Object.keys(lock.threads).find(realLink => normalizedLink === realLink.replace(/\/@/, '/').toLowerCase());
  56. if (!link)
  57. return [null, -1];
  58. const index = lock.threads[link].subscribers.findIndex(({ chatID, chatType }) => chat.chatID === chatID && chat.chatType === chatType);
  59. return [link, index];
  60. }
  61. function sub(chat, args, reply, lock, lockfile) {
  62. if (chat.chatType === "temp") {
  63. return reply('请先添加机器人为好友。');
  64. }
  65. if (args.length === 0) {
  66. return reply('找不到要订阅的链接。');
  67. }
  68. const match = parseLink(args[0]);
  69. if (!match) {
  70. return reply(`订阅链接格式错误:
  71. 示例:
  72. https://twitter.com/Saito_Shuka
  73. https://twitter.com/rikakomoe/lists/lovelive
  74. https://twitter.com/TomoyoKurosawa/status/1294613494860361729`);
  75. }
  76. let offset = '0';
  77. if (match[1]) {
  78. const matchStatus = /\/status\/(\d+)/.exec(match[1]);
  79. if (matchStatus) {
  80. offset = utils_1.BigNumOps.plus(matchStatus[1], '-1');
  81. delete match[1];
  82. }
  83. }
  84. const subscribeTo = (link, config = {}) => {
  85. const { addNew = false, msg = `已为此聊天订阅 ${link}` } = config;
  86. if (addNew) {
  87. lock.feed.push(link);
  88. lock.threads[link] = {
  89. offset,
  90. subscribers: [],
  91. updatedAt: '',
  92. };
  93. }
  94. lock.threads[link].subscribers.push(chat);
  95. logger.warn(`chat ${JSON.stringify(chat)} has subscribed ${link}`);
  96. fs.writeFileSync(path.resolve(lockfile), JSON.stringify(lock));
  97. reply(msg);
  98. };
  99. const [realLink, index] = linkFinder(match, chat, lock);
  100. if (index > -1)
  101. return reply('此聊天已订阅此链接。');
  102. if (realLink)
  103. return subscribeTo(realLink);
  104. const [rawUserName, more] = match;
  105. if (rawUserName.toLowerCase() === 'i' && /lists\/(\d+)/.exec(more)) {
  106. return subscribeTo(linkBuilder('i', more), { addNew: true });
  107. }
  108. twitter_1.ScreenNameNormalizer.normalizeLive(rawUserName).then(userName => {
  109. if (!userName)
  110. return reply(`找不到用户 ${rawUserName.replace(/^@?(.*)$/, '@$1')}。`);
  111. const link = linkBuilder(userName, more);
  112. const msg = (offset === '0') ?
  113. undefined :
  114. `已为此聊天订阅 ${link} 并回溯到此动态 ID(含)之后的第一条动态。
  115. (参见:https://blog.twitter.com/engineering/en_us/a/2010/announcing-snowflake.html)`;
  116. subscribeTo(link, { addNew: true, msg });
  117. });
  118. }
  119. exports.sub = sub;
  120. function unsub(chat, args, reply, lock, lockfile) {
  121. if (chat.chatType === "temp") {
  122. return reply('请先添加机器人为好友。');
  123. }
  124. if (args.length === 0) {
  125. return reply('找不到要退订的链接。');
  126. }
  127. const match = parseLink(args[0]);
  128. if (!match) {
  129. return reply('链接格式有误。');
  130. }
  131. const [link, index] = linkFinder(match, chat, lock);
  132. if (index === -1)
  133. return list(chat, args, msg => reply('您没有订阅此链接。\n' + msg), lock);
  134. else {
  135. lock.threads[link].subscribers.splice(index, 1);
  136. fs.writeFileSync(path.resolve(lockfile), JSON.stringify(lock));
  137. logger.warn(`chat ${JSON.stringify(chat)} has unsubscribed ${link}`);
  138. return reply(`已为此聊天退订 ${link}`);
  139. }
  140. }
  141. exports.unsub = unsub;
  142. function list(chat, _, reply, lock) {
  143. if (chat.chatType === "temp") {
  144. return reply('请先添加机器人为好友。');
  145. }
  146. const links = [];
  147. Object.keys(lock.threads).forEach(key => {
  148. if (lock.threads[key].subscribers.find(({ chatID, chatType }) => chat.chatID === chatID && chat.chatType === chatType))
  149. links.push(`${key} ${datetime_1.relativeDate(lock.threads[key].updatedAt)}`);
  150. });
  151. return reply('此聊天中订阅的链接:\n' + links.join('\n'));
  152. }
  153. exports.list = list;
  154. function view(chat, args, reply) {
  155. if (args.length === 0) {
  156. return reply('找不到要查看的链接。');
  157. }
  158. const match = /^(?:.*twitter.com\/[^\/?#]+\/status\/)?(\d+)/.exec(args[0]);
  159. if (!match) {
  160. return reply('链接格式有误。');
  161. }
  162. try {
  163. twitter_1.sendTweet(match[1], chat);
  164. }
  165. catch (e) {
  166. reply('推特机器人尚未加载完毕,请稍后重试。');
  167. }
  168. }
  169. exports.view = view;
  170. function query(chat, args, reply) {
  171. if (args.length === 0) {
  172. return reply('找不到要查询的用户。');
  173. }
  174. const match = /twitter.com\/([^\/?#]+)/.exec(args[0]) ||
  175. /^([^\/?#]+)$/.exec(args[0]);
  176. if (!match) {
  177. return reply('链接格式有误。');
  178. }
  179. const conf = { username: match[1], noreps: 'on', norts: 'off' };
  180. const confZH = {
  181. count: '数量上限',
  182. since: '起始点',
  183. until: '结束点',
  184. noreps: '忽略回复推文(on/off)',
  185. norts: '忽略原生转推(on/off)',
  186. };
  187. for (const arg of args.slice(1)) {
  188. const optMatch = /^(count|since|until|noreps|norts)=(.*)/.exec(arg);
  189. if (!optMatch)
  190. return reply(`未定义的查询参数:${arg}。`);
  191. const optKey = optMatch[1];
  192. if (optMatch.length === 1)
  193. return reply(`查询${confZH[optKey]}参数格式有误。`);
  194. conf[optKey] = optMatch[2];
  195. if (optMatch[2] === '')
  196. return reply(`查询${confZH[optKey]}参数值不可为空。`);
  197. }
  198. if (conf.count !== undefined && !Number(conf.count) || Math.abs(Number(conf.count)) > 50) {
  199. return reply('查询数量上限参数为零、非数值或超出取值范围。');
  200. }
  201. try {
  202. twitter_1.sendTimeline(conf, chat);
  203. }
  204. catch (e) {
  205. logger.error(`error querying timeline, error: ${e}`);
  206. reply('推特机器人尚未加载完毕,请稍后重试。');
  207. }
  208. }
  209. exports.query = query;
  210. //# sourceMappingURL=command.js.map