123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- import { writeFileSync } from 'fs';
- import axios from 'axios';
- import * as CallableInstance from 'callable-instance';
- import { XmlEntities } from 'html-entities';
- import * as temp from 'temp';
- import { getLogger } from './loggers';
- import { Message } from './koishi';
- import { Fleets, FullUser, MediaEntity } from './twitter';
- const xmlEntities = new XmlEntities();
- const ZHType = (type: string) => new class extends String {
- public type = super.toString();
- public toString = () => `[${super.toString()}]`;
- }(type);
- const typeInZH = {
- photo: ZHType('图片'),
- video: ZHType('视频'),
- animated_gif: ZHType('GIF'),
- };
- const logger = getLogger('webshot');
- class Webshot extends CallableInstance<[FullUser, Fleets, (...args) => void, number], Promise<void>> {
- private mode: number;
- constructor(_wsUrl: string, mode: number, onready?: (...args) => void) {
- super('webshot');
- this.mode = mode;
- onready();
- }
- private fetchMedia = (url: string): Promise<string> => new Promise<ArrayBuffer>((resolve, reject) => {
- logger.info(`fetching ${url}`);
- axios({
- method: 'get',
- url,
- responseType: 'arraybuffer',
- timeout: 150000,
- }).then(res => {
- if (res.status === 200) {
- logger.info(`successfully fetched ${url}`);
- resolve(res.data);
- } else {
- logger.error(`failed to fetch ${url}: ${res.status}`);
- reject();
- }
- }).catch (err => {
- logger.error(`failed to fetch ${url}: ${err instanceof Error ? err.message : err}`);
- reject();
- });
- }).then(data =>
- (ext => {
- const mediaTempFilePath = temp.path({suffix: `.${ext}`});
- writeFileSync(mediaTempFilePath, Buffer.from(data));
- const path = `file://${mediaTempFilePath}`;
- switch (ext) {
- case 'jpg':
- case 'png':
- return Message.Image(path);
- case 'mp4':
- return Message.Video(path);
- }
- logger.warn('unable to find MIME type of fetched media, failing this fetch');
- throw Error();
- })(((/\?format=([a-z]+)&/.exec(url)) ?? (/.*\/.*\.([^?]+)/.exec(url)))[1])
- );
- public webshot(
- user: FullUser,
- fleets: Fleets,
- callback: (msgs: string, text: string) => void,
- webshotDelay: number
- ): Promise<void> {
- let promise = new Promise<void>(resolve => {
- resolve();
- });
- fleets.forEach(fleet => {
- promise = promise.then(() => {
- logger.info(`working on ${user.screen_name}/${fleet.fleet_id}`);
- });
- let messageChain = '';
- // text processing
- 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 as string).join('\n') ?? '';
- messageChain += author + date;
- // fetch extra entities
- // tslint:disable-next-line: curly
- // eslint-disable-next-line curly
- if (1 - this.mode % 2) promise = promise.then(() => {
- const media: MediaEntity & {media_info?: MediaEntity} = fleet.media_entity;
- let url: string;
- if (fleet.media_key.media_category === 'TWEET_IMAGE') {
- media.type = 'photo';
- url = media.media_url_https.replace(/\.([a-z]+)$/, '?format=$1') + '&name=orig';
- } else {
- media.type = fleet.media_key.media_category === 'TWEET_VIDEO' ? 'video' : 'animated_gif';
- media.video_info = media.media_info.video_info;
- text += `[${typeInZH[media.type as keyof typeof typeInZH].type}]`;
- url = (media.video_info.variants as (
- typeof media.video_info.variants[0] & {bit_rate: number} // bitrate -> bit_rate
- )[])
- .filter(variant => variant.bit_rate !== undefined)
- .sort((var1, var2) => var2.bit_rate - var1.bit_rate)
- .map(variant => variant.url)[0]; // largest video
- }
- const altMessage = `\n[失败的${typeInZH[media.type as keyof typeof typeInZH].type}:${url}]`;
- return this.fetchMedia(url)
- .catch(error => {
- logger.warn('unable to fetch media, sending plain text instead...');
- return altMessage;
- })
- .then(msg => { messageChain += msg; });
- });
- promise.then(() => {
- logger.info(`done working on ${user.screen_name}/${fleet.fleet_id}, message chain:`);
- logger.info(JSON.stringify(messageChain));
- callback(messageChain, xmlEntities.decode(text));
- });
- });
- return promise;
- }
- }
- export default Webshot;
|