index.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. require('dotenv').config();
  2. const { TwitterApi } = require('twitter-api-v2');
  3. const BasePlatformService = require('./utils/BasePlatformService');
  4. const { getDb } = require('./utils/MongoDBConnector');
  5. const {
  6. TWITTER_API_KEY,
  7. TWITTER_API_SECRET,
  8. TWITTER_ACCESS_TOKEN,
  9. TWITTER_ACCESS_SECRET,
  10. TWITTER_BEARER_TOKEN,
  11. } = process.env;
  12. class TwitterService extends BasePlatformService {
  13. constructor() {
  14. super('twitter');
  15. this.client = null;
  16. this.roClient = null;
  17. }
  18. _initClients() {
  19. if (!TWITTER_BEARER_TOKEN && !TWITTER_API_KEY) return;
  20. // Read-only client (Bearer token — feed okuma için)
  21. if (TWITTER_BEARER_TOKEN) {
  22. this.roClient = new TwitterApi(TWITTER_BEARER_TOKEN);
  23. }
  24. // Read-write client (OAuth 1.0a — tweet atmak için)
  25. if (TWITTER_API_KEY && TWITTER_API_SECRET && TWITTER_ACCESS_TOKEN && TWITTER_ACCESS_SECRET) {
  26. this.client = new TwitterApi({
  27. appKey: TWITTER_API_KEY,
  28. appSecret: TWITTER_API_SECRET,
  29. accessToken: TWITTER_ACCESS_TOKEN,
  30. accessSecret: TWITTER_ACCESS_SECRET,
  31. });
  32. }
  33. }
  34. async getStatus() {
  35. this._initClients();
  36. if (!this.client) {
  37. return { connected: false, platform: 'twitter', error: 'Twitter credentials not configured' };
  38. }
  39. try {
  40. const me = await this.client.v2.me();
  41. return {
  42. connected: true,
  43. platform: 'twitter',
  44. userId: me.data.id,
  45. username: me.data.username,
  46. displayName: me.data.name,
  47. };
  48. } catch (err) {
  49. return { connected: false, platform: 'twitter', error: err.message };
  50. }
  51. }
  52. async fetchFeed({ limit = 20 } = {}) {
  53. this._initClients();
  54. const client = this.client || this.roClient;
  55. if (!client) throw new Error('Twitter credentials not configured');
  56. // Home timeline (OAuth 1.0a gerektirir)
  57. const me = await this.client.v2.me();
  58. const timeline = await this.client.v2.homeTimeline({
  59. max_results: Math.min(Number(limit), 100),
  60. 'tweet.fields': ['created_at', 'public_metrics', 'author_id', 'entities'],
  61. expansions: ['author_id', 'attachments.media_keys'],
  62. 'user.fields': ['name', 'username', 'profile_image_url'],
  63. 'media.fields': ['url', 'preview_image_url', 'alt_text', 'type'],
  64. });
  65. const users = {};
  66. const media = {};
  67. (timeline.includes?.users || []).forEach((u) => (users[u.id] = u));
  68. (timeline.includes?.media || []).forEach((m) => (media[m.media_key] = m));
  69. const items = (timeline.data?.data || []).map((tweet) => {
  70. const author = users[tweet.author_id] || {};
  71. const tweetMedia = (tweet.attachments?.media_keys || [])
  72. .map((key) => media[key])
  73. .filter(Boolean)
  74. .map((m) => ({ url: m.url || m.preview_image_url, type: m.type, alt: m.alt_text }));
  75. return this.normalizeFeedItem({
  76. originalId: tweet.id,
  77. author: {
  78. name: author.name || '',
  79. username: author.username || '',
  80. avatar: author.profile_image_url,
  81. profileUrl: `https://twitter.com/${author.username}`,
  82. },
  83. content: tweet.text,
  84. media: tweetMedia,
  85. platformTags: (tweet.entities?.hashtags || []).map((h) => h.tag),
  86. metrics: {
  87. likes: tweet.public_metrics?.like_count || 0,
  88. shares: tweet.public_metrics?.retweet_count || 0,
  89. comments: tweet.public_metrics?.reply_count || 0,
  90. views: tweet.public_metrics?.impression_count || 0,
  91. },
  92. url: `https://twitter.com/${author.username}/status/${tweet.id}`,
  93. createdAt: tweet.created_at,
  94. });
  95. });
  96. // MongoDB'ye kaydet (upsert)
  97. try {
  98. const db = await getDb();
  99. const col = db.collection('feeds');
  100. for (const item of items) {
  101. await col.updateOne(
  102. { platform: 'twitter', originalId: item.originalId },
  103. { $set: item },
  104. { upsert: true }
  105. );
  106. }
  107. } catch (err) {
  108. console.error('[Twitter] MongoDB write error:', err.message);
  109. }
  110. return items;
  111. }
  112. async publishPost({ content } = {}) {
  113. this._initClients();
  114. if (!this.client) throw new Error('Twitter write credentials not configured');
  115. const result = await this.client.v2.tweet(content);
  116. return { id: result.data.id, text: result.data.text };
  117. }
  118. }
  119. const service = new TwitterService();
  120. service.start(process.env.PORT || 3001);