Ver Fonte

fix message chain, fix CQ code syntax, remove reconnect

Mike L há 4 anos atrás
pai
commit
94b2570f91
6 ficheiros alterados com 108 adições e 85 exclusões
  1. 21 19
      dist/koishi.js
  2. 1 1
      dist/twitter.js
  3. 23 17
      dist/webshot.js
  4. 35 26
      src/koishi.ts
  5. 2 2
      src/twitter.ts
  6. 26 20
      src/webshot.ts

+ 21 - 19
dist/koishi.js

@@ -9,15 +9,28 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
     });
     });
 };
 };
 Object.defineProperty(exports, "__esModule", { value: true });
 Object.defineProperty(exports, "__esModule", { value: true });
-exports.ellipseBase64InMessage = exports.message = void 0;
+exports.Message = void 0;
 const koishi_1 = require("koishi");
 const koishi_1 = require("koishi");
 require("koishi-adapter-onebot");
 require("koishi-adapter-onebot");
 const command_1 = require("./command");
 const command_1 = require("./command");
 const loggers_1 = require("./loggers");
 const loggers_1 = require("./loggers");
+const utils_1 = require("./utils");
 const logger = loggers_1.getLogger('qqbot');
 const logger = loggers_1.getLogger('qqbot');
-exports.message = koishi_1.segment;
-const ellipseBase64InMessage = (msg) => msg.replace(/(?<=\[CQ:.*base64:\/\/).*?(,|\])/g, '...$1');
-exports.ellipseBase64InMessage = ellipseBase64InMessage;
+const cqUrlFix = (factory) => (...args) => factory(...args).replace(/(?<=\[CQ:.*)url=(?=(base64|file|https?):\/\/)/, 'file=');
+exports.Message = {
+    Image: cqUrlFix(koishi_1.segment.image),
+    Video: cqUrlFix(koishi_1.segment.video),
+    Voice: cqUrlFix(koishi_1.segment.audio),
+    ellipseBase64: (msg) => msg.replace(/(?<=\[CQ:.*base64:\/\/).*?(,|\])/g, '...$1'),
+    separateAttachment: (msg) => {
+        const attachments = [];
+        const message = msg.replace(/\[CQ:(image|video|record),.*?\]/g, code => {
+            attachments.push(code);
+            return '';
+        });
+        return { message, attachments };
+    },
+};
 class default_1 {
 class default_1 {
     constructor(opt) {
     constructor(opt) {
         this.getChat = (session) => __awaiter(this, void 0, void 0, function* () {
         this.getChat = (session) => __awaiter(this, void 0, void 0, function* () {
@@ -46,7 +59,7 @@ class default_1 {
                     };
                     };
             }
             }
         });
         });
-        this.sendTo = (subscriber, msg) => (() => {
+        this.sendTo = (subscriber, messageChain) => utils_1.chainPromises((splitted => [splitted.message, ...splitted.attachments])(exports.Message.separateAttachment(messageChain)).map(msg => {
             switch (subscriber.chatType) {
             switch (subscriber.chatType) {
                 case 'group':
                 case 'group':
                     return this.bot.sendMessage(subscriber.chatID.toString(), msg);
                     return this.bot.sendMessage(subscriber.chatID.toString(), msg);
@@ -55,13 +68,13 @@ class default_1 {
                 case 'temp':
                 case 'temp':
                     return this.bot.sendPrivateMessage(subscriber.chatID.qq.toString(), msg);
                     return this.bot.sendPrivateMessage(subscriber.chatID.qq.toString(), msg);
             }
             }
-        })()
+        }))
             .then(response => {
             .then(response => {
             logger.info(`pushing data to ${JSON.stringify(subscriber.chatID)} was successful, response:`);
             logger.info(`pushing data to ${JSON.stringify(subscriber.chatID)} was successful, response:`);
             logger.info(response);
             logger.info(response);
         })
         })
             .catch(reason => {
             .catch(reason => {
-            reason = exports.ellipseBase64InMessage(reason);
+            reason = exports.Message.ellipseBase64(reason);
             logger.error(`error pushing data to ${JSON.stringify(subscriber.chatID)}, reason: ${reason}`);
             logger.error(`error pushing data to ${JSON.stringify(subscriber.chatID)}, reason: ${reason}`);
             throw Error(reason);
             throw Error(reason);
         });
         });
@@ -161,20 +174,9 @@ count 与 since/until 并用时,取二者中实际查询结果较少者
                 }
                 }
             }), true);
             }), true);
         };
         };
