1
+ // Open a realtime stream of Tweets, filtered according to rules
2
+ // https://developer.twitter.com/en/docs/twitter-api/tweets/filtered-stream/quick-start
3
+
1
4
const needle = require ( 'needle' ) ;
2
5
3
6
// The code below sets the bearer token from your environment variables
4
- // To set environment variables on Mac OS X , run the export command below from the terminal:
7
+ // To set environment variables on macOS or Linux , run the export command below from the terminal:
5
8
// export BEARER_TOKEN='YOUR-TOKEN'
6
- const token = process . env . BEARER_TOKEN ;
9
+ const token = process . env . BEARER_TOKEN ;
7
10
8
- const rulesURL = 'https://api.twitter.com/2/tweets/search/stream/rules'
11
+ const rulesURL = 'https://api.twitter.com/2/tweets/search/stream/rules' ;
9
12
const streamURL = 'https://api.twitter.com/2/tweets/search/stream' ;
10
13
11
- // Edit rules as desired here below
12
- const rules = [
13
- { 'value' : 'dog has:images -is:retweet' , 'tag' : 'dog pictures' } ,
14
- { 'value' : 'cat has:images -grumpy' , 'tag' : 'cat pictures' } ,
15
- ] ;
14
+ // this sets up two rules - the value is the search terms to match on, and the tag is an identifier that
15
+ // will be applied to the Tweets return to show which rule they matched
16
+ // with a standard project with Basic Access, you can add up to 25 concurrent rules to your stream, and
17
+ // each rule can be up to 512 characters long
18
+
19
+ // Edit rules as desired below
20
+ const rules = [ {
21
+ 'value' : 'dog has:images -is:retweet' ,
22
+ 'tag' : 'dog pictures'
23
+ } ,
24
+ {
25
+ 'value' : 'cat has:images -grumpy' ,
26
+ 'tag' : 'cat pictures'
27
+ } ,
28
+ ] ;
16
29
17
30
async function getAllRules ( ) {
18
31
19
- const response = await needle ( 'get' , rulesURL , { headers : {
20
- "authorization" : `Bearer ${ token } `
21
- } } )
32
+ const response = await needle ( 'get' , rulesURL , {
33
+ headers : {
34
+ "authorization" : `Bearer ${ token } `
35
+ }
36
+ } )
22
37
23
38
if ( response . statusCode !== 200 ) {
24
39
throw new Error ( response . body ) ;
25
- return null ;
26
40
}
27
41
28
42
return ( response . body ) ;
@@ -32,7 +46,7 @@ async function deleteAllRules(rules) {
32
46
33
47
if ( ! Array . isArray ( rules . data ) ) {
34
48
return null ;
35
- }
49
+ }
36
50
37
51
const ids = rules . data . map ( rule => rule . id ) ;
38
52
@@ -42,16 +56,17 @@ async function deleteAllRules(rules) {
42
56
}
43
57
}
44
58
45
- const response = await needle ( 'post' , rulesURL , data , { headers : {
46
- "content-type" : "application/json" ,
47
- "authorization" : `Bearer ${ token } `
48
- } } )
59
+ const response = await needle ( 'post' , rulesURL , data , {
60
+ headers : {
61
+ "content-type" : "application/json" ,
62
+ "authorization" : `Bearer ${ token } `
63
+ }
64
+ } )
49
65
50
66
if ( response . statusCode !== 200 ) {
51
67
throw new Error ( response . body ) ;
52
- return null ;
53
68
}
54
-
69
+
55
70
return ( response . body ) ;
56
71
57
72
}
@@ -60,85 +75,84 @@ async function setRules() {
60
75
61
76
const data = {
62
77
"add" : rules
63
- }
78
+ }
64
79
65
- const response = await needle ( 'post' , rulesURL , data , { headers : {
66
- "content-type" : "application/json" ,
67
- "authorization" : `Bearer ${ token } `
68
- } } )
80
+ const response = await needle ( 'post' , rulesURL , data , {
81
+ headers : {
82
+ "content-type" : "application/json" ,
83
+ "authorization" : `Bearer ${ token } `
84
+ }
85
+ } )
69
86
70
87
if ( response . statusCode !== 201 ) {
71
88
throw new Error ( response . body ) ;
72
- return null ;
73
89
}
74
-
90
+
75
91
return ( response . body ) ;
76
92
77
93
}
78
94
79
95
function streamConnect ( ) {
80
- //Listen to the stream
81
- const options = {
82
- timeout : 20000
83
- }
84
-
96
+
85
97
const stream = needle . get ( streamURL , {
86
- headers : {
87
- Authorization : `Bearer ${ token } `
88
- }
89
- } , options ) ;
98
+ headers : {
99
+ "User-Agent" : "v2FilterStreamJS" ,
100
+ "Authorization" : `Bearer ${ token } `
101
+ } ,
102
+ timeout : 20000
103
+ } ) ;
90
104
91
105
stream . on ( 'data' , data => {
92
- try {
93
- const json = JSON . parse ( data ) ;
94
- console . log ( json ) ;
95
- } catch ( e ) {
96
- // Keep alive signal received. Do nothing.
97
- }
106
+ try {
107
+ const json = JSON . parse ( data ) ;
108
+ console . log ( json ) ;
109
+ } catch ( e ) {
110
+ // Keep alive signal received. Do nothing.
111
+ }
98
112
} ) . on ( 'error' , error => {
99
113
if ( error . code === 'ETIMEDOUT' ) {
100
114
stream . emit ( 'timeout' ) ;
101
115
}
102
116
} ) ;
103
117
104
118
return stream ;
105
-
119
+
106
120
}
107
121
108
122
109
123
( async ( ) => {
110
124
let currentRules ;
111
-
125
+
112
126
try {
113
- // Gets the complete list of rules currently applied to the stream
114
- currentRules = await getAllRules ( ) ;
115
-
116
- // Delete all rules. Comment the line below if you want to keep your existing rules.
117
- await deleteAllRules ( currentRules ) ;
118
-
119
- // Add rules to the stream. Comment the line below if you don't want to add new rules.
120
- await setRules ( ) ;
121
-
127
+ // Gets the complete list of rules currently applied to the stream
128
+ currentRules = await getAllRules ( ) ;
129
+
130
+ // Delete all rules. Comment the line below if you want to keep your existing rules.
131
+ await deleteAllRules ( currentRules ) ;
132
+
133
+ // Add rules to the stream. Comment the line below if you don't want to add new rules.
134
+ await setRules ( ) ;
135
+
122
136
} catch ( e ) {
123
- console . error ( e ) ;
124
- process . exit ( - 1 ) ;
137
+ console . error ( e ) ;
138
+ process . exit ( - 1 ) ;
125
139
}
126
-
140
+
127
141
// Listen to the stream.
128
142
// This reconnection logic will attempt to reconnect when a disconnection is detected.
129
- // To avoid rate limites , this logic implements exponential backoff, so the wait time
143
+ // To avoid rate limits , this logic implements exponential backoff, so the wait time
130
144
// will increase if the client cannot reconnect to the stream.
131
-
132
- const filteredStream = streamConnect ( )
145
+
146
+ const filteredStream = streamConnect ( ) ;
133
147
let timeout = 0 ;
134
148
filteredStream . on ( 'timeout' , ( ) => {
135
- // Reconnect on error
136
- console . warn ( 'A connection error occurred. Reconnecting…' ) ;
137
- setTimeout ( ( ) => {
138
- timeout ++ ;
139
- streamConnect ( token ) ;
140
- } , 2 ** timeout ) ;
141
- streamConnect ( token ) ;
149
+ // Reconnect on error
150
+ console . warn ( 'A connection error occurred. Reconnecting…' ) ;
151
+ setTimeout ( ( ) => {
152
+ timeout ++ ;
153
+ streamConnect ( ) ;
154
+ } , 2 ** timeout ) ;
155
+ streamConnect ( ) ;
142
156
} )
143
157
144
- } ) ( ) ;
158
+ } ) ( ) ;
0 commit comments