From fc3afa7d6841258ec2efaa4bdc04cb6b3567de39 Mon Sep 17 00:00:00 2001 From: Rishikant Sahu Date: Sun, 11 Feb 2024 13:28:45 +0530 Subject: [PATCH 01/16] Lock --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index a9cc01d5..bdf9ed39 100644 --- a/yarn.lock +++ b/yarn.lock @@ -343,7 +343,7 @@ chokidar@^3.5.2: optionalDependencies: fsevents "~2.3.2" -class-validator@0.14.1, class-validator@^0.14.1: +class-validator@0.14.1: version "0.14.1" resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.14.1.tgz#ff2411ed8134e9d76acfeb14872884448be98110" integrity sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ== From d8deeee6b259a6e808f9a6b5d744af8432f8ed35 Mon Sep 17 00:00:00 2001 From: Rishikant Sahu Date: Mon, 19 Feb 2024 11:37:11 +0530 Subject: [PATCH 02/16] Removed text processing to preserve the source text formatting in Tweets --- src/helper/JsonUtils.ts | 18 ------------------ src/models/data/Tweet.ts | 5 +---- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/src/helper/JsonUtils.ts b/src/helper/JsonUtils.ts index 03ece586..b26d11a9 100644 --- a/src/helper/JsonUtils.ts +++ b/src/helper/JsonUtils.ts @@ -46,24 +46,6 @@ export function findByFilter(data: NonNullable, key: string, value: return res; } -/** - * @param text - The text to be normalized. - * @returns The text after being formatted to remove unnecessary characters. - * - * @internal - */ -export function normalizeText(text: string): string { - let normalizedText: string = ''; // To store the normalized text - - // Removing unnecessary full stops, and other characters - normalizedText = text.replace(/\n/g, '.').replace(/[.]+[\s+.\s+]+/g, '. '); - - // Adding full-stop to the end if does not exist already - normalizedText = normalizedText.endsWith('.') ? normalizedText : normalizedText + '.'; - - return normalizedText; -} - /** * Searches for the key which has the given value in the given object. * diff --git a/src/models/data/Tweet.ts b/src/models/data/Tweet.ts index 96e2ab51..12171f68 100644 --- a/src/models/data/Tweet.ts +++ b/src/models/data/Tweet.ts @@ -9,9 +9,6 @@ import { // MODELS import { User } from './User'; -// PARSERS -import { normalizeText } from '../../helper/JsonUtils'; - /** * The details of a single Tweet. * @@ -75,7 +72,7 @@ export class Tweet { this.entities = new TweetEntities(tweet.legacy.entities); this.media = tweet.legacy.extended_entities?.media?.map((media) => new TweetMedia(media)); this.quoted = tweet.legacy.quoted_status_id_str; - this.fullText = normalizeText(tweet.legacy.full_text); + this.fullText = tweet.legacy.full_text; this.replyTo = tweet.legacy.in_reply_to_status_id_str; this.lang = tweet.legacy.lang; this.quoteCount = tweet.legacy.quote_count; From a3b7998eb4807f4855e8a9a88c1b7605f7a02087 Mon Sep 17 00:00:00 2001 From: Rishikant Sahu Date: Mon, 19 Feb 2024 13:08:47 +0530 Subject: [PATCH 03/16] Changed delimiter for list from ';' to ',' --- src/commands/Tweet.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/commands/Tweet.ts b/src/commands/Tweet.ts index 7d2d1928..488e8642 100644 --- a/src/commands/Tweet.ts +++ b/src/commands/Tweet.ts @@ -32,22 +32,22 @@ function createTweetCommand(rettiwt: Rettiwt): Command { .description('Fetch the list of tweets that match the given filter options') .argument('[count]', 'The number of tweets to fetch') .argument('[cursor]', 'The cursor to the batch of tweets to fetch') - .option('-f, --from ', "Matches the tweets made by list of given users, separated by ';'") - .option('-t, --to ', "Matches the tweets made to the list of given users, separated by ';'") - .option('-w, --words ', "Matches the tweets containing the given list of words, separated by ';'") + .option('-f, --from ', 'Matches the tweets made by the comma-separated list of given users') + .option('-t, --to ', 'Matches the tweets made to the comma-separated list of given users') + .option('-w, --words ', 'Matches the tweets containing the given comma-separated list of words') .option('-p, --phrase ', 'Matches the tweets containing the exact phrase') .option( '--optional-words ', - "Matches the tweets containing any of the given list of words, separated by ';'", + 'Matches the tweets containing any of the given comma-separated list of words', ) .option( '--exclude-words ', - "Matches the tweets that do not contain any of the give list of words, separated by ';'", + 'Matches the tweets that do not contain any of the give comma-separated list of words', ) - .option('-h, --hashtags ', "Matches the tweets containing the given list of hashtags, separated by ';'") + .option('-h, --hashtags ', 'Matches the tweets containing the given comma-separated list of hashtags') .option( '-m, --mentions ', - "Matches the tweets that mention the give list of usernames, separated by ';'", + 'Matches the tweets that mention the given comma-separated list of usernames', ) .option('-r, --min-replies ', 'Matches the tweets that have a minimum of given number of replies') .option('-l, --min-likes ', 'Matches the tweets that have a minimum of given number of likes') @@ -107,11 +107,11 @@ function createTweetCommand(rettiwt: Rettiwt): Command { .command('post') .description('Post a tweet (text only)') .argument('', 'The text to post as a tweet') - .option('-m, --media [string]', "The path to the media item(s) to be posted, separated by ';'") + .option('-m, --media [string]', 'Comma-separated list of path(s) to the media item(s) to be posted') .action(async (text: string, options?: { media?: string }) => { const result = await rettiwt.tweet.tweet( text, - options?.media ? options?.media.split(';').map((item) => ({ path: item })) : undefined, + options?.media ? options?.media.split(',').map((item) => ({ path: item })) : undefined, ); output(result); }); @@ -193,14 +193,14 @@ class TweetSearchOptions { */ public toTweetFilter(): TweetFilter { return new TweetFilter({ - fromUsers: this.from ? this.from.split(';') : undefined, - toUsers: this.to ? this.to.split(';') : undefined, - includeWords: this.words ? this.words.split(';') : undefined, + fromUsers: this.from ? this.from.split(',') : undefined, + toUsers: this.to ? this.to.split(',') : undefined, + includeWords: this.words ? this.words.split(',') : undefined, includePhrase: this.phrase, - optionalWords: this.optionalWords ? this.optionalWords.split(';') : undefined, - excludeWords: this.excludeWords ? this.excludeWords.split(';') : undefined, - hashtags: this.hashtags ? this.hashtags.split(';') : undefined, - mentions: this.mentions ? this.mentions.split(';') : undefined, + optionalWords: this.optionalWords ? this.optionalWords.split(',') : undefined, + excludeWords: this.excludeWords ? this.excludeWords.split(',') : undefined, + hashtags: this.hashtags ? this.hashtags.split(',') : undefined, + mentions: this.mentions ? this.mentions.split(',') : undefined, minReplies: this.minReplies, minLikes: this.minLikes, minRetweets: this.minRetweets, From d5e822ce741639251dda49f78a7249d8ae711e05 Mon Sep 17 00:00:00 2001 From: Rishikant Sahu Date: Wed, 21 Feb 2024 22:09:12 +0530 Subject: [PATCH 04/16] Added ability to post a reply to a tweet --- package.json | 2 +- src/commands/Tweet.ts | 7 ++++++- src/services/public/TweetService.ts | 23 +++++++++++++++++++++-- yarn.lock | 8 ++++---- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 52990522..0747b5eb 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "commander": "11.1.0", "https-proxy-agent": "7.0.2", "rettiwt-auth": "2.1.0", - "rettiwt-core": "3.3.1" + "rettiwt-core": "3.4.0-alpha.2" }, "devDependencies": { "@types/node": "20.4.1", diff --git a/src/commands/Tweet.ts b/src/commands/Tweet.ts index 488e8642..00a8108a 100644 --- a/src/commands/Tweet.ts +++ b/src/commands/Tweet.ts @@ -108,10 +108,15 @@ function createTweetCommand(rettiwt: Rettiwt): Command { .description('Post a tweet (text only)') .argument('', 'The text to post as a tweet') .option('-m, --media [string]', 'Comma-separated list of path(s) to the media item(s) to be posted') - .action(async (text: string, options?: { media?: string }) => { + .option( + '-r, --reply [string]', + 'The id of the tweet to which the reply is to be made, if the tweet is to be a reply', + ) + .action(async (text: string, options?: { media?: string; reply?: string }) => { const result = await rettiwt.tweet.tweet( text, options?.media ? options?.media.split(',').map((item) => ({ path: item })) : undefined, + options?.reply, ); output(result); }); diff --git a/src/services/public/TweetService.ts b/src/services/public/TweetService.ts index ef14a832..f7bf7624 100644 --- a/src/services/public/TweetService.ts +++ b/src/services/public/TweetService.ts @@ -261,9 +261,26 @@ export class TweetService extends FetcherService { * }); * ``` * + * @example Posting a reply to a tweet + * ``` + * import { Rettiwt } from 'rettiwt-api'; + * + * // Creating a new Rettiwt instance using the given 'API_KEY' + * const rettiwt = new Rettiwt({ apiKey: API_KEY }); + * + * // Posting a simple text reply, to a tweet with id "1234567890" + * rettiwt.tweet.tweet('Hello!', undefined, "1234567890") + * .then(res => { + * console.log(res); + * }) + * .catch(err => { + * console.log(err); + * }); + * ``` + * * @public */ - public async tweet(text: string, media?: TweetMediaArgs[]): Promise { + public async tweet(text: string, media?: TweetMediaArgs[], replyTo?: string): Promise { // Converting JSON args to object const tweet: TweetArgs = new TweetArgs({ text: text, media: media }); @@ -282,7 +299,9 @@ export class TweetService extends FetcherService { } // Posting the tweet - const data = await this.post(EResourceType.CREATE_TWEET, { tweet: { text: text, media: uploadedMedia } }); + const data = await this.post(EResourceType.CREATE_TWEET, { + tweet: { text: text, media: uploadedMedia, replyTo: replyTo }, + }); return data; } diff --git a/yarn.lock b/yarn.lock index bdf9ed39..8e50be40 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1090,10 +1090,10 @@ rettiwt-auth@2.1.0: cookiejar "2.1.4" https-proxy-agent "7.0.2" -rettiwt-core@3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/rettiwt-core/-/rettiwt-core-3.3.1.tgz#09ab32e66ba5b55d9ba9896e8dcd780f1b1f4e50" - integrity sha512-TNJFM1UQyfGT8FXPeqePMUk7qzBfoet2/Nzn7+Ma4ibyKdn+XCqaA1c10se02qYomQ7Lolc/KCZvhUPjwCJFXQ== +rettiwt-core@3.4.0-alpha.2: + version "3.4.0-alpha.2" + resolved "https://registry.yarnpkg.com/rettiwt-core/-/rettiwt-core-3.4.0-alpha.2.tgz#d3033d0db7a05f503e3b0aef5981d33897f0d08b" + integrity sha512-775cDTPWZXhY+znEOsjupIgj/Mmi8shKhLveShm1KnXEX1Qc5SjTEnpQv1dHmDaxiV9n6BIQ73E7K9AucBl+Mg== dependencies: axios "1.6.3" class-validator "0.14.1" From 0e619e2bacdd9d0d03427ab7ad5418c11437296d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Touati?= Date: Fri, 23 Feb 2024 12:35:58 +0100 Subject: [PATCH 05/16] Fix `replyTo` field documentation --- src/models/data/Tweet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/data/Tweet.ts b/src/models/data/Tweet.ts index 12171f68..387a38b7 100644 --- a/src/models/data/Tweet.ts +++ b/src/models/data/Tweet.ts @@ -36,7 +36,7 @@ export class Tweet { /** The full text content of the tweet. */ public fullText: string; - /** The rest id of the user to which the tweet is a reply. */ + /** The rest id of the tweet to which the tweet is a reply. */ public replyTo: string; /** The language in which the tweet is written. */ From 26c16318e86dd90d022348fa9df57c4a14114b8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Touati?= Date: Fri, 23 Feb 2024 17:23:01 +0100 Subject: [PATCH 06/16] Implement tweet streaming function --- src/services/public/TweetService.ts | 64 +++++++++++++++++++++++++++++ src/types/StreamFilter.ts | 8 ++++ 2 files changed, 72 insertions(+) create mode 100644 src/types/StreamFilter.ts diff --git a/src/services/public/TweetService.ts b/src/services/public/TweetService.ts index f7bf7624..c4729869 100644 --- a/src/services/public/TweetService.ts +++ b/src/services/public/TweetService.ts @@ -6,6 +6,7 @@ import { FetcherService } from '../internal/FetcherService'; // TYPES import { IRettiwtConfig } from '../../types/RettiwtConfig'; +import { StreamFilter } from '../../types/StreamFilter'; // MODELS import { Tweet } from '../../models/data/Tweet'; @@ -103,6 +104,69 @@ export class TweetService extends FetcherService { return data; } + /** + * Stream tweets in pseudo real-time using a query. + * + * @param query - The query be used for searching the tweets. + * @param pollingIntervalMs - The interval in milliseconds to poll for new tweets. + * @returns An async generator that yields matching tweets as they are found. + * + * @example + * ``` + * import { Rettiwt } from 'rettiwt-api'; + * + * // Creating a new Rettiwt instance using the given 'API_KEY' + * const rettiwt = new Rettiwt({ apiKey: API_KEY }); + * + * // Streaming all upcoming tweets that mention the user 'user1' + * (async () => { + * try { + * for await (const tweet of rettiwt.stream({ fromUsers: ['user1'] })) { + * console.log(tweet.fullText); + * } + * } catch (err) { + * console.log(err); + * } + * })(); + * ``` + */ + public async *stream(query: StreamFilter, pollingIntervalMs: number = 30000): AsyncGenerator { + const startDate = new Date(); + + let cursor: string | undefined = undefined; + let sinceId: string | undefined = undefined; + let nextSinceId: string | undefined = undefined; + + while (true) { + // Pause execution for the specified polling interval before proceeding to the next iteration + await new Promise(resolve => setTimeout(resolve, pollingIntervalMs)); + + // Search for tweets + const tweets = await this.search({ ...query, startDate, sinceId }, undefined, cursor); + + // Yield the matching tweets + for (const tweet of tweets.list) { + yield tweet; + } + + // Store the most recent tweet ID from this batch + if (tweets.list.length > 0 && cursor === undefined) { + nextSinceId = tweets.list[0].id; + } + + // If there are more tweets to fetch, adjust the cursor value + if (tweets.list.length > 0 && tweets.next) { + cursor = tweets.next.value; + } + // Else, start the next iteration from this batch's most recent tweet + else { + sinceId = nextSinceId; + nextSinceId = undefined; + cursor = undefined; + } + } + } + /** * Get the tweets from the tweet list with the given id. * diff --git a/src/types/StreamFilter.ts b/src/types/StreamFilter.ts new file mode 100644 index 00000000..b89b9de4 --- /dev/null +++ b/src/types/StreamFilter.ts @@ -0,0 +1,8 @@ +import { TweetFilter } from 'rettiwt-core'; + +/** + * Defines a filter for the streaming of tweets. + * + * @public + */ +export type StreamFilter = Omit From 1ebcfa87a5c66fcb695654c74ffec8f460158551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Touati?= Date: Fri, 23 Feb 2024 18:23:15 +0100 Subject: [PATCH 07/16] Fix doc --- src/services/public/TweetService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/public/TweetService.ts b/src/services/public/TweetService.ts index c4729869..4c5875e7 100644 --- a/src/services/public/TweetService.ts +++ b/src/services/public/TweetService.ts @@ -118,7 +118,7 @@ export class TweetService extends FetcherService { * // Creating a new Rettiwt instance using the given 'API_KEY' * const rettiwt = new Rettiwt({ apiKey: API_KEY }); * - * // Streaming all upcoming tweets that mention the user 'user1' + * // Streaming all upcoming tweets from user 'user1' * (async () => { * try { * for await (const tweet of rettiwt.stream({ fromUsers: ['user1'] })) { From 8fa84f4e5e8221d0c020780d08af4edb98d98217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Touati?= Date: Fri, 23 Feb 2024 18:32:22 +0100 Subject: [PATCH 08/16] Add missing `@public` --- src/services/public/TweetService.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/services/public/TweetService.ts b/src/services/public/TweetService.ts index 4c5875e7..5822ca6f 100644 --- a/src/services/public/TweetService.ts +++ b/src/services/public/TweetService.ts @@ -129,6 +129,8 @@ export class TweetService extends FetcherService { * } * })(); * ``` + * + * @public */ public async *stream(query: StreamFilter, pollingIntervalMs: number = 30000): AsyncGenerator { const startDate = new Date(); From 001625e8d63420fb70ba5b8b1e4a083f76242a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Touati?= Date: Fri, 23 Feb 2024 20:23:54 +0100 Subject: [PATCH 09/16] Fix example --- src/services/public/TweetService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/public/TweetService.ts b/src/services/public/TweetService.ts index 5822ca6f..5a17fce4 100644 --- a/src/services/public/TweetService.ts +++ b/src/services/public/TweetService.ts @@ -121,7 +121,7 @@ export class TweetService extends FetcherService { * // Streaming all upcoming tweets from user 'user1' * (async () => { * try { - * for await (const tweet of rettiwt.stream({ fromUsers: ['user1'] })) { + * for await (const tweet of rettiwt.tweet.stream({ fromUsers: ['user1'] })) { * console.log(tweet.fullText); * } * } catch (err) { From beb244ec1cae3d3fc7a4ea05b6fe636cc43c3eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Touati?= Date: Sat, 24 Feb 2024 18:23:16 +0100 Subject: [PATCH 10/16] Fix tweet stream duplicates --- src/services/public/TweetService.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/public/TweetService.ts b/src/services/public/TweetService.ts index 5a17fce4..1bc9d21e 100644 --- a/src/services/public/TweetService.ts +++ b/src/services/public/TweetService.ts @@ -163,7 +163,6 @@ export class TweetService extends FetcherService { // Else, start the next iteration from this batch's most recent tweet else { sinceId = nextSinceId; - nextSinceId = undefined; cursor = undefined; } } From 168ee01fbbd93415d010e106659bf95b13cf6b01 Mon Sep 17 00:00:00 2001 From: Rishikant Sahu Date: Mon, 26 Feb 2024 09:19:30 +0530 Subject: [PATCH 11/16] Updated Rettiwt-Core to latest stable --- package.json | 2 +- src/models/data/Tweet.ts | 4 +--- yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 52a48765..894197cd 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "commander": "11.1.0", "https-proxy-agent": "7.0.2", "rettiwt-auth": "2.1.0", - "rettiwt-core": "3.4.0-alpha.2" + "rettiwt-core": "3.4.0" }, "devDependencies": { "@types/node": "20.4.1", diff --git a/src/models/data/Tweet.ts b/src/models/data/Tweet.ts index 5bc1840d..45ffbfb3 100644 --- a/src/models/data/Tweet.ts +++ b/src/models/data/Tweet.ts @@ -72,9 +72,7 @@ export class Tweet { this.entities = new TweetEntities(tweet.legacy.entities); this.media = tweet.legacy.extended_entities?.media?.map((media) => new TweetMedia(media)); this.quoted = tweet.legacy.quoted_status_id_str; - this.fullText = tweet.note_tweet - ? tweet.note_tweet.note_tweet_results.result.text - : normalizeText(tweet.legacy.full_text); + this.fullText = tweet.note_tweet ? tweet.note_tweet.note_tweet_results.result.text : tweet.legacy.full_text; this.replyTo = tweet.legacy.in_reply_to_status_id_str; this.lang = tweet.legacy.lang; this.quoteCount = tweet.legacy.quote_count; diff --git a/yarn.lock b/yarn.lock index 8e50be40..3342ed1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1090,10 +1090,10 @@ rettiwt-auth@2.1.0: cookiejar "2.1.4" https-proxy-agent "7.0.2" -rettiwt-core@3.4.0-alpha.2: - version "3.4.0-alpha.2" - resolved "https://registry.yarnpkg.com/rettiwt-core/-/rettiwt-core-3.4.0-alpha.2.tgz#d3033d0db7a05f503e3b0aef5981d33897f0d08b" - integrity sha512-775cDTPWZXhY+znEOsjupIgj/Mmi8shKhLveShm1KnXEX1Qc5SjTEnpQv1dHmDaxiV9n6BIQ73E7K9AucBl+Mg== +rettiwt-core@3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/rettiwt-core/-/rettiwt-core-3.4.0.tgz#515aa168a5a64effa6cb2340debafebf5d4ee8a1" + integrity sha512-7N2wV+AB5fJVMzyE0s6FS9jZ/4z/eXIM1DlhOqeWous36PZSDPprINIcdPM8LVmfMEpq4aanplHkqCheNGLXBw== dependencies: axios "1.6.3" class-validator "0.14.1" From 83d691690b85f0b28acdb1e3a7bdae5987d45514 Mon Sep 17 00:00:00 2001 From: Rishikant Sahu Date: Mon, 26 Feb 2024 09:34:03 +0530 Subject: [PATCH 12/16] Updated actions --- .github/workflows/documentation.yml | 4 ++-- .github/workflows/publish.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 95f68812..bfa2a9b2 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -2,8 +2,8 @@ name: Documentation run-name: Building the documentation for the release on: - push: - branches: release + release: + types: [released] jobs: # Builds and packages the documentation diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index dfd005bd..512e8dd3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,7 +3,7 @@ run-name: Publishing the package to NPM on: release: - types: [created] + types: [released] jobs: # Packages and publishes the package to NPM From 632a092d2ee0a174bedee1e1c400ff15584ad8dc Mon Sep 17 00:00:00 2001 From: Rishikant Sahu Date: Mon, 26 Feb 2024 09:44:15 +0530 Subject: [PATCH 13/16] Used TweetFilter instead of StreamFilter --- src/services/public/TweetService.ts | 7 +++---- src/types/StreamFilter.ts | 8 -------- 2 files changed, 3 insertions(+), 12 deletions(-) delete mode 100644 src/types/StreamFilter.ts diff --git a/src/services/public/TweetService.ts b/src/services/public/TweetService.ts index 1bc9d21e..ceccce8c 100644 --- a/src/services/public/TweetService.ts +++ b/src/services/public/TweetService.ts @@ -6,7 +6,6 @@ import { FetcherService } from '../internal/FetcherService'; // TYPES import { IRettiwtConfig } from '../../types/RettiwtConfig'; -import { StreamFilter } from '../../types/StreamFilter'; // MODELS import { Tweet } from '../../models/data/Tweet'; @@ -132,7 +131,7 @@ export class TweetService extends FetcherService { * * @public */ - public async *stream(query: StreamFilter, pollingIntervalMs: number = 30000): AsyncGenerator { + public async *stream(filter: TweetFilter, pollingInterval: number = 60000): AsyncGenerator { const startDate = new Date(); let cursor: string | undefined = undefined; @@ -141,10 +140,10 @@ export class TweetService extends FetcherService { while (true) { // Pause execution for the specified polling interval before proceeding to the next iteration - await new Promise(resolve => setTimeout(resolve, pollingIntervalMs)); + await new Promise((resolve) => setTimeout(resolve, pollingInterval)); // Search for tweets - const tweets = await this.search({ ...query, startDate, sinceId }, undefined, cursor); + const tweets = await this.search({ ...filter, startDate: startDate, sinceId: sinceId }, undefined, cursor); // Yield the matching tweets for (const tweet of tweets.list) { diff --git a/src/types/StreamFilter.ts b/src/types/StreamFilter.ts deleted file mode 100644 index b89b9de4..00000000 --- a/src/types/StreamFilter.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { TweetFilter } from 'rettiwt-core'; - -/** - * Defines a filter for the streaming of tweets. - * - * @public - */ -export type StreamFilter = Omit From 4487df84fd741ac633d9674edc750338800b8a44 Mon Sep 17 00:00:00 2001 From: Rishikant Sahu Date: Tue, 27 Feb 2024 14:12:06 +0530 Subject: [PATCH 14/16] Added ability to stream tweets realtime --- src/commands/Tweet.ts | 30 +++++++++++++++++++++++------ src/services/public/TweetService.ts | 23 +++++++++++----------- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/commands/Tweet.ts b/src/commands/Tweet.ts index 00a8108a..50815d9a 100644 --- a/src/commands/Tweet.ts +++ b/src/commands/Tweet.ts @@ -57,13 +57,27 @@ function createTweetCommand(rettiwt: Rettiwt): Command { .option('--exclude-replies', 'Matches the tweets that are not replies') .option('-s, --start ', 'Matches the tweets made since the given date (valid date string)') .option('-e, --end ', 'Matches the tweets made upto the given date (valid date string)') + .option('--stream', 'Stream the filtered tweets in pseudo-realtime') + .option('-i, --interval ', 'The polling interval (in ms) to use for streaming. Default is 60000') .action(async (count?: string, cursor?: string, options?: TweetSearchOptions) => { - const tweets = await rettiwt.tweet.search( - new TweetSearchOptions(options).toTweetFilter(), - count ? parseInt(count) : undefined, - cursor, - ); - output(tweets); + // If search results are to be streamed + if (options?.stream) { + for await (const tweet of rettiwt.tweet.stream( + new TweetSearchOptions(options).toTweetFilter(), + options?.interval, + )) { + output(tweet); + } + } + // If a normal search is to be done + else { + const tweets = await rettiwt.tweet.search( + new TweetSearchOptions(options).toTweetFilter(), + count ? parseInt(count) : undefined, + cursor, + ); + output(tweets); + } }); // List @@ -166,6 +180,8 @@ class TweetSearchOptions { public excludeReplies?: boolean = false; public start?: string; public end?: string; + public stream?: boolean; + public interval?: number; /** * Initializes a new object from the given options. @@ -189,6 +205,8 @@ class TweetSearchOptions { this.excludeReplies = options?.excludeReplies; this.start = options?.start; this.end = options?.end; + this.stream = options?.stream; + this.interval = options?.interval; } /** diff --git a/src/services/public/TweetService.ts b/src/services/public/TweetService.ts index ceccce8c..cfecc65c 100644 --- a/src/services/public/TweetService.ts +++ b/src/services/public/TweetService.ts @@ -104,9 +104,9 @@ export class TweetService extends FetcherService { } /** - * Stream tweets in pseudo real-time using a query. + * Stream tweets in pseudo real-time using a filter. * - * @param query - The query be used for searching the tweets. + * @param filter - The filter to be used for searching the tweets. * @param pollingIntervalMs - The interval in milliseconds to poll for new tweets. * @returns An async generator that yields matching tweets as they are found. * @@ -118,15 +118,16 @@ export class TweetService extends FetcherService { * const rettiwt = new Rettiwt({ apiKey: API_KEY }); * * // Streaming all upcoming tweets from user 'user1' - * (async () => { - * try { - * for await (const tweet of rettiwt.tweet.stream({ fromUsers: ['user1'] })) { - * console.log(tweet.fullText); - * } - * } catch (err) { - * console.log(err); - * } - * })(); + * async () => { + * try { + * for await (const tweet of rettiwt.tweet.stream({ fromUsers: ['user1'] })) { + * console.log(tweet.fullText); + * } + * } + * catch (err) { + * console.log(err); + * } + * }(); * ``` * * @public From 5c290bcb615b7a953bd5134aed5416004bfc8c60 Mon Sep 17 00:00:00 2001 From: Rishikant Sahu Date: Tue, 27 Feb 2024 14:24:06 +0530 Subject: [PATCH 15/16] Updated docs --- README.md | 1 + src/services/public/TweetService.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f10e303c..0887dba4 100644 --- a/README.md +++ b/README.md @@ -268,6 +268,7 @@ So far, the following operations are supported: - [Retweeting/reposting a tweet](https://rishikant181.github.io/Rettiwt-API/classes/TweetService.html#retweet) - [Getting the list of users who retweeted/reposted a given tweet](https://rishikant181.github.io/Rettiwt-API/classes/TweetService.html#retweeters) - [Searching for the list of tweets that match a given filter](https://rishikant181.github.io/Rettiwt-API/classes/TweetService.html#search) +- [Streaming filtered tweets in pseudo-realtime](https://rishikant181.github.io/Rettiwt-API/classes/TweetService.html#stream) - [Posting a new tweet](https://rishikant181.github.io/Rettiwt-API/classes/TweetService.html#tweet) ### Users diff --git a/src/services/public/TweetService.ts b/src/services/public/TweetService.ts index cfecc65c..3ae35827 100644 --- a/src/services/public/TweetService.ts +++ b/src/services/public/TweetService.ts @@ -107,7 +107,7 @@ export class TweetService extends FetcherService { * Stream tweets in pseudo real-time using a filter. * * @param filter - The filter to be used for searching the tweets. - * @param pollingIntervalMs - The interval in milliseconds to poll for new tweets. + * @param pollingIntervalMs - The interval in milliseconds to poll for new tweets. Default interval is 60000 ms. * @returns An async generator that yields matching tweets as they are found. * * @example @@ -120,7 +120,7 @@ export class TweetService extends FetcherService { * // Streaming all upcoming tweets from user 'user1' * async () => { * try { - * for await (const tweet of rettiwt.tweet.stream({ fromUsers: ['user1'] })) { + * for await (const tweet of rettiwt.tweet.stream({ fromUsers: ['user1'] }, 1000)) { * console.log(tweet.fullText); * } * } From 0ce8e9e52668a4e3775304fde8fa809b1e52bee1 Mon Sep 17 00:00:00 2001 From: Rishikant Sahu Date: Tue, 27 Feb 2024 14:24:26 +0530 Subject: [PATCH 16/16] Bumped version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 894197cd..9763ff83 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rettiwt-api", - "version": "2.5.3", + "version": "2.6.0", "main": "dist/index.js", "types": "dist/index.d.ts", "description": "An API for fetching data from TwitterAPI, without any rate limits!",