-        this.listen = (logMsg = 'connecting to bot provider...') => __awaiter(this, void 0, void 0, function* () {
-            logger.warn(logMsg);
-            try {
-                yield this.app.start();
-            }
-            catch (err) {
-                logger.error(`error connecting to bot provider at ${this.app.options.server}, will retry in 2.5s...`);
-                yield koishi_1.sleep(2500);
-                yield this.listen('retry connecting...');
-            }
-        });
         this.connect = () => __awaiter(this, void 0, void 0, function* () {
         this.connect = () => __awaiter(this, void 0, void 0, function* () {
             this.initBot();
             this.initBot();
-            yield this.listen();
+            yield this.app.start();
             this.bot = this.app.getBot('onebot');
             this.bot = this.app.getBot('onebot');
         });
         });
         logger.warn(`Initialized koishi on ${opt.host}:${opt.port} with access_token ${opt.access_token}`);
         logger.warn(`Initialized koishi on ${opt.host}:${opt.port} with access_token ${opt.access_token}`);

+ 1 - 1
dist/twitter.js

@@ -142,7 +142,7 @@ class default_1 {
         };
         };
         this.sendTweets = (source, ...to) => (msg, text, author) => {
         this.sendTweets = (source, ...to) => (msg, text, author) => {
             to.forEach(subscriber => {
             to.forEach(subscriber => {
-                logger.info(`pushing data${source ? ` of ${koishi_1.ellipseBase64InMessage(source)}` : ''} to ${JSON.stringify(subscriber)}`);
+                logger.info(`pushing data${source ? ` of ${koishi_1.Message.ellipseBase64(source)}` : ''} to ${JSON.stringify(subscriber)}`);
                 retryOnError(() => this.bot.sendTo(subscriber, msg), (_, count, terminate) => {
                 retryOnError(() => this.bot.sendTo(subscriber, msg), (_, count, terminate) => {
                     if (count <= maxTrials) {
                     if (count <= maxTrials) {
                         logger.warn(`retry sending to ${subscriber.chatID} for the ${ordinal(count)} time...`);
                         logger.warn(`retry sending to ${subscriber.chatID} for the ${ordinal(count)} time...`);

+ 23 - 17
dist/webshot.js

@@ -1,5 +1,6 @@
 "use strict";
 "use strict";
 Object.defineProperty(exports, "__esModule", { value: true });
 Object.defineProperty(exports, "__esModule", { value: true });
+const fs_1 = require("fs");
 const util_1 = require("util");
 const util_1 = require("util");
 const axios_1 = require("axios");
 const axios_1 = require("axios");
 const CallableInstance = require("callable-instance");
 const CallableInstance = require("callable-instance");
@@ -7,6 +8,7 @@ const html_entities_1 = require("html-entities");
 const pngjs_1 = require("pngjs");
 const pngjs_1 = require("pngjs");
 const puppeteer = require("playwright");
 const puppeteer = require("playwright");
 const sharp = require("sharp");
 const sharp = require("sharp");
+const temp = require("temp");
 const loggers_1 = require("./loggers");
 const loggers_1 = require("./loggers");
 const koishi_1 = require("./koishi");
 const koishi_1 = require("./koishi");
 const utils_1 = require("./utils");
 const utils_1 = require("./utils");
@@ -51,9 +53,11 @@ class Webshot extends CallableInstance {
             logger.info('not working on a tweet');
             logger.info('not working on a tweet');
         };
         };
         this.renderWebshot = (url, height, webshotDelay) => {
         this.renderWebshot = (url, height, webshotDelay) => {
+            temp.track();
             const jpeg = (data) => data.pipe(sharp()).jpeg({ quality: 90, trellisQuantisation: true });
             const jpeg = (data) => data.pipe(sharp()).jpeg({ quality: 90, trellisQuantisation: true });
-            const sharpToBase64 = (pic) => new Promise(resolve => {
-                pic.toBuffer().then(buffer => resolve(`base64://${buffer.toString('base64')}`));
+            const sharpToFile = (pic) => new Promise(resolve => {
+                const webshotTempFilePath = temp.path({ suffix: '.jpg' });
+                pic.toFile(webshotTempFilePath).then(() => resolve(`file://${webshotTempFilePath}`));
             });
             });
             const promise = new Promise((resolve, reject) => {
             const promise = new Promise((resolve, reject) => {
                 const width = 720;
                 const width = 720;
@@ -188,20 +192,20 @@ class Webshot extends CallableInstance {
                                     this.data = this.data.slice(0, idx(this.width, boundary));
                                     this.data = this.data.slice(0, idx(this.width, boundary));
                                     this.height = boundary;
                                     this.height = boundary;
                                 }
                                 }
-                                sharpToBase64(jpeg(this.pack())).then(base64 => {
+                                sharpToFile(jpeg(this.pack())).then(path => {
                                     logger.info(`finished webshot for ${url}`);
                                     logger.info(`finished webshot for ${url}`);
-                                    resolve({ base64, boundary });
+                                    resolve({ path, boundary });
                                 });
                                 });
                             }
                             }
                             else if (height >= 8 * 1920) {
                             else if (height >= 8 * 1920) {
                                 logger.warn('too large, consider as a bug, returning');
                                 logger.warn('too large, consider as a bug, returning');
-                                sharpToBase64(jpeg(this.pack())).then(base64 => {
-                                    resolve({ base64, boundary: 0 });
+                                sharpToFile(jpeg(this.pack())).then(path => {
+                                    resolve({ path, boundary: 0 });
                                 });
                                 });
                             }
                             }
                             else {
                             else {
                                 logger.info('unable to find boundary, try shooting a larger image');
                                 logger.info('unable to find boundary, try shooting a larger image');
-                                resolve({ base64: '', boundary });
+                                resolve({ path: '', boundary });
                             }
                             }
                         }).parse(screenshot);
                         }).parse(screenshot);
                     })
                     })
@@ -209,7 +213,7 @@ class Webshot extends CallableInstance {
                         if (err instanceof Error && err.name !== 'TimeoutError')
                         if (err instanceof Error && err.name !== 'TimeoutError')
                             throw err;
                             throw err;
                         logger.error(`error shooting webshot for ${url}, could not load web page of tweet`);
                         logger.error(`error shooting webshot for ${url}, could not load web page of tweet`);
-                        resolve({ base64: '', boundary: 0 });
+                        resolve({ path: '', boundary: 0 });
                     })
                     })
                         .finally(() => { page.close(); });
                         .finally(() => { page.close(); });
                 })
                 })
