"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.snowflake = exports.parseAction = exports.processTweetBody = exports.recurringKeywords = exports.keywordMap = void 0; const emojiStrip = require("emoji-strip"); const fs = require("fs"); const path = require("path"); const Twitter = require("twitter"); const html_entities_1 = require("html-entities"); const loggers_1 = require("./loggers"); const utils_1 = require("./utils"); const wiki_1 = require("./wiki"); exports.keywordMap = { 'ガチャ.*予告': '卡池', '(選べる.*|一日一回無料|スタートダッシュ.*)ガチャ': '卡池', 'イベント開催決定|フォトオーディション開催': '活动', 'タワー.*追加': '新塔', '(生放送|配信)予告': '生放', 'メンテナンス予告': '维护', '(育成応援|お仕事).*開催': '工作', '新曲一部公開|3DLIVE映像公開': '新曲', 'キャラクター紹介': '组合', '今後のアップデート情報': '计划', '(? { const urls = tweet.entities.urls ? tweet.entities.urls.map(url => url.expanded_url) : []; const [title, body] = emojiStrip(xmlEntities.decode(tweet.full_text)) .replace(/(?<=\n|^)(.*)(?:\: |:)(https?:\/\/.*?)(?=\s|$)/g, '[$2 $1]') .replace(/https?:\/\/.*?(?=\s|$)/g, () => urls.length ? urls.splice(0, 1)[0] : '') .replace(/(?<=\n|^)[\/]\n/g, '') .replace(/((?<=\s)#.*?\s+)+$/g, '') .trim() .match(/(.*?)\n\n(.*)/s).slice(1); const dateMatch = /(\d+\/\d+)\(.\).*(?:から|~)\s*\n/.exec(body); const date = (dateMatch ? new Date(`${new Date(tweet.created_at).getFullYear()}/${dateMatch[1]}`) : new Date(tweet.created_at)) .toLocaleDateString('en-CA', { timeZone: 'Asia/Tokyo' }) .replace(/-/g, ''); const pageTitle = title.replace(/[【】\/\n]/g, '') + `${exports.recurringKeywords.some(keyword => new RegExp(keyword).exec(title)) ? `-${date}` : ''}`; return { title, body, pageTitle, date }; }; exports.processTweetBody = processTweetBody; const parseAction = (action) => { if (!action || !Object.keys(action)) return '(无)'; return `\n 标题:${action.title} 操作:${action.new ? '新建' : '更新'} 媒体:${action.mediafiles.map((fileName, index) => `\n ${index + 1}. ${fileName}`).join('') || '(无)'} 链接:${action.pageid ? `https://wiki.biligame.com/idolypride/index.php?curid=${action.pageid}` : '(无)'} `; }; exports.parseAction = parseAction; const TWITTER_EPOCH = 1288834974657; const snowflake = (epoch) => Number.isNaN(epoch) ? undefined : utils_1.BigNumOps.lShift(String(epoch - 1 - TWITTER_EPOCH), 22); exports.snowflake = snowflake; const logger = (0, loggers_1.getLogger)('twitter'); class default_1 { constructor(opt) { this.launch = () => { this.publisher.login(this.wikiSessionCookie).then(() => { setTimeout(this.work, this.workInterval * 1000); }); }; this.work = () => { const lock = this.lock; if (this.workInterval < 1) this.workInterval = 1; const currentFeed = 'https://twitter.com/idolypride'; logger.debug(`pulling feed ${currentFeed}`); const promise = new Promise(resolve => { let config; let endpoint; const match = /https:\/\/twitter.com\/([^\/]+)/.exec(currentFeed); if (match) { config = { screen_name: match[1], exclude_replies: true, include_rts: false, tweet_mode: 'extended', }; endpoint = 'statuses/user_timeline'; } if (endpoint) { const offset = lock.offset; if (offset > 0) config.since_id = offset; const getMore = (lastTweets = []) => this.client.get(endpoint, config, (error, tweets) => { if (error) { if (error instanceof Array && error.length > 0 && error[0].code === 34) { logger.warn(`error on fetching tweets for ${currentFeed}: ${JSON.stringify(error)}`); lock.subscribers.forEach(subscriber => { logger.info(`sending notfound message of ${currentFeed} to ${JSON.stringify(subscriber)}`); this.bot.sendTo(subscriber, `错误:链接 ${currentFeed} 指向的用户或列表不存在。`).catch(); }); } else { logger.error(`unhandled error on fetching tweets for ${currentFeed}: ${JSON.stringify(error)}`); } } if (!(tweets instanceof Array) || tweets.length === 0) return resolve(lastTweets); if (offset <= 0) return resolve(lastTweets.concat(tweets)); config.max_id = utils_1.BigNumOps.plus(tweets.slice(-1)[0].id_str, '-1'); getMore(lastTweets.concat(tweets)); }); getMore(); } }); promise.then((tweets) => { logger.debug(`api returned ${JSON.stringify(tweets)} for feed ${currentFeed}`); const updateDate = () => lock.updatedAt = new Date().toString(); if (tweets.length === 0) { updateDate(); return; } const updateOffset = (offset) => lock.offset = offset; if (lock.offset === '-1') { updateOffset(tweets[0].id_str); return; } if (lock.offset === '0') tweets.splice(1); return (0, utils_1.chainPromises)(tweets.slice(0).reverse().map(tweet => () => { const match = /(.*?)\n\n(.*)/s.exec(tweet.full_text); if (match) for (const keyword in exports.keywordMap) { if (new RegExp(keyword).exec(match[1])) { const tweetUrl = `${currentFeed}/status/${tweet.id_str}`; logger.info(`working on ${tweetUrl}`); return this.publisher.post(tweet, exports.keywordMap[keyword]) .then(action => { if (action.result === 'Success') { this.lock.lastActions.push(action); logger.info(`successfully posted content of ${tweetUrl} to bwiki, link:`); logger.info(`https://wiki.biligame.com/idolypride/index.php?curid=${action.pageid}`); const message = `已更新如下页面:${(0, exports.parseAction)(action)}`; return Promise.all(this.lock.subscribers.map(subscriber => this.bot.sendTo(subscriber, message))).then(() => { updateDate(); updateOffset(tweet.id_str); return true; }); } }); } } return Promise.resolve(false); })).then(updated => { if (updated) return; updateDate(); updateOffset(tweets[0].id_str); }); }) .then(() => { let timeout = this.workInterval * 1000; if (timeout < 1000) timeout = 1000; fs.writeFileSync(path.resolve(this.lockfile), JSON.stringify(lock)); setTimeout(() => { this.work(); }, timeout); }); }; this.client = new Twitter({ consumer_key: opt.consumerKey, consumer_secret: opt.consumerSecret, access_token_key: opt.accessTokenKey, access_token_secret: opt.accessTokenSecret, }); this.bot = opt.bot; this.publisher = new wiki_1.default(opt.lock); this.lockfile = opt.lockfile; this.lock = opt.lock; this.workInterval = opt.workInterval; this.wikiSessionCookie = opt.wikiSessionCookie; } } exports.default = default_1;