refactoring

This commit is contained in:
Trevor Hobenshield
2023-03-22 19:49:55 -07:00
parent d2a511a850
commit 5862098809
6 changed files with 502 additions and 34 deletions

View File

@@ -7,13 +7,12 @@
"metadata": {},
"outputs": [],
"source": [
"from twitter import scraper\n",
"from twitter.login import login\n",
"from twitter.utils import find_key\n",
"\n",
"import re\n",
"import time\n",
"import pandas as pd"
"import pandas as pd\n",
"\n",
"from twitter.scraper import Scraper\n",
"from twitter.utils import find_key"
]
},
{
@@ -24,7 +23,7 @@
"outputs": [],
"source": [
"username, password = ...,...\n",
"s = login(username, password)"
"scraper = Scraper(username, password)"
]
},
{
@@ -32,8 +31,8 @@
"execution_count": null,
"outputs": [],
"source": [
"tweets = scraper.tweets(s, [33836629]).pop()\n",
"tweets_and_replies = scraper.tweets_and_replies(s, [33836629]).pop()"
"tweets = scraper.tweets([33836629]).pop()\n",
"tweets_and_replies = scraper.tweets_and_replies([33836629]).pop()"
],
"metadata": {
"collapsed": false

460
info/info.py Normal file
View File

@@ -0,0 +1,460 @@
GRAPHQL_ERROR_CODES = {
0: "DefaultApiError",
3: "InvalidCoordinates",
4: "InvalidGranularity",
5: "InvalidAccuracy",
6: "NoDataForPoint",
7: "NoDataForPointRadius",
8: "InvalidId",
9: "InvalidMaxResults",
10: "RockdoveError",
11: "InvalidIp",
12: "MustProvideCoordinatesIpQueryOrAttributes",
13: "NoLocationForIp",
14: "OverlimitAddressBookApi",
15: "AddressBookDarkmoded",
16: "AddressBookPermissionsError",
17: "AddressBookLookupNotFound",
18: "TooManyTerms",
19: "RetweetDarkmoded",
20: "NoScreenNameProvided",
21: "ContributorsNotEnabled",
22: "NotAuthorizedToViewUser",
23: "BulkLookupDarkmoded",
24: "UnsupportedProfileImageSize",
25: "MissingQuery",
26: "AutocompleteMustBeTrueOrFalse",
27: "AccountLocked",
28: "GenericDarkmode",
29: "TimeOut",
30: "WoeidDataUnavailable",
31: "InvalidTimescale",
32: "InvalidCredentials",
33: "OverLimit",
34: "GenericNotFound",
35: "TrendDataUnavailable",
36: "CantReportYourselfAsSpam",
37: "GenericAccessDenied",
38: "MissingParameter",
39: "InvalidCreationToken",
41: "RockdoveInvalidArgumentError",
42: "InvalidAttribute",
43: "AttributeAccessDenied",
44: "InvalidParameter",
46: "InvalidPlaceJson",
47: "InvalidRequestUrl",
48: "TimeoutRequestRainbird",
49: "NoFollowRequest",
50: "GenericUserNotFound",
51: "PromotedContentOfflineError",
52: "PromotedSearchNoQuery",
53: "BasicAuthDisabled",
54: "CassowaryError",
55: "ResourceNotFound",
56: "InvalidEmailAddress",
57: "PasswordResetPermissionsError",
58: "PasswordResetExpiredToken",
59: "PasswordResetInvalidHash",
60: "PasswordResetMismatchedEntries",
61: "ClientNotPermitted",
62: "CustomSaveErrors",
63: "OtherUserSuspended",
64: "CurrentUserSuspended",
65: "StrictMustBeTrueOrFalse",
66: "RequireActivityMustBeTrueOrFalse",
67: "BackendServiceUnavailable",
68: "EndpointDeprecated",
69: "TalonUrlMalware",
70: "InvalidPromotedContentLogEvent",
71: "EmailDeliveryError",
72: "ApplicationNotFound",
73: "ApplicationNotDeleted",
74: "ApplicationDomainNotRevoked",
75: "ApplicationKeysNotReset",
76: "ApplicationImageNotProcessed",
77: "ApplicationNoManageRight",
78: "ApplicationNoAdminRight",
79: "InvalidTrimPlace",
80: "CurationDarkmoded",
81: "ContributorsAccessLevelNotValid",
82: "ContributorsTargetUserNotSpecified",
83: "ContributorsTargetUserNotValid",
84: "TalonUrlUnrenderable",
85: "ValidationFailure",
86: "WrongHttpMethod",
87: "ClientNotPrivileged",
88: "RateLimitExceeded",
89: "BadOauthToken",
90: "ContributionNotPermitted",
91: "InvalidUtf8",
92: "SslRequired",
93: "DmAccessRequired",
94: "PageIsForbidden",
95: "InvalidLanguage",
96: "InvalidIds",
97: "EndpointFeatureDeprecated",
98: "FlagPossiblySensitiveScribeError",
99: "AuthenticityTokenError",
100: "GenericThriftException",
101: "InvalidReverseAuthCredentials",
102: "DarkmodedFeature",
103: "TrendsAvailableTransientException",
104: "ListAdminRightsError",
105: "MaximumMembersExceeded",
106: "AddBlockedUserError",
107: "NoTargetUser",
108: "TargetUserNotFound",
109: "TargetUserNotRelatedToList",
110: "ListNotAMemberError",
111: "TargetUserSuspended",
112: "InsufficientListParameters",
113: "InsufficientTargetUserParameters",
114: "InvalidCurrentPassword",
115: "ListUnauthorizedSubscriptionError",
116: "PasswordSmsResetPwSeedNotExist",
117: "PasswordSmsResetOptOut",
118: "ArgumentTooLarge",
119: "NarrowcastNotSupported",
120: "AccountUpdateFailure",
121: "InvalidHexColor",
122: "UpdateProfileColorsError",
123: "ImageUpdateError",
124: "AttributeUpdateError",
125: "GeolocationError",
126: "LoggedOut",
127: "ArchiveDeprecated",
128: "LocationUpdateFailure",
129: "EmailRateLimitExceeded",
130: "OverCapacity",
131: "InternalError",
132: "UnusedBackgroundUploadError",
133: "NoSelectedBackgroundError",
134: "TooManyDevices",
135: "OauthTimestampException",
136: "BlockedUserError",
137: "PushForbidden",
138: "FollowingInformationUnavailable",
139: "DuplicateFavorite",
140: "FollowingStatusUnauthorized",
141: "InactiveUser",
142: "ProtectedStatusFavoriteError",
143: "FavoriteRateLimitExceeded",
144: "StatusNotFound",
145: "RecordInvalid",
146: "OtherUserNotBlocked",
147: "SelfBlockError",
148: "UnsupportedDevice",
149: "InvalidEnabledFor",
150: "DirectMessageOtherUserNotFollowing",
151: "MessageSendError",
152: "DirectMessageDestroyPermissionsError",
153: "DirectMessageDeleteError",
154: "DirectMessageNotFound",
155: "MessageSendUnknownError",
156: "DowntimeAlert",
157: "VerifiedDeviceNotFound",
158: "SelfFollowError",
159: "GenericSuspended",
160: "DuplicateFollowRequest",
161: "FollowRateLimitExceeded",
162: "FollowBlockedUserError",
163: "IndeterminateSource",
164: "TargetUserNotSpecified",
165: "MultipleMissingParameters",
166: "MultipleUserNotFound",
167: "FollowError",
168: "StatusNotFoundForbidden",
169: "StatusRelatedResultsForbidden",
170: "ForbiddenMissingParameter",
171: "SearchDeletionError",
172: "SearchCreationError",
173: "ConfirmEmailExpiredCode",
174: "ConfirmEmailInvalidCode",
175: "ConfirmEmailInvalidStateChange",
176: "ConfirmEmailAlreadyConfirmed",
177: "ConfirmEmailSuccessChanged",
178: "ConfirmEmailSuccessNew",
179: "StatusViewForbidden",
180: "GenericEndpointOffline",
181: "TimeParameterOrderError",
182: "ParameterDeprecated",
183: "StatusActionPermissionError",
184: "StatusUpdateError",
185: "OverStatusUpdateLimit",
186: "StatusTooLongError",
187: "DuplicateStatusError",
188: "StatusMalwareError",
189: "StatusCreationError",
190: "UnknownInterpreterError",
191: "OverPhotoLimit",
192: "OverMediaEntitiesPerUpdateLimit",
193: "MediaTooLarge",
194: "StatusUpdateForbidden",
195: "InvalidRequestUrlForbidden",
196: "TimelineAuthorizationRequired",
197: "CategoryNotFound",
198: "ContactLoadError",
199: "IdsOfContactsError",
200: "GenericForbidden",
201: "GetRequired",
202: "InternalApplicationAuthenticationDenied",
203: "DeviceError",
204: "DestinationError",
205: "SpamRateLimitExceeded",
206: "InvalidDeviceRelationship",
207: "AlreadyActivated",
208: "FormatNotSupported",
209: "DirectMessageMustFollowFirst",
210: "TokenLimitExceeded",
211: "InvalidBrandBanner",
212: "ProfileBannerUploadsDisabled",
213: "ProcessingInProgress",
214: "GenericBadRequest",
215: "BadAuthenticationData",
216: "ShareViaEmailRateLimitExceeded",
217: "ProtectedStatusShareViaEmailError",
218: "RestrictedAccessShareViaEmailError",
219: "ShareViaEmailIpRateLimitExceeded",
220: "RestrictedAuthToken",
221: "CursorInvalid",
222: "TieredActionSignupSpammer",
223: "EmailTweetSendingError",
224: "MissingEmailAddress",
225: "TieredActionFollowSpammer",
226: "TieredActionTweetSpammer",
227: "TieredActionFollowCreeper",
228: "TieredActionTweetCreeper",
229: "AmbiguousCredentials",
230: "UserSleeping",
231: "RequiresLoginVerification",
232: "CannotEnableLoginVerificationPhone",
233: "CannotEnableLoginVerificationAlreadyEnabled",
234: "CannotEnableLoginVerificationUnconfirmedEmail",
235: "ExpiredLoginVerificationRequest",
236: "IncorrectChallengeResponse",
237: "MissingLoginVerificationRequest",
238: "NewPasswordWeak",
239: "BadGuestToken",
240: "TieredActionSignupSpammerPhoneVerify",
241: "RejectedLoginVerificationRequest",
242: "DeactivatedUser",
243: "OverLimitLogin",
244: "ForcePasswordReset",
245: "OverLimitLoginVerificationStart",
246: "OverLimitLoginVerificationAttempt",
247: "CannotEnableLoginVerificationPush",
248: "LoginVerificationAlreadyEnabled",
249: "CloudIpRestricted",
250: "UserMustBeAlcoholAgeScreened",
251: "EndpointRetired",
252: "DmSpamTimeout",
253: "NotYetApprovedLoginVerification",
254: "OfflineCodeSync",
255: "RequiresTemporaryPassword",
256: "CannotFollowFromCountry",
257: "BadDeviceToken",
258: "AppsCreateRequiresConfirmedEmail",
259: "AppsCreateRequiresVerifiedPhone",
260: "AppsCreateRejectedForAbuse",
261: "AppInReadOnlyMode",
262: "CurrentUserNeedsPhoneVerification",
263: "TieredActionChallengeCaptcha",
264: "TargetUserNotFollowing",
265: "TargetUserNotFavoriteFollowing",
266: "FailureSendingLoginVerificationRequest",
267: "InvalidCredentialsOneFactorEligible",
268: "MissingOneFactorLoginVerificationParams",
269: "UserIsNotSdkUser",
270: "AppsUpdateSettingsRequiresVerifiedPhone",
271: "SelfMuteError",
272: "NotMutingTargetUser",
273: "ScheduledInPast",
274: "ScheduledTooFarInFuture",
275: "TooLateToEdit",
276: "ScheduleInvalid",
277: "DirectMessageRecipientDoesNotFollowSenderWithUnverifiedPhoneNumber",
278: "DirectMessageUserNotInConversation",
279: "DirectMessageConversationNotFound",
280: "DirectMessageTooManyParticipants",
281: "DirectMessageTooFewParticipants",
282: "DirectMessageRecipientBlocksSender",
283: "TieredActionFavoriteSpammer",
284: "DeviceRegistrationGeneralError",
285: "DeviceAlreadyRegistered",
286: "DeviceOperatorUnsupported",
287: "UserAlreadyHasVerifiedPhone",
288: "CannotReuseCurrentPassword",
289: "DevicePinInvalid",
290: "DevicePinRequired",
291: "UnexpectedDeviceProvided",
292: "TieredActionConversationSpammer",
293: "SmsVerifyGeneralError",
294: "SmsVerifyInvalidPin",
295: "SmsVerifyRateLimitExceeded",
296: "DtabOverrideDarkmoded",
297: "DirectMessageCannotHaveBothTweetAndMedia",
298: "DirectMessageTweetNotFound",
299: "DeviceRegistrationRateExceeded",
300: "DeviceRegistrationInvalidInput",
301: "DeviceRegistrationPending",
302: "DeviceRegistrationOperationFailed",
303: "DeviceRegistrationPhoneNormalizationFailed",
304: "DeviceRegistrationPhoneCountryDetectionFailed",
305: "CannotIdentifyByEmail",
306: "TieredActionAccessTokenGrantSpam",
307: "TieredActionAccessTokenRevokeSpam",
308: "NoSmsVerifyExists",
309: "DeviceNotVerified",
310: "ExpiredPin",
311: "DirectMessageDuplicate",
312: "LocationNameMustBeSpecified",
313: "EULANotAccepted",
314: "VideoTranscodingError",
315: "ClientCaptchaRequired",
316: "CannotContributeToYourself",
317: "AccountHasTooManyContributors",
318: "AccountHasTooManyContributees",
319: "CannotChangePassword",
320: "ContributorsAccessLevelInsufficient",
321: "DirectMessageConversationNameTooLong",
322: "DirectMessageGenericUserCouldNotBeAdded",
323: "AnimatedGifMultipleImages",
324: "InvalidMediaId",
325: "MediaNotFound",
326: "AccessDeniedByBouncer",
327: "AlreadyRetweeted",
328: "InvalidRetweetForStatus",
329: "NonsupportingClientRequiresLoginVerification",
330: "ContributorsGenericUserCouldNotBeAdded",
331: "MobileSettingsUserNotFound",
332: "MobileSettingsTemplateNotFound",
333: "MobileSettingsFileNotFound",
334: "MobileSettingsUnsupportedTransport",
335: "MobileSettingsSettingNotFound",
336: "MobileSettingsInvalidValueFound",
337: "MobileSettingsSettingObjectNotFound",
338: "MobileSettingsEnabledForMissing",
339: "MobileSettingsNoDevicesFound",
340: "MobileSettingsNoIncomingPushSettings",
341: "MobileSettingsNoIncomingSmsSettings",
342: "MobileSettingsIncorrectApplicationId",
343: "MobileSettingsNoIncomingSettings",
344: "UserActionRateLimitExceeded",
345: "OneFactorMethodIsNotSupported",
346: "UserIsNotOneFactorEligible",
347: "InvalidRequestToken",
348: "ClientApplicationNotPermitted",
349: "DirectMessageCannotDmOtherUser",
350: "OauthException",
351: "MobileSettingsCouldNotUpdateSleep",
352: "ParameterLimitExceeded",
353: "DeniedByApiCsrfProtection",
354: "DirectMessageTooLongError",
355: "GenericConflict",
356: "GenericValidationFailure",
357: "RequiredFieldMissing",
358: "JsonProcessingError",
359: "ValueTooLarge",
360: "ValueTooSmall",
361: "ValueCannotBeEmpty",
362: "TimeNotFuture",
363: "InvalidCountryCodes",
364: "InvalidTimeGranularity",
365: "InvalidUUID",
366: "InvalidValues", # or MalformedVariables ?
367: "SizeOutOfRange",
368: "TimeNotPast",
369: "InvalidJsonSyntax",
370: "DigitsCannotReuseCurrentEmail",
371: "MentionLimitInTweetExceeded",
372: "UrlLimitInTweetExceeded",
373: "HashtagLimitInTweetExceeded",
374: "ExpiredQrCode",
375: "InvalidQrCode",
376: "MissingCredentials",
377: "TokenRetrievalException",
378: "TokenMissing",
379: "DataminrUserNotLinked",
380: "ABLiveSyncIsDisabled",
381: "SoftUserCreationSpamDenied",
382: "SoftUserActionSpamDenied",
383: "CashtagLimitInTweetExceeded",
384: "HashtagLengthLimitInTweetExceeded",
385: "InReplyToTweetNotFound",
386: "AttachmentTypesLimitInTweetExceeded",
387: "NotEnoughFollowers",
388: "FeatureAccessLimited",
389: "DirectMessagesSenderBlocksRecipient",
390: "SearchRecordingNotFound",
391: "MaximumSearchRecordingsExceeded",
392: "SessionNotFound",
393: "SessionModificationNotAuthorized",
394: "SessionModificationFailed",
395: "VoiceVerifyRateLimitExceeded",
396: "BlockUserFailed",
397: "InvalidMetricsJson",
398: "OnboardingFlowFailure",
399: "OnboardingFlowRetriableFailure",
400: "NoTwoFactorAuthMethodFound",
401: "MomentCapsuleAccessError",
402: "CannotEnrollLoginVerificationNotYetEnabled",
403: "IneligibleFor2faAfterModification",
404: "CookiesRequired",
405: "DuplicateBookmark",
406: "ProtectedTweetBookmarkError",
407: "DirectMessageInactiveDevice",
408: "InvalidUrl",
409: "BirthdateRequired",
410: "PasswordVerificationRequired",
411: "DirectMessageSenderInSecretDmsDisabledCountry",
412: "DirectMessageRecipientInSecretDmsDisabledCountry",
413: "DirectMessageSenderDeviceIsNotActiveForSecretDms",
414: "DirectMessageRecipientDeviceIsNotActiveForSecretDms",
415: "CallbackUrlLocked",
416: "InvalidOrSuspendedApp",
417: "InvalidDesktopCallback",
418: "DirectMessageSenderIsNotRegisteredForSecretDms",
419: "DirectMessageRecipientIsNotRegisteredForSecretDms",
420: "ReservedErrorCode",
421: "TweetIsBounced",
422: "TweetIsBounceDeleted",
423: "InvalidHeaders",
424: "MomentUnavailableForNewsCamera",
425: "TweetEngagementsLimited",
426: "InvalidRequestIpv6Token",
427: "IpResolverNotAvailable",
428: "ValidIpv6TokenRequired",
429: "HarmfulLink",
430: "ConversationControlNotAllowed",
431: "ConversationControlNotSupported",
432: "ConversationControlNotAuthorized",
433: "ConversationControlReplyRestricted",
434: "NotMutingTargetList",
435: "ConversationControlInvalidParameter",
436: "PassswordRequiredForEmailUpdate",
437: "NewPasswordShort",
438: "NewPasswordLong",
439: "NudgeReceived",
440: "CommunityUserNotAuthorized",
441: "CommunityNotFound",
442: "CommunityRetweetNotAllowed",
443: "CommunityInvalidParams",
444: "CommunityReplyTweetNotAllowed",
445: "RestrictedSession",
446: "TokenSecurityLevelAgreementPolicyFailure",
447: "SuperFollowsCreateNotAuthorized",
448: "SuperFollowsInvalidParams",
449: "TOOMomentsList",
450: "CommunityProtectedUserCannotTweet",
451: "ExclusiveTweetEngagementNotAllowed",
452: "SteamCreationException",
453: "V11Restricted",
454: "SteamGetException",
455: "TrustedFriendsInvalidParams",
456: "TrustedFriendsRetweetNotAllowed",
1002: "DuplicateRequest",
1003: "GenericRequestFailed",
1004: "Offline"
}

View File

@@ -15,7 +15,7 @@ if platform.system() != 'Windows':
setup(
name="twitter-api-client",
version="0.4.2",
version="0.4.3",
description="Twitter API",
long_description=dedent('''
Complete implementation of the undocumented Twitter API

View File

@@ -84,6 +84,9 @@ def log(fn=None, *, level: int = logging.DEBUG, info: list = None) -> callable:
class Account:
V1_URL = 'https://api.twitter.com/1.1'
V2_URL = 'https://api.twitter.com/2' # /search
GRAPHQL_URL = 'https://api.twitter.com/graphql'
def __init__(self, username: str, password: str):
self.session = login(username, password)
@@ -93,14 +96,14 @@ class Account:
payload = deepcopy(operations[name])
qid = payload['queryId']
payload['variables'] |= variables
url = f"https://api.twitter.com/graphql/{qid}/{name}"
url = f"{self.GRAPHQL_URL}/{qid}/{name}"
r = self.session.post(url, headers=get_headers(self.session), json=payload)
return r
def api(self, path: str, settings: dict) -> Response:
headers = get_headers(self.session)
headers['content-type'] = 'application/x-www-form-urlencoded'
url = f'https://api.twitter.com/1.1/{path}'
url = f'{self.V1_URL}/{path}'
r = self.session.post(url, headers=headers, data=urlencode(settings))
return r
@@ -111,7 +114,7 @@ class Account:
qid = params['queryId']
params['variables']['target'] = {"participant_ids": receivers}
params['variables']['requestId'] = str(uuid1(getnode())) # can be anything
url = f"https://api.twitter.com/graphql/{qid}/{name}"
url = f"{self.GRAPHQL_URL}/{qid}/{name}"
if filename:
media_id = self.upload_media(filename, is_dm=True)
params['variables']['message']['media'] = {'id': media_id, 'text': text}
@@ -151,7 +154,7 @@ class Account:
if poll_params := kwargs.get('poll_params', {}):
params['variables'] |= poll_params
url = f"https://api.twitter.com/graphql/{qid}/{name}"
url = f"{self.GRAPHQL_URL}/{qid}/{name}"
r = self.session.post(url, headers=get_headers(self.session), json=params)
return r
@@ -180,7 +183,7 @@ class Account:
elif isinstance(m, str):
media_id = self.upload_media(m)
params['variables']['post_tweet_request']['media_ids'].append(media_id)
url = f"https://api.twitter.com/graphql/{qid}/{name}"
url = f"{self.GRAPHQL_URL}/{qid}/{name}"
r = self.session.post(url, headers=get_headers(self.session), json=params)
return r
@@ -272,7 +275,7 @@ class Account:
@log(info=['text'])
def add_alt_text(self, media_id: int, text: str) -> Response:
params = {"media_id": media_id, "alt_text": {"text": text}}
url = 'https://api.twitter.com/1.1/media/metadata/create.json'
url = f'{self.V1_URL}/media/metadata/create.json'
r = self.session.post(url, headers=get_headers(self.session), json=params)
return r
@@ -341,9 +344,9 @@ class Account:
def update_pinned_lists(self, list_ids: list[int]) -> Response:
"""
Update pinned lists
Reset all pinned lists and pin all specified lists in the order they are provided.
@param list_ids: list of list ids to pin
@return: response
"""
@@ -448,13 +451,13 @@ class Account:
qid = params['queryId']
params['variables']['rest_id'] = rest_id
query = build_query(params)
url = f"https://api.twitter.com/graphql/{qid}/{name}?{query}"
url = f"{self.GRAPHQL_URL}/{qid}/{name}?{query}"
r = self.session.get(url, headers=get_headers(self.session))
return r
@log(info=['json'])
def remove_interests(self, *args):
url = 'https://api.twitter.com/1.1/account/personalization/twitter_interests.json'
url = f'{self.V1_URL}/account/personalization/twitter_interests.json'
r = self.session.get(url, headers=get_headers(self.session))
current_interests = r.json()['interested_in']
if args == 'all':
@@ -469,14 +472,14 @@ class Account:
}
}
}
url = 'https://api.twitter.com/1.1/account/personalization/p13n_preferences.json'
url = f'{self.V1_URL}/account/personalization/p13n_preferences.json'
r = self.session.post(url, headers=get_headers(self.session), json=payload)
return r
@log(info=['json'])
def update_profile_image(self, filename: str) -> Response:
media_id = self.upload_media(filename, is_profile=True)
url = 'https://api.twitter.com/1.1/account/update_profile_image.json'
url = f'{self.V1_URL}/account/update_profile_image.json'
headers = get_headers(self.session)
params = {'media_id': media_id}
r = self.session.post(url, headers=headers, params=params)
@@ -485,7 +488,7 @@ class Account:
@log
def update_profile_banner(self, filename: str) -> Response:
media_id = self.upload_media(filename, is_profile=True)
url = 'https://api.twitter.com/1.1/account/update_profile_banner.json'
url = f'{self.V1_URL}/account/update_profile_banner.json'
headers = get_headers(self.session)
params = {'media_id': media_id}
r = self.session.post(url, headers=headers, params=params)
@@ -493,7 +496,7 @@ class Account:
@log
def update_profile_info(self, **kwargs) -> Response:
url = 'https://api.twitter.com/1.1/account/update_profile.json'
url = f'{self.V1_URL}/account/update_profile.json'
headers = get_headers(self.session)
r = self.session.post(url, headers=headers, params=kwargs)
return r
@@ -502,14 +505,14 @@ class Account:
def update_search_settings(self, settings: dict) -> Response:
"""
Update account search settings
@param settings: search filtering settings to enable/disable
@return: authenticated session
"""
twid = int(self.session.cookies.get_dict()['twid'].split('=')[-1].strip('"'))
headers = get_headers(self.session)
r = self.session.post(
url=f'https://api.twitter.com/1.1/strato/column/User/{twid}/search/searchSafety',
url=f'{self.V1_URL}/strato/column/User/{twid}/search/searchSafety',
headers=headers,
json=settings,
)

View File

@@ -1,3 +1,4 @@
import ast
import json
import re
import subprocess
@@ -16,10 +17,16 @@ def find_api_script(res: requests.Response) -> str:
@param res: response from homepage: https://twitter.com
@return: url to api script
"""
base = 'https://abs.twimg.com/responsive-web/client-web'
for s in bs4.BeautifulSoup(res.text, 'html.parser').select('script'):
if x := re.search('(?<=api:")\w+(?=")', s.text):
key = x.group() + 'a' # wtf?
return f'https://abs.twimg.com/responsive-web/client-web/api.{key}.js'
temp = s.text.split('+"."+')[-1].split('[e]+"a.js"')[0]
# temp = s.text.split('function(e){return e+"."+')[-1].split('[e]+"a.js"')[0]
if temp.startswith('{'):
endpoints = json.loads(temp.replace('vendor:', '"vendor":').replace('api:', '"api":'))
Path('endpoints.json').write_text(json.dumps(endpoints, indent=2))
Path('endpoint_urls.txt').write_text('\n'.join((f'{base}/{k}.{v}a.js' for k,v in endpoints.items())))
js = 'api.' + endpoints['api'] + "a.js" # search for `+"a.js"` in homepage source
return f'{base}/{js}'
def get_operations(session: Session) -> tuple:
@@ -28,7 +35,7 @@ def get_operations(session: Session) -> tuple:
@return: list of operations
"""
headers = {
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36',
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36',
}
r1 = session.get('https://twitter.com', headers=headers)
script = find_api_script(r1)

View File

@@ -39,6 +39,7 @@ logger = logging.getLogger(__name__)
class Scraper:
GRAPHQL_URL = 'https://api.twitter.com/graphql'
def __init__(self, username: str, password: str):
self.session = login(username, password)
@@ -89,7 +90,7 @@ class Scraper:
qid = params['queryId']
params['variables']['userIds'] = ids
q = build_query(params)
url = f"https://api.twitter.com/graphql/{qid}/{name}?{q}"
url = f"{self.GRAPHQL_URL}/{qid}/{name}?{q}"
headers = get_headers(self.session)
headers['content-type'] = "application/json"
users = self.session.get(url, headers=headers).json()
@@ -109,7 +110,7 @@ class Scraper:
for _id in ids:
params['variables'][key] = _id
q = build_query(params)
urls.append((_id, f"https://api.twitter.com/graphql/{qid}/{name}?{q}"))
urls.append((_id, f"{self.GRAPHQL_URL}/{qid}/{name}?{q}"))
headers = get_headers(self.session)
headers['content-type'] = "application/json"
res = asyncio.run(self.process(urls, headers))
@@ -130,8 +131,6 @@ class Scraper:
r = await session.get(api_url)
limits = {k: v for k, v in r.headers.items() if 'x-rate-limit' in k}
logger.debug(f'{limits = }')
text = await r.text()
logger.debug(f'{text = }')
if r.status == 429:
logger.debug(f'rate limit exceeded: {url}')
return {}
@@ -176,7 +175,7 @@ class Scraper:
while 1:
params['variables']['cursor'] = cursor
query = build_query(params)
url = f"https://api.twitter.com/graphql/{qid}/{name}?{query}"
url = f"{self.GRAPHQL_URL}/{qid}/{name}?{query}"
# code [353]: "This request requires a matching csrf cookie and header."
r, _data = await self.backoff(lambda: session.get(url))