#!/usr/bin/env node import * as fs from 'fs'; import * as path from 'path'; import * as commandLineUsage from 'command-line-usage'; import * as exampleConfig from '../config.example.json'; import { list, sub, unsub } from './command'; import { getLogger, setLogLevels } from './loggers'; import QQBot from './mirai'; import Worker from './twitter'; const logger = getLogger(); const sections: commandLineUsage.Section[] = [ { header: 'MiraiTS Twitter Bot', content: 'The QQ Bot that forwards twitters.', }, { header: 'Synopsis', content: [ '$ mirai-twitter-bot {underline config.json}', '$ mirai-twitter-bot {bold --help}', ], }, { header: 'Documentation', content: [ 'Project home: {underline https://github.com/CL-Jeremy/mirai-twitter-bot}', 'Example config: {underline https://git.io/JJ0jN}', ], }, ]; const usage = commandLineUsage(sections); const args = process.argv.slice(2); if (args.length === 0 || args[0] === 'help' || args[0] === '-h' || args[0] === '--help') { console.log(usage); process.exit(0); } const configPath = args[0]; type Config = typeof exampleConfig; let config: Config; try { config = JSON.parse(fs.readFileSync(path.resolve(configPath), 'utf8')) as Config; } catch (e) { console.log('Failed to parse config file: ', configPath); console.log(usage); process.exit(1); } const requiredFields = [ 'twitter_consumer_key', 'twitter_consumer_secret', 'twitter_access_token_key', 'twitter_access_token_secret', ]; const warningFields = [ 'mirai_http_host', 'mirai_http_port', 'mirai_access_token', ]; const optionalFields = [ 'lockfile', 'work_interval', 'webshot_delay', 'loglevel', 'mode', 'resume_on_start', ].concat(warningFields); if (requiredFields.some((value) => config[value] === undefined)) { console.log(`${requiredFields.join(', ')} are required`); process.exit(1); } optionalFields.forEach(key => { if (config[key] === undefined || typeof(config[key]) !== typeof (exampleConfig[key])) { if (key in warningFields) logger.warn(`${key} is undefined, use ${exampleConfig[key] || 'empty string'} as default`); config[key] = exampleConfig[key as keyof Config]; } }); setLogLevels(config.loglevel); let lock: ILock; if (fs.existsSync(path.resolve(config.lockfile))) { try { lock = JSON.parse(fs.readFileSync(path.resolve(config.lockfile), 'utf8')) as ILock; } catch (err) { logger.error(`Failed to parse lockfile ${config.lockfile}: `, err); lock = { workon: 0, feed: [], threads: {}, }; } fs.access(path.resolve(config.lockfile), fs.constants.W_OK, err => { if (err) { logger.fatal(`cannot write lockfile ${path.resolve(config.lockfile)}, permission denied`); process.exit(1); } }); } else { lock = { workon: 0, feed: [], threads: {}, }; try { fs.writeFileSync(path.resolve(config.lockfile), JSON.stringify(lock)); } catch (err) { logger.fatal(`cannot write lockfile ${path.resolve(config.lockfile)}, permission denied`); process.exit(1); } } if (!config.resume_on_start) { Object.keys(lock.threads).forEach(key => { lock.threads[key].offset = '-1'; }); } const qq = new QQBot({ access_token: config.mirai_access_token, host: config.mirai_http_host, port: config.mirai_http_port, bot_id: config.mirai_bot_qq, list: (c, a, cb) => list(c, a, cb, lock), sub: (c, a, cb) => sub(c, a, cb, lock, config.lockfile), unsub: (c, a, cb) => unsub(c, a, cb, lock, config.lockfile), }); const worker = new Worker({ consumerKey: config.twitter_consumer_key, consumerSecret: config.twitter_consumer_secret, accessTokenKey: config.twitter_access_token_key, accessTokenSecret: config.twitter_access_token_secret, privateAuthToken: config.twitter_private_auth_token, privateCsrfToken: config.twitter_private_csrf_token, lock, lockfile: config.lockfile, workInterval: config.work_interval, bot: qq, webshotDelay: config.webshot_delay, mode: config.mode, wsUrl: config.playwright_ws_spec_endpoint, }); worker.launch(); qq.connect();