webshot.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. "use strict";
  2. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  3. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  4. return new (P || (P = Promise))(function (resolve, reject) {
  5. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  6. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  7. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  8. step((generator = generator.apply(thisArg, _arguments || [])).next());
  9. });
  10. };
  11. Object.defineProperty(exports, "__esModule", { value: true });
  12. const axios_1 = require("axios");
  13. const CallableInstance = require("callable-instance");
  14. const html_entities_1 = require("html-entities");
  15. const gifski_1 = require("./gifski");
  16. const loggers_1 = require("./loggers");
  17. const mirai_1 = require("./mirai");
  18. const xmlEntities = new html_entities_1.XmlEntities();
  19. const ZHType = (type) => new class extends String {
  20. constructor() {
  21. super(...arguments);
  22. this.type = super.toString();
  23. this.toString = () => `[${super.toString()}]`;
  24. }
  25. }(type);
  26. const typeInZH = {
  27. photo: ZHType('图片'),
  28. video: ZHType('视频'),
  29. animated_gif: ZHType('GIF'),
  30. };
  31. const logger = loggers_1.getLogger('webshot');
  32. class Webshot extends CallableInstance {
  33. constructor(_wsUrl, mode, onready) {
  34. super('webshot');
  35. this.fetchMedia = (url) => {
  36. const gif = (data) => {
  37. const matchDims = /\/(\d+)x(\d+)\//.exec(url);
  38. if (matchDims) {
  39. const [width, height] = matchDims.slice(1).map(Number);
  40. const factor = width + height > 1600 ? 0.375 : 0.5;
  41. return gifski_1.default(data, width * factor);
  42. }
  43. return gifski_1.default(data);
  44. };
  45. return new Promise((resolve, reject) => {
  46. logger.info(`fetching ${url}`);
  47. axios_1.default({
  48. method: 'get',
  49. url,
  50. responseType: 'arraybuffer',
  51. timeout: 150000,
  52. }).then(res => {
  53. if (res.status === 200) {
  54. logger.info(`successfully fetched ${url}`);
  55. resolve(res.data);
  56. }
  57. else {
  58. logger.error(`failed to fetch ${url}: ${res.status}`);
  59. reject();
  60. }
  61. }).catch(err => {
  62. logger.error(`failed to fetch ${url}: ${err instanceof Error ? err.message : err}`);
  63. reject();
  64. });
  65. }).then(data => {
  66. var _a;
  67. return ((ext) => __awaiter(this, void 0, void 0, function* () {
  68. switch (ext) {
  69. case 'jpg':
  70. return { mimetype: 'image/jpeg', data };
  71. case 'png':
  72. return { mimetype: 'image/png', data };
  73. case 'mp4':
  74. try {
  75. return { mimetype: 'image/gif', data: yield gif(data) };
  76. }
  77. catch (err) {
  78. logger.error(err);
  79. throw Error(err);
  80. }
  81. }
  82. }))(((_a = (/\?format=([a-z]+)&/.exec(url))) !== null && _a !== void 0 ? _a : (/.*\/.*\.([^?]+)/.exec(url)))[1])
  83. .catch(() => {
  84. logger.warn('unable to find MIME type of fetched media, failing this fetch');
  85. throw Error();
  86. });
  87. }).then(typedData => `data:${typedData.mimetype};base64,${Buffer.from(typedData.data).toString('base64')}`);
  88. };
  89. this.mode = mode;
  90. onready();
  91. }
  92. webshot(user, fleets, uploader, callback, webshotDelay) {
  93. let promise = new Promise(resolve => {
  94. resolve();
  95. });
  96. fleets.forEach(fleet => {
  97. var _a, _b;
  98. promise = promise.then(() => {
  99. logger.info(`working on ${user.screen_name}/${fleet.fleet_id}`);
  100. });
  101. const messageChain = [];
  102. const author = `${user.name} (@${user.screen_name}):\n`;
  103. const date = `${new Date(fleet.created_at)}\n`;
  104. 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 : '';
  105. messageChain.push(mirai_1.Message.Plain(author + date));
  106. if (1 - this.mode % 2)
  107. promise = promise.then(() => {
  108. const media = fleet.media_entity;
  109. let url;
  110. if (fleet.media_key.media_category === 'TWEET_IMAGE') {
  111. media.type = 'photo';
  112. url = media.media_url_https.replace(/\.([a-z]+)$/, '?format=$1') + '&name=orig';
  113. }
  114. else {
  115. media.type = fleet.media_key.media_category === 'TWEET_VIDEO' ? 'video' : 'animated_gif';
  116. media.video_info = media.media_info.video_info;
  117. text += `[${typeInZH[media.type].type}]`;
  118. url = media.video_info.variants
  119. .filter(variant => variant.bit_rate !== undefined)
  120. .sort((var1, var2) => var2.bit_rate - var1.bit_rate)
  121. .map(variant => variant.url)[0];
  122. }
  123. const altMessage = mirai_1.Message.Plain(`\n[失败的${typeInZH[media.type].type}:${url}]`);
  124. return this.fetchMedia(url)
  125. .then(base64url => uploader(mirai_1.Message.Image('', base64url, media.type === 'photo' ? url : `${url} as gif`), () => altMessage))
  126. .catch(error => {
  127. logger.warn('unable to fetch media, sending plain text instead...');
  128. return altMessage;
  129. })
  130. .then(msg => {
  131. messageChain.push(msg);
  132. });
  133. });
  134. promise.then(() => {
  135. logger.info(`done working on ${user.screen_name}/${fleet.fleet_id}, message chain:`);
  136. logger.info(JSON.stringify(messageChain));
  137. callback(messageChain, xmlEntities.decode(text));
  138. });
  139. });
  140. return promise;
  141. }
  142. }
  143. exports.default = Webshot;