main.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. #!/usr/bin/env node
  2. import * as fs from 'fs';
  3. import * as path from 'path';
  4. import * as commandLineUsage from 'command-line-usage';
  5. import * as exampleConfig from '../config.example.json';
  6. import { list, sub, unsub } from './command';
  7. import { getLogger, setLogLevels } from './loggers';
  8. import QQBot from './koishi';
  9. import Worker from './twitter';
  10. const logger = getLogger();
  11. const sections: commandLineUsage.Section[] = [
  12. {
  13. header: 'GoCQHTTP Instagram Bot',
  14. content: 'The QQ Bot that forwards Instagram.',
  15. },
  16. {
  17. header: 'Synopsis',
  18. content: [
  19. '$ cq-instagram-bot {underline config.json}',
  20. '$ cq-instagram-bot {bold --help}',
  21. ],
  22. },
  23. {
  24. header: 'Documentation',
  25. content: [
  26. 'Project home: {underline https://github.com/CL-Jeremy/mirai-twitter-bot}',
  27. 'Example config: {underline https://git.io/JJ0jN}',
  28. ],
  29. },
  30. ];
  31. const usage = commandLineUsage(sections);
  32. const args = process.argv.slice(2);
  33. if (args.length === 0 || args[0] === 'help' || args[0] === '-h' || args[0] === '--help') {
  34. console.log(usage);
  35. process.exit(0);
  36. }
  37. const configPath = args[0];
  38. type Config = typeof exampleConfig;
  39. let config: Config;
  40. try {
  41. config = JSON.parse(fs.readFileSync(path.resolve(configPath), 'utf8')) as Config;
  42. } catch (e) {
  43. console.log('Failed to parse config file: ', configPath);
  44. console.log(usage);
  45. process.exit(1);
  46. }
  47. const requiredFields = [
  48. 'ig_username', 'ig_password',
  49. 'cq_bot_qq', ...(config.mode || exampleConfig.mode) === 0 ? ['playwright_ws_spec_endpoint'] : [],
  50. ];
  51. const warningFields = [
  52. 'cq_ws_host', 'cq_ws_port', 'cq_access_token',
  53. ];
  54. const optionalFields = [
  55. 'lockfile', 'inactive_hours', 'work_interval', 'webshot_delay', 'loglevel', 'mode', 'resume_on_start', 'ig_socks_proxy',
  56. ].concat(warningFields);
  57. if (requiredFields.some((value) => config[value] === undefined)) {
  58. console.log(`${requiredFields.join(', ')} are required`);
  59. process.exit(1);
  60. }
  61. optionalFields.forEach(key => {
  62. if (config[key] === undefined || typeof(config[key]) !== typeof (exampleConfig[key])) {
  63. if (warningFields.includes(key)) logger.warn(`${key} is undefined, use ${exampleConfig[key] || 'empty string'} as default`);
  64. config[key] = exampleConfig[key as keyof Config];
  65. }
  66. });
  67. ['ig_session_lockfile', ...(config.mode || exampleConfig.mode) === 0 ? ['webshot_cookies_lockfile'] : []].forEach(key => {
  68. if (!config[key]) {
  69. logger.warn(`${key} is undefined, use <username>.${key.replace('_lockfile', '.lock')} as default`);
  70. config[key] = `${config.ig_username}.${key.replace('_lockfile', '.lock')}`;
  71. }
  72. });
  73. const k = 'ig_2fa_code_receiver_port';
  74. if (!config[k] || config[k] < 2048 || config[k] > 65536) {
  75. logger.warn(`invalid value of config.${k}, use ${exampleConfig[k]} as default`);
  76. config[k] = exampleConfig[k];
  77. }
  78. setLogLevels(config.loglevel);
  79. let lock: ILock;
  80. if (fs.existsSync(path.resolve(config.lockfile))) {
  81. try {
  82. lock = JSON.parse(fs.readFileSync(path.resolve(config.lockfile), 'utf8')) as ILock;
  83. } catch (err) {
  84. logger.error(`Failed to parse lockfile ${config.lockfile}: `, err);
  85. lock = {
  86. workon: 0,
  87. feed: [],
  88. threads: {},
  89. };
  90. }
  91. fs.access(path.resolve(config.lockfile), fs.constants.W_OK, err => {
  92. if (err) {
  93. logger.fatal(`cannot write lockfile ${path.resolve(config.lockfile)}, permission denied`);
  94. process.exit(1);
  95. }
  96. });
  97. } else {
  98. lock = {
  99. workon: 0,
  100. feed: [],
  101. threads: {},
  102. };
  103. try {
  104. fs.writeFileSync(path.resolve(config.lockfile), JSON.stringify(lock));
  105. } catch (err) {
  106. logger.fatal(`cannot write lockfile ${path.resolve(config.lockfile)}, permission denied`);
  107. process.exit(1);
  108. }
  109. }
  110. if (!config.resume_on_start) {
  111. Object.keys(lock.threads).forEach(key => {
  112. lock.threads[key].offset = '-1';
  113. });
  114. }
  115. const qq = new QQBot({
  116. access_token: config.cq_access_token,
  117. host: config.cq_ws_host,
  118. port: config.cq_ws_port,
  119. bot_id: config.cq_bot_qq,
  120. list: (c, a, cb) => list(c, a, cb, lock),
  121. sub: (c, a, cb) => sub(c, a, cb, lock, config.lockfile),
  122. unsub: (c, a, cb) => unsub(c, a, cb, lock, config.lockfile),
  123. });
  124. const worker = new Worker({
  125. sessionLockfile: config.ig_session_lockfile,
  126. credentials: [config.ig_username, config.ig_password],
  127. codeServicePort: config.ig_2fa_code_receiver_port,
  128. proxyUrl: config.ig_socks_proxy,
  129. lock,
  130. lockfile: config.lockfile,
  131. inactiveHours: config.inactive_hours,
  132. workInterval: config.work_interval,
  133. bot: qq,
  134. webshotDelay: config.webshot_delay,
  135. webshotCookiesLockfile: config.webshot_cookies_lockfile,
  136. mode: config.mode,
  137. wsUrl: config.playwright_ws_spec_endpoint,
  138. });
  139. worker.session.init().then(worker.launch);
  140. qq.connect();