File size: 5,717 Bytes
32c065c
0ba67b4
32c065c
0ba67b4
 
32c065c
0ba67b4
bcf906f
0ba67b4
 
bcf906f
32c065c
 
0ba67b4
 
32c065c
0ba67b4
 
 
32c065c
0ba67b4
32c065c
 
0ba67b4
 
 
 
 
 
32c065c
0ba67b4
 
32c065c
 
 
bcf906f
32c065c
 
bcf906f
 
32c065c
bcf906f
 
32c065c
bcf906f
32c065c
bcf906f
32c065c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0ba67b4
32c065c
0ba67b4
32c065c
bcf906f
 
0ba67b4
32c065c
 
 
 
 
 
 
bcf906f
0ba67b4
 
32c065c
bcf906f
 
 
0ba67b4
32c065c
 
 
 
 
 
bcf906f
32c065c
 
0ba67b4
32c065c
 
0ba67b4
32c065c
 
 
 
 
 
 
0ba67b4
 
32c065c
bcf906f
32c065c
 
0ba67b4
32c065c
bcf906f
32c065c
 
0ba67b4
 
 
32c065c
 
 
0ba67b4
32c065c
bcf906f
32c065c
 
bcf906f
0ba67b4
32c065c
 
 
0ba67b4
 
 
 
 
32c065c
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import os
import time
import random
import requests
import logging
from typing import List, Dict, Optional

# Set up logging
logger = logging.getLogger(__name__)

# Simple User-Agent header – change to your own
HEADERS = {"User-Agent": "AltomicAi/3.0 (by /u/yourusername)"}


class RateLimiter:
    """Simple rate limiter to enforce a given number of requests per second."""
    def __init__(self, requests_per_second: float):
        self.interval = 1.0 / requests_per_second if requests_per_second > 0 else 0
        self.last_request = 0.0

    def wait(self):
        if self.interval <= 0:
            return
        now = time.time()
        elapsed = now - self.last_request
        if elapsed < self.interval:
            time.sleep(self.interval - elapsed)
        self.last_request = time.time()


class RedditClient:
    BASE_URL = "https://www.reddit.com"

    def __init__(self, requests_per_second: float = 0.3):
        self.rate_limiter = RateLimiter(requests_per_second)

        # Load proxies from environment variable (Hugging Face secret)
        proxy_list_str = os.environ.get("PROXY_LIST")
        print(f"[DEBUG] PROXY_LIST env var: {'SET (length ' + str(len(proxy_list_str)) + ')' if proxy_list_str else 'NOT SET'}", flush=True)

        self.proxy_list = self._parse_proxies(proxy_list_str)
        print(f"[DEBUG] Loaded {len(self.proxy_list)} proxies", flush=True)

        if self.proxy_list:
            print(f"[DEBUG] First proxy: {self.proxy_list[0] if self.proxy_list else 'None'}", flush=True)
        else:
            print("[WARNING] No proxies loaded. Requests will be made directly (likely blocked).", flush=True)

    def _parse_proxies(self, content: Optional[str]) -> List[Dict[str, str]]:
        """Convert proxy lines (http://user:pass@ip:port) to requests proxy dicts."""
        proxies = []
        if not content:
            return proxies
        for line in content.strip().splitlines():
            line = line.strip()
            if line and not line.startswith('#'):
                # Ensure the line starts with http:// or https://
                if not line.startswith('http'):
                    line = f"http://{line}"
                proxies.append({"http": line, "https": line})
        return proxies

    def _get_random_proxy(self) -> Optional[Dict[str, str]]:
        if not self.proxy_list:
            return None
        return random.choice(self.proxy_list)

    def _request(self, url: str, params: dict = None) -> dict:
        """Make a request with rate limiting and proxy rotation."""
        self.rate_limiter.wait()
        proxy = self._get_random_proxy()
        print(f"[DEBUG] Requesting {url} with proxy: {proxy if proxy else 'DIRECT'}", flush=True)

        try:
            resp = requests.get(
                url,
                headers=HEADERS,
                params=params,
                proxies=proxy,
                timeout=15
            )
            print(f"[DEBUG] Response status: {resp.status_code}", flush=True)
            resp.raise_for_status()
            return resp.json()
        except requests.exceptions.RequestException as e:
            print(f"[ERROR] Reddit request failed: {e}", flush=True)
            if hasattr(e, 'response') and e.response is not None:
                print(f"[ERROR] Response text: {e.response.text[:500]}", flush=True)
            return {}

    def get_top_posts(self, subreddit: str, limit: int = 30, time_filter: str = "day") -> List[dict]:
        """
        Fetch top posts from a subreddit, handling pagination up to `limit` posts.
        Returns a list of post dicts.
        """
        print(f"[DEBUG] Fetching top posts from r/{subreddit}, limit={limit}, time_filter={time_filter}", flush=True)
        all_posts = []
        after = None
        per_page = min(limit, 100)
        remaining = limit

        while remaining > 0:
            params = {
                "limit": min(remaining, per_page),
                "t": time_filter,
            }
            if after:
                params["after"] = after

            url = f"{self.BASE_URL}/r/{subreddit}/top.json"
            data = self._request(url, params)
            if not data:
                print(f"[WARNING] No data returned from Reddit for {url}", flush=True)
                break

            children = data.get("data", {}).get("children", [])
            if not children:
                print(f"[INFO] No children in response for {subreddit}", flush=True)
                break

            for child in children:
                all_posts.append(child["data"])
                remaining -= 1
                if remaining == 0:
                    break

            after = data.get("data", {}).get("after")
            if not after:
                print(f"[INFO] No 'after' token, pagination finished", flush=True)
                break

        print(f"[DEBUG] Fetched {len(all_posts)} posts from r/{subreddit}", flush=True)
        return all_posts

    def get_post_comments(self, post_id: str, limit: int = 20) -> List[dict]:
        """Fetch top-level comments for a post."""
        url = f"{self.BASE_URL}/comments/{post_id}.json"
        data = self._request(url)
        if not data or not isinstance(data, list) or len(data) < 2:
            return []
        comments_data = data[1].get("data", {}).get("children", [])
        comments = [c["data"] for c in comments_data if c["kind"] == "t1"]
        return comments[:limit]

    def get_subreddit_info(self, subreddit: str) -> dict:
        """Fetch subreddit metadata."""
        url = f"{self.BASE_URL}/r/{subreddit}/about.json"
        data = self._request(url)
        return data.get("data", {}) if data else {}