py-tw-client 1.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_tw_client-1.0.1/LICENSE +21 -0
- py_tw_client-1.0.1/PKG-INFO +489 -0
- py_tw_client-1.0.1/py_tw_client.egg-info/PKG-INFO +489 -0
- py_tw_client-1.0.1/py_tw_client.egg-info/SOURCES.txt +16 -0
- py_tw_client-1.0.1/py_tw_client.egg-info/dependency_links.txt +1 -0
- py_tw_client-1.0.1/py_tw_client.egg-info/requires.txt +13 -0
- py_tw_client-1.0.1/py_tw_client.egg-info/top_level.txt +1 -0
- py_tw_client-1.0.1/setup.cfg +4 -0
- py_tw_client-1.0.1/setup.py +494 -0
- py_tw_client-1.0.1/twitter/__init__.py +0 -0
- py_tw_client-1.0.1/twitter/__version__.py +5 -0
- py_tw_client-1.0.1/twitter/account.py +837 -0
- py_tw_client-1.0.1/twitter/constants.py +757 -0
- py_tw_client-1.0.1/twitter/exceptions.py +32 -0
- py_tw_client-1.0.1/twitter/login.py +170 -0
- py_tw_client-1.0.1/twitter/scraper.py +958 -0
- py_tw_client-1.0.1/twitter/search.py +232 -0
- py_tw_client-1.0.1/twitter/util.py +337 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Trevor Hobenshield
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: py-tw-client
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Implementation of X/Twitter v1, v2, and GraphQL APIs.
|
|
5
|
+
Home-page: https://github.com/repen/twitter-api-client
|
|
6
|
+
Author: Plugin.py
|
|
7
|
+
Author-email: 9keepa@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: twitter api client async search automation bot scrape
|
|
10
|
+
Classifier: Environment :: Web Environment
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Natural Language :: English
|
|
13
|
+
Classifier: Operating System :: Unix
|
|
14
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
15
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
16
|
+
Classifier: Programming Language :: Python
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Requires-Python: >=3.10.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: aiofiles
|
|
28
|
+
Requires-Dist: nest_asyncio
|
|
29
|
+
Requires-Dist: httpx
|
|
30
|
+
Requires-Dist: tqdm
|
|
31
|
+
Requires-Dist: orjson
|
|
32
|
+
Requires-Dist: m3u8
|
|
33
|
+
Requires-Dist: websockets
|
|
34
|
+
Requires-Dist: uvloop; platform_system != "Windows"
|
|
35
|
+
Requires-Dist: XClientTransaction
|
|
36
|
+
Requires-Dist: requests
|
|
37
|
+
Requires-Dist: beautifulsoup4
|
|
38
|
+
Dynamic: author
|
|
39
|
+
Dynamic: author-email
|
|
40
|
+
Dynamic: classifier
|
|
41
|
+
Dynamic: description
|
|
42
|
+
Dynamic: description-content-type
|
|
43
|
+
Dynamic: home-page
|
|
44
|
+
Dynamic: keywords
|
|
45
|
+
Dynamic: license
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
Dynamic: requires-dist
|
|
48
|
+
Dynamic: requires-python
|
|
49
|
+
Dynamic: summary
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
## Implementation of X/Twitter v1, v2, and GraphQL APIs.
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
## Table of Contents
|
|
57
|
+
|
|
58
|
+
* [Installation](#installation)
|
|
59
|
+
* [Automation](#automation)
|
|
60
|
+
* [Scraping](#scraping)
|
|
61
|
+
* [Get all user/tweet data](#get-all-usertweet-data)
|
|
62
|
+
* [Resume Pagination](#resume-pagination)
|
|
63
|
+
* [Search](#search)
|
|
64
|
+
* [Spaces](#spaces)
|
|
65
|
+
* [Live Audio Capture](#live-audio-capture)
|
|
66
|
+
* [Live Transcript Capture](#live-transcript-capture)
|
|
67
|
+
* [Search and Metadata](#search-and-metadata)
|
|
68
|
+
* [Automated Solvers](#automated-solvers)
|
|
69
|
+
* [Example API Responses](#example-api-responses)
|
|
70
|
+
|
|
71
|
+
### Installation
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install twitter-api-client
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Automation
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from twitter.account import Account
|
|
81
|
+
|
|
82
|
+
## sign-in with credentials
|
|
83
|
+
email, username, password = ..., ..., ...
|
|
84
|
+
account = Account(email, username, password)
|
|
85
|
+
|
|
86
|
+
## or, resume session using cookies
|
|
87
|
+
# account = Account(cookies={"ct0": ..., "auth_token": ...})
|
|
88
|
+
|
|
89
|
+
## or, resume session using cookies (JSON file)
|
|
90
|
+
# account = Account(cookies='twitter.cookies')
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
account.tweet('test 123')
|
|
94
|
+
account.untweet(123456)
|
|
95
|
+
account.retweet(123456)
|
|
96
|
+
account.unretweet(123456)
|
|
97
|
+
account.reply('foo', tweet_id=123456)
|
|
98
|
+
account.quote('bar', tweet_id=123456)
|
|
99
|
+
account.schedule_tweet('schedule foo', 1681851240)
|
|
100
|
+
account.unschedule_tweet(123456)
|
|
101
|
+
|
|
102
|
+
account.tweet('hello world', media=[
|
|
103
|
+
{'media': 'test.jpg', 'alt': 'some alt text', 'tagged_users': [123]},
|
|
104
|
+
{'media': 'test.jpeg', 'alt': 'some alt text', 'tagged_users': [123]},
|
|
105
|
+
{'media': 'test.png', 'alt': 'some alt text', 'tagged_users': [123]},
|
|
106
|
+
{'media': 'test.jfif', 'alt': 'some alt text', 'tagged_users': [123]},
|
|
107
|
+
])
|
|
108
|
+
|
|
109
|
+
account.schedule_tweet('foo bar', '2023-04-18 15:42', media=[
|
|
110
|
+
{'media': 'test.gif', 'alt': 'some alt text'},
|
|
111
|
+
])
|
|
112
|
+
|
|
113
|
+
account.schedule_reply('hello world', '2023-04-19 15:42', tweet_id=123456, media=[
|
|
114
|
+
{'media': 'test.gif', 'alt': 'some alt text'},
|
|
115
|
+
])
|
|
116
|
+
|
|
117
|
+
account.dm('my message', [1234], media='test.jpg')
|
|
118
|
+
|
|
119
|
+
account.create_poll('test poll 123', ['hello', 'world', 'foo', 'bar'], 10080)
|
|
120
|
+
|
|
121
|
+
# tweets
|
|
122
|
+
account.like(123456)
|
|
123
|
+
account.unlike(123456)
|
|
124
|
+
account.bookmark(123456)
|
|
125
|
+
account.unbookmark(123456)
|
|
126
|
+
account.pin(123456)
|
|
127
|
+
account.unpin(123456)
|
|
128
|
+
|
|
129
|
+
# users
|
|
130
|
+
account.follow(1234)
|
|
131
|
+
account.unfollow(1234)
|
|
132
|
+
account.mute(1234)
|
|
133
|
+
account.unmute(1234)
|
|
134
|
+
account.enable_notifications(1234)
|
|
135
|
+
account.disable_notifications(1234)
|
|
136
|
+
account.block(1234)
|
|
137
|
+
account.unblock(1234)
|
|
138
|
+
|
|
139
|
+
# user profile
|
|
140
|
+
account.update_profile_image('test.jpg')
|
|
141
|
+
account.update_profile_banner('test.png')
|
|
142
|
+
account.update_profile_info(name='Foo Bar', description='test 123', location='Victoria, BC')
|
|
143
|
+
|
|
144
|
+
# topics
|
|
145
|
+
account.follow_topic(111)
|
|
146
|
+
account.unfollow_topic(111)
|
|
147
|
+
|
|
148
|
+
# lists
|
|
149
|
+
account.create_list('My List', 'description of my list', private=False)
|
|
150
|
+
account.update_list(222, 'My Updated List', 'some updated description', private=False)
|
|
151
|
+
account.update_list_banner(222, 'test.png')
|
|
152
|
+
account.delete_list_banner(222)
|
|
153
|
+
account.add_list_member(222, 1234)
|
|
154
|
+
account.remove_list_member(222, 1234)
|
|
155
|
+
account.delete_list(222)
|
|
156
|
+
account.pin_list(222)
|
|
157
|
+
account.unpin_list(222)
|
|
158
|
+
|
|
159
|
+
# refresh all pinned lists in this order
|
|
160
|
+
account.update_pinned_lists([222, 111, 333])
|
|
161
|
+
|
|
162
|
+
# unpin all lists
|
|
163
|
+
account.update_pinned_lists([])
|
|
164
|
+
|
|
165
|
+
# get timelines
|
|
166
|
+
timeline = account.home_timeline()
|
|
167
|
+
latest_timeline = account.home_latest_timeline(limit=500)
|
|
168
|
+
|
|
169
|
+
# get bookmarks
|
|
170
|
+
bookmarks = account.bookmarks()
|
|
171
|
+
|
|
172
|
+
# get DM inbox metadata
|
|
173
|
+
inbox = account.dm_inbox()
|
|
174
|
+
|
|
175
|
+
# get DMs from all conversations
|
|
176
|
+
dms = account.dm_history()
|
|
177
|
+
|
|
178
|
+
# get DMs from specific conversations
|
|
179
|
+
dms = account.dm_history(['123456-789012', '345678-901234'])
|
|
180
|
+
|
|
181
|
+
# search DMs by keyword
|
|
182
|
+
dms = account.dm_search('test123')
|
|
183
|
+
|
|
184
|
+
# delete entire conversation
|
|
185
|
+
account.dm_delete(conversation_id='123456-789012')
|
|
186
|
+
|
|
187
|
+
# delete (hide) specific DM
|
|
188
|
+
account.dm_delete(message_id='123456')
|
|
189
|
+
|
|
190
|
+
# get all scheduled tweets
|
|
191
|
+
scheduled_tweets = account.scheduled_tweets()
|
|
192
|
+
|
|
193
|
+
# delete a scheduled tweet
|
|
194
|
+
account.delete_scheduled_tweet(12345678)
|
|
195
|
+
|
|
196
|
+
# get all draft tweets
|
|
197
|
+
draft_tweets = account.draft_tweets()
|
|
198
|
+
|
|
199
|
+
# delete a draft tweet
|
|
200
|
+
account.delete_draft_tweet(12345678)
|
|
201
|
+
|
|
202
|
+
# delete all scheduled tweets
|
|
203
|
+
account.clear_scheduled_tweets()
|
|
204
|
+
|
|
205
|
+
# delete all draft tweets
|
|
206
|
+
account.clear_draft_tweets()
|
|
207
|
+
|
|
208
|
+
# example configuration
|
|
209
|
+
account.update_settings({
|
|
210
|
+
"address_book_live_sync_enabled": False,
|
|
211
|
+
"allow_ads_personalization": False,
|
|
212
|
+
"allow_authenticated_periscope_requests": True,
|
|
213
|
+
"allow_dm_groups_from": "following",
|
|
214
|
+
"allow_dms_from": "following",
|
|
215
|
+
"allow_location_history_personalization": False,
|
|
216
|
+
"allow_logged_out_device_personalization": False,
|
|
217
|
+
"allow_media_tagging": "none",
|
|
218
|
+
"allow_sharing_data_for_third_party_personalization": False,
|
|
219
|
+
"alt_text_compose_enabled": None,
|
|
220
|
+
"always_use_https": True,
|
|
221
|
+
"autoplay_disabled": False,
|
|
222
|
+
"country_code": "us",
|
|
223
|
+
"discoverable_by_email": False,
|
|
224
|
+
"discoverable_by_mobile_phone": False,
|
|
225
|
+
"display_sensitive_media": False,
|
|
226
|
+
"dm_quality_filter": "enabled",
|
|
227
|
+
"dm_receipt_setting": "all_disabled",
|
|
228
|
+
"geo_enabled": False,
|
|
229
|
+
"include_alt_text_compose": True,
|
|
230
|
+
"include_mention_filter": True,
|
|
231
|
+
"include_nsfw_admin_flag": True,
|
|
232
|
+
"include_nsfw_user_flag": True,
|
|
233
|
+
"include_ranked_timeline": True,
|
|
234
|
+
"language": "en",
|
|
235
|
+
"mention_filter": "unfiltered",
|
|
236
|
+
"nsfw_admin": False,
|
|
237
|
+
"nsfw_user": False,
|
|
238
|
+
"personalized_trends": True,
|
|
239
|
+
"protected": False,
|
|
240
|
+
"ranked_timeline_eligible": None,
|
|
241
|
+
"ranked_timeline_setting": None,
|
|
242
|
+
"require_password_login": False,
|
|
243
|
+
"requires_login_verification": False,
|
|
244
|
+
"sleep_time": {
|
|
245
|
+
"enabled": False,
|
|
246
|
+
"end_time": None,
|
|
247
|
+
"start_time": None
|
|
248
|
+
},
|
|
249
|
+
"translator_type": "none",
|
|
250
|
+
"universal_quality_filtering_enabled": "enabled",
|
|
251
|
+
"use_cookie_personalization": False,
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
# example configuration
|
|
255
|
+
account.update_search_settings({
|
|
256
|
+
"optInFiltering": True, # filter nsfw content
|
|
257
|
+
"optInBlocking": True, # filter blocked accounts
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
notifications = account.notifications()
|
|
261
|
+
|
|
262
|
+
account.change_password('old pwd','new pwd')
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Scraping
|
|
267
|
+
|
|
268
|
+
#### Get all user/tweet data
|
|
269
|
+
|
|
270
|
+
Two special batch queries `scraper.tweets_by_ids` and `scraper.users_by_ids` should be preferred when applicable. These endpoints are more much more efficient and have higher rate limits than their unbatched counterparts. See the table below for a comparison.
|
|
271
|
+
|
|
272
|
+
| Endpoint | Batch Size | Rate Limit |
|
|
273
|
+
|---------------|----------------|---------------|
|
|
274
|
+
| tweets_by_ids | ~220 | 500 / 15 mins |
|
|
275
|
+
| tweets_by_id | 1 | 50 / 15 mins |
|
|
276
|
+
| users_by_ids | ~220 | 100 / 15 mins |
|
|
277
|
+
| users_by_id | 1 | 500 / 15 mins |
|
|
278
|
+
|
|
279
|
+
*As of Fall 2023 login by username/password is unstable. Using cookies is now recommended.*
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
from twitter.scraper import Scraper
|
|
283
|
+
|
|
284
|
+
## sign-in with credentials
|
|
285
|
+
email, username, password = ..., ..., ...
|
|
286
|
+
scraper = Scraper(email, username, password)
|
|
287
|
+
|
|
288
|
+
## or, resume session using cookies
|
|
289
|
+
# scraper = Scraper(cookies={"ct0": ..., "auth_token": ...})
|
|
290
|
+
|
|
291
|
+
## or, resume session using cookies (JSON file)
|
|
292
|
+
# scraper = Scraper(cookies='twitter.cookies')
|
|
293
|
+
|
|
294
|
+
## or, initialize guest session (limited endpoints)
|
|
295
|
+
# from twitter.util import init_session
|
|
296
|
+
# scraper = Scraper(session=init_session())
|
|
297
|
+
|
|
298
|
+
# user data
|
|
299
|
+
users = scraper.users(['foo', 'bar', 'hello', 'world'])
|
|
300
|
+
users = scraper.users_by_ids([123, 234, 345]) # preferred
|
|
301
|
+
users = scraper.users_by_id([123, 234, 345])
|
|
302
|
+
tweets = scraper.tweets([123, 234, 345])
|
|
303
|
+
likes = scraper.likes([123, 234, 345])
|
|
304
|
+
tweets_and_replies = scraper.tweets_and_replies([123, 234, 345])
|
|
305
|
+
media = scraper.media([123, 234, 345])
|
|
306
|
+
following = scraper.following([123, 234, 345])
|
|
307
|
+
followers = scraper.followers([123, 234, 345])
|
|
308
|
+
scraper.tweet_stats([111111, 222222, 333333])
|
|
309
|
+
|
|
310
|
+
# get recommended users based on user
|
|
311
|
+
scraper.recommended_users()
|
|
312
|
+
scraper.recommended_users([123])
|
|
313
|
+
|
|
314
|
+
# tweet data
|
|
315
|
+
tweets = scraper.tweets_by_ids([987, 876, 754]) # preferred
|
|
316
|
+
tweets = scraper.tweets_by_id([987, 876, 754])
|
|
317
|
+
tweet_details = scraper.tweets_details([987, 876, 754])
|
|
318
|
+
retweeters = scraper.retweeters([987, 876, 754])
|
|
319
|
+
favoriters = scraper.favoriters([987, 876, 754])
|
|
320
|
+
|
|
321
|
+
scraper.download_media([
|
|
322
|
+
111111,
|
|
323
|
+
222222,
|
|
324
|
+
333333,
|
|
325
|
+
444444,
|
|
326
|
+
])
|
|
327
|
+
|
|
328
|
+
# trends
|
|
329
|
+
scraper.trends()
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
#### Resume Pagination
|
|
333
|
+
**Pagination is already done by default**, however there are circumstances where you may need to resume pagination from a specific cursor. For example, the `Followers` endpoint only allows for 50 requests every 15 minutes. In this case, we can resume from where we left off by providing a specific cursor value.
|
|
334
|
+
```python
|
|
335
|
+
from twitter.scraper import Scraper
|
|
336
|
+
|
|
337
|
+
email, username, password = ...,...,...
|
|
338
|
+
scraper = Scraper(email, username, password)
|
|
339
|
+
|
|
340
|
+
user_id = 44196397
|
|
341
|
+
cursor = '1767341853908517597|1663601806447476672' # example cursor
|
|
342
|
+
limit = 100 # arbitrary limit for demonstration
|
|
343
|
+
follower_subset, last_cursor = scraper.followers([user_id], limit=limit, cursor=cursor)
|
|
344
|
+
|
|
345
|
+
# use last_cursor to resume pagination
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
#### Search
|
|
349
|
+
|
|
350
|
+
```python
|
|
351
|
+
from twitter.search import Search
|
|
352
|
+
|
|
353
|
+
email, username, password = ..., ..., ...
|
|
354
|
+
# default output directory is `data/search_results` if save=True
|
|
355
|
+
search = Search(email, username, password, save=True, debug=1)
|
|
356
|
+
|
|
357
|
+
res = search.run(
|
|
358
|
+
limit=37,
|
|
359
|
+
retries=5,
|
|
360
|
+
queries=[
|
|
361
|
+
{
|
|
362
|
+
'category': 'Top',
|
|
363
|
+
'query': 'paperswithcode -tensorflow -tf'
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
'category': 'Latest',
|
|
367
|
+
'query': 'test'
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
'category': 'People',
|
|
371
|
+
'query': 'brasil portugal -argentina'
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
'category': 'Photos',
|
|
375
|
+
'query': 'greece'
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
'category': 'Videos',
|
|
379
|
+
'query': 'italy'
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
)
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**Search Operators Reference**
|
|
386
|
+
|
|
387
|
+
https://developer.twitter.com/en/docs/twitter-api/v1/rules-and-filtering/search-operators
|
|
388
|
+
|
|
389
|
+
https://developer.twitter.com/en/docs/twitter-api/tweets/search/integrate/build-a-query
|
|
390
|
+
|
|
391
|
+
### Spaces
|
|
392
|
+
|
|
393
|
+
#### Live Audio Capture
|
|
394
|
+
|
|
395
|
+
Capture live audio for up to 500 streams per IP
|
|
396
|
+
|
|
397
|
+
```python
|
|
398
|
+
from twitter.scraper import Scraper
|
|
399
|
+
from twitter.util import init_session
|
|
400
|
+
|
|
401
|
+
session = init_session() # initialize guest session, no login required
|
|
402
|
+
scraper = Scraper(session=session)
|
|
403
|
+
|
|
404
|
+
rooms = [...]
|
|
405
|
+
scraper.spaces_live(rooms=rooms) # capture live audio from list of rooms
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
#### Live Transcript Capture
|
|
409
|
+
|
|
410
|
+
**Raw transcript chunks**
|
|
411
|
+
|
|
412
|
+
```python
|
|
413
|
+
from twitter.scraper import Scraper
|
|
414
|
+
from twitter.util import init_session
|
|
415
|
+
|
|
416
|
+
session = init_session() # initialize guest session, no login required
|
|
417
|
+
scraper = Scraper(session=session)
|
|
418
|
+
|
|
419
|
+
# room must be live, i.e. in "Running" state
|
|
420
|
+
scraper.space_live_transcript('1zqKVPlQNApJB', frequency=2) # word-level live transcript. (dirty, on-the-fly transcription before post-processing)
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
**Processed (final) transcript chunks**
|
|
424
|
+
|
|
425
|
+
```python
|
|
426
|
+
from twitter.scraper import Scraper
|
|
427
|
+
from twitter.util import init_session
|
|
428
|
+
|
|
429
|
+
session = init_session() # initialize guest session, no login required
|
|
430
|
+
scraper = Scraper(session=session)
|
|
431
|
+
|
|
432
|
+
# room must be live, i.e. in "Running" state
|
|
433
|
+
scraper.space_live_transcript('1zqKVPlQNApJB', frequency=1) # finalized live transcript. (clean)
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
#### Search and Metadata
|
|
437
|
+
```python
|
|
438
|
+
from twitter.scraper import Scraper
|
|
439
|
+
from twitter.util import init_session
|
|
440
|
+
from twitter.constants import SpaceCategory
|
|
441
|
+
|
|
442
|
+
session = init_session() # initialize guest session, no login required
|
|
443
|
+
scraper = Scraper(session=session)
|
|
444
|
+
|
|
445
|
+
# download audio and chat-log from space
|
|
446
|
+
spaces = scraper.spaces(rooms=['1eaJbrAPnBVJX', '1eaJbrAlZjjJX'], audio=True, chat=True)
|
|
447
|
+
|
|
448
|
+
# pull metadata only
|
|
449
|
+
spaces = scraper.spaces(rooms=['1eaJbrAPnBVJX', '1eaJbrAlZjjJX'])
|
|
450
|
+
|
|
451
|
+
# search for spaces in "Upcoming", "Top" and "Live" categories
|
|
452
|
+
spaces = scraper.spaces(search=[
|
|
453
|
+
{
|
|
454
|
+
'filter': SpaceCategory.Upcoming,
|
|
455
|
+
'query': 'hello'
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
'filter': SpaceCategory.Top,
|
|
459
|
+
'query': 'world'
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
'filter': SpaceCategory.Live,
|
|
463
|
+
'query': 'foo bar'
|
|
464
|
+
}
|
|
465
|
+
])
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Automated Solvers
|
|
469
|
+
|
|
470
|
+
> This requires installation of the [proton-api-client](https://pypi.org/project/proton-api-client) package
|
|
471
|
+
|
|
472
|
+
To set up automated email confirmation/verification solvers, add your Proton Mail credentials below as shown.
|
|
473
|
+
This removes the need to manually solve email challenges via the web app. These credentials can be used
|
|
474
|
+
in `Scraper`, `Account`, and `Search` constructors.
|
|
475
|
+
|
|
476
|
+
E.g.
|
|
477
|
+
|
|
478
|
+
```python
|
|
479
|
+
from twitter.account import Account
|
|
480
|
+
from twitter.util import get_code
|
|
481
|
+
from proton.client import ProtonMail
|
|
482
|
+
|
|
483
|
+
proton_username, proton_password = ..., ...
|
|
484
|
+
proton = lambda: get_code(ProtonMail(proton_username, proton_password))
|
|
485
|
+
|
|
486
|
+
email, username, password = ..., ..., ...
|
|
487
|
+
account = Account(email, username, password, proton=proton)
|
|
488
|
+
```
|
|
489
|
+
|