command.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.view = exports.unsub = exports.list = exports.sub = 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 logger = loggers_1.getLogger('command');
  10. function parseLink(link) {
  11. let match = link.match(/twitter.com\/([^\/?#]+)\/lists\/([^\/?#]+)/) ||
  12. link.match(/^([^\/?#]+)\/([^\/?#]+)$/);
  13. if (match)
  14. return [match[1], `/lists/${match[2]}`];
  15. match =
  16. link.match(/twitter.com\/([^\/?#]+)\/status\/(\d+)/);
  17. if (match)
  18. return [match[1], `/status/${match[2]}`];
  19. match =
  20. link.match(/twitter.com\/([^\/?#]+)/) ||
  21. link.match(/^([^\/?#]+)$/);
  22. if (match)
  23. return [match[1]];
  24. return;
  25. }
  26. function linkBuilder(userName, more = '') {
  27. if (!userName)
  28. return;
  29. return `https://twitter.com/${userName}${more}`;
  30. }
  31. function linkFinder(checkedMatch, chat, lock) {
  32. var _a;
  33. const normalizedLink = linkBuilder(twitter_1.ScreenNameNormalizer.normalize(checkedMatch[0]), (_a = checkedMatch[1]) === null || _a === void 0 ? void 0 : _a.toLowerCase());
  34. const link = Object.keys(lock.threads).find(realLink => normalizedLink === realLink.replace(/\/@/, '/').toLowerCase());
  35. if (!link)
  36. return [null, -1];
  37. const index = lock.threads[link].subscribers.findIndex(({ chatID, chatType }) => chat.chatID === chatID && chat.chatType === chatType);
  38. return [link, index];
  39. }
  40. function sub(chat, args, reply, lock, lockfile) {
  41. if (chat.chatType === "temp" /* Temp */) {
  42. return reply('请先添加机器人为好友。');
  43. }
  44. if (args.length === 0) {
  45. return reply('找不到要订阅媒体推文的链接。');
  46. }
  47. const match = parseLink(args[0]);
  48. if (!match) {
  49. return reply(`订阅链接格式错误:
  50. 示例:
  51. https://twitter.com/Saito_Shuka
  52. https://twitter.com/rikakomoe/lists/lovelive
  53. https://twitter.com/TomoyoKurosawa/status/1294613494860361729`);
  54. }
  55. let offset = '0';
  56. if (match[1]) {
  57. const matchStatus = match[1].match(/\/status\/(\d+)/);
  58. if (matchStatus) {
  59. offset = twitter_1.bigNumPlus(matchStatus[1], '-1');
  60. delete match[1];
  61. }
  62. }
  63. const subscribeTo = (link, config = {}) => {
  64. const { addNew = false, msg = `已为此聊天订阅 ${link} 的媒体推文` } = config;
  65. if (addNew) {
  66. lock.feed.push(link);
  67. lock.threads[link] = {
  68. offset,
  69. subscribers: [],
  70. updatedAt: '',
  71. };
  72. }
  73. lock.threads[link].subscribers.push(chat);
  74. logger.warn(`chat ${JSON.stringify(chat)} has subscribed ${link}`);
  75. fs.writeFileSync(path.resolve(lockfile), JSON.stringify(lock));
  76. reply(msg);
  77. };
  78. const [realLink, index] = linkFinder(match, chat, lock);
  79. if (index > -1)
  80. return reply('此聊天已订阅此链接。');
  81. if (realLink)
  82. return subscribeTo(realLink);
  83. const [rawUserName, more] = match;
  84. if (rawUserName.toLowerCase() === 'i' && (more === null || more === void 0 ? void 0 : more.match(/lists\/(\d+)/))) {
  85. return subscribeTo(linkBuilder('i', more), { addNew: true });
  86. }
  87. twitter_1.ScreenNameNormalizer.normalizeLive(rawUserName).then(userName => {
  88. if (!userName)
  89. return reply(`找不到用户 ${rawUserName.replace(/^@?(.*)$/, '@$1')}。`);
  90. const link = linkBuilder(userName, more);
  91. const msg = (offset === '0') ?
  92. undefined :
  93. `已为此聊天订阅 ${link} 的媒体动态并回溯到此动态 ID(含)之后的第一条媒体动态。
  94. (参见:https://blog.twitter.com/engineering/en_us/a/2010/announcing-snowflake.html)`;
  95. subscribeTo(link, { addNew: true, msg });
  96. });
  97. }
  98. exports.sub = sub;
  99. function unsub(chat, args, reply, lock, lockfile) {
  100. if (chat.chatType === "temp" /* Temp */) {
  101. return reply('请先添加机器人为好友。');
  102. }
  103. if (args.length === 0) {
  104. return reply('找不到要退订媒体推文的链接。');
  105. }
  106. const match = parseLink(args[0]);
  107. if (!match) {
  108. return reply('链接格式有误。');
  109. }
  110. const [link, index] = linkFinder(match, chat, lock);
  111. if (index === -1)
  112. return list(chat, args, msg => reply('您没有订阅此链接的媒体推文。\n' + msg), lock);
  113. else {
  114. lock.threads[link].subscribers.splice(index, 1);
  115. fs.writeFileSync(path.resolve(lockfile), JSON.stringify(lock));
  116. logger.warn(`chat ${JSON.stringify(chat)} has unsubscribed ${link}`);
  117. return reply(`已为此聊天退订 ${link} 的媒体推文`);
  118. }
  119. }
  120. exports.unsub = unsub;
  121. function list(chat, _, reply, lock) {
  122. if (chat.chatType === "temp" /* Temp */) {
  123. return reply('请先添加机器人为好友。');
  124. }
  125. const links = [];
  126. Object.keys(lock.threads).forEach(key => {
  127. if (lock.threads[key].subscribers.find(({ chatID, chatType }) => chat.chatID === chatID && chat.chatType === chatType))
  128. links.push(`${key} ${datetime_1.relativeDate(lock.threads[key].updatedAt)}`);
  129. });
  130. return reply('此聊天中订阅媒体推文的链接:\n' + links.join('\n'));
  131. }
  132. exports.list = list;
  133. function view(chat, args, reply) {
  134. if (args.length === 0) {
  135. return reply('找不到要查看的链接。');
  136. }
  137. const match = args[0].match(/^(?:.*twitter.com\/[^\/?#]+\/status\/)?(\d+)/);
  138. if (!match) {
  139. return reply('链接格式有误。');
  140. }
  141. try {
  142. twitter_1.sendTweet(match[1], chat);
  143. }
  144. catch (e) {
  145. reply('推特机器人尚未加载完毕,请稍后重试。');
  146. }
  147. }
  148. exports.view = view;