Blame


1 ecdd11be 2024-09-11 continue #!/usr/bin/env python3
2 1719b036 2024-09-03 continue from json import loads as json_loads
3 29500f51 2024-09-20 continue from mimetypes import guess_type
4 d76a52db 2024-09-17 continue from os import environ
5 1719b036 2024-09-03 continue from pathlib import Path
6 4acbf994 2024-09-25 continue from sys import stdout, stderr
7 577b12da 2024-09-24 continue from sqlite3 import connect as sqlite3_connect
8 4acbf994 2024-09-25 continue from time import clock_gettime, CLOCK_MONOTONIC
9 15efabff 2024-09-13 continue from urllib.error import HTTPError, URLError
10 e03b8dd3 2024-09-25 continue from urllib.parse import urlencode, urlunsplit, urljoin, urlsplit, parse_qsl, unquote, quote
11 1719b036 2024-09-03 continue from urllib.request import urlopen
12 a7cabe95 2024-09-04 continue from html.parser import HTMLParser
13 1719b036 2024-09-03 continue
14 1719b036 2024-09-03 continue
15 a7cabe95 2024-09-04 continue class _BaseTag:
16 a7cabe95 2024-09-04 continue def __init__(self, tag, attrs):
17 a7cabe95 2024-09-04 continue self.tag = tag
18 a7cabe95 2024-09-04 continue
19 a7cabe95 2024-09-04 continue def on_data(self, data):
20 e03b8dd3 2024-09-25 continue raise AssertionError("The method must be overridden")
21 a7cabe95 2024-09-04 continue
22 a7cabe95 2024-09-04 continue def flush(self):
23 e03b8dd3 2024-09-25 continue raise AssertionError("The method must be overridden")
24 a7cabe95 2024-09-04 continue
25 a7cabe95 2024-09-04 continue
26 a7cabe95 2024-09-04 continue class ParagraphTag(_BaseTag):
27 a7cabe95 2024-09-04 continue def __init__(self, tag, attrs):
28 a7cabe95 2024-09-04 continue super().__init__(tag, attrs)
29 a7cabe95 2024-09-04 continue self.content = []
30 1be7bb9a 2024-09-05 continue self.footer = []
31 a7cabe95 2024-09-04 continue
32 a7cabe95 2024-09-04 continue def on_data(self, data):
33 a7cabe95 2024-09-04 continue self.content.append(data.strip())
34 a7cabe95 2024-09-04 continue
35 a7cabe95 2024-09-04 continue def flush(self):
36 f6854b29 2024-10-05 continue result = " ".join(" ".join(data.split()) for data in self.content if data)
37 1be7bb9a 2024-09-05 continue footer = self.footer
38 a7cabe95 2024-09-04 continue self.content = []
39 1be7bb9a 2024-09-05 continue self.footer = []
40 f6854b29 2024-10-05 continue return "\n".join([result] + footer) + "\n" if result else ""
41 c093e3cd 2024-10-13 continue
42 a7cabe95 2024-09-04 continue
43 a7cabe95 2024-09-04 continue class LinkTag(_BaseTag):
44 1be7bb9a 2024-09-05 continue def __init__(self, href, paragraph, tag, attrs):
45 a7cabe95 2024-09-04 continue super().__init__(tag, attrs)
46 1be7bb9a 2024-09-05 continue self.href = href
47 1be7bb9a 2024-09-05 continue self.paragraph = paragraph
48 a7cabe95 2024-09-04 continue self.content = []
49 a7cabe95 2024-09-04 continue
50 a7cabe95 2024-09-04 continue def on_data(self, data):
51 1be7bb9a 2024-09-05 continue if not self.content:
52 1f653a98 2024-09-05 continue self.paragraph.on_data("↳")
53 1be7bb9a 2024-09-05 continue self.paragraph.on_data(data)
54 a7cabe95 2024-09-04 continue self.content.append(data.strip())
55 a7cabe95 2024-09-04 continue
56 a7cabe95 2024-09-04 continue def flush(self):
57 1be7bb9a 2024-09-05 continue text = " ".join(" ".join(data.split()) for data in self.content if data)
58 1be7bb9a 2024-09-05 continue self.paragraph.footer.append(f"=> {self.href} {text}")
59 1be7bb9a 2024-09-05 continue return ""
60 a7cabe95 2024-09-04 continue
61 a7cabe95 2024-09-04 continue
62 280fbeb1 2024-10-11 continue class LiItemTag(ParagraphTag):
63 a7cabe95 2024-09-04 continue def flush(self):
64 a7cabe95 2024-09-04 continue content = super().flush()
65 a7cabe95 2024-09-04 continue return f"* {content}" if content else ""
66 a7cabe95 2024-09-04 continue
67 a7cabe95 2024-09-04 continue
68 a7cabe95 2024-09-04 continue class QuoteTag(ParagraphTag):
69 a7cabe95 2024-09-04 continue def flush(self):
70 a7cabe95 2024-09-04 continue content = super().flush()
71 a7cabe95 2024-09-04 continue return f"> {content}" if content else ""
72 a7cabe95 2024-09-04 continue
73 a7cabe95 2024-09-04 continue
74 ae087ef0 2024-09-04 continue class HeaderTag(ParagraphTag):
75 ae087ef0 2024-09-04 continue def flush(self):
76 ae087ef0 2024-09-04 continue content = super().flush()
77 ae087ef0 2024-09-04 continue if not content:
78 ae087ef0 2024-09-04 continue return ""
79 ae087ef0 2024-09-04 continue return f"{'#' * int(self.tag[1:])} {content}"
80 ae087ef0 2024-09-04 continue
81 ae087ef0 2024-09-04 continue
82 ae087ef0 2024-09-04 continue class PreformattedTag(_BaseTag):
83 ae087ef0 2024-09-04 continue def __init__(self, tag, attrs):
84 ae087ef0 2024-09-04 continue super().__init__(tag, attrs)
85 ae087ef0 2024-09-04 continue self.content = ""
86 ae087ef0 2024-09-04 continue
87 ae087ef0 2024-09-04 continue def on_data(self, data):
88 ae087ef0 2024-09-04 continue self.content += data
89 ae087ef0 2024-09-04 continue
90 ae087ef0 2024-09-04 continue def flush(self):
91 f6854b29 2024-10-05 continue result = self.content
92 ae087ef0 2024-09-04 continue self.content = ""
93 f6854b29 2024-10-05 continue return f"```\n{result}\n```\n" if result else ""
94 ae087ef0 2024-09-04 continue
95 ae087ef0 2024-09-04 continue
96 a7cabe95 2024-09-04 continue class HtmlToGmi(HTMLParser):
97 29500f51 2024-09-20 continue def __init__(self, base_url, fn_media_url):
98 a7cabe95 2024-09-04 continue super().__init__()
99 a7cabe95 2024-09-04 continue self.gmi_text = []
100 a7cabe95 2024-09-04 continue self.stack = []
101 1f653a98 2024-09-05 continue self.base_url = base_url
102 29500f51 2024-09-20 continue self.fn_media_url = fn_media_url
103 a7cabe95 2024-09-04 continue
104 f6854b29 2024-10-05 continue def feed(self, data):
105 f6854b29 2024-10-05 continue super().feed(data)
106 a7cabe95 2024-09-04 continue while self.stack:
107 a7cabe95 2024-09-04 continue self.gmi_text.append(self.stack.pop().flush())
108 a7cabe95 2024-09-04 continue return "\n".join(gmi_text for gmi_text in self.gmi_text if gmi_text)
109 a7cabe95 2024-09-04 continue
110 a7cabe95 2024-09-04 continue def handle_starttag(self, tag, attrs):
111 1b9c1500 2024-09-05 continue def _push(elem):
112 1b9c1500 2024-09-05 continue if self.stack:
113 1b9c1500 2024-09-05 continue self.gmi_text.append(self.stack[-1].flush())
114 1b9c1500 2024-09-05 continue self.stack.append(elem)
115 a7cabe95 2024-09-04 continue
116 1b9c1500 2024-09-05 continue if tag == "p":
117 1b9c1500 2024-09-05 continue _push(ParagraphTag(tag, attrs))
118 1b9c1500 2024-09-05 continue elif tag == "pre":
119 1b9c1500 2024-09-05 continue _push(PreformattedTag(tag, attrs))
120 ae087ef0 2024-09-04 continue elif tag in {"h1", "h2", "h3", "h4", "h5", "h6"}:
121 1b9c1500 2024-09-05 continue _push(HeaderTag(tag, attrs))
122 ae087ef0 2024-09-04 continue elif tag in {"li", "dt"}:
123 280fbeb1 2024-10-11 continue _push(LiItemTag(tag, attrs))
124 ae087ef0 2024-09-04 continue elif tag in {"blockquote", "q"}:
125 1b9c1500 2024-09-05 continue _push(QuoteTag(tag, attrs))
126 a7cabe95 2024-09-04 continue elif tag == "a":
127 4e7950e1 2024-09-30 continue href = dict(attrs).get("href")
128 1be7bb9a 2024-09-05 continue if href:
129 1f653a98 2024-09-05 continue self.stack.append(LinkTag(urljoin(self.base_url, href), self._get_current_paragraph(), tag, attrs))
130 a7cabe95 2024-09-04 continue elif tag == "img":
131 4e7950e1 2024-09-30 continue img = dict(attrs)
132 1b9c1500 2024-09-05 continue title = img.get("title") or ""
133 1b9c1500 2024-09-05 continue if img.get("class") == "emu" and title and self.stack:
134 1b9c1500 2024-09-05 continue self.stack[-1].on_data(title)
135 1b9c1500 2024-09-05 continue else:
136 1b9c1500 2024-09-05 continue src = img.get("src")
137 1b9c1500 2024-09-05 continue if src:
138 280fbeb1 2024-10-11 continue http_img_url = urljoin(self.base_url, src)
139 280fbeb1 2024-10-11 continue mime, _ = guess_type(http_img_url)
140 280fbeb1 2024-10-11 continue img_url = self.fn_media_url(mime, http_img_url)
141 280fbeb1 2024-10-11 continue self.gmi_text.append(f"=> {img_url} {title or http_img_url}")
142 5c749e57 2024-09-05 continue elif tag == "br":
143 5c749e57 2024-09-05 continue if self.stack:
144 5c749e57 2024-09-05 continue self.gmi_text.append(self.stack[-1].flush())
145 a7cabe95 2024-09-04 continue
146 a7cabe95 2024-09-04 continue def handle_data(self, data):
147 a7cabe95 2024-09-04 continue if not self.stack:
148 1b9c1500 2024-09-05 continue self.stack.append(ParagraphTag("p", []))
149 a7cabe95 2024-09-04 continue self.stack[-1].on_data(data)
150 a7cabe95 2024-09-04 continue
151 a7cabe95 2024-09-04 continue def handle_endtag(self, tag):
152 1b9c1500 2024-09-05 continue if self.stack and tag == self.stack[-1].tag:
153 1b9c1500 2024-09-05 continue self.gmi_text.append(self.stack.pop().flush())
154 11eb9ffa 2024-09-05 continue
155 1be7bb9a 2024-09-05 continue def _get_current_paragraph(self):
156 1be7bb9a 2024-09-05 continue for elem in reversed(self.stack):
157 1be7bb9a 2024-09-05 continue if isinstance(elem, ParagraphTag):
158 1be7bb9a 2024-09-05 continue return elem
159 a7cabe95 2024-09-04 continue
160 1be7bb9a 2024-09-05 continue self.stack = [ParagraphTag("p", [])] + self.stack
161 1be7bb9a 2024-09-05 continue return self.stack[0]
162 1be7bb9a 2024-09-05 continue
163 1be7bb9a 2024-09-05 continue
164 e03b8dd3 2024-09-25 continue class LonkUrl:
165 e03b8dd3 2024-09-25 continue def __init__(self, raw_url):
166 e03b8dd3 2024-09-25 continue self._splitted_url = urlsplit(raw_url)
167 e03b8dd3 2024-09-25 continue self.splitted_path = [part for part in self._splitted_url.path.split("/") if part]
168 e03b8dd3 2024-09-25 continue self.page = self.splitted_path[-1]
169 e03b8dd3 2024-09-25 continue self._base_path = []
170 e03b8dd3 2024-09-25 continue for path_part in self.splitted_path:
171 e03b8dd3 2024-09-25 continue self._base_path.append(path_part)
172 e03b8dd3 2024-09-25 continue if path_part == "lonk":
173 e03b8dd3 2024-09-25 continue break
174 e03b8dd3 2024-09-25 continue
175 e03b8dd3 2024-09-25 continue def build(self, page, query=""):
176 e03b8dd3 2024-09-25 continue page = page if isinstance(page, list) else [page]
177 e03b8dd3 2024-09-25 continue return urlunsplit(
178 e03b8dd3 2024-09-25 continue ("gemini", self._splitted_url.netloc, "/".join(self._base_path + page), query, "")
179 e03b8dd3 2024-09-25 continue )
180 e03b8dd3 2024-09-25 continue
181 fafb2ce7 2024-10-02 continue def media(self, mime, url):
182 fafb2ce7 2024-10-02 continue return self.build("proxy", urlencode({"m": mime, "u": url})) if mime else url
183 fafb2ce7 2024-10-02 continue
184 e03b8dd3 2024-09-25 continue @property
185 e03b8dd3 2024-09-25 continue def query(self):
186 e03b8dd3 2024-09-25 continue return self._splitted_url.query
187 09255143 2024-09-25 continue
188 09255143 2024-09-25 continue
189 09255143 2024-09-25 continue class HonkUrl:
190 09255143 2024-09-25 continue def __init__(self, raw_url, token):
191 09255143 2024-09-25 continue self._splitted_url = urlsplit(raw_url)
192 09255143 2024-09-25 continue self._token = token
193 e03b8dd3 2024-09-25 continue
194 056d3709 2024-10-10 continue def build(self, scheme=None, netloc=None, path="", query="", fragment=""):
195 09255143 2024-09-25 continue return urlunsplit(
196 09255143 2024-09-25 continue (
197 09255143 2024-09-25 continue scheme or self._splitted_url.scheme,
198 09255143 2024-09-25 continue netloc or self._splitted_url.netloc,
199 09255143 2024-09-25 continue path,
200 09255143 2024-09-25 continue query,
201 09255143 2024-09-25 continue fragment,
202 09255143 2024-09-25 continue )
203 09255143 2024-09-25 continue )
204 e03b8dd3 2024-09-25 continue
205 056d3709 2024-10-10 continue def get(self, action, answer_is_json=True, **kwargs):
206 c093e3cd 2024-10-13 continue start_time = clock_gettime(CLOCK_MONOTONIC)
207 c093e3cd 2024-10-13 continue try:
208 c093e3cd 2024-10-13 continue query = {**{"action": action, "token": self._token}, **kwargs}
209 c093e3cd 2024-10-13 continue with urlopen(self.build(path="api", query=urlencode(query)), timeout=45) as response:
210 c093e3cd 2024-10-13 continue answer = response.read().decode("utf8")
211 c093e3cd 2024-10-13 continue return json_loads(answer) if answer_is_json else answer
212 c093e3cd 2024-10-13 continue finally:
213 c093e3cd 2024-10-13 continue stderr.write(f"GET {action} {kwargs}|{clock_gettime(CLOCK_MONOTONIC) - start_time:.3f}sec.\n")
214 09255143 2024-09-25 continue
215 09255143 2024-09-25 continue
216 280fbeb1 2024-10-11 continue def db_create_schema(db_con):
217 4acbf994 2024-09-25 continue db_con.execute(
218 4acbf994 2024-09-25 continue """
219 577b12da 2024-09-24 continue CREATE TABLE
220 577b12da 2024-09-24 continue client (
221 c093e3cd 2024-10-13 continue cert_hash TEXT PRIMARY KEY,
222 577b12da 2024-09-24 continue honk_url TEXT NOT NULL,
223 577b12da 2024-09-24 continue token TEXT NOT NULL
224 0fc41442 2024-09-25 continue )
225 0fc41442 2024-09-25 continue """
226 0fc41442 2024-09-25 continue )
227 0fc41442 2024-09-25 continue
228 577b12da 2024-09-24 continue
229 4e7950e1 2024-09-30 continue def db_connect():
230 577b12da 2024-09-24 continue db_file_path = Path(__file__).parent / ".local" / "db"
231 577b12da 2024-09-24 continue db_file_path.parent.mkdir(parents=True, exist_ok=True)
232 577b12da 2024-09-24 continue db_exist = db_file_path.exists()
233 577b12da 2024-09-24 continue db_con = sqlite3_connect(db_file_path)
234 577b12da 2024-09-24 continue if not db_exist:
235 e03b8dd3 2024-09-25 continue with db_con:
236 280fbeb1 2024-10-11 continue db_create_schema(db_con)
237 577b12da 2024-09-24 continue return db_con
238 280fbeb1 2024-10-11 continue
239 280fbeb1 2024-10-11 continue
240 280fbeb1 2024-10-11 continue def print_header(page_name):
241 280fbeb1 2024-10-11 continue print("20 text/gemini\r")
242 280fbeb1 2024-10-11 continue print(f"# 𝓗 onk: {page_name}\r")
243 280fbeb1 2024-10-11 continue print("\r")
244 280fbeb1 2024-10-11 continue
245 280fbeb1 2024-10-11 continue
246 280fbeb1 2024-10-11 continue def print_menu(lonk_url, honk_url, gethonks_answer=None):
247 c8840e58 2024-10-13 continue print("## 📝 Menu\r")
248 280fbeb1 2024-10-11 continue print(f"=> {lonk_url.build('newhonk')} new honk\r")
249 280fbeb1 2024-10-11 continue print(f"=> {lonk_url.build([])} lonk home\r")
250 280fbeb1 2024-10-11 continue
251 280fbeb1 2024-10-11 continue if gethonks_answer:
252 280fbeb1 2024-10-11 continue line = f"=> {lonk_url.build('atme')} @me"
253 280fbeb1 2024-10-11 continue if gethonks_answer["mecount"]:
254 280fbeb1 2024-10-11 continue line += f' ({gethonks_answer["mecount"]})'
255 280fbeb1 2024-10-11 continue print(line + "\r")
256 280fbeb1 2024-10-11 continue
257 280fbeb1 2024-10-11 continue line = f"=> {honk_url.build(path='chatter')} chatter"
258 280fbeb1 2024-10-11 continue if gethonks_answer["chatcount"]:
259 280fbeb1 2024-10-11 continue line += f' ({gethonks_answer["chatcount"]})'
260 280fbeb1 2024-10-11 continue print(line + "\r")
261 280fbeb1 2024-10-11 continue
262 280fbeb1 2024-10-11 continue print(f"=> {lonk_url.build('search')} search\r")
263 280fbeb1 2024-10-11 continue print(f"=> {lonk_url.build('longago')} long ago\r")
264 280fbeb1 2024-10-11 continue print(f"=> {lonk_url.build('myhonks')} my honks\r")
265 280fbeb1 2024-10-11 continue print(f"=> {lonk_url.build('gethonkers')} honkers\r")
266 280fbeb1 2024-10-11 continue print(f"=> {lonk_url.build('addhonker')} add new honker\r")
267 280fbeb1 2024-10-11 continue
268 280fbeb1 2024-10-11 continue
269 280fbeb1 2024-10-11 continue def print_gethonks(gethonks_answer, lonk_url, honk_url):
270 280fbeb1 2024-10-11 continue print_menu(lonk_url, honk_url, gethonks_answer)
271 280fbeb1 2024-10-11 continue print("\r")
272 280fbeb1 2024-10-11 continue
273 280fbeb1 2024-10-11 continue for honk in gethonks_answer.get("honks") or []:
274 280fbeb1 2024-10-11 continue convoy = honk["Convoy"]
275 280fbeb1 2024-10-11 continue re_url = honk.get("RID")
276 280fbeb1 2024-10-11 continue oondle = honk.get("Oondle")
277 280fbeb1 2024-10-11 continue from_ = f'{oondle} (🔁 {honk["Handle"]})' if oondle else f'{honk["Handle"]}'
278 280fbeb1 2024-10-11 continue lines = [
279 280fbeb1 2024-10-11 continue f'##{"# ↱" if re_url else ""} From {from_} {honk["Date"]}',
280 280fbeb1 2024-10-11 continue f'=> {lonk_url.build("convoy", urlencode({"c": convoy}))} Convoy {convoy}',
281 280fbeb1 2024-10-11 continue f'=> {honk["XID"]}',
282 280fbeb1 2024-10-11 continue ]
283 280fbeb1 2024-10-11 continue if re_url:
284 280fbeb1 2024-10-11 continue lines.append(f'=> {re_url} Re: {re_url}')
285 280fbeb1 2024-10-11 continue lines.append("")
286 280fbeb1 2024-10-11 continue lines.append(HtmlToGmi(honk_url.build(), lonk_url.media).feed(honk["HTML"]))
287 280fbeb1 2024-10-11 continue for donk in honk.get("Donks") or []:
288 ff1d2e46 2024-10-21 continue if donk.get("XID"):
289 ff1d2e46 2024-10-21 continue donk_url = honk_url.build(path=f'/d/{donk["XID"]}')
290 ff1d2e46 2024-10-21 continue else:
291 ff1d2e46 2024-10-21 continue donk_url = urljoin(honk["XID"], donk["URL"])
292 ff1d2e46 2024-10-21 continue donk_mime = donk["Media"]
293 280fbeb1 2024-10-11 continue lines.append(f'=> {lonk_url.media(donk_mime, donk_url)} {donk_url}')
294 ff1d2e46 2024-10-21 continue donk_text = donk.get("Desc") or donk.get("Name") or None
295 280fbeb1 2024-10-11 continue if donk_text:
296 280fbeb1 2024-10-11 continue lines.append(donk_text)
297 280fbeb1 2024-10-11 continue lines.append("")
298 280fbeb1 2024-10-11 continue if honk.get("Public"):
299 280fbeb1 2024-10-11 continue lines.append(f'=> {lonk_url.build("bonk", urlencode({"w": honk["XID"]}))} ↺ bonk')
300 280fbeb1 2024-10-11 continue honk_back_url = lonk_url.build(
301 280fbeb1 2024-10-11 continue [
302 280fbeb1 2024-10-11 continue quote(honk["Handles"] or " ", safe=""),
303 280fbeb1 2024-10-11 continue quote(honk["XID"], safe=""),
304 280fbeb1 2024-10-11 continue "honkback",
305 280fbeb1 2024-10-11 continue ]
306 280fbeb1 2024-10-11 continue )
307 280fbeb1 2024-10-11 continue lines.append(f'=> {honk_back_url} ↱ honk back')
308 280fbeb1 2024-10-11 continue for xonker in (honk.get("Honker"), honk.get("Oonker")):
309 280fbeb1 2024-10-11 continue if xonker:
310 280fbeb1 2024-10-11 continue lines.append(f'=> {lonk_url.build("honker", urlencode({"xid": xonker}))} honks of {xonker}')
311 280fbeb1 2024-10-11 continue print("\r\n".join(lines))
312 280fbeb1 2024-10-11 continue print("\r")
313 4acbf994 2024-09-25 continue
314 280fbeb1 2024-10-11 continue if gethonks_answer.get("honks"):
315 280fbeb1 2024-10-11 continue print("\r")
316 280fbeb1 2024-10-11 continue print_menu(lonk_url, honk_url, gethonks_answer)
317 4acbf994 2024-09-25 continue
318 280fbeb1 2024-10-11 continue
319 8b4f10af 2024-10-03 continue class _LonkTreeItem:
320 c093e3cd 2024-10-13 continue def __init__(self, honk=None):
321 c093e3cd 2024-10-13 continue self.honk = honk
322 8b4f10af 2024-10-03 continue self.thread = []
323 6c43924a 2024-10-01 continue
324 8b4f10af 2024-10-03 continue def iterate_honks(self):
325 c093e3cd 2024-10-13 continue if self.honk is not None:
326 c093e3cd 2024-10-13 continue yield self.honk
327 c093e3cd 2024-10-13 continue yield from self.thread
328 4e7950e1 2024-09-30 continue
329 4e7950e1 2024-09-30 continue
330 c093e3cd 2024-10-13 continue def page_lonk(lonk_url, honk_url):
331 056d3709 2024-10-10 continue gethonks_answer = honk_url.get("gethonks", page="home")
332 6c43924a 2024-10-01 continue
333 8b4f10af 2024-10-03 continue lonk_page = {}
334 277b69f7 2024-10-22 continue for honk in reversed(gethonks_answer["honks"]):
335 277b69f7 2024-10-22 continue if honk.get("RID"):
336 277b69f7 2024-10-22 continue lonk_page.setdefault(honk["Convoy"], None)
337 277b69f7 2024-10-22 continue else:
338 277b69f7 2024-10-22 continue lonk_page[honk["Convoy"]] = _LonkTreeItem(honk)
339 9f761a0c 2024-09-25 continue
340 277b69f7 2024-10-22 continue correction_map = {}
341 277b69f7 2024-10-22 continue for convoy in list(reversed([convoy for convoy, item in lonk_page.items() if item is None]))[:36]:
342 277b69f7 2024-10-22 continue convoy = correction_map.get(convoy, convoy)
343 277b69f7 2024-10-22 continue if lonk_page.get(convoy) is not None:
344 277b69f7 2024-10-22 continue continue
345 277b69f7 2024-10-22 continue for honk in honk_url.get("gethonks", page="convoy", c=convoy)["honks"]:
346 277b69f7 2024-10-22 continue if honk["What"] == "honked":
347 277b69f7 2024-10-22 continue if convoy != honk["Convoy"]:
348 277b69f7 2024-10-22 continue correction_map[convoy] = honk["Convoy"]
349 277b69f7 2024-10-22 continue convoy = honk["Convoy"]
350 277b69f7 2024-10-22 continue if lonk_page.get(convoy) is None:
351 277b69f7 2024-10-22 continue lonk_page[convoy] = _LonkTreeItem(honk)
352 277b69f7 2024-10-22 continue break
353 277b69f7 2024-10-22 continue else:
354 277b69f7 2024-10-22 continue lonk_page[convoy] = _LonkTreeItem(None)
355 277b69f7 2024-10-22 continue
356 277b69f7 2024-10-22 continue for honk in reversed(gethonks_answer.pop("honks")):
357 8b4f10af 2024-10-03 continue if honk.get("RID"):
358 277b69f7 2024-10-22 continue item = lonk_page.get(correction_map.get(honk["Convoy"], honk["Convoy"]))
359 277b69f7 2024-10-22 continue if item is not None:
360 277b69f7 2024-10-22 continue item.thread.append(honk)
361 9f761a0c 2024-09-25 continue
362 8b4f10af 2024-10-03 continue gethonks_answer["honks"] = []
363 277b69f7 2024-10-22 continue for item in reversed(lonk_page.values()):
364 277b69f7 2024-10-22 continue if item is None:
365 277b69f7 2024-10-22 continue break
366 277b69f7 2024-10-22 continue gethonks_answer["honks"] += list(item.iterate_honks())
367 895356d0 2024-10-01 continue
368 280fbeb1 2024-10-11 continue print_header("lonk home")
369 8b4f10af 2024-10-03 continue print_gethonks(gethonks_answer, lonk_url, honk_url)
370 9f761a0c 2024-09-25 continue
371 4e7950e1 2024-09-30 continue
372 c093e3cd 2024-10-13 continue def page_convoy(lonk_url, honk_url):
373 895356d0 2024-10-01 continue query = {pair[0]: pair[1] for pair in parse_qsl(lonk_url.query)}
374 895356d0 2024-10-01 continue if "c" not in query:
375 895356d0 2024-10-01 continue print("51 Not found\r")
376 577b12da 2024-09-24 continue return
377 383d16ea 2024-09-04 continue
378 056d3709 2024-10-10 continue gethonks_answer = honk_url.get("gethonks", page="convoy", c=query["c"])
379 280fbeb1 2024-10-11 continue print_header(f"convoy {query['c']}")
380 8b4f10af 2024-10-03 continue print_gethonks(gethonks_answer, lonk_url, honk_url)
381 383d16ea 2024-09-04 continue
382 895356d0 2024-10-01 continue
383 c093e3cd 2024-10-13 continue def page_search(lonk_url, honk_url):
384 2ff52a39 2024-10-09 continue if not lonk_url.query:
385 29cd802f 2024-10-09 continue print("10 What are we looking for?\r")
386 29cd802f 2024-10-09 continue return
387 2ff52a39 2024-10-09 continue
388 2ff52a39 2024-10-09 continue q = unquote(lonk_url.query)
389 056d3709 2024-10-10 continue gethonks_answer = honk_url.get("gethonks", page="search", q=q)
390 280fbeb1 2024-10-11 continue print_header(f"search - {q}")
391 2ff52a39 2024-10-09 continue print_gethonks(gethonks_answer, lonk_url, honk_url)
392 2ff52a39 2024-10-09 continue
393 2ff52a39 2024-10-09 continue
394 c093e3cd 2024-10-13 continue def page_atme(lonk_url, honk_url):
395 056d3709 2024-10-10 continue gethonks_answer = honk_url.get("gethonks", page="atme")
396 280fbeb1 2024-10-11 continue print_header("@me")
397 8b4f10af 2024-10-03 continue print_gethonks(gethonks_answer, lonk_url, honk_url)
398 895356d0 2024-10-01 continue
399 895356d0 2024-10-01 continue
400 c093e3cd 2024-10-13 continue def page_longago(lonk_url, honk_url):
401 056d3709 2024-10-10 continue gethonks_answer = honk_url.get("gethonks", page="longago")
402 280fbeb1 2024-10-11 continue print_header("long ago")
403 b5ebf4c7 2024-10-09 continue print_gethonks(gethonks_answer, lonk_url, honk_url)
404 b5ebf4c7 2024-10-09 continue
405 b5ebf4c7 2024-10-09 continue
406 c093e3cd 2024-10-13 continue def page_myhonks(lonk_url, honk_url):
407 056d3709 2024-10-10 continue gethonks_answer = honk_url.get("gethonks", page="myhonks")
408 280fbeb1 2024-10-11 continue print_header("my honks")
409 3601f1e9 2024-10-10 continue print_gethonks(gethonks_answer, lonk_url, honk_url)
410 3601f1e9 2024-10-10 continue
411 3601f1e9 2024-10-10 continue
412 c093e3cd 2024-10-13 continue def page_honker(lonk_url, honk_url):
413 3601f1e9 2024-10-10 continue xid = {pair[0]: pair[1] for pair in parse_qsl(lonk_url.query)}.get("xid")
414 3601f1e9 2024-10-10 continue if not xid:
415 3601f1e9 2024-10-10 continue print("51 Not found\r")
416 3601f1e9 2024-10-10 continue return
417 3601f1e9 2024-10-10 continue
418 056d3709 2024-10-10 continue gethonks_answer = honk_url.get("gethonks", page="honker", xid=xid)
419 280fbeb1 2024-10-11 continue print_header(f"honks of {xid}")
420 3601f1e9 2024-10-10 continue print_gethonks(gethonks_answer, lonk_url, honk_url)
421 895356d0 2024-10-01 continue
422 895356d0 2024-10-01 continue
423 c093e3cd 2024-10-13 continue def bonk(lonk_url, honk_url):
424 f6854b29 2024-10-05 continue what = {pair[0]: pair[1] for pair in parse_qsl(lonk_url.query)}.get("w")
425 f6854b29 2024-10-05 continue if not what:
426 f6854b29 2024-10-05 continue print("51 Not found\r")
427 f6854b29 2024-10-05 continue return
428 f6854b29 2024-10-05 continue
429 056d3709 2024-10-10 continue honk_url.get("zonkit", wherefore="bonk", what=what, answer_is_json=False)
430 3601f1e9 2024-10-10 continue print(f'30 {lonk_url.build("myhonks")}\r')
431 3efde7cb 2024-10-10 continue
432 3efde7cb 2024-10-10 continue
433 c093e3cd 2024-10-13 continue def gethonkers(lonk_url, honk_url):
434 280fbeb1 2024-10-11 continue print_header("honkers")
435 280fbeb1 2024-10-11 continue print_menu(lonk_url, honk_url)
436 3efde7cb 2024-10-10 continue
437 056d3709 2024-10-10 continue honkers = honk_url.get("gethonkers").get("honkers") or []
438 3efde7cb 2024-10-10 continue for honker in honkers:
439 3efde7cb 2024-10-10 continue print(f'## {honker.get("Name") or honker["XID"]}\r')
440 3efde7cb 2024-10-10 continue for field_name, display_name in zip(("Name", "XID", "Flavor"), ("name", "url", "flavor")):
441 3efde7cb 2024-10-10 continue value = honker.get(field_name)
442 3efde7cb 2024-10-10 continue if value:
443 3efde7cb 2024-10-10 continue print(f'{display_name}: {value}\r')
444 3efde7cb 2024-10-10 continue if honker.get("Flavor") == "sub":
445 3efde7cb 2024-10-10 continue print(f'=> {lonk_url.build("unsubscribe", urlencode({"honkerid": honker["ID"]}))} unsubscribe\r')
446 3efde7cb 2024-10-10 continue else:
447 3efde7cb 2024-10-10 continue print(f'=> {lonk_url.build("subscribe", urlencode({"honkerid": honker["ID"]}))} (re)subscribe\r')
448 280fbeb1 2024-10-11 continue print(f'=> {lonk_url.build("honker", urlencode({"xid": honker["XID"]}))} honks of {honker["XID"]}\r')
449 3efde7cb 2024-10-10 continue print('\r')
450 3efde7cb 2024-10-10 continue
451 3efde7cb 2024-10-10 continue if honkers:
452 3efde7cb 2024-10-10 continue print("\r")
453 280fbeb1 2024-10-11 continue print_menu(lonk_url, honk_url)
454 3efde7cb 2024-10-10 continue
455 3efde7cb 2024-10-10 continue
456 c093e3cd 2024-10-13 continue def addhonker(lonk_url, honk_url):
457 3efde7cb 2024-10-10 continue if not lonk_url.query:
458 056d3709 2024-10-10 continue print("10 honker url: \r")
459 3efde7cb 2024-10-10 continue return
460 f6854b29 2024-10-05 continue
461 3efde7cb 2024-10-10 continue url = unquote(lonk_url.query)
462 3efde7cb 2024-10-10 continue try:
463 056d3709 2024-10-10 continue honk_url.get("savehonker", url=url, answer_is_json=False)
464 3efde7cb 2024-10-10 continue print(f'30 {lonk_url.build("gethonkers")}\r')
465 3efde7cb 2024-10-10 continue except HTTPError as error:
466 280fbeb1 2024-10-11 continue print_header("add new honker")
467 280fbeb1 2024-10-11 continue print_menu(lonk_url, honk_url)
468 3efde7cb 2024-10-10 continue print("\r")
469 3efde7cb 2024-10-10 continue print('## Error\r')
470 3efde7cb 2024-10-10 continue print(f'> {error.fp.read().decode("utf8")}\r')
471 f6854b29 2024-10-05 continue
472 3efde7cb 2024-10-10 continue
473 c093e3cd 2024-10-13 continue def unsubscribe(lonk_url, honk_url):
474 3efde7cb 2024-10-10 continue honkerid = {pair[0]: pair[1] for pair in parse_qsl(lonk_url.query)}.get("honkerid")
475 3efde7cb 2024-10-10 continue if not honkerid:
476 3efde7cb 2024-10-10 continue print("51 Not found\r")
477 3efde7cb 2024-10-10 continue return
478 3efde7cb 2024-10-10 continue
479 3efde7cb 2024-10-10 continue url = unquote(lonk_url.query)
480 056d3709 2024-10-10 continue honk_url.get("savehonker", honkerid=honkerid, unsub="unsub", answer_is_json=False)
481 3efde7cb 2024-10-10 continue print(f'30 {lonk_url.build("gethonkers")}\r')
482 3efde7cb 2024-10-10 continue
483 3efde7cb 2024-10-10 continue
484 c093e3cd 2024-10-13 continue def subscribe(lonk_url, honk_url):
485 3efde7cb 2024-10-10 continue honkerid = {pair[0]: pair[1] for pair in parse_qsl(lonk_url.query)}.get("honkerid")
486 3efde7cb 2024-10-10 continue if not honkerid:
487 3efde7cb 2024-10-10 continue print("51 Not found\r")
488 3efde7cb 2024-10-10 continue return
489 3efde7cb 2024-10-10 continue
490 3efde7cb 2024-10-10 continue url = unquote(lonk_url.query)
491 056d3709 2024-10-10 continue honk_url.get("savehonker", honkerid=honkerid, sub="sub", answer_is_json=False)
492 3efde7cb 2024-10-10 continue print(f'30 {lonk_url.build("gethonkers")}\r')
493 3efde7cb 2024-10-10 continue
494 3efde7cb 2024-10-10 continue
495 c093e3cd 2024-10-13 continue def newhonk(lonk_url, honk_url):
496 056d3709 2024-10-10 continue if not lonk_url.query:
497 056d3709 2024-10-10 continue print("10 let's make some noise: \r")
498 056d3709 2024-10-10 continue return
499 056d3709 2024-10-10 continue
500 056d3709 2024-10-10 continue noise = unquote(lonk_url.query)
501 056d3709 2024-10-10 continue honk_url.get("honk", noise=noise, answer_is_json=False)
502 056d3709 2024-10-10 continue print(f'30 {lonk_url.build("myhonks")}\r')
503 056d3709 2024-10-10 continue
504 056d3709 2024-10-10 continue
505 c093e3cd 2024-10-13 continue def honkback(lonk_url, honk_url):
506 056d3709 2024-10-10 continue if not lonk_url.query:
507 056d3709 2024-10-10 continue handles = unquote(lonk_url.splitted_path[-3]).strip()
508 056d3709 2024-10-10 continue rid = unquote(lonk_url.splitted_path[-2])
509 056d3709 2024-10-10 continue print(f"10 Answer to {handles or rid}:\r")
510 056d3709 2024-10-10 continue return
511 056d3709 2024-10-10 continue
512 056d3709 2024-10-10 continue noise = unquote(lonk_url.query)
513 056d3709 2024-10-10 continue rid = unquote(lonk_url.splitted_path[-2])
514 056d3709 2024-10-10 continue honk_url.get("honk", noise=noise, rid=rid, answer_is_json=False)
515 056d3709 2024-10-10 continue print(f'30 {lonk_url.build("myhonks")}\r')
516 056d3709 2024-10-10 continue
517 056d3709 2024-10-10 continue
518 895356d0 2024-10-01 continue def authenticated(cert_hash, lonk_url, fn_impl):
519 895356d0 2024-10-01 continue db_con = db_connect()
520 c093e3cd 2024-10-13 continue row = db_con.execute("SELECT honk_url, token FROM client WHERE cert_hash=?", (cert_hash, )).fetchone()
521 895356d0 2024-10-01 continue if not row:
522 895356d0 2024-10-01 continue print(f'30 {lonk_url.build("ask_server")}\r')
523 895356d0 2024-10-01 continue return
524 c093e3cd 2024-10-13 continue honk_url, token = row
525 895356d0 2024-10-01 continue with db_con:
526 c093e3cd 2024-10-13 continue fn_impl(lonk_url, HonkUrl(honk_url, token))
527 895356d0 2024-10-01 continue
528 895356d0 2024-10-01 continue
529 280fbeb1 2024-10-11 continue def new_client_stage_1_ask_server(lonk_url):
530 280fbeb1 2024-10-11 continue if not lonk_url.query:
531 280fbeb1 2024-10-11 continue print("10 Honk server URL\r")
532 280fbeb1 2024-10-11 continue return
533 280fbeb1 2024-10-11 continue splitted = urlsplit(unquote(lonk_url.query))
534 280fbeb1 2024-10-11 continue path = [quote(urlunsplit((splitted.scheme, splitted.netloc, "", "", "")), safe=""), "ask_username"]
535 280fbeb1 2024-10-11 continue print(f'30 {lonk_url.build(path)}\r')
536 280fbeb1 2024-10-11 continue
537 280fbeb1 2024-10-11 continue
538 280fbeb1 2024-10-11 continue def new_client_stage_2_ask_username(lonk_url):
539 280fbeb1 2024-10-11 continue if not lonk_url.query:
540 280fbeb1 2024-10-11 continue print("10 Honk user name\r")
541 280fbeb1 2024-10-11 continue return
542 280fbeb1 2024-10-11 continue if len(lonk_url.splitted_path) < 3:
543 280fbeb1 2024-10-11 continue print('59 Bad request\r')
544 280fbeb1 2024-10-11 continue return
545 280fbeb1 2024-10-11 continue quoted_server = lonk_url.splitted_path[-2]
546 280fbeb1 2024-10-11 continue path = [quoted_server, quote(unquote(lonk_url.query), safe=""), "ask_password"]
547 280fbeb1 2024-10-11 continue print(f'30 {lonk_url.build(path)}\r')
548 280fbeb1 2024-10-11 continue
549 280fbeb1 2024-10-11 continue
550 280fbeb1 2024-10-11 continue def new_client_stage_3_ask_password(cert_hash, lonk_url):
551 280fbeb1 2024-10-11 continue if not lonk_url.query:
552 280fbeb1 2024-10-11 continue print("11 Honk user password\r")
553 280fbeb1 2024-10-11 continue return
554 280fbeb1 2024-10-11 continue if len(lonk_url.splitted_path) < 4:
555 280fbeb1 2024-10-11 continue print('59 Bad request\r')
556 280fbeb1 2024-10-11 continue return
557 280fbeb1 2024-10-11 continue
558 280fbeb1 2024-10-11 continue honk_url = unquote(lonk_url.splitted_path[-3])
559 280fbeb1 2024-10-11 continue post_data = {
560 280fbeb1 2024-10-11 continue "username": unquote(lonk_url.splitted_path[-2]),
561 280fbeb1 2024-10-11 continue "password": unquote(lonk_url.query),
562 280fbeb1 2024-10-11 continue "gettoken": "1",
563 280fbeb1 2024-10-11 continue }
564 280fbeb1 2024-10-11 continue with urlopen(honk_url + "/dologin", data=urlencode(post_data).encode(), timeout=15) as response:
565 280fbeb1 2024-10-11 continue token = response.read().decode("utf8")
566 280fbeb1 2024-10-11 continue db_con = db_connect()
567 280fbeb1 2024-10-11 continue with db_con:
568 280fbeb1 2024-10-11 continue db_con.execute(
569 280fbeb1 2024-10-11 continue "INSERT INTO client (cert_hash, honk_url, token) VALUES (?, ?, ?)",
570 280fbeb1 2024-10-11 continue (cert_hash, honk_url, token)
571 280fbeb1 2024-10-11 continue )
572 280fbeb1 2024-10-11 continue print(f'30 {lonk_url.build([])}\r')
573 280fbeb1 2024-10-11 continue
574 280fbeb1 2024-10-11 continue
575 b7194e03 2024-09-12 continue def proxy(mime, url):
576 f6854b29 2024-10-05 continue with urlopen(url, timeout=10) as response:
577 15efabff 2024-09-13 continue stdout.buffer.write(b"20 " + mime.encode() + b"\r\n")
578 15efabff 2024-09-13 continue while True:
579 f6854b29 2024-10-05 continue content = response.read(512 * 1024)
580 15efabff 2024-09-13 continue if not content:
581 15efabff 2024-09-13 continue break
582 15efabff 2024-09-13 continue stdout.buffer.write(content)
583 b7194e03 2024-09-12 continue
584 15efabff 2024-09-13 continue
585 4acbf994 2024-09-25 continue def vgi(cert_hash, raw_url):
586 4acbf994 2024-09-25 continue lonk_url = LonkUrl(raw_url)
587 e03b8dd3 2024-09-25 continue if lonk_url.page == "lonk":
588 8b4f10af 2024-10-03 continue authenticated(cert_hash, lonk_url, page_lonk)
589 895356d0 2024-10-01 continue elif lonk_url.page == "convoy":
590 8b4f10af 2024-10-03 continue authenticated(cert_hash, lonk_url, page_convoy)
591 895356d0 2024-10-01 continue elif lonk_url.page == "atme":
592 8b4f10af 2024-10-03 continue authenticated(cert_hash, lonk_url, page_atme)
593 2ff52a39 2024-10-09 continue elif lonk_url.page == "search":
594 2ff52a39 2024-10-09 continue authenticated(cert_hash, lonk_url, page_search)
595 b5ebf4c7 2024-10-09 continue elif lonk_url.page == "longago":
596 b5ebf4c7 2024-10-09 continue authenticated(cert_hash, lonk_url, page_longago)
597 3601f1e9 2024-10-10 continue elif lonk_url.page == "myhonks":
598 3601f1e9 2024-10-10 continue authenticated(cert_hash, lonk_url, page_myhonks)
599 3601f1e9 2024-10-10 continue elif lonk_url.page == "honker":
600 3601f1e9 2024-10-10 continue authenticated(cert_hash, lonk_url, page_honker)
601 f6854b29 2024-10-05 continue elif lonk_url.page == "bonk":
602 f6854b29 2024-10-05 continue authenticated(cert_hash, lonk_url, bonk)
603 3efde7cb 2024-10-10 continue elif lonk_url.page == "gethonkers":
604 3efde7cb 2024-10-10 continue authenticated(cert_hash, lonk_url, gethonkers)
605 3efde7cb 2024-10-10 continue elif lonk_url.page == "addhonker":
606 3efde7cb 2024-10-10 continue authenticated(cert_hash, lonk_url, addhonker)
607 3efde7cb 2024-10-10 continue elif lonk_url.page == "unsubscribe":
608 3efde7cb 2024-10-10 continue authenticated(cert_hash, lonk_url, unsubscribe)
609 3efde7cb 2024-10-10 continue elif lonk_url.page == "subscribe":
610 3efde7cb 2024-10-10 continue authenticated(cert_hash, lonk_url, subscribe)
611 056d3709 2024-10-10 continue elif lonk_url.page == "newhonk":
612 056d3709 2024-10-10 continue authenticated(cert_hash, lonk_url, newhonk)
613 056d3709 2024-10-10 continue elif lonk_url.page == "honkback":
614 056d3709 2024-10-10 continue authenticated(cert_hash, lonk_url, honkback)
615 e03b8dd3 2024-09-25 continue elif lonk_url.page == "ask_server":
616 e03b8dd3 2024-09-25 continue new_client_stage_1_ask_server(lonk_url)
617 e03b8dd3 2024-09-25 continue elif lonk_url.page == "ask_username":
618 e03b8dd3 2024-09-25 continue new_client_stage_2_ask_username(lonk_url)
619 e03b8dd3 2024-09-25 continue elif lonk_url.page == "ask_password":
620 e03b8dd3 2024-09-25 continue new_client_stage_3_ask_password(cert_hash, lonk_url)
621 e03b8dd3 2024-09-25 continue elif lonk_url.page == "proxy":
622 e03b8dd3 2024-09-25 continue query = {pair[0]: pair[1] for pair in parse_qsl(lonk_url.query)}
623 b7194e03 2024-09-12 continue if "m" not in query or "u" not in query:
624 b7194e03 2024-09-12 continue print("51 Not found\r")
625 b7194e03 2024-09-12 continue else:
626 b7194e03 2024-09-12 continue proxy(mime=query["m"], url=query["u"])
627 b7194e03 2024-09-12 continue else:
628 b7194e03 2024-09-12 continue print("51 Not found\r")
629 b7194e03 2024-09-12 continue
630 b7194e03 2024-09-12 continue
631 383d16ea 2024-09-04 continue if __name__ == '__main__':
632 4e7950e1 2024-09-30 continue cert_hash_ = environ.get("VGI_CERT_HASH")
633 4e7950e1 2024-09-30 continue if cert_hash_:
634 15efabff 2024-09-13 continue try:
635 4acbf994 2024-09-25 continue start_time = clock_gettime(CLOCK_MONOTONIC)
636 4acbf994 2024-09-25 continue try:
637 4e7950e1 2024-09-30 continue input_url = input().strip()
638 4e7950e1 2024-09-30 continue vgi(cert_hash_, input_url)
639 4acbf994 2024-09-25 continue finally:
640 4e7950e1 2024-09-30 continue stderr.write(f"{cert_hash_}|{input_url}|{clock_gettime(CLOCK_MONOTONIC) - start_time:.3f}sec.\n")
641 15efabff 2024-09-13 continue except HTTPError as error:
642 3efde7cb 2024-10-10 continue stderr.write(f"{error}\n")
643 15efabff 2024-09-13 continue print(f"43 Remote server return {error.code}: {error.reason}\r")
644 15efabff 2024-09-13 continue except URLError as error:
645 3efde7cb 2024-10-10 continue stderr.write(f"{error}\n")
646 15efabff 2024-09-13 continue print(f"43 Error while trying to access remote server: {error.reason}\r")
647 dd301323 2024-09-17 continue except TimeoutError as error:
648 3efde7cb 2024-10-10 continue stderr.write(f"{error}\n")
649 dd301323 2024-09-17 continue print(f"43 Error while trying to access remote server: {error}\r")
650 b7194e03 2024-09-12 continue else:
651 3efde7cb 2024-10-10 continue stderr.write("Certificate required\n")
652 b7194e03 2024-09-12 continue print("60 Certificate required\r")