mirror of
https://github.com/trevorhobenshield/twitter-api-client.git
synced 2025-12-25 02:03:15 -05:00
refactoring
This commit is contained in:
@@ -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
460
info/info.py
Normal 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"
|
||||
}
|
||||
|
||||
|
||||
2
setup.py
2
setup.py
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user