xapiweb 1.5.0__py3-none-any.whl

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.
@@ -0,0 +1,84 @@
1
+ """Mutes & blocks (REST + GraphQL)."""
2
+ from .. import errors as E
3
+ from ._base import BaseResource
4
+
5
+
6
+ class Moderation(BaseResource):
7
+ # ---- mutes ----
8
+ def mute(self, user_id, verify=True):
9
+ """Proven: test_mute_pair.py"""
10
+ resp = self._s.rest_post_form("mutes/users/create.json",
11
+ {"user_id": str(user_id), "skip_status": 1})
12
+ if verify and str(user_id) not in [str(i) for i in (self.mutes_ids().get("ids") or [])]:
13
+ raise E.XVerifyError(f"Mute of {user_id} not confirmed in ids.")
14
+ return resp
15
+
16
+ def unmute(self, user_id, verify=True):
17
+ """Proven: test_mute_pair.py"""
18
+ resp = self._s.rest_post_form("mutes/users/destroy.json", {"user_id": str(user_id)})
19
+ if verify and str(user_id) in [str(i) for i in (self.mutes_ids().get("ids") or [])]:
20
+ raise E.XVerifyError(f"Unmute of {user_id} not confirmed in ids.")
21
+ return resp
22
+
23
+ def mutes_ids(self):
24
+ """Proven: test_mutes_ids.py"""
25
+ return self._s.rest_get("mutes/users/ids.json", {"cursor": -1})
26
+
27
+ def mutes_list(self):
28
+ """Proven: test_mutes_list.py"""
29
+ return self._s.rest_get("mutes/users/list.json", {"cursor": -1})
30
+
31
+ def muted_accounts_gql(self):
32
+ """Proven: test_muted_accounts_gql.py"""
33
+ return self._s.gql_get("MutedAccounts", {})
34
+
35
+ def advanced_filters(self):
36
+ """May 404 (dead-ish). Proven: test_mutes_advanced_filters.py"""
37
+ return self._s.rest_get("mutes/advanced_filters.json", {})
38
+
39
+ # ---- blocks ----
40
+ def block(self, user_id, verify=True):
41
+ """Proven: test_block_pair.py"""
42
+ resp = self._s.rest_post_form("blocks/create.json",
43
+ {"user_id": str(user_id), "skip_status": 1})
44
+ if verify and str(user_id) not in [str(i) for i in (self.blocks_ids().get("ids") or [])]:
45
+ raise E.XVerifyError(f"Block of {user_id} not confirmed in ids.")
46
+ return resp
47
+
48
+ def unblock(self, user_id, verify=True):
49
+ """Proven: test_block_pair.py"""
50
+ resp = self._s.rest_post_form("blocks/destroy.json", {"user_id": str(user_id)})
51
+ if verify and str(user_id) in [str(i) for i in (self.blocks_ids().get("ids") or [])]:
52
+ raise E.XVerifyError(f"Unblock of {user_id} not confirmed in ids.")
53
+ return resp
54
+
55
+ def blocks_ids(self):
56
+ """Proven: test_blocks_ids.py"""
57
+ return self._s.rest_get("blocks/ids.json", {"cursor": -1})
58
+
59
+ def blocks_list(self):
60
+ """Proven: test_blocks_list.py"""
61
+ return self._s.rest_get("blocks/list.json", {"cursor": -1, "skip_status": 1})
62
+
63
+ def blocked_accounts_gql(self, count=5):
64
+ """Proven: test_blocked_accounts_gql.py"""
65
+ return self._s.gql_get("BlockedAccountsAll", {"count": count})
66
+
67
+ def blocked_imported_gql(self):
68
+ """Proven: test_blocked_imported_gql.py"""
69
+ return self._s.gql_get("BlockedAccountsImported", {})
70
+
71
+ # ---- DM-specific ----
72
+ def dm_block(self, user_id, verify=True):
73
+ """Lowercase-dm op; ids differ from REST. Proven: test_dm_block_pair.py"""
74
+ resp = self._s.gql_post("dmBlockUser", {"target_user_id": str(user_id)})
75
+ if verify and "Blocked" not in resp.raw:
76
+ raise E.XVerifyError("dmBlock ack missing 'Blocked'.")
77
+ return resp
78
+
79
+ def dm_unblock(self, user_id, verify=True):
80
+ """Proven: test_dm_block_pair.py"""
81
+ resp = self._s.gql_post("dmUnblockUser", {"target_user_id": str(user_id)})
82
+ if verify and "Unblocked" not in resp.raw:
83
+ raise E.XVerifyError("dmUnblock ack missing 'Unblocked'.")
84
+ return resp
@@ -0,0 +1,84 @@
1
+ """Notifications: full list + badge-count polling + realtime SSE."""
2
+ import http.client
3
+ import re
4
+
5
+ from ._base import BaseResource
6
+
7
+
8
+ def extract_notifications(resp_or_data, limit=50):
9
+ """Pull [{id, type, icon, text, users, time, url, tweet_id}] out of a
10
+ NotificationsTimeline response. type = element like 'users_liked_your_tweet',
11
+ 'users_followed_you', 'mention'. users = [{screen_name, name, id}].
12
+ Page with xapiweb.resources.timelines.cursors(resp)['bottom']. xapiweb helper."""
13
+ data = resp_or_data.data if hasattr(resp_or_data, "data") else resp_or_data
14
+ out = []
15
+
16
+ def walk(o):
17
+ if len(out) >= limit:
18
+ return
19
+ if isinstance(o, dict):
20
+ it = o.get("itemContent")
21
+ if isinstance(it, dict) and it.get("__typename") == "TimelineNotification":
22
+ rm = it.get("rich_message") or {}
23
+ users = []
24
+ for ent in rm.get("entities") or []:
25
+ ur = (((ent.get("ref") or {}).get("user_results") or {}).get("result") or {})
26
+ core = ur.get("core") or {}
27
+ leg = ur.get("legacy") or {}
28
+ sn = core.get("screen_name") or leg.get("screen_name")
29
+ if sn:
30
+ users.append({"screen_name": sn,
31
+ "name": core.get("name") or leg.get("name"),
32
+ "id": core.get("rest_id") or ur.get("rest_id")})
33
+ url = (it.get("notification_url") or {}).get("url") or ""
34
+ m = re.search(r"/status/(\d+)", url)
35
+ out.append({"id": it.get("id"),
36
+ "type": (o.get("clientEventInfo") or {}).get("element"),
37
+ "icon": it.get("notification_icon"),
38
+ "text": rm.get("text") or "",
39
+ "users": users,
40
+ "time": it.get("timestamp_ms"),
41
+ "url": url,
42
+ "tweet_id": m.group(1) if m else None})
43
+ for v in o.values():
44
+ walk(v)
45
+ elif isinstance(o, list):
46
+ for v in o:
47
+ walk(v)
48
+
49
+ walk(data)
50
+ return out
51
+
52
+
53
+ class Notifications(BaseResource):
54
+ def list(self, timeline_type="All", count=20, cursor=None):
55
+ """Full notifications list. timeline_type: 'All'|'Verified'|'Mentions'
56
+ (all 200-proven). Parse with extract_notifications(resp); page with
57
+ timelines.cursors(resp)['bottom'] -> cursor=. Proven live 2026-09-20
58
+ (user-captured qid)."""
59
+ v = {"timeline_type": timeline_type, "count": count}
60
+ if cursor:
61
+ v["cursor"] = cursor
62
+ return self._s.gql_get("NotificationsTimeline", v, with_toggles=False)
63
+
64
+ def badge_counts(self):
65
+ """{dm_unread_count, ntab_unread_count, total_unread_count, ...} — the polling
66
+ endpoint. Proven: test_viewer_badge_counts.py"""
67
+ return self._s.gql_get("ViewerBadgeCounts", {},
68
+ with_features=False, with_toggles=False)
69
+
70
+ def live_events(self, seconds=6, topic=None):
71
+ """Hold the SSE stream ~`seconds` and return {'status':, 'sample':}.
72
+ Full notifications timeline (AuthTimeline) is UNTESTED. Proven: test_live_pipeline.py"""
73
+ topic = topic or f"/live_content/{self._me()}"
74
+ conn = http.client.HTTPSConnection("api.x.com", timeout=seconds + 6)
75
+ conn.request("GET", f"/live_pipeline/events?topic={topic}",
76
+ headers=self._s.headers("GET", "/live_pipeline/events"))
77
+ resp = conn.getresponse()
78
+ try:
79
+ conn.sock.settimeout(seconds)
80
+ sample = resp.read(300).decode("utf-8", "replace")
81
+ except Exception:
82
+ sample = "(connected, quiet window)"
83
+ conn.close()
84
+ return {"status": resp.status, "topic": topic, "sample": sample[:300]}
@@ -0,0 +1,102 @@
1
+ """Settings & preferences (reads + safe writes; blind writes need confirm=True)."""
2
+ from .. import errors as E
3
+ from ._base import BaseResource
4
+
5
+
6
+ class Settings(BaseResource):
7
+ def account(self):
8
+ """Txn-gated, alt host. Proven: test_account_settings.py"""
9
+ return self._s.rest_get("account/settings.json", {"include_mention_filter": "true"},
10
+ host="https://api.x.com", prefix="/1.1/")
11
+
12
+ def help_config(self):
13
+ """Proven: test_help_settings.py"""
14
+ return self._s.rest_get("help/settings.json", {})
15
+
16
+ def email_phone_info(self):
17
+ """PRIVATE account emails/phones — handle with care. Proven: test_email_phone_info.py"""
18
+ return self._s.rest_get("users/email_phone_info.json", {})
19
+
20
+ def saved_searches(self):
21
+ """Txn-gated, alive. Proven: test_saved_searches.py"""
22
+ return self._s.rest_get("saved_searches/list.json", {})
23
+
24
+ def alt_text_get(self):
25
+ """Proven: test_alt_text_preference.py"""
26
+ return self._s.gql_get("getAltTextPromptPreference", {},
27
+ with_features=False, with_toggles=False)
28
+
29
+ def alt_text_set(self, prompt_type="None", verify=True):
30
+ """'None' = app default (default-equivalent). Proven: test_alt_text_pair.py"""
31
+ resp = self._s.gql_post("updateAltTextPromptPreference", {"promptType": prompt_type})
32
+ E.guard(resp, "updateAltTextPromptPreference")
33
+ if verify:
34
+ back = self._s.gql_post("getAltTextPromptPreference", {})
35
+ if f'"{prompt_type}"' not in back.raw:
36
+ raise E.XVerifyError("Alt-text read-back mismatch.")
37
+ return resp
38
+
39
+ def creator_subscriptions(self, user_id=None):
40
+ """Proven: test_creator_subscriptions.py"""
41
+ return self._s.gql_get("UserCreatorSubscriptions",
42
+ {"userId": str(user_id or self._me()),
43
+ "includePromotedContent": False})
44
+
45
+ def creator_subscribers(self, user_id=None):
46
+ """Proven: test_creator_subscribers.py"""
47
+ return self._s.gql_get("UserCreatorSubscribers",
48
+ {"userId": str(user_id or self._me()),
49
+ "includePromotedContent": False})
50
+
51
+ def phone_state(self):
52
+ """Proven: test_profile_phone_state.py"""
53
+ return self._s.gql_get("ProfileUserPhoneState", {})
54
+
55
+ def multi_accounts(self):
56
+ """Proven: test_multi_accounts.py"""
57
+ return self._s.rest_get("account/multi/list.json", {})
58
+
59
+ def oauth_apps(self):
60
+ """Authorized apps. Proven: test_oauth_apps.py"""
61
+ return self._s.rest_get("oauth/list.json", {})
62
+
63
+ def rate_limits(self):
64
+ """Proven: test_rate_limits.py"""
65
+ return self._s.rest_get("application/rate_limit_status.json", {})
66
+
67
+ def client_education_flag(self, flag="NewUserPromptEducation"):
68
+ """Proven: test_client_education_flag.py"""
69
+ resp = self._s.gql_post("PutClientEducationFlag", {"flag": flag})
70
+ return E.guard(resp, "PutClientEducationFlag")
71
+
72
+ def phone_label_enable(self):
73
+ """Code 37 'Cannot find user phone' when no phone. Proven: test_verified_phone_pair.py"""
74
+ return self._s.gql_post("EnableVerifiedPhoneLabel", {})
75
+
76
+ def phone_label_disable(self):
77
+ """Proven: test_verified_phone_pair.py"""
78
+ return self._s.gql_post("DisableVerifiedPhoneLabel", {})
79
+
80
+ def data_saver_mode(self, device_id="Windows/Firefox"):
81
+ """Proven: test_data_saver_mode.py"""
82
+ return self._s.gql_get("DataSaverMode", {"device_id": device_id},
83
+ with_features=False, with_toggles=False)
84
+
85
+ def write_data_saver(self, data_saver_enabled, device_id=None, video_autoplay=None, confirm=False):
86
+ """NEVER auto-fire: current videoAutoplay is unreadable. Shape: test_write_datasaver_shape.py."""
87
+ if not confirm:
88
+ raise E.XError("write_data_saver() needs confirm=True (current value unreadable).")
89
+ v = {"dataSaverEnabled": bool(data_saver_enabled)}
90
+ if device_id is not None:
91
+ v["deviceId"] = device_id
92
+ if video_autoplay is not None:
93
+ v["videoAutoplay"] = video_autoplay
94
+ return self._s.gql_post("WriteDataSaverPreferences", v)
95
+
96
+ def write_audiospaces_sharing(self, user_id=None, sharing=None, confirm=False):
97
+ """NEVER auto-fire: no read API. Shape: test_sharing_audiospaces_shape.py."""
98
+ if not confirm:
99
+ raise E.XError("write_audiospaces_sharing() needs confirm=True (no read API to restore).")
100
+ return self._s.gql_post("SharingAudiospacesListeningDataWithFollowersUpdate", {
101
+ "userId": str(user_id or self._me()),
102
+ "sharingAudiospacesListeningDataWithFollowers": sharing})
@@ -0,0 +1,228 @@
1
+ """Timelines: home, user timelines, search, followers, pins + URT parsing helpers."""
2
+ from ._base import BaseResource
3
+
4
+
5
+ def extract_tweets(resp_or_data, limit=50):
6
+ """Pull [{rest_id, author, text, views, retweeted, favorited, counts}] out of any
7
+ timeline response (walks URT instructions). xapiweb helper."""
8
+ data = resp_or_data.data if hasattr(resp_or_data, "data") else resp_or_data
9
+ tweets, seen = [], set()
10
+
11
+ def walk(obj):
12
+ if len(tweets) >= limit:
13
+ return
14
+ if isinstance(obj, dict):
15
+ tr = obj.get("tweet_results")
16
+ if isinstance(tr, dict):
17
+ r = tr.get("result", {})
18
+ rid = r.get("rest_id") if isinstance(r, dict) else None
19
+ if rid and rid not in seen:
20
+ seen.add(rid)
21
+ leg = r.get("legacy", {}) or {}
22
+ author = "?"
23
+ try:
24
+ author = r["core"]["user_results"]["result"]["legacy"]["screen_name"]
25
+ except Exception:
26
+ try:
27
+ author = r["core"]["user_results"]["result"]["core"]["screen_name"]
28
+ except Exception:
29
+ pass
30
+ tweets.append({"rest_id": rid, "author": author,
31
+ "text": leg.get("full_text") or "",
32
+ "views": _views(r),
33
+ "retweeted": leg.get("retweeted"),
34
+ "favorited": leg.get("favorited"),
35
+ "retweet_count": leg.get("retweet_count"),
36
+ "favorite_count": leg.get("favorite_count")})
37
+ for v in obj.values():
38
+ walk(v)
39
+ elif isinstance(obj, list):
40
+ for v in obj:
41
+ walk(v)
42
+
43
+ walk(data.get("data", data) if isinstance(data, dict) else data)
44
+ return tweets
45
+
46
+
47
+ def _views(result):
48
+ """result.views.count -> int (or None). Lives at result level, not legacy."""
49
+ try:
50
+ return int((result.get("views") or {}).get("count"))
51
+ except (TypeError, ValueError):
52
+ return None
53
+
54
+
55
+ def cursors(resp_or_data):
56
+ """Return {'top': ..., 'bottom': ...} pagination cursors (may be None). xapiweb helper."""
57
+ data = resp_or_data.data if hasattr(resp_or_data, "data") else resp_or_data
58
+ out = {"top": None, "bottom": None}
59
+
60
+ def walk(obj):
61
+ if isinstance(obj, dict):
62
+ if obj.get("cursorType") in ("Top", "Bottom") and obj.get("value"):
63
+ out[obj["cursorType"].lower()] = obj["value"]
64
+ for v in obj.values():
65
+ walk(v)
66
+ elif isinstance(obj, list):
67
+ for v in obj:
68
+ walk(v)
69
+
70
+ walk(data)
71
+ return out
72
+
73
+
74
+ class Timelines(BaseResource):
75
+ def home(self, count=5, seen_ids=None):
76
+ """Home/For-you (POST). Proven: test_home_timeline.py"""
77
+ return self._s.gql_post("HomeTimeline", {
78
+ "count": count, "includePromotedContent": True,
79
+ "requestContext": "launch", "withCommunity": True,
80
+ "seenTweetIds": seen_ids or []}, with_features=True)
81
+
82
+ # ---- user timelines (GET) ----
83
+ def _user_tl(self, op, user_id, count, extra=None):
84
+ v = {"userId": str(user_id or self._me()), "count": count,
85
+ "includePromotedContent": False, "withVoice": True}
86
+ if extra:
87
+ v.update(extra)
88
+ return self._s.gql_get(op, v)
89
+
90
+ def user_tweets(self, user_id=None, count=5):
91
+ """Proven: test_user_tweets.py"""
92
+ return self._user_tl("UserTweets", user_id, count,
93
+ {"includePromotedContent": True})
94
+
95
+ def user_replies(self, user_id=None, count=5):
96
+ """Proven: test_user_replies.py"""
97
+ return self._s.gql_get("UserRepliesTimeline",
98
+ {"userId": str(user_id or self._me()), "count": count,
99
+ "includePromotedContent": False, "withVoice": True})
100
+
101
+ def user_media(self, user_id=None, count=5):
102
+ """Proven: test_user_media.py"""
103
+ return self._user_tl("UserMedia", user_id, count, {"withClientEventToken": False})
104
+
105
+ def user_likes(self, user_id=None, count=5):
106
+ """Proven: test_user_likes.py"""
107
+ return self._user_tl("Likes", user_id, count, {"withClientEventToken": False})
108
+
109
+ def user_reposts(self, user_id=None, count=5):
110
+ """Proven: test_user_reposts.py"""
111
+ return self._user_tl("UserRepostsTimeline", user_id, count)
112
+
113
+ def user_video(self, user_id=None, count=5):
114
+ """Proven: test_user_video_timeline.py"""
115
+ return self._user_tl("UserVideoTimeline", user_id, count, {"withClientEventToken": False})
116
+
117
+ def user_highlights(self, user_id=None, count=5):
118
+ """Proven: test_user_highlights.py"""
119
+ return self._user_tl("UserHighlightsTweets", user_id, count)
120
+
121
+ def user_articles(self, user_id=None, count=5):
122
+ """Proven: test_user_articles.py"""
123
+ return self._user_tl("UserArticlesTweets", user_id, count)
124
+
125
+ def user_photo(self, user_id=None, count=5):
126
+ """Proven: test_user_photo_timeline.py"""
127
+ return self._user_tl("UserPhotoTimeline", user_id, count)
128
+
129
+ def user_originals(self, user_id=None, count=5):
130
+ """Proven: test_user_originals_timeline.py"""
131
+ return self._s.gql_get("UserOriginalsTimeline",
132
+ {"userId": str(user_id or self._me()), "count": count,
133
+ "includePromotedContent": True,
134
+ "withQuickPromoteEligibilityTweetFields": True, "withVoice": True})
135
+
136
+ def user_tweets_and_replies(self, user_id=None, count=5):
137
+ """POST-only. Proven: test_user_tweets_and_replies.py"""
138
+ return self._s.gql_post("UserTweetsAndReplies", {
139
+ "userId": str(user_id or self._me()), "count": count,
140
+ "includePromotedContent": False, "withVoice": True}, with_features=True)
141
+
142
+ def user_super_follow(self, user_id=None, count=5):
143
+ """Proven: test_user_super_follow_tweets.py"""
144
+ return self._user_tl("UserSuperFollowTweets", user_id, count)
145
+
146
+ def user_promoted(self, user_id=None, count=5):
147
+ """Proven: test_user_promoted_tweets.py"""
148
+ return self._s.gql_get("UserPromotedTweets",
149
+ {"userId": str(user_id or self._me()), "count": count})
150
+
151
+ def user_promotable(self, user_id=None, count=5):
152
+ """Proven: test_user_promotable_tweets.py"""
153
+ return self._s.gql_get("UserPromotableTweets",
154
+ {"userId": str(user_id or self._me()), "count": count})
155
+
156
+ # ---- follow graphs (GraphQL) ----
157
+ def followers(self, user_id=None, count=5):
158
+ """POST-only (GET 404s). Proven: test_followers_gql.py"""
159
+ return self._s.gql_post("Followers", {
160
+ "userId": str(user_id or self._me()), "count": count,
161
+ "includePromotedContent": False}, with_features=True)
162
+
163
+ def following(self, user_id=None, count=5):
164
+ """Proven: test_following_gql.py"""
165
+ return self._s.gql_get("Following", {
166
+ "userId": str(user_id or self._me()), "count": count,
167
+ "includePromotedContent": False}, with_toggles=False)
168
+
169
+ def blue_verified_followers(self, user_id=None, count=5):
170
+ """Proven: test_blue_verified_followers.py"""
171
+ return self._s.gql_get("BlueVerifiedFollowers", {
172
+ "userId": str(user_id or self._me()), "count": count,
173
+ "includePromotedContent": False}, with_toggles=False)
174
+
175
+ def followers_you_know(self, user_id=None, count=5):
176
+ """Proven: test_followers_you_know.py"""
177
+ return self._s.gql_get("FollowersYouKnow", {
178
+ "userId": str(user_id or self._me()), "count": count,
179
+ "includePromotedContent": False})
180
+
181
+ # ---- search ----
182
+ def search(self, raw_query, count=5, product="Top"):
183
+ """POST-only. Proven: test_search_timeline.py"""
184
+ return self._s.gql_post("SearchTimeline", {
185
+ "rawQuery": raw_query, "count": count,
186
+ "querySource": "typed_query", "product": product}, with_features=True)
187
+
188
+ def list_search(self, list_id, raw_query, count=5):
189
+ """Proven: test_list_search.py"""
190
+ return self._s.gql_get("ListSearchTimeline",
191
+ {"listId": str(list_id), "rawQuery": raw_query, "count": count})
192
+
193
+ def communities_post_search(self, raw_query, count=5):
194
+ """Proven: test_communities_post_search.py"""
195
+ return self._s.gql_get("GlobalCommunitiesPostSearchTimeline",
196
+ {"rawQuery": raw_query, "count": count})
197
+
198
+ def communities_latest_search(self, raw_query, count=5):
199
+ """Proven: test_communities_latest_search.py"""
200
+ return self._s.gql_get("GlobalCommunitiesLatestPostSearchTimeline",
201
+ {"rawQuery": raw_query, "count": count})
202
+
203
+ # ---- misc timelines ----
204
+ def generic_by_id(self, timeline_id):
205
+ """Ids are opaque server handles; bogus id => routed 200 error. Proven: test_generic_timeline.py"""
206
+ return self._s.gql_get("GenericTimelineById", {"timelineId": str(timeline_id)},
207
+ with_features=False, with_toggles=False)
208
+
209
+ def moderated(self, root_tweet_id, count=5):
210
+ """Community moderation queue view. Proven: test_moderated_timeline.py"""
211
+ return self._s.gql_get("ModeratedTimeline", {
212
+ "rootTweetId": str(root_tweet_id), "count": count, "includePromotedContent": False})
213
+
214
+ def pinned(self, user_id=None):
215
+ """Profile pinned timelines. Proven: test_pinned_timelines.py"""
216
+ return self._s.gql_get("PinnedTimelines", {"userId": str(user_id or self._me())},
217
+ with_toggles=False)
218
+
219
+ def pinnable(self):
220
+ """Proven: test_pinnable_timelines.py"""
221
+ return self._s.gql_get("PinnableTimelines", {})
222
+
223
+ def profile_filter(self, user_id=None, bucket="posts"):
224
+ """BROKEN server-side (timeline.bucket 500s for every user, 2026-09-19).
225
+ Other buckets => 422. Proven: test_profile_filter.py"""
226
+ return self._s.gql_post("ProfileFilter", {
227
+ "userId": str(user_id or self._me()), "includePromotedContent": False,
228
+ "withVoice": True, "bucket": bucket})
@@ -0,0 +1,49 @@
1
+ """Trends, explore, search helpers, geo."""
2
+ from ._base import BaseResource
3
+
4
+
5
+ class Trends(BaseResource):
6
+ def available(self):
7
+ """Trend locations (woeids). Proven: test_trends_available.py"""
8
+ return self._s.rest_get("trends/available.json", {})
9
+
10
+ def place(self, woeid=1):
11
+ """Proven: test_trends_place.py"""
12
+ return self._s.rest_get("trends/place.json", {"id": woeid})
13
+
14
+ def history(self, trend_id):
15
+ """Proven: test_trend_history.py"""
16
+ return self._s.gql_get("TrendHistory", {"trendId": str(trend_id)})
17
+
18
+ def relevant_users(self, trend_id):
19
+ """Proven: test_trend_relevant_users.py"""
20
+ return self._s.gql_get("TrendRelevantUsers", {"trendId": str(trend_id)})
21
+
22
+ def sidebar(self):
23
+ """Explore sidebar. Proven: test_explore_sidebar.py"""
24
+ return self._s.gql_get("ExploreSidebar", {}, with_toggles=False)
25
+
26
+ def explore_page(self, candidate="news", tab="news"):
27
+ """Proven: test_explore_page.py"""
28
+ return self._s.gql_get("ExplorePage", {"candidateId": candidate, "tabId": tab})
29
+
30
+ def connect_tab(self, count=5):
31
+ """Proven: test_connect_tab.py"""
32
+ return self._s.gql_get("ConnectTabTimeline", {"count": count})
33
+
34
+ def creator_studio_tab(self):
35
+ """Proven: test_creator_studio_tab.py"""
36
+ return self._s.gql_get("CreatorStudioTabBarItemQuery", {},
37
+ with_features=False, with_toggles=False)
38
+
39
+ def finance_tags(self, query):
40
+ """Proven: test_finance_tags.py"""
41
+ return self._s.gql_get("FinanceSearchTags", {"query": query})
42
+
43
+ def geo_search(self, query):
44
+ """E.g. 'Colombo'. Proven: test_geo_search.py"""
45
+ return self._s.rest_get("geo/search.json", {"query": query})
46
+
47
+ def typeahead(self, q):
48
+ """Search typeahead. Proven: test_search_typeahead.py"""
49
+ return self._s.rest_get("search/typeahead.json", {"q": q, "src": "search_box"})