@@ -219,7 +223,7 @@ class Webshot extends CallableInstance {
                 if (data.boundary === null)
                 if (data.boundary === null)
                     return this.renderWebshot(url, height + 1920, webshotDelay);
                     return this.renderWebshot(url, height + 1920, webshotDelay);
                 else
                 else
-                    return data.base64;
+                    return data.path;
             }).catch(error => this.reconnect(error)
             }).catch(error => this.reconnect(error)
                 .then(() => this.renderWebshot(url, height, webshotDelay)));
                 .then(() => this.renderWebshot(url, height, webshotDelay)));
         };
         };
@@ -228,7 +232,7 @@ class Webshot extends CallableInstance {
             axios_1.default({
             axios_1.default({
                 method: 'get',
                 method: 'get',
                 url,
                 url,
-                responseType: 'arraybuffer',
+                responseType: 'stream',
                 timeout: 150000,
                 timeout: 150000,
             }).then(res => {
             }).then(res => {
                 if (res.status === 200) {
                 if (res.status === 200) {
@@ -246,13 +250,15 @@ class Webshot extends CallableInstance {
         }).then(data => {
         }).then(data => {
             var _a;
             var _a;
             return (ext => {
             return (ext => {
-                const base64 = `base64://${Buffer.from(data).toString('base64')}`;
+                const mediaTempFilePath = temp.path({ suffix: ext });
+                data.pipe(fs_1.createWriteStream(mediaTempFilePath));
+                const path = `file://${mediaTempFilePath}`;
                 switch (ext) {
                 switch (ext) {
                     case 'jpg':
                     case 'jpg':
                     case 'png':
                     case 'png':
-                        return koishi_1.message.image(base64);
+                        return koishi_1.Message.Image(path);
                     case 'mp4':
                     case 'mp4':
-                        return koishi_1.message.video(base64);
+                        return koishi_1.Message.Video(path);
                 }
                 }
                 logger.warn('unable to find MIME type of fetched media, failing this fetch');
                 logger.warn('unable to find MIME type of fetched media, failing this fetch');
                 throw Error();
                 throw Error();
@@ -304,9 +310,9 @@ class Webshot extends CallableInstance {
                         ] });
                         ] });
                 };
                 };
                 promise = promise.then(() => this.renderWebshot(url, 1920, webshotDelay))
                 promise = promise.then(() => this.renderWebshot(url, 1920, webshotDelay))
-                    .then(base64url => {
-                    if (base64url)
-                        return koishi_1.message.image(base64url);
+                    .then(fileurl => {
+                    if (fileurl)
+                        return koishi_1.Message.Image(fileurl);
                     return author + text;
                     return author + text;
                 })
                 })
                     .then(msg => {
                     .then(msg => {
@@ -357,7 +363,7 @@ class Webshot extends CallableInstance {
             }
             }
             promise.then(() => {
             promise.then(() => {
                 logger.info(`done working on ${twi.user.screen_name}/${twi.id_str}, message chain:`);
                 logger.info(`done working on ${twi.user.screen_name}/${twi.id_str}, message chain:`);
-                logger.info(JSON.stringify(koishi_1.ellipseBase64InMessage(messageChain)));
+                logger.info(JSON.stringify(koishi_1.Message.ellipseBase64(messageChain)));
                 callback(messageChain, xmlEntities.decode(text), author);
                 callback(messageChain, xmlEntities.decode(text), author);
             });
             });
         });
         });

+ 35 - 26
src/koishi.ts

@@ -1,8 +1,9 @@
-import { App, Bot, segment, Session, sleep } from 'koishi';
+import { App, Bot, segment, Session } from 'koishi';
 import 'koishi-adapter-onebot';
 import 'koishi-adapter-onebot';
 
 
 import { parseCmd, query, view } from './command';
 import { parseCmd, query, view } from './command';
 import { getLogger } from './loggers';
 import { getLogger } from './loggers';
+import { chainPromises } from './utils';
 
 
 const logger = getLogger('qqbot');
 const logger = getLogger('qqbot');
 
 
@@ -16,8 +17,24 @@ interface IQQProps {
   unsub(chat: IChat, args: string[], replyfn: (msg: string) => any): void;
   unsub(chat: IChat, args: string[], replyfn: (msg: string) => any): void;
 }
 }
 
 
-export const message = segment;
-export const ellipseBase64InMessage = (msg: string) => msg.replace(/(?<=\[CQ:.*base64:\/\/).*?(,|\])/g, '...$1');
+const cqUrlFix = (factory: segment.Factory<string | ArrayBuffer | Buffer>) =>
+  (...args: Parameters<typeof factory>) => 
+    factory(...args).replace(/(?<=\[CQ:.*)url=(?=(base64|file|https?):\/\/)/, 'file=');
+
+export const Message = {
+  Image: cqUrlFix(segment.image),
+  Video: cqUrlFix(segment.video),
+  Voice: cqUrlFix(segment.audio),
+  ellipseBase64: (msg: string) => msg.replace(/(?<=\[CQ:.*base64:\/\/).*?(,|\])/g, '...$1'),
+  separateAttachment: (msg: string) => {
+    const attachments: string[] = [];
+    const message = msg.replace(/\[CQ:(image|video|record),.*?\]/g, code => {
+      attachments.push(code);
+      return '';
+    });
+    return {message, attachments};
+  },
+};
 
 
 export default class {
 export default class {
 
 
@@ -52,22 +69,25 @@ export default class {
     }
     }
   };
   };
 
 
-  public sendTo = (subscriber: IChat, msg: string) => (() => {
-    switch (subscriber.chatType) {
-      case 'group':
-        return this.bot.sendMessage(subscriber.chatID.toString(), msg);
-      case 'private':
-        return this.bot.sendPrivateMessage(subscriber.chatID.toString(), msg);
-      case 'temp': // currently unable to open session, awaiting OneBot v12
-        return this.bot.sendPrivateMessage(subscriber.chatID.qq.toString(), msg);
-    }
-  })()
+  public sendTo = (subscriber: IChat, messageChain: string) => chainPromises(
+    (splitted => [splitted.message, ...splitted.attachments])(
+      Message.separateAttachment(messageChain)
+    ).map(msg => {
+      switch (subscriber.chatType) {
+        case 'group':
+          return this.bot.sendMessage(subscriber.chatID.toString(), msg);
+        case 'private':
+          return this.bot.sendPrivateMessage(subscriber.chatID.toString(), msg);
+        case 'temp': // currently unable to open session, awaiting OneBot v12
+          return this.bot.sendPrivateMessage(subscriber.chatID.qq.toString(), msg);
+      }
+    }))
     .then(response => {
     .then(response => {
       logger.info(`pushing data to ${JSON.stringify(subscriber.chatID)} was successful, response:`);
       logger.info(`pushing data to ${JSON.stringify(subscriber.chatID)} was successful, response:`);
       logger.info(response);
       logger.info(response);
     })
     })
     .catch(reason => {
     .catch(reason => {
-      reason = ellipseBase64InMessage(reason);
+      reason = Message.ellipseBase64(reason);
       logger.error(`error pushing data to ${JSON.stringify(subscriber.chatID)}, reason: ${reason}`);
       logger.error(`error pushing data to ${JSON.stringify(subscriber.chatID)}, reason: ${reason}`);
       throw Error(reason);
       throw Error(reason);
     });
     });
@@ -173,20 +193,9 @@ count 与 since/until 并用时,取二者中实际查询结果较少者
     }, true);
     }, true);
   };
   };
 
 
