wiki.js 9.0 KB

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