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
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
19 a7cabe95 2024-09-04 continue def on_data(self, data):
20 e03b8dd3 2024-09-25 continue raise AssertionError("The method must be overridden")
22 a7cabe95 2024-09-04 continue def flush(self):
23 e03b8dd3 2024-09-25 continue raise AssertionError("The method must be overridden")
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 = []
32 a7cabe95 2024-09-04 continue def on_data(self, data):
33 a7cabe95 2024-09-04 continue self.content.append(data.strip())
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 ""
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 = []
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())
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 ""
62 a7cabe95 2024-09-04 continue class LitItemTag(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 ""
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 ""
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}"
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 = ""
87 ae087ef0 2024-09-04 continue def on_data(self, data):
88 ae087ef0 2024-09-04 continue self.content += data
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 ""
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
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)
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)
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 1b9c1500 2024-09-05 continue _push(LitItemTag(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)
136 1b9c1500 2024-09-05 continue src = img.get("src")
137 1b9c1500 2024-09-05 continue if src:
138 29500f51 2024-09-20 continue img_url = urljoin(self.base_url, src)
139 29500f51 2024-09-20 continue mime, _ = guess_type(img_url)
140 29500f51 2024-09-20 continue img_url = self.fn_media_url(mime, img_url)
141 0da6afa7 2024-10-03 continue self.gmi_text.append(f"=> {img_url} {title or 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())
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)
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())
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
160 1be7bb9a 2024-09-05 continue self.stack = [ParagraphTag("p", [])] + self.stack
161 1be7bb9a 2024-09-05 continue return self.stack[0]
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":
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, "")
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
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
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
194 056d3709 2024-10-10 continue def build(self, scheme=None, netloc=None, path="", query="", fragment=""):
195 09255143 2024-09-25 continue return urlunsplit(
197 09255143 2024-09-25 continue scheme or self._splitted_url.scheme,
198 09255143 2024-09-25 continue netloc or self._splitted_url.netloc,
201 09255143 2024-09-25 continue fragment,
205 056d3709 2024-10-10 continue def get(self, action, answer_is_json=True, **kwargs):
206 f6854b29 2024-10-05 continue query = {**{"action": action, "token": self._token}, **kwargs}
207 056d3709 2024-10-10 continue with urlopen(self.build(path="api", query=urlencode(query)), timeout=45) as response:
208 f6854b29 2024-10-05 continue answer = response.read().decode("utf8")
209 f6854b29 2024-10-05 continue return json_loads(answer) if answer_is_json else answer
212 4e7950e1 2024-09-30 continue def _create_schema(db_con):
213 4acbf994 2024-09-25 continue db_con.execute(
215 577b12da 2024-09-24 continue CREATE TABLE
216 577b12da 2024-09-24 continue client (
217 9f761a0c 2024-09-25 continue client_id INTEGER PRIMARY KEY,
218 577b12da 2024-09-24 continue cert_hash TEXT UNIQUE,
219 577b12da 2024-09-24 continue honk_url TEXT NOT NULL,
220 577b12da 2024-09-24 continue token TEXT NOT NULL
224 da7270e1 2024-10-01 continue db_con.execute(
226 da7270e1 2024-10-01 continue CREATE TABLE
227 4acbf994 2024-09-25 continue convoy (
228 9f761a0c 2024-09-25 continue convoy_id INTEGER PRIMARY KEY,
229 9f761a0c 2024-09-25 continue convoy TEXT,
230 4acbf994 2024-09-25 continue client_id INTEGER,
231 9f761a0c 2024-09-25 continue honk_id INTEGER,
232 3601f1e9 2024-10-10 continue handle TEXT,
233 3601f1e9 2024-10-10 continue oondle TEXT,
234 9f761a0c 2024-09-25 continue url TEXT,
235 9f761a0c 2024-09-25 continue html TEXT,
236 9f761a0c 2024-09-25 continue date TEXT,
237 7c41d255 2024-10-09 continue public INTEGER,
238 3e30fc1f 2024-10-09 continue handles TEXT,
239 3601f1e9 2024-10-10 continue honker_url TEXT,
240 3601f1e9 2024-10-10 continue oonker_url TEXT,
241 9f761a0c 2024-09-25 continue FOREIGN KEY (client_id) REFERENCES client (client_id),
242 9f761a0c 2024-09-25 continue UNIQUE (convoy, client_id),
243 9f761a0c 2024-09-25 continue UNIQUE (honk_id, client_id)
247 4acbf994 2024-09-25 continue db_con.execute(
249 4acbf994 2024-09-25 continue CREATE TABLE
251 9f761a0c 2024-09-25 continue donk_id INTEGER PRIMARY KEY,
252 8b4f10af 2024-10-03 continue client_id INTEGER,
253 9f761a0c 2024-09-25 continue convoy_id INTEGER,
254 4acbf994 2024-09-25 continue url TEXT NOT NULL,
255 9f761a0c 2024-09-25 continue mime TEXT,
256 9f761a0c 2024-09-25 continue alt_text TEXT,
257 8b4f10af 2024-10-03 continue FOREIGN KEY (client_id) REFERENCES client (client_id),
258 9f761a0c 2024-09-25 continue FOREIGN KEY (convoy_id) REFERENCES convoy (convoy_id)
262 da7270e1 2024-10-01 continue db_con.execute(
264 da7270e1 2024-10-01 continue CREATE TABLE
265 3601f1e9 2024-10-10 continue xonker (
266 3601f1e9 2024-10-10 continue xonker_id INTEGER PRIMARY KEY,
267 3601f1e9 2024-10-10 continue client_id INTEGER,
268 3601f1e9 2024-10-10 continue convoy_id INTEGER,
269 3601f1e9 2024-10-10 continue url TEXT NOT NULL,
270 3601f1e9 2024-10-10 continue FOREIGN KEY (client_id) REFERENCES client (client_id),
271 3601f1e9 2024-10-10 continue FOREIGN KEY (convoy_id) REFERENCES convoy (convoy_id)
275 3601f1e9 2024-10-10 continue db_con.execute(
277 3601f1e9 2024-10-10 continue CREATE TABLE
279 da7270e1 2024-10-01 continue db_schema_version INTEGER
283 da7270e1 2024-10-01 continue db_con.execute("INSERT INTO meta(db_schema_version) VALUES (?)", (1, ))
286 4e7950e1 2024-09-30 continue def db_connect():
287 577b12da 2024-09-24 continue db_file_path = Path(__file__).parent / ".local" / "db"
288 577b12da 2024-09-24 continue db_file_path.parent.mkdir(parents=True, exist_ok=True)
289 577b12da 2024-09-24 continue db_exist = db_file_path.exists()
290 577b12da 2024-09-24 continue db_con = sqlite3_connect(db_file_path)
291 577b12da 2024-09-24 continue if not db_exist:
292 e03b8dd3 2024-09-25 continue with db_con:
293 4e7950e1 2024-09-30 continue _create_schema(db_con)
294 577b12da 2024-09-24 continue return db_con
297 8b4f10af 2024-10-03 continue class _LonkTreeItem:
298 3601f1e9 2024-10-10 continue def __init__(self, convoy_id, convoy, honk_id, handle, oondle, url, html, date, public, handles, honker, oonker):
299 4e7950e1 2024-09-30 continue self.convoy_id = convoy_id
300 4e7950e1 2024-09-30 continue self.convoy = convoy
301 4e7950e1 2024-09-30 continue self.honk_id = honk_id
302 3601f1e9 2024-10-10 continue self.handle = handle
303 3601f1e9 2024-10-10 continue self.oondle = oondle
304 4e7950e1 2024-09-30 continue self.url = url
305 4e7950e1 2024-09-30 continue self.html = html
306 4e7950e1 2024-09-30 continue self.date = date
307 7c41d255 2024-10-09 continue self.public = bool(public)
308 3e30fc1f 2024-10-09 continue self.handles = handles
309 3601f1e9 2024-10-10 continue self.honker = honker
310 3601f1e9 2024-10-10 continue self.oonker = oonker
311 8b4f10af 2024-10-03 continue self.donks = []
312 8b4f10af 2024-10-03 continue self.thread = []
314 8b4f10af 2024-10-03 continue def iterate_honks(self):
315 8b4f10af 2024-10-03 continue if self.html is not None:
316 8b4f10af 2024-10-03 continue yield {
317 8b4f10af 2024-10-03 continue "Convoy": self.convoy,
318 3601f1e9 2024-10-10 continue "Handle": self.handle,
319 3601f1e9 2024-10-10 continue "Oondle": self.oondle,
320 8b4f10af 2024-10-03 continue "ID": self.honk_id,
321 8b4f10af 2024-10-03 continue "XID": self.url,
322 8b4f10af 2024-10-03 continue "HTML": self.html,
323 8b4f10af 2024-10-03 continue "Date": self.date,
324 7c41d255 2024-10-09 continue "Public": self.public,
325 3e30fc1f 2024-10-09 continue "Handles": self.handles,
326 3601f1e9 2024-10-10 continue "Honker": self.honker,
327 3601f1e9 2024-10-10 continue "Oonker": self.oonker,
328 8b4f10af 2024-10-03 continue "Donks": [
329 8b4f10af 2024-10-03 continue {"URL": donk[0], "Media": donk[1], "Desc": donk[2]}
330 8b4f10af 2024-10-03 continue for donk in self.donks
333 8b4f10af 2024-10-03 continue child_honks = self.thread[:]
334 8b4f10af 2024-10-03 continue child_honks.reverse()
335 8b4f10af 2024-10-03 continue yield from reversed(child_honks)
338 8b4f10af 2024-10-03 continue def page_lonk(db_con, client_id, lonk_url, honk_url):
339 056d3709 2024-10-10 continue gethonks_answer = honk_url.get("gethonks", page="home")
341 8b4f10af 2024-10-03 continue lonk_page = {}
342 5910837c 2024-10-03 continue for honk in reversed(gethonks_answer.pop("honks", None) or []):
343 9f761a0c 2024-09-25 continue convoy = honk["Convoy"]
345 8b4f10af 2024-10-03 continue if convoy not in lonk_page:
346 9f761a0c 2024-09-25 continue row = db_con.execute(
349 3601f1e9 2024-10-10 continue convoy_id, convoy, honk_id, handle, oondle, url, html, date, public, handles, honker_url, oonker_url
353 3601f1e9 2024-10-10 continue client_id=? AND convoy=?
355 8b4f10af 2024-10-03 continue (client_id, convoy)
356 9f761a0c 2024-09-25 continue ).fetchone()
357 8b4f10af 2024-10-03 continue if row:
358 8b4f10af 2024-10-03 continue lonk_page[convoy] = _LonkTreeItem(*row)
359 8b4f10af 2024-10-03 continue res_donks = db_con.execute(
360 8b4f10af 2024-10-03 continue "SELECT url, mime, alt_text FROM donk WHERE client_id=? AND convoy_id=?",
361 8b4f10af 2024-10-03 continue (client_id, lonk_page[convoy].convoy_id, )
363 8b4f10af 2024-10-03 continue while True:
364 8b4f10af 2024-10-03 continue donks = res_donks.fetchmany()
365 8b4f10af 2024-10-03 continue if not donks:
368 8b4f10af 2024-10-03 continue for donk in donks:
369 8b4f10af 2024-10-03 continue donk_url, donk_mime, donk_text = donk
370 8b4f10af 2024-10-03 continue lonk_page[convoy].donks.append((donk_url, donk_mime, donk_text))
372 8b4f10af 2024-10-03 continue if convoy not in lonk_page:
373 3601f1e9 2024-10-10 continue def _save_convoy(convoy, honk):
374 7c41d255 2024-10-09 continue is_public = 1 if honk.get("Public") else 0
375 4e7950e1 2024-09-30 continue row = db_con.execute(
377 4acbf994 2024-09-25 continue INSERT INTO
378 3601f1e9 2024-10-10 continue convoy(convoy, client_id, honk_id, handle, oondle, url, html, date, public, handles, honker_url, oonker_url)
380 3601f1e9 2024-10-10 continue (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
381 8b4f10af 2024-10-03 continue RETURNING
382 3601f1e9 2024-10-10 continue convoy_id, convoy, honk_id, handle, oondle, url, html, date, public, handles, honker_url, oonker_url
384 3601f1e9 2024-10-10 continue (convoy, client_id, honk["ID"], honk["Handle"], honk.get("Oondle"), honk["XID"], honk["HTML"], honk["Date"], is_public, honk["Handles"], honk.get("Honker"), honk.get("Oonker"))
385 4e7950e1 2024-09-30 continue ).fetchone()
386 3601f1e9 2024-10-10 continue lonk_page[convoy] = _LonkTreeItem(*row)
388 8b4f10af 2024-10-03 continue for donk in (honk.get("Donks") or []):
389 056d3709 2024-10-10 continue donk_url = honk_url.build(path=f'/d/{donk["XID"]}') if donk.get("XID") else donk["URL"]
390 8b4f10af 2024-10-03 continue donk_mime, donk_text = donk["Media"], donk.get("Desc") or donk.get("Name") or None
391 8b4f10af 2024-10-03 continue db_con.execute(
392 8b4f10af 2024-10-03 continue "INSERT INTO donk (client_id, convoy_id, url, mime, alt_text) VALUES (?, ?, ?, ?, ?)",
393 8b4f10af 2024-10-03 continue (client_id, lonk_page[convoy].convoy_id, donk_url, donk_mime, donk_text, )
395 8b4f10af 2024-10-03 continue lonk_page[convoy].donks.append((donk_url, donk_mime, donk_text))
397 8b4f10af 2024-10-03 continue if honk.get("RID"):
398 056d3709 2024-10-10 continue for honk_in_convoy in honk_url.get("gethonks", page="convoy", c=convoy)["honks"]:
399 8b4f10af 2024-10-03 continue if not honk_in_convoy.get("RID"):
400 3601f1e9 2024-10-10 continue _save_convoy(convoy, honk_in_convoy)
403 3601f1e9 2024-10-10 continue _save_convoy(convoy, {"ID": None, "Handle": None, "XID": None, "HTML": None, "Date": None, "Handles": None})
405 3601f1e9 2024-10-10 continue _save_convoy(convoy, honk)
407 8b4f10af 2024-10-03 continue if honk.get("RID"):
408 8b4f10af 2024-10-03 continue lonk_page[convoy].thread.append(honk)
410 8b4f10af 2024-10-03 continue gethonks_answer["honks"] = []
411 5910837c 2024-10-03 continue for tree_item in reversed(lonk_page.values()):
412 8b4f10af 2024-10-03 continue gethonks_answer["honks"] += list(tree_item.iterate_honks())
414 8b4f10af 2024-10-03 continue print("20 text/gemini\r")
415 8b4f10af 2024-10-03 continue print("# 𝓗 onk: lonk\r")
416 8b4f10af 2024-10-03 continue print("\r")
417 8b4f10af 2024-10-03 continue print_gethonks(gethonks_answer, lonk_url, honk_url)
420 8b4f10af 2024-10-03 continue def page_convoy(db_con, client_id, lonk_url, honk_url):
421 895356d0 2024-10-01 continue query = {pair[0]: pair[1] for pair in parse_qsl(lonk_url.query)}
422 895356d0 2024-10-01 continue if "c" not in query:
423 895356d0 2024-10-01 continue print("51 Not found\r")
426 056d3709 2024-10-10 continue gethonks_answer = honk_url.get("gethonks", page="convoy", c=query["c"])
427 895356d0 2024-10-01 continue print("20 text/gemini\r")
428 895356d0 2024-10-01 continue print(f"# 𝓗 onk: convoy {query['c']}\r")
429 895356d0 2024-10-01 continue print("\r")
430 8b4f10af 2024-10-03 continue print_gethonks(gethonks_answer, lonk_url, honk_url)
433 2ff52a39 2024-10-09 continue def page_search(db_con, client_id, lonk_url, honk_url):
434 2ff52a39 2024-10-09 continue if not lonk_url.query:
435 29cd802f 2024-10-09 continue print("10 What are we looking for?\r")
438 2ff52a39 2024-10-09 continue q = unquote(lonk_url.query)
439 056d3709 2024-10-10 continue gethonks_answer = honk_url.get("gethonks", page="search", q=q)
440 2ff52a39 2024-10-09 continue print("20 text/gemini\r")
441 2ff52a39 2024-10-09 continue print(f"# 𝓗 onk: search - {q}\r")
442 2ff52a39 2024-10-09 continue print("\r")
443 2ff52a39 2024-10-09 continue print_gethonks(gethonks_answer, lonk_url, honk_url)
446 8b4f10af 2024-10-03 continue def page_atme(db_con, client_id, lonk_url, honk_url):
447 056d3709 2024-10-10 continue gethonks_answer = honk_url.get("gethonks", page="atme")
448 895356d0 2024-10-01 continue print("20 text/gemini\r")
449 895356d0 2024-10-01 continue print("# 𝓗 onk: @me")
450 895356d0 2024-10-01 continue print("\r")
451 8b4f10af 2024-10-03 continue print_gethonks(gethonks_answer, lonk_url, honk_url)
454 b5ebf4c7 2024-10-09 continue def page_longago(db_con, client_id, lonk_url, honk_url):
455 056d3709 2024-10-10 continue gethonks_answer = honk_url.get("gethonks", page="longago")
456 b5ebf4c7 2024-10-09 continue print("20 text/gemini\r")
457 b5ebf4c7 2024-10-09 continue print("# 𝓗 onk: long ago")
458 b5ebf4c7 2024-10-09 continue print("\r")
459 b5ebf4c7 2024-10-09 continue print_gethonks(gethonks_answer, lonk_url, honk_url)
462 3601f1e9 2024-10-10 continue def page_myhonks(db_con, client_id, lonk_url, honk_url):
463 056d3709 2024-10-10 continue gethonks_answer = honk_url.get("gethonks", page="myhonks")
464 3601f1e9 2024-10-10 continue print("20 text/gemini\r")
465 3601f1e9 2024-10-10 continue print("# 𝓗 onk: my honks")
466 3601f1e9 2024-10-10 continue print("\r")
467 3601f1e9 2024-10-10 continue print_gethonks(gethonks_answer, lonk_url, honk_url)
470 3601f1e9 2024-10-10 continue def page_honker(db_con, client_id, lonk_url, honk_url):
471 3601f1e9 2024-10-10 continue xid = {pair[0]: pair[1] for pair in parse_qsl(lonk_url.query)}.get("xid")
472 3601f1e9 2024-10-10 continue if not xid:
473 3601f1e9 2024-10-10 continue print("51 Not found\r")
476 056d3709 2024-10-10 continue gethonks_answer = honk_url.get("gethonks", page="honker", xid=xid)
477 3601f1e9 2024-10-10 continue print("20 text/gemini\r")
478 3601f1e9 2024-10-10 continue print(f"# 𝓗 onk: honks of {xid}\r")
479 3601f1e9 2024-10-10 continue print("\r")
480 3601f1e9 2024-10-10 continue print_gethonks(gethonks_answer, lonk_url, honk_url)
483 3efde7cb 2024-10-10 continue def menu(lonk_url, honk_url, gethonks_answer=None):
484 56e61afd 2024-10-10 continue print(f"## 📝 Menu\r")
485 056d3709 2024-10-10 continue print(f"=> {lonk_url.build('newhonk')} new honk\r")
486 3efde7cb 2024-10-10 continue print(f"=> {lonk_url.build([])} lonk home\r")
488 3efde7cb 2024-10-10 continue if gethonks_answer:
489 f7f08ebc 2024-10-07 continue line = f"=> {lonk_url.build('atme')} @me"
490 f7f08ebc 2024-10-07 continue if gethonks_answer["mecount"]:
491 f7f08ebc 2024-10-07 continue line += f' ({gethonks_answer["mecount"]})'
492 f7f08ebc 2024-10-07 continue print(line + "\r")
494 056d3709 2024-10-10 continue line = f"=> {honk_url.build(path='chatter')} chatter"
495 f7f08ebc 2024-10-07 continue if gethonks_answer["chatcount"]:
496 f7f08ebc 2024-10-07 continue line += f' ({gethonks_answer["chatcount"]})'
497 f7f08ebc 2024-10-07 continue print(line + "\r")
499 3efde7cb 2024-10-10 continue print(f"=> {lonk_url.build('search')} search\r")
500 3efde7cb 2024-10-10 continue print(f"=> {lonk_url.build('longago')} long ago\r")
501 3efde7cb 2024-10-10 continue print(f"=> {lonk_url.build('myhonks')} my honks\r")
502 3efde7cb 2024-10-10 continue print(f"=> {lonk_url.build('gethonkers')} honkers\r")
503 3efde7cb 2024-10-10 continue print(f"=> {lonk_url.build('addhonker')} add new honker\r")
506 3efde7cb 2024-10-10 continue def print_gethonks(gethonks_answer, lonk_url, honk_url):
507 3efde7cb 2024-10-10 continue menu(lonk_url, honk_url, gethonks_answer)
508 895356d0 2024-10-01 continue print("\r")
510 895356d0 2024-10-01 continue for honk in gethonks_answer.get("honks") or []:
511 7b744842 2024-10-01 continue convoy = honk["Convoy"]
512 895356d0 2024-10-01 continue re_url = honk.get("RID")
513 3601f1e9 2024-10-10 continue oondle = honk.get("Oondle")
514 3601f1e9 2024-10-10 continue from_ = f'{oondle} (🔁 {honk["Handle"]})' if oondle else f'{honk["Handle"]}'
515 895356d0 2024-10-01 continue lines = [
516 3601f1e9 2024-10-10 continue f'##{"# ↱" if re_url else ""} From {from_} {honk["Date"]}',
517 7b744842 2024-10-01 continue f'=> {lonk_url.build("convoy", urlencode({"c": convoy}))} Convoy {convoy}',
518 895356d0 2024-10-01 continue f'=> {honk["XID"]}',
520 895356d0 2024-10-01 continue if re_url:
521 895356d0 2024-10-01 continue lines.append(f'=> {re_url} Re: {re_url}')
522 1f466cd2 2024-10-04 continue lines.append("")
523 056d3709 2024-10-10 continue lines.append(HtmlToGmi(honk_url.build(), lonk_url.media).feed(honk["HTML"]))
524 895356d0 2024-10-01 continue for donk in honk.get("Donks") or []:
525 056d3709 2024-10-10 continue donk_url = honk_url.build(path=f'/d/{donk["XID"]}') if donk.get("XID") else donk["URL"]
526 895356d0 2024-10-01 continue donk_mime, donk_text = donk["Media"], donk.get("Desc") or donk.get("Name") or None
527 0da6afa7 2024-10-03 continue lines.append(f'=> {lonk_url.media(donk_mime, donk_url)} {donk_url}')
528 895356d0 2024-10-01 continue if donk_text:
529 895356d0 2024-10-01 continue lines.append(donk_text)
530 f6854b29 2024-10-05 continue lines.append("")
531 7c41d255 2024-10-09 continue if honk.get("Public"):
532 7c41d255 2024-10-09 continue lines.append(f'=> {lonk_url.build("bonk", urlencode({"w": honk["XID"]}))} ↺ bonk')
533 056d3709 2024-10-10 continue honk_back_url = lonk_url.build(
535 056d3709 2024-10-10 continue quote(honk["Handles"] or " ", safe=""),
536 056d3709 2024-10-10 continue quote(honk["XID"], safe=""),
537 056d3709 2024-10-10 continue "honkback",
540 056d3709 2024-10-10 continue lines.append(f'=> {honk_back_url} ↱ honk back')
541 3601f1e9 2024-10-10 continue for xonker in (honk.get("Honker"), honk.get("Oonker")):
542 3601f1e9 2024-10-10 continue if xonker:
543 3601f1e9 2024-10-10 continue lines.append(f'=> {lonk_url.build("honker", urlencode({"xid": xonker}))} honks of {xonker}')
544 895356d0 2024-10-01 continue print("\r\n".join(lines))
545 895356d0 2024-10-01 continue print("\r")
547 29cd802f 2024-10-09 continue if gethonks_answer.get("honks"):
548 b5ebf4c7 2024-10-09 continue print("\r")
549 3efde7cb 2024-10-10 continue menu(lonk_url, honk_url, gethonks_answer)
552 e03b8dd3 2024-09-25 continue def new_client_stage_1_ask_server(lonk_url):
553 577b12da 2024-09-24 continue if not lonk_url.query:
554 0fc41442 2024-09-25 continue print("10 Honk server URL\r")
556 577b12da 2024-09-24 continue splitted = urlsplit(unquote(lonk_url.query))
557 e03b8dd3 2024-09-25 continue path = [quote(urlunsplit((splitted.scheme, splitted.netloc, "", "", "")), safe=""), "ask_username"]
558 e03b8dd3 2024-09-25 continue print(f'30 {lonk_url.build(path)}\r')
561 e03b8dd3 2024-09-25 continue def new_client_stage_2_ask_username(lonk_url):
562 e03b8dd3 2024-09-25 continue if not lonk_url.query:
563 e03b8dd3 2024-09-25 continue print("10 Honk user name\r")
565 e03b8dd3 2024-09-25 continue if len(lonk_url.splitted_path) < 3:
566 e03b8dd3 2024-09-25 continue print('59 Bad request\r')
568 e03b8dd3 2024-09-25 continue quoted_server = lonk_url.splitted_path[-2]
569 e03b8dd3 2024-09-25 continue path = [quoted_server, quote(unquote(lonk_url.query), safe=""), "ask_password"]
570 e03b8dd3 2024-09-25 continue print(f'30 {lonk_url.build(path)}\r')
573 e03b8dd3 2024-09-25 continue def new_client_stage_3_ask_password(cert_hash, lonk_url):
574 e03b8dd3 2024-09-25 continue if not lonk_url.query:
575 b0c5ad68 2024-09-30 continue print("11 Honk user password\r")
577 e03b8dd3 2024-09-25 continue if len(lonk_url.splitted_path) < 4:
578 e03b8dd3 2024-09-25 continue print('59 Bad request\r')
581 e03b8dd3 2024-09-25 continue honk_url = unquote(lonk_url.splitted_path[-3])
582 e03b8dd3 2024-09-25 continue post_data = {
583 e03b8dd3 2024-09-25 continue "username": unquote(lonk_url.splitted_path[-2]),
584 e03b8dd3 2024-09-25 continue "password": unquote(lonk_url.query),
585 e03b8dd3 2024-09-25 continue "gettoken": "1",
587 f6854b29 2024-10-05 continue with urlopen(honk_url + "/dologin", data=urlencode(post_data).encode(), timeout=15) as response:
588 f6854b29 2024-10-05 continue token = response.read().decode("utf8")
589 4e7950e1 2024-09-30 continue db_con = db_connect()
590 e03b8dd3 2024-09-25 continue with db_con:
591 e03b8dd3 2024-09-25 continue db_con.execute(
592 e03b8dd3 2024-09-25 continue "INSERT INTO client (cert_hash, honk_url, token) VALUES (?, ?, ?)",
593 e03b8dd3 2024-09-25 continue (cert_hash, honk_url, token)
595 e03b8dd3 2024-09-25 continue print(f'30 {lonk_url.build([])}\r')
598 f6854b29 2024-10-05 continue def bonk(db_con, client_id, lonk_url, honk_url):
599 f6854b29 2024-10-05 continue what = {pair[0]: pair[1] for pair in parse_qsl(lonk_url.query)}.get("w")
600 f6854b29 2024-10-05 continue if not what:
601 f6854b29 2024-10-05 continue print("51 Not found\r")
604 056d3709 2024-10-10 continue honk_url.get("zonkit", wherefore="bonk", what=what, answer_is_json=False)
605 3601f1e9 2024-10-10 continue print(f'30 {lonk_url.build("myhonks")}\r')
608 3efde7cb 2024-10-10 continue def gethonkers(db_con, client_id, lonk_url, honk_url):
609 3efde7cb 2024-10-10 continue print("20 text/gemini\r")
610 3efde7cb 2024-10-10 continue print("# 𝓗 onk: honkers\r")
611 3efde7cb 2024-10-10 continue print("\r")
613 3efde7cb 2024-10-10 continue menu(lonk_url, honk_url)
614 3efde7cb 2024-10-10 continue print("\r")
616 056d3709 2024-10-10 continue honkers = honk_url.get("gethonkers").get("honkers") or []
617 3efde7cb 2024-10-10 continue for honker in honkers:
618 3efde7cb 2024-10-10 continue print(f'## {honker.get("Name") or honker["XID"]}\r')
619 3efde7cb 2024-10-10 continue for field_name, display_name in zip(("Name", "XID", "Flavor"), ("name", "url", "flavor")):
620 3efde7cb 2024-10-10 continue value = honker.get(field_name)
621 3efde7cb 2024-10-10 continue if value:
622 3efde7cb 2024-10-10 continue print(f'{display_name}: {value}\r')
623 3efde7cb 2024-10-10 continue if honker.get("Flavor") == "sub":
624 3efde7cb 2024-10-10 continue print(f'=> {lonk_url.build("unsubscribe", urlencode({"honkerid": honker["ID"]}))} unsubscribe\r')
626 3efde7cb 2024-10-10 continue print(f'=> {lonk_url.build("subscribe", urlencode({"honkerid": honker["ID"]}))} (re)subscribe\r')
627 3efde7cb 2024-10-10 continue print('\r')
629 3efde7cb 2024-10-10 continue if honkers:
630 3efde7cb 2024-10-10 continue print("\r")
631 3efde7cb 2024-10-10 continue menu(lonk_url, honk_url)
634 3efde7cb 2024-10-10 continue def addhonker(db_con, client_id, lonk_url, honk_url):
635 3efde7cb 2024-10-10 continue if not lonk_url.query:
636 056d3709 2024-10-10 continue print("10 honker url: \r")
639 3efde7cb 2024-10-10 continue url = unquote(lonk_url.query)
641 056d3709 2024-10-10 continue honk_url.get("savehonker", url=url, answer_is_json=False)
642 3efde7cb 2024-10-10 continue print(f'30 {lonk_url.build("gethonkers")}\r')
643 3efde7cb 2024-10-10 continue except HTTPError as error:
644 3efde7cb 2024-10-10 continue print("20 text/gemini\r")
645 3efde7cb 2024-10-10 continue print("# 𝓗 onk: add new honker\r")
646 3efde7cb 2024-10-10 continue print("\r")
647 3efde7cb 2024-10-10 continue menu(lonk_url, honk_url)
648 3efde7cb 2024-10-10 continue print("\r")
649 3efde7cb 2024-10-10 continue print('## Error\r')
650 3efde7cb 2024-10-10 continue print(f'> {error.fp.read().decode("utf8")}\r')
653 3efde7cb 2024-10-10 continue def unsubscribe(db_con, client_id, lonk_url, honk_url):
654 3efde7cb 2024-10-10 continue honkerid = {pair[0]: pair[1] for pair in parse_qsl(lonk_url.query)}.get("honkerid")
655 3efde7cb 2024-10-10 continue if not honkerid:
656 3efde7cb 2024-10-10 continue print("51 Not found\r")
659 3efde7cb 2024-10-10 continue url = unquote(lonk_url.query)
660 056d3709 2024-10-10 continue honk_url.get("savehonker", honkerid=honkerid, unsub="unsub", answer_is_json=False)
661 3efde7cb 2024-10-10 continue print(f'30 {lonk_url.build("gethonkers")}\r')
664 3efde7cb 2024-10-10 continue def subscribe(db_con, client_id, lonk_url, honk_url):
665 3efde7cb 2024-10-10 continue honkerid = {pair[0]: pair[1] for pair in parse_qsl(lonk_url.query)}.get("honkerid")
666 3efde7cb 2024-10-10 continue if not honkerid:
667 3efde7cb 2024-10-10 continue print("51 Not found\r")
670 3efde7cb 2024-10-10 continue url = unquote(lonk_url.query)
671 056d3709 2024-10-10 continue honk_url.get("savehonker", honkerid=honkerid, sub="sub", answer_is_json=False)
672 3efde7cb 2024-10-10 continue print(f'30 {lonk_url.build("gethonkers")}\r')
675 056d3709 2024-10-10 continue def newhonk(db_con, client_id, lonk_url, honk_url):
676 056d3709 2024-10-10 continue if not lonk_url.query:
677 056d3709 2024-10-10 continue print("10 let's make some noise: \r")
680 056d3709 2024-10-10 continue noise = unquote(lonk_url.query)
681 056d3709 2024-10-10 continue honk_url.get("honk", noise=noise, answer_is_json=False)
682 056d3709 2024-10-10 continue print(f'30 {lonk_url.build("myhonks")}\r')
685 056d3709 2024-10-10 continue def honkback(db_con, client_id, lonk_url, honk_url):
686 056d3709 2024-10-10 continue if not lonk_url.query:
687 056d3709 2024-10-10 continue handles = unquote(lonk_url.splitted_path[-3]).strip()
688 056d3709 2024-10-10 continue rid = unquote(lonk_url.splitted_path[-2])
689 056d3709 2024-10-10 continue print(f"10 Answer to {handles or rid}:\r")
692 056d3709 2024-10-10 continue noise = unquote(lonk_url.query)
693 056d3709 2024-10-10 continue rid = unquote(lonk_url.splitted_path[-2])
694 056d3709 2024-10-10 continue honk_url.get("honk", noise=noise, rid=rid, answer_is_json=False)
695 056d3709 2024-10-10 continue print(f'30 {lonk_url.build("myhonks")}\r')
698 895356d0 2024-10-01 continue def authenticated(cert_hash, lonk_url, fn_impl):
699 895356d0 2024-10-01 continue db_con = db_connect()
700 895356d0 2024-10-01 continue row = db_con.execute("SELECT client_id, honk_url, token FROM client WHERE cert_hash=?", (cert_hash, )).fetchone()
701 895356d0 2024-10-01 continue if not row:
702 895356d0 2024-10-01 continue print(f'30 {lonk_url.build("ask_server")}\r')
704 895356d0 2024-10-01 continue client_id, honk_url, token = row
705 895356d0 2024-10-01 continue with db_con:
706 fafb2ce7 2024-10-02 continue fn_impl(db_con, client_id, lonk_url, HonkUrl(honk_url, token))
709 b7194e03 2024-09-12 continue def proxy(mime, url):
710 f6854b29 2024-10-05 continue with urlopen(url, timeout=10) as response:
711 15efabff 2024-09-13 continue stdout.buffer.write(b"20 " + mime.encode() + b"\r\n")
712 15efabff 2024-09-13 continue while True:
713 f6854b29 2024-10-05 continue content = response.read(512 * 1024)
714 15efabff 2024-09-13 continue if not content:
716 15efabff 2024-09-13 continue stdout.buffer.write(content)
719 4acbf994 2024-09-25 continue def vgi(cert_hash, raw_url):
720 4acbf994 2024-09-25 continue lonk_url = LonkUrl(raw_url)
721 e03b8dd3 2024-09-25 continue if lonk_url.page == "lonk":
722 8b4f10af 2024-10-03 continue authenticated(cert_hash, lonk_url, page_lonk)
723 895356d0 2024-10-01 continue elif lonk_url.page == "convoy":
724 8b4f10af 2024-10-03 continue authenticated(cert_hash, lonk_url, page_convoy)
725 895356d0 2024-10-01 continue elif lonk_url.page == "atme":
726 8b4f10af 2024-10-03 continue authenticated(cert_hash, lonk_url, page_atme)
727 2ff52a39 2024-10-09 continue elif lonk_url.page == "search":
728 2ff52a39 2024-10-09 continue authenticated(cert_hash, lonk_url, page_search)
729 b5ebf4c7 2024-10-09 continue elif lonk_url.page == "longago":
730 b5ebf4c7 2024-10-09 continue authenticated(cert_hash, lonk_url, page_longago)
731 3601f1e9 2024-10-10 continue elif lonk_url.page == "myhonks":
732 3601f1e9 2024-10-10 continue authenticated(cert_hash, lonk_url, page_myhonks)
733 3601f1e9 2024-10-10 continue elif lonk_url.page == "honker":
734 3601f1e9 2024-10-10 continue authenticated(cert_hash, lonk_url, page_honker)
735 f6854b29 2024-10-05 continue elif lonk_url.page == "bonk":
736 f6854b29 2024-10-05 continue authenticated(cert_hash, lonk_url, bonk)
737 3efde7cb 2024-10-10 continue elif lonk_url.page == "gethonkers":
738 3efde7cb 2024-10-10 continue authenticated(cert_hash, lonk_url, gethonkers)
739 3efde7cb 2024-10-10 continue elif lonk_url.page == "addhonker":
740 3efde7cb 2024-10-10 continue authenticated(cert_hash, lonk_url, addhonker)
741 3efde7cb 2024-10-10 continue elif lonk_url.page == "unsubscribe":
742 3efde7cb 2024-10-10 continue authenticated(cert_hash, lonk_url, unsubscribe)
743 3efde7cb 2024-10-10 continue elif lonk_url.page == "subscribe":
744 3efde7cb 2024-10-10 continue authenticated(cert_hash, lonk_url, subscribe)
745 056d3709 2024-10-10 continue elif lonk_url.page == "newhonk":
746 056d3709 2024-10-10 continue authenticated(cert_hash, lonk_url, newhonk)
747 056d3709 2024-10-10 continue elif lonk_url.page == "honkback":
748 056d3709 2024-10-10 continue authenticated(cert_hash, lonk_url, honkback)
749 e03b8dd3 2024-09-25 continue elif lonk_url.page == "ask_server":
750 e03b8dd3 2024-09-25 continue new_client_stage_1_ask_server(lonk_url)
751 e03b8dd3 2024-09-25 continue elif lonk_url.page == "ask_username":
752 e03b8dd3 2024-09-25 continue new_client_stage_2_ask_username(lonk_url)
753 e03b8dd3 2024-09-25 continue elif lonk_url.page == "ask_password":
754 e03b8dd3 2024-09-25 continue new_client_stage_3_ask_password(cert_hash, lonk_url)
755 e03b8dd3 2024-09-25 continue elif lonk_url.page == "proxy":
756 e03b8dd3 2024-09-25 continue query = {pair[0]: pair[1] for pair in parse_qsl(lonk_url.query)}
757 b7194e03 2024-09-12 continue if "m" not in query or "u" not in query:
758 b7194e03 2024-09-12 continue print("51 Not found\r")
760 b7194e03 2024-09-12 continue proxy(mime=query["m"], url=query["u"])
762 b7194e03 2024-09-12 continue print("51 Not found\r")
765 383d16ea 2024-09-04 continue if __name__ == '__main__':
766 4e7950e1 2024-09-30 continue cert_hash_ = environ.get("VGI_CERT_HASH")
767 4e7950e1 2024-09-30 continue if cert_hash_:
769 4acbf994 2024-09-25 continue start_time = clock_gettime(CLOCK_MONOTONIC)
771 4e7950e1 2024-09-30 continue input_url = input().strip()
772 4e7950e1 2024-09-30 continue vgi(cert_hash_, input_url)
773 4acbf994 2024-09-25 continue finally:
774 4e7950e1 2024-09-30 continue stderr.write(f"{cert_hash_}|{input_url}|{clock_gettime(CLOCK_MONOTONIC) - start_time:.3f}sec.\n")
775 15efabff 2024-09-13 continue except HTTPError as error:
776 3efde7cb 2024-10-10 continue stderr.write(f"{error}\n")
777 15efabff 2024-09-13 continue print(f"43 Remote server return {error.code}: {error.reason}\r")
778 15efabff 2024-09-13 continue except URLError as error:
779 3efde7cb 2024-10-10 continue stderr.write(f"{error}\n")
780 15efabff 2024-09-13 continue print(f"43 Error while trying to access remote server: {error.reason}\r")
781 dd301323 2024-09-17 continue except TimeoutError as error:
782 3efde7cb 2024-10-10 continue stderr.write(f"{error}\n")
783 dd301323 2024-09-17 continue print(f"43 Error while trying to access remote server: {error}\r")
785 3efde7cb 2024-10-10 continue stderr.write("Certificate required\n")
786 b7194e03 2024-09-12 continue print("60 Certificate required\r")