twitter.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const fs = require("fs");
  4. const path = require("path");
  5. const Twitter = require("twitter");
  6. const loggers_1 = require("./loggers");
  7. const mirai_1 = require("./mirai");
  8. const webshot_1 = require("./webshot");
  9. const logger = loggers_1.getLogger('twitter');
  10. class default_1 {
  11. constructor(opt) {
  12. this.launch = () => {
  13. this.webshot = new webshot_1.default(this.webshotOutDir, this.mode, () => setTimeout(this.work, this.workInterval * 1000));
  14. };
  15. this.work = () => {
  16. const lock = this.lock;
  17. if (this.workInterval < 1)
  18. this.workInterval = 1;
  19. if (lock.feed.length === 0) {
  20. setTimeout(() => {
  21. this.work();
  22. }, this.workInterval * 1000);
  23. return;
  24. }
  25. if (lock.workon >= lock.feed.length)
  26. lock.workon = 0;
  27. if (!lock.threads[lock.feed[lock.workon]] ||
  28. !lock.threads[lock.feed[lock.workon]].subscribers ||
  29. lock.threads[lock.feed[lock.workon]].subscribers.length === 0) {
  30. logger.warn(`nobody subscribes thread ${lock.feed[lock.workon]}, removing from feed`);
  31. delete lock.threads[lock.feed[lock.workon]];
  32. lock.feed.splice(lock.workon, 1);
  33. fs.writeFileSync(path.resolve(this.lockfile), JSON.stringify(lock));
  34. this.work();
  35. return;
  36. }
  37. logger.debug(`pulling feed ${lock.feed[lock.workon]}`);
  38. const promise = new Promise(resolve => {
  39. let match = lock.feed[lock.workon].match(/https:\/\/twitter.com\/([^\/]+)\/lists\/([^\/]+)/);
  40. let config;
  41. let endpoint;
  42. if (match) {
  43. config = {
  44. owner_screen_name: match[1],
  45. slug: match[2],
  46. tweet_mode: 'extended',
  47. };
  48. endpoint = 'lists/statuses';
  49. }
  50. else {
  51. match = lock.feed[lock.workon].match(/https:\/\/twitter.com\/([^\/]+)/);
  52. if (match) {
  53. config = {
  54. screen_name: match[1],
  55. exclude_replies: false,
  56. tweet_mode: 'extended',
  57. };
  58. endpoint = 'statuses/user_timeline';
  59. }
  60. }
  61. if (endpoint) {
  62. const offset = lock.threads[lock.feed[lock.workon]].offset;
  63. if (offset > 0)
  64. config.since_id = offset;
  65. this.client.get(endpoint, config, (error, tweets, response) => {
  66. if (error) {
  67. if (error instanceof Array && error.length > 0 && error[0].code === 34) {
  68. logger.warn(`error on fetching tweets for ${lock.feed[lock.workon]}: ${JSON.stringify(error)}`);
  69. lock.threads[lock.feed[lock.workon]].subscribers.forEach(subscriber => {
  70. logger.info(`sending notfound message of ${lock.feed[lock.workon]} to ${JSON.stringify(subscriber)}`);
  71. this.bot.sendTo(subscriber, `链接 ${lock.feed[lock.workon]} 指向的用户或列表不存在,请退订。`).catch();
  72. });
  73. }
  74. else {
  75. logger.error(`unhandled error on fetching tweets for ${lock.feed[lock.workon]}: ${JSON.stringify(error)}`);
  76. }
  77. resolve();
  78. }
  79. else
  80. resolve(tweets);
  81. });
  82. }
  83. });
  84. promise.then((tweets) => {
  85. logger.debug(`api returned ${JSON.stringify(tweets)} for feed ${lock.feed[lock.workon]}`);
  86. if (!tweets || tweets.length === 0) {
  87. lock.threads[lock.feed[lock.workon]].updatedAt = new Date().toString();
  88. return;
  89. }
  90. if (lock.threads[lock.feed[lock.workon]].offset === -1) {
  91. lock.threads[lock.feed[lock.workon]].offset = tweets[0].id_str;
  92. return;
  93. }
  94. if (lock.threads[lock.feed[lock.workon]].offset === 0)
  95. tweets.splice(1);
  96. return this.webshot(tweets, msg => {
  97. lock.threads[lock.feed[lock.workon]].subscribers.forEach(subscriber => {
  98. logger.info(`pushing data of thread ${lock.feed[lock.workon]} to ${JSON.stringify(subscriber)}`);
  99. this.bot.sendTo(subscriber, msg)
  100. .catch(reason => {
  101. if (typeof (msg) !== 'string') {
  102. logger.warn(`retry sending to ${subscriber.chatID}`);
  103. msg.forEach((message, pos) => {
  104. if (message.type === 'Image') {
  105. msg[pos] = mirai_1.MiraiMessage.Plain(`[失败的图片:${message.path}]`);
  106. }
  107. });
  108. this.bot.sendTo(subscriber, msg).catch();
  109. }
  110. });
  111. });
  112. }, this.webshotDelay)
  113. .then(() => {
  114. lock.threads[lock.feed[lock.workon]].offset = tweets[0].id_str;
  115. lock.threads[lock.feed[lock.workon]].updatedAt = new Date().toString();
  116. });
  117. })
  118. .then(() => {
  119. lock.workon++;
  120. let timeout = this.workInterval * 1000 / lock.feed.length;
  121. if (timeout < 1000)
  122. timeout = 1000;
  123. fs.writeFileSync(path.resolve(this.lockfile), JSON.stringify(lock));
  124. setTimeout(() => {
  125. this.work();
  126. }, timeout);
  127. });
  128. };
  129. this.client = new Twitter({
  130. consumer_key: opt.consumer_key,
  131. consumer_secret: opt.consumer_secret,
  132. access_token_key: opt.access_token_key,
  133. access_token_secret: opt.access_token_secret,
  134. });
  135. this.lockfile = opt.lockfile;
  136. this.lock = opt.lock;
  137. this.workInterval = opt.workInterval;
  138. this.bot = opt.bot;
  139. this.webshotDelay = opt.webshotDelay;
  140. this.webshotOutDir = opt.webshotOutDir;
  141. this.mode = opt.mode;
  142. }
  143. }
  144. exports.default = default_1;