123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123 |
- 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 { MediaItem } 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('视频'),
- };
- const logger = getLogger('webshot');
- class Webshot extends CallableInstance<[MediaItem[], (...args) => void, number], Promise<void>> {
- constructor(_wsUrl: string, _mode: number, onready?: (...args) => void) {
- super('webshot');
- 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();
- })(/\/.*\.(.+?)\?/.exec(url)[1])
- );
- public webshot(
- mediaItems: MediaItem[],
- callback: (msgs: string, text: string, author: string) => void,
- webshotDelay: number
- ): Promise<void> {
- let promise = new Promise<void>(resolve => {
- resolve();
- });
- mediaItems.forEach(item => {
- promise = promise.then(() => {
- logger.info(`working on ${item.user.username}/${item.code}`);
- });
- let messageChain = '';
- // text processing
- const author = `${item.user.full_name} (@${item.user.username}):\n`;
- const date = `${new Date(item.taken_at * 1000)}\n`;
- messageChain += author + date;
- // fetch extra entities
- const type = (mediaItem): keyof typeof typeInZH =>
- (mediaItem as MediaItem).video_versions ? 'video' : 'photo';
- const fetchBestCandidate =(
- candidates: (Partial<typeof item.video_versions[0]> & typeof item.image_versions2.candidates[0])[],
- mediaType: keyof typeof typeInZH
- ) => {
- const url = candidates
- .sort((var1, var2) => var2.width + (var2?.type || 0) - var1.width - (var1?.type || 0))
- .map(variant => variant.url)[0]; // largest media
- const altMessage = `\n[失败的${typeInZH[mediaType].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 = promise.then(() => {
- if (item.video_versions) {
- return fetchBestCandidate(item.video_versions, type(item));
- } else if (item.image_versions2) {
- return fetchBestCandidate(item.image_versions2.candidates, type(item));
- }
- });
- promise.then(() => {
- logger.info(`done working on ${item.user.username}/${item.code}, message chain:`);
- logger.info(JSON.stringify(Message.ellipseBase64(messageChain)));
- callback(messageChain, xmlEntities.decode(item.caption), author);
- });
- });
- return promise;
- }
- }
- export default Webshot;
|