Sfoglia il codice sorgente

re-add view command for fleets

Mike L 4 anni fa
parent
commit
e5cc9e643b
8 ha cambiato i file con 147 aggiunte e 46 eliminazioni
  1. 19 2
      dist/command.js
  2. 5 0
      dist/mirai.js
  3. 41 15
      dist/twitter.js
  4. 2 2
      dist/webshot.js
  5. 19 3
      src/command.ts
  6. 6 1
      src/mirai.ts
  7. 53 21
      src/twitter.ts
  8. 2 2
      src/webshot.ts

+ 19 - 2
dist/command.js

@@ -1,6 +1,6 @@
 "use strict";
 Object.defineProperty(exports, "__esModule", { value: true });
-exports.unsub = exports.list = exports.sub = exports.parseCmd = void 0;
+exports.view = exports.unsub = exports.list = exports.sub = exports.parseCmd = void 0;
 const fs = require("fs");
 const path = require("path");
 const datetime_1 = require("./datetime");
@@ -28,7 +28,7 @@ function parseCmd(message) {
 }
 exports.parseCmd = parseCmd;
 function parseLink(link) {
-    let match = link.match(/twitter.com\/([^\/?#]+)/) ||
+    const match = link.match(/twitter.com\/([^\/?#]+)/) ||
         link.match(/^([^\/?#]+)$/);
     if (match)
         return [match[1]];
@@ -123,3 +123,20 @@ function list(chat, _, reply, lock) {
     return reply('此聊天中订阅推特故事的链接:\n' + links.join('\n'));
 }
 exports.list = list;
+function view(chat, args, reply) {
+    if (args.length === 0) {
+        return reply('找不到要查看的链接。');
+    }
+    const checkedMatch = parseLink(args[0]);
+    if (!checkedMatch) {
+        return reply(`订阅链接格式错误:
+示例:https://twitter.com/sunflower930316`);
+    }
+    try {
+        twitter_1.sendAllFleets(checkedMatch[0], chat);
+    }
+    catch (e) {
+        reply('推特机器人尚未加载完毕,请稍后重试。');
+    }
+}
+exports.view = view;

+ 5 - 0
dist/mirai.js

@@ -153,6 +153,10 @@ class default_1 {
                 const chat = yield this.getChat(msg);
                 const cmdObj = command_1.parseCmd(msg.plain);
                 switch (cmdObj.cmd) {
+                    case 'twitterfleets_view':
+                    case 'twitterfleets_get':
+                        command_1.view(chat, cmdObj.args, msg.reply);
+                        break;
                     case 'twitterfleets_sub':
                     case 'twitterfleets_subscribe':
                         this.botInfo.sub(chat, cmdObj.args, msg.reply);
@@ -168,6 +172,7 @@ class default_1 {
                     case 'help':
                         msg.reply(`推特故事搬运机器人:
 /twitterfleets - 查询当前聊天中的推特故事订阅
+/twitterfleets_view〈链接〉- 查看该用户当前可见的所有 Fleets
 /twitterfleets_subscribe [链接] - 订阅 Twitter Fleets 搬运
 /twitterfleets_unsubscribe [链接] - 退订 Twitter Fleets 搬运`);
                 }

+ 41 - 15
dist/twitter.js

@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
     });
 };
 Object.defineProperty(exports, "__esModule", { value: true });
-exports.ScreenNameNormalizer = void 0;
+exports.sendAllFleets = exports.ScreenNameNormalizer = void 0;
 const fs = require("fs");
 const path = require("path");
 const request = require("request");
@@ -40,6 +40,10 @@ class ScreenNameNormalizer {
 exports.ScreenNameNormalizer = ScreenNameNormalizer;
 ScreenNameNormalizer.permaFeeds = {};
 ScreenNameNormalizer.normalize = (username) => username.toLowerCase().replace(/^@/, '');
+let sendAllFleets = (username, receiver) => {
+    throw Error();
+};
+exports.sendAllFleets = sendAllFleets;
 const logger = loggers_1.getLogger('twitter');
 const maxTrials = 3;
 const uploadTimeout = 10000;
@@ -108,6 +112,15 @@ class default_1 {
                 });
             });
         };
+        this.getFleets = (userID) => new Promise((resolve, reject) => {
+            const endpoint = `https://api.twitter.com/fleets/v1/user_fleets?user_id=${userID}`;
+            this.privateClient.get(endpoint, (error, fleetFeed, _) => {
+                if (error)
+                    reject(error);
+                else
+                    resolve(fleetFeed);
+            });
+        });
         this.work = () => {
             const lock = this.lock;
             if (this.workInterval < 1)
@@ -140,17 +153,8 @@ class default_1 {
                 logger.error(`cannot get endpoint for feed ${currentFeed}`);
                 return;
             }
-            let endpoint = `https://api.twitter.com/fleets/v1/user_fleets?user_id=${match[1]}`;
-            const promise = new Promise((resolve, reject) => {
-                this.privateClient.get(endpoint, (error, fleetFeed, _) => {
-                    if (error)
-                        reject(error);
-                    else
-                        resolve(fleetFeed);
-                });
-            });
             this.client.get('users/show', { user_id: match[1] })
-                .then((fullUser) => { user = fullUser; return promise; })
+                .then((fullUser) => { user = fullUser; return this.getFleets(match[1]); })
                 .catch(error => {
                 logger.error(`unhandled error on fetching fleets for ${currentFeed}: ${JSON.stringify(error)}`);
             })
@@ -171,9 +175,7 @@ class default_1 {
                     return;
                 }
                 if (currentThread.offset !== '0') {
-                    const readCount = fleets.findIndex(fleet => {
-                        return Number(utils_1.BigNumOps.plus(fleet.fleet_id.substring(3), `-${currentThread.offset}`)) > 0;
-                    });
+                    const readCount = fleets.findIndex(fleet => Number(utils_1.BigNumOps.plus(fleet.fleet_id.substring(3), `-${currentThread.offset}`)) > 0);
                     if (readCount === -1)
                         return;
                     fleets = fleets.slice(readCount);
@@ -202,7 +204,7 @@ class default_1 {
             bearer_token: 'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA',
         });
         this.privateClient.request = request.defaults({
-            headers: Object.assign(Object.assign({}, this.privateClient.options.request_options.headers), { 'Content-Type': 'application/x-www-form-urlencoded', 'Cookie': `auth_token=${opt.private_auth_token}; ct0=${opt.private_csrf_token};`, 'X-CSRF-Token': opt.private_csrf_token })
+            headers: Object.assign(Object.assign({}, this.privateClient.options.request_options.headers), { 'Content-Type': 'application/x-www-form-urlencoded', Cookie: `auth_token=${opt.private_auth_token}; ct0=${opt.private_csrf_token};`, 'X-CSRF-Token': opt.private_csrf_token }),
         });
         this.lockfile = opt.lockfile;
         this.lock = opt.lock;
@@ -210,6 +212,30 @@ class default_1 {
         this.bot = opt.bot;
         this.mode = opt.mode;
         ScreenNameNormalizer._queryUser = this.queryUser;
+        exports.sendAllFleets = (username, receiver) => {
+            this.client.get('users/show', { screen_name: username })
+                .then((user) => {
+                const feed = `https://twitter.com/${user.screen_name}`;
+                return this.getFleets(user.id_str)
+                    .catch(error => {
+                    logger.error(`unhandled error while fetching fleets for ${feed}: ${JSON.stringify(error)}`);
+                    this.bot.sendTo(receiver, `获取 Fleets 时出现错误:${error}`);
+                })
+                    .then((fleetFeed) => {
+                    if (!fleetFeed || fleetFeed.fleet_threads.length === 0) {
+                        this.bot.sendTo(receiver, `当前用户(@${user.screen_name})没有可用的 Fleets。`);
+                        return;
+                    }
+                    this.workOnFleets(user, fleetFeed.fleet_threads[0].fleets, this.sendFleets(`thread ${feed}`, receiver));
+                });
+            })
+                .catch((err) => {
+                if (err[0].code !== 50) {
+                    logger.warn(`error looking up user: ${err[0].message}, unable to fetch fleets`);
+                }
+                this.bot.sendTo(receiver, `找不到用户 ${username.replace(/^@?(.*)$/, '@$1')}。`);
+            });
+        };
     }
 }
 exports.default = default_1;

+ 2 - 2
dist/webshot.js

@@ -100,8 +100,8 @@ class Webshot extends CallableInstance {
             });
             const messageChain = [];
             // text processing
-            let author = `${user.name} (@${user.screen_name}):\n`;
-            let date = `${new Date(fleet.created_at)}\n`;
+            const author = `${user.name} (@${user.screen_name}):\n`;
+            const date = `${new Date(fleet.created_at)}\n`;
             let text = (_b = author + date + ((_a = fleet.media_bounding_boxes) === null || _a === void 0 ? void 0 : _a.map(box => box.entity.value).join('\n'))) !== null && _b !== void 0 ? _b : '';
             messageChain.push(mirai_1.Message.Plain(author + date));
             // fetch extra entities

+ 19 - 3
src/command.ts

@@ -3,7 +3,7 @@ import * as path from 'path';
 
 import { relativeDate } from './datetime';
 import { getLogger } from './loggers';
-import { ScreenNameNormalizer as normalizer } from './twitter';
+import { sendAllFleets, ScreenNameNormalizer as normalizer } from './twitter';
 
 const logger = getLogger('command');
 
@@ -31,7 +31,7 @@ function parseCmd(message: string): {
 }
 
 function parseLink(link: string): string[] {
-  let match =
+  const match =
     link.match(/twitter.com\/([^\/?#]+)/) ||
     link.match(/^([^\/?#]+)$/);
   if (match) return [match[1]];
@@ -132,4 +132,20 @@ function list(chat: IChat, _: string[], reply: (msg: string) => any, lock: ILock
   return reply('此聊天中订阅推特故事的链接:\n' + links.join('\n'));
 }
 
-export { parseCmd, sub, list, unsub };
+function view(chat: IChat, args: string[], reply: (msg: string) => any): void {
+  if (args.length === 0) {
+    return reply('找不到要查看的链接。');
+  }
+  const checkedMatch = parseLink(args[0]);
+  if (!checkedMatch) {
+    return reply(`订阅链接格式错误:
+示例:https://twitter.com/sunflower930316`);
+  }
+  try {
+    sendAllFleets(checkedMatch[0], chat);
+  } catch (e) {
+    reply('推特机器人尚未加载完毕,请稍后重试。');
+  }
+}
+
+export { parseCmd, sub, list, unsub, view };

+ 6 - 1
src/mirai.ts

@@ -4,7 +4,7 @@ import Mirai, { MessageType } from 'mirai-ts';
 import MiraiMessage from 'mirai-ts/dist/message';
 import * as temp from 'temp';
 
-import { parseCmd } from './command';
+import { parseCmd, view } from './command';
 import { getLogger } from './loggers';
 
 const logger = getLogger('qqbot');
@@ -177,6 +177,10 @@ export default class {
       const chat = await this.getChat(msg);
       const cmdObj = parseCmd(msg.plain);
       switch (cmdObj.cmd) {
+        case 'twitterfleets_view':
+        case 'twitterfleets_get':
+          view(chat, cmdObj.args, msg.reply);
+          break;
         case 'twitterfleets_sub':
         case 'twitterfleets_subscribe':
           this.botInfo.sub(chat, cmdObj.args, msg.reply);
@@ -192,6 +196,7 @@ export default class {
         case 'help':
           msg.reply(`推特故事搬运机器人:
 /twitterfleets - 查询当前聊天中的推特故事订阅
+/twitterfleets_view〈链接〉- 查看该用户当前可见的所有 Fleets
 /twitterfleets_subscribe [链接] - 订阅 Twitter Fleets 搬运
 /twitterfleets_unsubscribe [链接] - 退订 Twitter Fleets 搬运`);
       }

+ 53 - 21
src/twitter.ts

@@ -52,6 +52,10 @@ export class ScreenNameNormalizer {
   }
 }
 
+export let sendAllFleets = (username: string, receiver: IChat): void => {
+  throw Error();
+};
+
 const logger = getLogger('twitter');
 const maxTrials = 3;
 const uploadTimeout = 10000;
@@ -89,9 +93,9 @@ type TwitterMod = {
   -readonly [K in keyof Twitter]: Twitter[K];
 } & {
   options?: any;
-}
+};
 
-export type Fleet = {
+interface IFleet {
   created_at: string;
   deleted_at: string;
   expiration: string;
@@ -120,14 +124,19 @@ export type Fleet = {
   text: string;
   user_id: number;
   user_id_str: string;
-};
+}
 
-export type Fleets = Fleet[];
+export type Fleet = IFleet;
+export type Fleets = IFleet[];
+
+interface IFleetFeed {
+  fleet_threads: {fleets: Fleets}[];
+}
 
 export default class {
 
   private client: Twitter;
-  private privateClient: TwitterMod
+  private privateClient: TwitterMod;
   private lock: ILock;
   private lockfile: string;
   private workInterval: number;
@@ -150,9 +159,9 @@ export default class {
       headers: {
         ...this.privateClient.options.request_options.headers,
         'Content-Type': 'application/x-www-form-urlencoded',
-        'Cookie': `auth_token=${opt.private_auth_token}; ct0=${opt.private_csrf_token};`,
+        Cookie: `auth_token=${opt.private_auth_token}; ct0=${opt.private_csrf_token};`,
         'X-CSRF-Token': opt.private_csrf_token,
-      }
+      },
     });
     this.lockfile = opt.lockfile;
     this.lock = opt.lock;
@@ -160,6 +169,30 @@ export default class {
     this.bot = opt.bot;
     this.mode = opt.mode;
     ScreenNameNormalizer._queryUser = this.queryUser;
+    sendAllFleets = (username, receiver) => {
+      this.client.get('users/show', {screen_name: username})
+      .then((user: FullUser) => {
+        const feed = `https://twitter.com/${user.screen_name}`;
+        return this.getFleets(user.id_str)
+        .catch(error => {
+          logger.error(`unhandled error while fetching fleets for ${feed}: ${JSON.stringify(error)}`);
+          this.bot.sendTo(receiver, `获取 Fleets 时出现错误:${error}`);
+        })
+        .then((fleetFeed: IFleetFeed) => {
+          if (!fleetFeed || fleetFeed.fleet_threads.length === 0) {
+            this.bot.sendTo(receiver, `当前用户(@${user.screen_name})没有可用的 Fleets。`);
+            return;
+          }
+          this.workOnFleets(user, fleetFeed.fleet_threads[0].fleets, this.sendFleets(`thread ${feed}`, receiver));
+        });
+      })
+      .catch((err: {code: number, message: string}[]) => {
+        if (err[0].code !== 50) {
+          logger.warn(`error looking up user: ${err[0].message}, unable to fetch fleets`);
+        }
+        this.bot.sendTo(receiver, `找不到用户 ${username.replace(/^@?(.*)$/, '@$1')}。`);
+      });
+    };
   }
 
   public launch = () => {
@@ -218,6 +251,14 @@ export default class {
       });
     });
   }
+  
+  private getFleets = (userID: string) => new Promise<IFleetFeed | void>((resolve, reject) => {
+    const endpoint = `https://api.twitter.com/fleets/v1/user_fleets?user_id=${userID}`;
+    this.privateClient.get(endpoint, (error, fleetFeed: IFleetFeed, _) => {
+      if (error) reject(error);
+      else resolve(fleetFeed);
+    });
+  })
 
   public work = () => {
     const lock = this.lock;
@@ -243,7 +284,6 @@ export default class {
     const currentFeed = lock.feed[lock.workon];
     logger.debug(`pulling feed ${currentFeed}`);
 
-    type FleetFeed = {fleet_threads: {fleets: Fleets}[]};
     let user: FullUser;
     let match = currentFeed.match(/https:\/\/twitter.com\/([^\/]+)/);
     if (match) match = lock.threads[currentFeed].permaFeed.match(/https:\/\/twitter.com\/i\/user\/([^\/]+)/);
@@ -251,20 +291,13 @@ export default class {
       logger.error(`cannot get endpoint for feed ${currentFeed}`);
       return;
     }
-    let endpoint = `https://api.twitter.com/fleets/v1/user_fleets?user_id=${match[1]}`;
-    const promise = new Promise<FleetFeed | void>((resolve, reject) => {
-      this.privateClient.get(endpoint, (error, fleetFeed: FleetFeed, _) => {
-        if (error) reject(error);
-        else resolve(fleetFeed);
-      });
-    });
 
     this.client.get('users/show', {user_id: match[1]})
-    .then((fullUser: FullUser) => { user = fullUser; return promise; })
+    .then((fullUser: FullUser) => { user = fullUser; return this.getFleets(match[1]); })
     .catch(error => {
       logger.error(`unhandled error on fetching fleets for ${currentFeed}: ${JSON.stringify(error)}`);
     })
-    .then((fleetFeed: FleetFeed) => {
+    .then((fleetFeed: IFleetFeed) => {
       logger.debug(`private api returned ${JSON.stringify(fleetFeed)} for feed ${currentFeed}`);
       logger.debug(`api returned ${JSON.stringify(user)} for owner of feed ${currentFeed}`);
       const currentThread = lock.threads[currentFeed];
@@ -278,9 +311,8 @@ export default class {
 
       if (currentThread.offset === '-1') { updateOffset(); return; }
       if (currentThread.offset !== '0') {
-        const readCount = fleets.findIndex(fleet => {
-          return Number(BigNumOps.plus(fleet.fleet_id.substring(3), `-${currentThread.offset}`)) > 0;
-        });
+        const readCount = fleets.findIndex(fleet =>
+          Number(BigNumOps.plus(fleet.fleet_id.substring(3), `-${currentThread.offset}`)) > 0);
         if (readCount === -1) return;
         fleets = fleets.slice(readCount);
       }
@@ -296,6 +328,6 @@ export default class {
       setTimeout(() => {
         this.work();
       }, timeout);
-    })
+    });
   }
 }

+ 2 - 2
src/webshot.ts

@@ -111,8 +111,8 @@ extends CallableInstance<
       const messageChain: MessageChain = [];
 
       // text processing
-      let author = `${user.name} (@${user.screen_name}):\n`;
-      let date = `${new Date(fleet.created_at)}\n`;
+      const author = `${user.name} (@${user.screen_name}):\n`;
+      const date = `${new Date(fleet.created_at)}\n`;
       let text = author + date + fleet.media_bounding_boxes?.map(box => box.entity.value).join('\n') ?? '';
       messageChain.push(Message.Plain(author + date));