-  private listen = async (logMsg = 'connecting to bot provider...'): Promise<void> => {
-    logger.warn(logMsg);
-    try {
-      await this.app.start();
-    } catch (err) {
-      logger.error(`error connecting to bot provider at ${this.app.options.server}, will retry in 2.5s...`);
-      await sleep(2500);
-      await this.listen('retry connecting...');
-    }
-  };
-
   public connect = async () => {
   public connect = async () => {
     this.initBot();
     this.initBot();
-    await this.listen();
+    await this.app.start();
     this.bot = this.app.getBot('onebot');
     this.bot = this.app.getBot('onebot');
   };
   };
 
 

+ 2 - 2
src/twitter.ts

@@ -4,7 +4,7 @@ import * as Twitter from 'twitter';
 import TwitterTypes from 'twitter-d';
 import TwitterTypes from 'twitter-d';
 
 
 import { getLogger } from './loggers';
 import { getLogger } from './loggers';
-import QQBot, { ellipseBase64InMessage } from './koishi';
+import QQBot, { Message } from './koishi';
 import { chainPromises, BigNumOps } from './utils';
 import { chainPromises, BigNumOps } from './utils';
 import Webshot from './webshot';
 import Webshot from './webshot';
 
 
@@ -276,7 +276,7 @@ export default class {
 
 
   private sendTweets = (source?: string, ...to: IChat[]) => (msg: string, text: string, author: string) => {
   private sendTweets = (source?: string, ...to: IChat[]) => (msg: string, text: string, author: string) => {
     to.forEach(subscriber => {
     to.forEach(subscriber => {
-      logger.info(`pushing data${source ? ` of ${ellipseBase64InMessage(source)}` : ''} to ${JSON.stringify(subscriber)}`);
+      logger.info(`pushing data${source ? ` of ${Message.ellipseBase64(source)}` : ''} to ${JSON.stringify(subscriber)}`);
       retryOnError(
       retryOnError(
         () => this.bot.sendTo(subscriber, msg),
         () => this.bot.sendTo(subscriber, msg),
         (_, count, terminate: (doNothing: Promise<void>) => void) => {
         (_, count, terminate: (doNothing: Promise<void>) => void) => {

+ 26 - 20
src/webshot.ts

@@ -1,4 +1,5 @@
-import { Readable } from 'stream';
+import { createWriteStream } from 'fs';
+import { Readable, Stream } from 'stream';
 import { promisify } from 'util';
 import { promisify } from 'util';
 
 
 import axios from 'axios';
 import axios from 'axios';
@@ -7,9 +8,10 @@ import { XmlEntities } from 'html-entities';
 import { PNG } from 'pngjs';
 import { PNG } from 'pngjs';
 import * as puppeteer from 'playwright';
 import * as puppeteer from 'playwright';
 import * as sharp from 'sharp';
 import * as sharp from 'sharp';
+import * as temp from 'temp';
 
 
 import { getLogger } from './loggers';
 import { getLogger } from './loggers';
-import { ellipseBase64InMessage, message } from './koishi';
+import { Message } from './koishi';
 import { MediaEntity, Tweets } from './twitter';
 import { MediaEntity, Tweets } from './twitter';
 import { chainPromises } from './utils';
 import { chainPromises } from './utils';
 
 
@@ -73,11 +75,13 @@ class Webshot extends CallableInstance<[Tweets, (...args) => void, number], Prom
   };
   };
 
 
   private renderWebshot = (url: string, height: number, webshotDelay: number): Promise<string> => {
   private renderWebshot = (url: string, height: number, webshotDelay: number): Promise<string> => {
+    temp.track();
     const jpeg = (data: Readable) => data.pipe(sharp()).jpeg({quality: 90, trellisQuantisation: true});
     const jpeg = (data: Readable) => data.pipe(sharp()).jpeg({quality: 90, trellisQuantisation: true});
-    const sharpToBase64 = (pic: sharp.Sharp) => new Promise<string>(resolve => {
-      pic.toBuffer().then(buffer => resolve(`base64://${buffer.toString('base64')}`));
+    const sharpToFile = (pic: sharp.Sharp) => new Promise<string>(resolve => {
+      const webshotTempFilePath = temp.path({suffix: '.jpg'});
+      pic.toFile(webshotTempFilePath).then(() => resolve(`file://${webshotTempFilePath}`));
     });
     });
-    const promise = new Promise<{ base64: string, boundary: null | number }>((resolve, reject) => {
+    const promise = new Promise<{ path: string, boundary: null | number }>((resolve, reject) => {
       const width = 720;
       const width = 720;
       const zoomFactor = 2;
       const zoomFactor = 2;
       logger.info(`shooting ${width}*${height} webshot for ${url}`);
       logger.info(`shooting ${width}*${height} webshot for ${url}`);
@@ -221,25 +225,25 @@ class Webshot extends CallableInstance<[Tweets, (...args) => void, number], Prom
                     this.height = boundary;
                     this.height = boundary;
                   }
                   }
 
 
-                  sharpToBase64(jpeg(this.pack())).then(base64 => {
+                  sharpToFile(jpeg(this.pack())).then(path => {
                     logger.info(`finished webshot for ${url}`);
                     logger.info(`finished webshot for ${url}`);
-                    resolve({base64, boundary});
+                    resolve({path, boundary});
                   });
                   });
                 } else if (height >= 8 * 1920) {
                 } else if (height >= 8 * 1920) {
                   logger.warn('too large, consider as a bug, returning');
                   logger.warn('too large, consider as a bug, returning');
-                  sharpToBase64(jpeg(this.pack())).then(base64 => {
-                    resolve({base64, boundary: 0});
+                  sharpToFile(jpeg(this.pack())).then(path => {
+                    resolve({path, boundary: 0});
                   });
                   });
                 } else {
                 } else {
                   logger.info('unable to find boundary, try shooting a larger image');
                   logger.info('unable to find boundary, try shooting a larger image');
-                  resolve({base64: '', boundary});
+                  resolve({path: '', boundary});
                 }
                 }
               }).parse(screenshot);
               }).parse(screenshot);
             })
             })
             .catch(err => {
             .catch(err => {
               if (err instanceof Error && err.name !== 'TimeoutError') throw err;
               if (err instanceof Error && err.name !== 'TimeoutError') throw err;
               logger.error(`error shooting webshot for ${url}, could not load web page of tweet`);
               logger.error(`error shooting webshot for ${url}, could not load web page of tweet`);
-              resolve({base64: '', boundary: 0});
+              resolve({path: '', boundary: 0});
             })
             })
             .finally(() => { page.close(); });
             .finally(() => { page.close(); });
         })
         })
