|
| 1 | +# Xquik X/Twitter Retriever |
| 2 | +# |
| 3 | +# Searches X (Twitter) for real-time perspectives, dev discussions, |
| 4 | +# product feedback, breaking news, and expert opinions. |
| 5 | +# $0.00015 per tweet — 33x cheaper than the official X API. |
| 6 | + |
| 7 | +import json |
| 8 | +import os |
| 9 | +import urllib.parse |
| 10 | +import urllib.request |
| 11 | + |
| 12 | + |
| 13 | +class XquikSearch: |
| 14 | + """ |
| 15 | + Xquik X/Twitter search retriever. |
| 16 | +
|
| 17 | + Searches tweets via the Xquik REST API and returns results in the |
| 18 | + standard {title, href, body} format used by all GPT Researcher retrievers. |
| 19 | +
|
| 20 | + Set XQUIK_API_KEY in your environment. Get one at https://xquik.com |
| 21 | + """ |
| 22 | + |
| 23 | + def __init__(self, query, query_domains=None, **kwargs): |
| 24 | + self.query = query |
| 25 | + self.query_domains = query_domains |
| 26 | + self.api_key = self.get_api_key() |
| 27 | + |
| 28 | + def get_api_key(self): |
| 29 | + try: |
| 30 | + api_key = os.environ["XQUIK_API_KEY"] |
| 31 | + except KeyError: |
| 32 | + raise Exception( |
| 33 | + "Xquik API key not found. Please set the XQUIK_API_KEY " |
| 34 | + "environment variable. Get a key at https://xquik.com" |
| 35 | + ) |
| 36 | + return api_key |
| 37 | + |
| 38 | + def search(self, max_results=10): |
| 39 | + """ |
| 40 | + Search X/Twitter via Xquik API. |
| 41 | +
|
| 42 | + Returns: |
| 43 | + list: Search results as [{title, href, body}, ...] |
| 44 | + """ |
| 45 | + print(f"Searching X/Twitter with query: {self.query}...") |
| 46 | + |
| 47 | + try: |
| 48 | + results = self._search_tweets(max_results) |
| 49 | + return results |
| 50 | + except Exception as e: |
| 51 | + print(f"Error: {e}. Failed fetching X/Twitter sources. Resulting in empty response.") |
| 52 | + return [] |
| 53 | + |
| 54 | + def _search_tweets(self, max_results): |
| 55 | + params = urllib.parse.urlencode({ |
| 56 | + "q": self.query, |
| 57 | + "limit": min(max_results, 200), |
| 58 | + "queryType": "Top", |
| 59 | + }) |
| 60 | + url = f"https://xquik.com/api/v1/x/tweets/search?{params}" |
| 61 | + |
| 62 | + req = urllib.request.Request(url, headers={ |
| 63 | + "X-API-Key": self.api_key, |
| 64 | + "Accept": "application/json", |
| 65 | + "User-Agent": "gpt-researcher/1.0", |
| 66 | + }) |
| 67 | + |
| 68 | + with urllib.request.urlopen(req, timeout=15) as resp: |
| 69 | + data = json.loads(resp.read().decode("utf-8")) |
| 70 | + |
| 71 | + tweets = data.get("tweets", []) |
| 72 | + search_results = [] |
| 73 | + |
| 74 | + for tweet in tweets[:max_results]: |
| 75 | + author = tweet.get("author", {}) |
| 76 | + username = author.get("username", "unknown") |
| 77 | + text = tweet.get("text", "") |
| 78 | + tweet_id = tweet.get("id", "") |
| 79 | + |
| 80 | + likes = tweet.get("likeCount", 0) |
| 81 | + retweets = tweet.get("retweetCount", 0) |
| 82 | + views = tweet.get("viewCount", 0) |
| 83 | + |
| 84 | + engagement = f"{likes} likes, {retweets} RTs" |
| 85 | + if views: |
| 86 | + engagement += f", {views} views" |
| 87 | + |
| 88 | + search_results.append({ |
| 89 | + "title": f"@{username}: {text[:120]}{'...' if len(text) > 120 else ''}", |
| 90 | + "href": f"https://x.com/{username}/status/{tweet_id}", |
| 91 | + "body": f"{text}\n\n[{engagement}]", |
| 92 | + }) |
| 93 | + |
| 94 | + return search_results |
0 commit comments