149 lines
3.9 KiB
Python
149 lines
3.9 KiB
Python
|
import requests
|
||
|
import sys
|
||
|
import os
|
||
|
import math
|
||
|
import time
|
||
|
import shelve
|
||
|
|
||
|
from requests.adapters import HTTPAdapter, Retry
|
||
|
|
||
|
from config import config
|
||
|
|
||
|
|
||
|
class DramaClient:
|
||
|
BASE_URL = "https://rdrama.net"
|
||
|
|
||
|
def __init__(self):
|
||
|
self.db = shelve.open(f"{config['data_dir']}/client_state.p", writeback=True)
|
||
|
self.db.setdefault("processed_replies", set())
|
||
|
|
||
|
self.session = requests.Session()
|
||
|
retries = Retry(
|
||
|
total=5, backoff_factor=5, status_forcelist=[500, 502, 503, 504, 521]
|
||
|
)
|
||
|
self.session.mount("https://", HTTPAdapter(max_retries=retries))
|
||
|
|
||
|
self.chud_phrase = self.get("/@me").get("chud_phrase", "")
|
||
|
|
||
|
def get(self, endpoint):
|
||
|
print("GET", endpoint)
|
||
|
time.sleep(5)
|
||
|
|
||
|
while True:
|
||
|
r = self.session.get(
|
||
|
f"{self.BASE_URL}{endpoint}",
|
||
|
headers={"Authorization": config["api_token"]},
|
||
|
)
|
||
|
|
||
|
if "502 Bad Gateway" in r.text:
|
||
|
print("Received 502")
|
||
|
time.sleep(10)
|
||
|
continue
|
||
|
|
||
|
break
|
||
|
|
||
|
# Return None for country club and chudrama posts.
|
||
|
if r.status_code == 403:
|
||
|
return None
|
||
|
|
||
|
if r.status_code != 200:
|
||
|
print("Error!", r, r.status_code, r.content)
|
||
|
sys.exit(1)
|
||
|
|
||
|
return r.json()
|
||
|
|
||
|
def post(self, endpoint, payload=None, files=[]):
|
||
|
print("POST", endpoint, f"Payload:\n{payload}")
|
||
|
time.sleep(5)
|
||
|
|
||
|
while True:
|
||
|
r = self.session.post(
|
||
|
f"{self.BASE_URL}{endpoint}",
|
||
|
payload,
|
||
|
headers={"Authorization": config["api_token"]},
|
||
|
files=files,
|
||
|
)
|
||
|
|
||
|
if "502 Bad Gateway" in r.text:
|
||
|
print("Received 502")
|
||
|
time.sleep(10)
|
||
|
continue
|
||
|
|
||
|
break
|
||
|
|
||
|
if r.status_code != 200:
|
||
|
print("Error!", r, r.status_code, r.content)
|
||
|
sys.exit(1)
|
||
|
|
||
|
return r.json()
|
||
|
|
||
|
def fetch_new_comments(self, limit=0):
|
||
|
comments = []
|
||
|
|
||
|
last_processed_id = self.db.get("last_processed_id", -1)
|
||
|
earliest_id = math.inf
|
||
|
page = 1
|
||
|
|
||
|
# Fetch comments until we find the last one processed.
|
||
|
while earliest_id > last_processed_id:
|
||
|
page_comments = self.fetch_page(page)
|
||
|
|
||
|
if len(page_comments) == 0:
|
||
|
break
|
||
|
|
||
|
earliest_id = min([c["id"] for c in page_comments])
|
||
|
comments += [c for c in page_comments if c["id"] > last_processed_id]
|
||
|
|
||
|
if limit > 0 and len(comments) >= limit:
|
||
|
break
|
||
|
|
||
|
page += 1
|
||
|
|
||
|
if not comments:
|
||
|
return []
|
||
|
|
||
|
self.db["last_processed_id"] = max(c["id"] for c in comments)
|
||
|
self.db.sync()
|
||
|
|
||
|
# New comments may have pushed others to page n+1 while fetching.
|
||
|
deduped_comments = {c["id"]: c for c in comments}.values()
|
||
|
|
||
|
# Oldest first.
|
||
|
comments.reverse()
|
||
|
|
||
|
return comments
|
||
|
|
||
|
def fetch_new_replies(self):
|
||
|
notifs = self.get("/unread")["data"]
|
||
|
notifs = [n for n in notifs if n["body"]]
|
||
|
return notifs
|
||
|
|
||
|
def fetch_page(self, page):
|
||
|
return self.get(f"/comments?page={page}")["data"]
|
||
|
|
||
|
def fetch_context(self, comment):
|
||
|
post = self.get(f"/post/{comment['post_id']}")
|
||
|
|
||
|
if not post:
|
||
|
return None, None
|
||
|
|
||
|
comments = [comment]
|
||
|
while parent_id := comments[-1].get("parent_comment_id", None):
|
||
|
parent = self.get(f"/comment/{parent_id}")
|
||
|
comments.append(parent)
|
||
|
|
||
|
comments.reverse()
|
||
|
|
||
|
return post, comments
|
||
|
|
||
|
def reply(self, body, comment):
|
||
|
if self.chud_phrase and self.chud_phrase not in body:
|
||
|
body += f"\n{self.chud_phrase}"
|
||
|
|
||
|
payload = {
|
||
|
"parent_fullname": f"c_{comment['id']}",
|
||
|
"body": body,
|
||
|
}
|
||
|
|
||
|
self.post("/comment", payload)
|