wiki.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const axios_1 = require("axios");
  4. const fetchCookie = require("fetch-cookie");
  5. const fs_1 = require("fs");
  6. const mediawiki2_1 = require("mediawiki2");
  7. const node_fetch_1 = require("node-fetch");
  8. const playwright_1 = require("playwright");
  9. const gifski_1 = require("./gifski");
  10. const loggers_1 = require("./loggers");
  11. const twitter_1 = require("./twitter");
  12. const logger = (0, loggers_1.getLogger)('wiki');
  13. const baseUrl = 'https://wiki.biligame.com/idolypride';
  14. class default_1 {
  15. constructor(lock) {
  16. this.login = (sessdata) => playwright_1.firefox.launch().then(browser => {
  17. const jar = this.bot.cookieJar;
  18. return browser.newPage().then(page => page.context().addCookies([{
  19. name: 'SESSDATA',
  20. value: sessdata,
  21. domain: '.biligame.com',
  22. path: '/',
  23. }])
  24. .then(() => page.route('**/*.{png,jpg,jpeg,gif}', route => route.abort()))
  25. .then(() => page.route('*://*.baidu.com/**', route => route.abort()))
  26. .then(() => page.goto(`${baseUrl}/index.php?curid=2`, { waitUntil: 'networkidle' }))
  27. .then(() => { logger.info('logging in via browser...'); return page.context().cookies(); })
  28. .then(cookies => {
  29. const uidIndex = cookies.findIndex(cookie => cookie.name === 'gamecenter_wiki_UserName');
  30. if (!uidIndex)
  31. throw new Error('auth error');
  32. return Promise.all(cookies.map(({ name, value, domain, path }) => jar.setCookie(`${name}=${value}; Domain=${domain}; Path=${path}`, baseUrl))).then(() => cookies[uidIndex].value);
  33. })
  34. .then(uid => {
  35. logger.info(`finished logging in via browser, wiki username: ${uid}`);
  36. this.bot.fetch = fetchCookie(node_fetch_1.default, jar);
  37. return browser.close();
  38. })
  39. .catch((err) => browser.close().then(() => {
  40. logger.fatal(`error logging in via browser, error: ${err}`);
  41. process.exit(0);
  42. })));
  43. });
  44. this.fetchMedia = (url) => new Promise((resolve, reject) => {
  45. logger.info(`fetching ${url}`);
  46. const fetch = () => (0, axios_1.default)({
  47. method: 'get',
  48. url,
  49. responseType: 'arraybuffer',
  50. timeout: 150000,
  51. }).then(res => {
  52. if (res.status === 200) {
  53. logger.info(`successfully fetched ${url}`);
  54. resolve(res.data);
  55. }
  56. else {
  57. logger.error(`failed to fetch ${url}: ${res.status}`);
  58. reject();
  59. }
  60. }).catch(err => {
  61. logger.error(`failed to fetch ${url}: ${err instanceof Error ? err.message : err}`);
  62. logger.info(`trying to fetch ${url} again...`);
  63. fetch();
  64. });
  65. fetch();
  66. }).then(data => {
  67. var _a;
  68. return (([_, filename, ext]) => {
  69. if (ext) {
  70. const mediaFileName = `${filename}.${ext}`;
  71. (0, fs_1.writeFileSync)(mediaFileName, Buffer.from(data));
  72. return (ext === 'mp4' ?
  73. (0, gifski_1.default)(mediaFileName, 320) :
  74. Promise.resolve(mediaFileName));
  75. }
  76. logger.warn('unable to find MIME type of fetched media, failing this fetch');
  77. throw Error();
  78. })((_a = /([^/]*)\?format=([a-z]+)&/.exec(url)) !== null && _a !== void 0 ? _a : /.*\/([^/]*)\.([^?]+)/.exec(url));
  79. });
  80. this.uploadMediaItems = (tweet, fileNamePrefix, indexOffset = 0) => {
  81. const mediaItems = [];
  82. if (tweet.extended_entities) {
  83. tweet.extended_entities.media.forEach((media, index) => {
  84. let url;
  85. if (media.type === 'photo') {
  86. url = media.media_url_https.replace(/\.([a-z]+)$/, '?format=$1') + '&name=orig';
  87. }
  88. else {
  89. url = media.video_info.variants
  90. .filter(variant => variant.bitrate !== undefined)
  91. .sort((var1, var2) => var2.bitrate - var1.bitrate)
  92. .map(variant => variant.url)[0];
  93. }
  94. const mediaPromise = this.fetchMedia(url)
  95. .then(mediaFileName => {
  96. const filename = `${fileNamePrefix}${indexOffset + index + 1}.${mediaFileName.split('.')[1]}`;
  97. logger.info(`uploading ${mediaFileName} as ${filename}...`);
  98. return this.bot.simpleUpload({
  99. file: mediaFileName,
  100. filename,
  101. })
  102. .then(() => filename)
  103. .catch(error => {
  104. if (error instanceof mediawiki2_1.WikiError && error.data.result === 'Warning') {
  105. const { duplicate } = error.data.warnings;
  106. if (duplicate)
  107. return duplicate[0];
  108. }
  109. else
  110. throw error;
  111. });
  112. });
  113. mediaItems.push(mediaPromise);
  114. });
  115. }
  116. return Promise.all(mediaItems);
  117. };
  118. this.appendMedia = (tweet, genre, indexOffset) => {
  119. const { pageTitle } = (0, twitter_1.processTweetBody)(tweet);
  120. const pageLongTitle = `公告/${pageTitle}`;
  121. const mediaFilePrefix = `公告-${genre}-${pageTitle}-`;
  122. return this.uploadMediaItems(tweet, mediaFilePrefix, indexOffset)
  123. .then(fileNames => {
  124. logger.info(`updating page ${pageLongTitle}...`);
  125. return this.bot.edit({
  126. title: pageLongTitle,
  127. appendtext: `${fileNames.map(fileName => `[[文件:${fileName}|无框|左]]\n`).join('')}`,
  128. bot: true,
  129. notminor: true,
  130. nocreate: true,
  131. })
  132. .then(({ new: isNewPost, newtimestamp, pageid, result, title }) => ({
  133. pageid,
  134. title,
  135. new: isNewPost,
  136. mediafiles: fileNames,
  137. result,
  138. timestamp: new Date(newtimestamp).toString(),
  139. }));
  140. })
  141. .catch(error => {
  142. logger.error(`error updating page, error: ${error}`);
  143. return {
  144. pageid: undefined,
  145. title: pageLongTitle,
  146. new: undefined,
  147. mediafiles: [],
  148. result: 'Failed',
  149. timestamp: undefined,
  150. };
  151. });
  152. };
  153. this.post = (tweet, genre) => {
  154. const { title, body, pageTitle, date } = (0, twitter_1.processTweetBody)(tweet);
  155. const pageLongTitle = `公告/${pageTitle}`;
  156. const mediaFilePrefix = `公告-${genre}-${pageTitle}-`;
  157. const sameTitleAction = this.lock.lastActions.find(action => action.title === pageLongTitle);
  158. if (sameTitleAction)
  159. return this.appendMedia(tweet, genre, sameTitleAction.mediafiles.length);
  160. return this.uploadMediaItems(tweet, mediaFilePrefix)
  161. .then(fileNames => {
  162. logger.info(`creating page ${pageLongTitle}`);
  163. return this.bot.edit({
  164. title: pageLongTitle,
  165. basetimestamp: new Date(),
  166. text: `{{文章戳
  167. |文章上级页面=公告
  168. |子类别=${genre}
  169. |时间=${date}
  170. |作者=IDOLY PRIDE
  171. |是否原创=否
  172. |来源=[https://twitter.com/idolypride IDOLY PRIDE]
  173. |原文地址=[https://twitter.com/idolypride/status/${tweet.id_str} ${pageTitle}]
  174. }}
  175. ====${title.replace('\n', '<br />')}====
  176. <poem>
  177. ${body}
  178. </poem>
  179. ${fileNames.map(fileName => `[[文件:${fileName}|无框|左]]`).join('\n')}
  180. `,
  181. bot: true,
  182. notminor: true,
  183. createonly: true,
  184. })
  185. .then(({ new: isNewPost, newtimestamp, pageid, result, title }) => ({
  186. pageid,
  187. title,
  188. new: isNewPost,
  189. mediafiles: fileNames,
  190. result,
  191. timestamp: new Date(newtimestamp).toString(),
  192. }));
  193. })
  194. .catch(error => {
  195. logger.error(`error creating page, error: ${error}`);
  196. return {
  197. pageid: undefined,
  198. title: pageLongTitle,
  199. new: undefined,
  200. mediafiles: [],
  201. result: 'Failed',
  202. timestamp: undefined,
  203. };
  204. });
  205. };
  206. this.bot = new mediawiki2_1.MWBot(`${baseUrl}/api.php`);
  207. this.lock = lock;
  208. const updateCsrfToken = this.bot.updateCsrfToken.bind(this.bot);
  209. this.bot.updateCsrfToken = (function () {
  210. setTimeout(() => { this.lastCsrfToken = undefined; }, 7200000);
  211. return updateCsrfToken();
  212. }).bind(this.bot);
  213. }
  214. }
  215. exports.default = default_1;