twitter.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const fs = require("fs");
  4. const log4js = require("log4js");
  5. const path = require("path");
  6. const redis = require("redis");
  7. const sha1 = require("sha1");
  8. const Twitter = require("twitter");
  9. const webshot_1 = require("./webshot");
  10. const logger = log4js.getLogger('twitter');
  11. logger.level = global.loglevel;
  12. class default_1 {
  13. constructor(opt) {
  14. this.launch = () => {
  15. if (this.redisConfig) {
  16. this.redisClient = redis.createClient({
  17. host: this.redisConfig.redisHost,
  18. port: this.redisConfig.redisPort,
  19. });
  20. }
  21. this.webshot = new webshot_1.default(() => setTimeout(this.work, this.workInterval * 1000));
  22. };
  23. this.work = () => {
  24. const lock = this.lock;
  25. if (this.workInterval < 1)
  26. this.workInterval = 1;
  27. if (lock.feed.length === 0) {
  28. setTimeout(() => {
  29. this.work();
  30. }, this.workInterval * 1000);
  31. return;
  32. }
  33. if (lock.workon >= lock.feed.length)
  34. lock.workon = 0;
  35. if (!lock.threads[lock.feed[lock.workon]] ||
  36. !lock.threads[lock.feed[lock.workon]].subscribers ||
  37. lock.threads[lock.feed[lock.workon]].subscribers.length === 0) {
  38. logger.warn(`nobody subscribes thread ${lock.feed[lock.workon]}, removing from feed`);
  39. delete lock.threads[lock.feed[lock.workon]];
  40. lock.feed.splice(lock.workon, 1);
  41. fs.writeFileSync(path.resolve(this.lockfile), JSON.stringify(lock));
  42. this.work();
  43. return;
  44. }
  45. logger.debug(`pulling feed ${lock.feed[lock.workon]}`);
  46. const promise = new Promise(resolve => {
  47. let match = lock.feed[lock.workon].match(/https:\/\/twitter.com\/([^\/]+)\/lists\/([^\/]+)/);
  48. let config;
  49. let endpoint;
  50. if (match) {
  51. config = {
  52. owner_screen_name: match[1],
  53. slug: match[2],
  54. tweet_mode: 'extended',
  55. };
  56. endpoint = 'lists/statuses';
  57. }
  58. else {
  59. match = lock.feed[lock.workon].match(/https:\/\/twitter.com\/([^\/]+)/);
  60. if (match) {
  61. config = {
  62. screen_name: match[1],
  63. exclude_replies: false,
  64. tweet_mode: 'extended',
  65. };
  66. endpoint = 'statuses/user_timeline';
  67. }
  68. }
  69. if (endpoint) {
  70. const offset = lock.threads[lock.feed[lock.workon]].offset;
  71. if (offset > 0)
  72. config.since_id = offset;
  73. this.client.get(endpoint, config, (error, tweets, response) => {
  74. if (error) {
  75. if (error instanceof Array && error.length > 0 && error[0].code === 34) {
  76. logger.warn(`error on fetching tweets for ${lock.feed[lock.workon]}: ${JSON.stringify(error)}`);
  77. lock.threads[lock.feed[lock.workon]].subscribers.forEach(subscriber => {
  78. logger.info(`sending notfound message of ${lock.feed[lock.workon]} to ${JSON.stringify(subscriber)}`);
  79. this.bot.bot('send_msg', {
  80. message_type: subscriber.chatType,
  81. user_id: subscriber.chatID,
  82. group_id: subscriber.chatID,
  83. discuss_id: subscriber.chatID,
  84. message: `链接 ${lock.feed[lock.workon]} 指向的用户或列表不存在,请退订。`,
  85. });
  86. });
  87. }
  88. else {
  89. logger.error(`unhandled error on fetching tweets for ${lock.feed[lock.workon]}: ${JSON.stringify(error)}`);
  90. }
  91. resolve();
  92. }
  93. else
  94. resolve(tweets);
  95. });
  96. }
  97. });
  98. promise.then((tweets) => {
  99. logger.debug(`api returned ${JSON.stringify(tweets)} for feed ${lock.feed[lock.workon]}`);
  100. if (!tweets || tweets.length === 0) {
  101. lock.threads[lock.feed[lock.workon]].updatedAt = new Date().toString();
  102. return;
  103. }
  104. if (lock.threads[lock.feed[lock.workon]].offset === -1) {
  105. lock.threads[lock.feed[lock.workon]].offset = tweets[0].id_str;
  106. return;
  107. }
  108. if (lock.threads[lock.feed[lock.workon]].offset === 0)
  109. tweets.splice(1);
  110. return this.webshot(this.mode, tweets, (msg, text, author) => {
  111. lock.threads[lock.feed[lock.workon]].subscribers.forEach(subscriber => {
  112. logger.info(`pushing data of thread ${lock.feed[lock.workon]} to ${JSON.stringify(subscriber)}`);
  113. let hash = JSON.stringify(subscriber) + text;
  114. logger.debug(hash);
  115. hash = sha1(JSON.stringify(subscriber) + text);
  116. logger.debug(hash);
  117. const twtext = `${author.name}(@${author.screen_name}):\n${text}`;
  118. const send = () => {
  119. this.bot.bot('send_msg', {
  120. message_type: subscriber.chatType,
  121. user_id: subscriber.chatID,
  122. group_id: subscriber.chatID,
  123. discuss_id: subscriber.chatID,
  124. message: this.mode === 0 ? msg : twtext,
  125. });
  126. };
  127. if (this.redisClient) {
  128. this.redisClient.exists(hash, (err, res) => {
  129. logger.debug('redis: ', res);
  130. if (err) {
  131. logger.error('redis error: ', err);
  132. }
  133. else if (res) {
  134. logger.info('key hash exists, skip this subscriber');
  135. return;
  136. }
  137. send();
  138. this.redisClient.set(hash, 'true', 'EX', this.redisConfig.redisExpireTime, (setErr, setRes) => {
  139. logger.debug('redis: ', setRes);
  140. if (setErr) {
  141. logger.error('redis error: ', setErr);
  142. }
  143. });
  144. });
  145. }
  146. else
  147. send();
  148. });
  149. }, this.webshotDelay)
  150. .then(() => {
  151. lock.threads[lock.feed[lock.workon]].offset = tweets[0].id_str;
  152. lock.threads[lock.feed[lock.workon]].updatedAt = new Date().toString();
  153. });
  154. })
  155. .then(() => {
  156. lock.workon++;
  157. let timeout = this.workInterval * 1000 / lock.feed.length;
  158. if (timeout < 1000)
  159. timeout = 1000;
  160. fs.writeFileSync(path.resolve(this.lockfile), JSON.stringify(lock));
  161. setTimeout(() => {
  162. this.work();
  163. }, timeout);
  164. });
  165. };
  166. this.client = new Twitter({
  167. consumer_key: opt.consumer_key,
  168. consumer_secret: opt.consumer_secret,
  169. access_token_key: opt.access_token_key,
  170. access_token_secret: opt.access_token_secret,
  171. });
  172. this.lockfile = opt.lockfile;
  173. this.lock = opt.lock;
  174. this.workInterval = opt.workInterval;
  175. this.bot = opt.bot;
  176. this.webshotDelay = opt.webshotDelay;
  177. this.redisConfig = opt.redis;
  178. this.mode = opt.mode;
  179. }
  180. }
  181. exports.default = default_1;