@@ -247,18 +251,18 @@ class Webshot extends CallableInstance<[Tweets, (...args) => void, number], Prom
     });
     });
     return promise.then(data => {
     return promise.then(data => {
       if (data.boundary === null) return this.renderWebshot(url, height + 1920, webshotDelay);
       if (data.boundary === null) return this.renderWebshot(url, height + 1920, webshotDelay);
-      else return data.base64;
+      else return data.path;
     }).catch(error => this.reconnect(error)
     }).catch(error => this.reconnect(error)
       .then(() => this.renderWebshot(url, height, webshotDelay))
       .then(() => this.renderWebshot(url, height, webshotDelay))
     );
     );
   };
   };
 
 
-  private fetchMedia = (url: string): Promise<string> => new Promise<ArrayBuffer>((resolve, reject) => {
+  private fetchMedia = (url: string): Promise<string> => new Promise<Stream>((resolve, reject) => {
     logger.info(`fetching ${url}`);
     logger.info(`fetching ${url}`);
     axios({
     axios({
       method: 'get',
       method: 'get',
       url,
       url,
-      responseType: 'arraybuffer',
+      responseType: 'stream',
       timeout: 150000,
       timeout: 150000,
     }).then(res => {
     }).then(res => {
       if (res.status === 200) {
       if (res.status === 200) {
@@ -274,13 +278,15 @@ class Webshot extends CallableInstance<[Tweets, (...args) => void, number], Prom
     });
     });
   }).then(data =>
   }).then(data =>
     (ext => {
     (ext => {
-      const base64 = `base64://${Buffer.from(data).toString('base64')}`;
+      const mediaTempFilePath = temp.path({suffix: ext});
+      data.pipe(createWriteStream(mediaTempFilePath));
+      const path = `file://${mediaTempFilePath}`;
       switch (ext) {
       switch (ext) {
         case 'jpg':
         case 'jpg':
         case 'png':
         case 'png':
-          return message.image(base64);
+          return Message.Image(path);
         case 'mp4':
         case 'mp4':
-          return message.video(base64);
+          return Message.Video(path);
       }
       }
       logger.warn('unable to find MIME type of fetched media, failing this fetch');
       logger.warn('unable to find MIME type of fetched media, failing this fetch');
       throw Error();
       throw Error();
@@ -335,8 +341,8 @@ class Webshot extends CallableInstance<[Tweets, (...args) => void, number], Prom
           };
           };
         };
         };
         promise = promise.then(() => this.renderWebshot(url, 1920, webshotDelay))
         promise = promise.then(() => this.renderWebshot(url, 1920, webshotDelay))
-          .then(base64url => {
-            if (base64url) return message.image(base64url);
+          .then(fileurl => {
+            if (fileurl) return Message.Image(fileurl);
             return author + text;
             return author + text;
           })
           })
           .then(msg => {
           .then(msg => {
@@ -389,7 +395,7 @@ class Webshot extends CallableInstance<[Tweets, (...args) => void, number], Prom
       }
       }
       promise.then(() => {
       promise.then(() => {
         logger.info(`done working on ${twi.user.screen_name}/${twi.id_str}, message chain:`);
         logger.info(`done working on ${twi.user.screen_name}/${twi.id_str}, message chain:`);
-        logger.info(JSON.stringify(ellipseBase64InMessage(messageChain)));
+        logger.info(JSON.stringify(Message.ellipseBase64(messageChain)));
         callback(messageChain, xmlEntities.decode(text), author);
         callback(messageChain, xmlEntities.decode(text), author);
       });
       });
     });
     });