#!/usr/bin/env python3 """Обновить Telegra.ph страницу содержимым markdown-файла.""" import json import re import subprocess import sys import urllib.parse import urllib.request ACCESS_TOKEN = "c38dcadb86e6edd7efc76496d9171d38beef6dc0f6a7ef2cd79bbae70e46" PATH = "Nastrojka-VPN-04-24-2" TITLE = "Настройка VPN" AUTHOR = "Олег" # --- Inline markdown parser --- # Order: 1) protect `code` and [text](url) with placeholders # 2) parse bold/italic on remaining text # 3) restore placeholders CODE_RE = re.compile(r"`([^`]+?)`") LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)") BOLD_RE = re.compile(r"\*\*(.+?)\*\*") ITALIC_STAR_RE = re.compile(r"(? last: out.append(n[last:m.start()]) out.append({"tag": tag, "children": parse_inline(m.group(1))}) last = m.end() if last < len(n): out.append(n[last:]) return out nodes = [text] nodes = apply_pattern(nodes, BOLD_RE, "strong") nodes = apply_pattern(nodes, ITALIC_STAR_RE, "em") nodes = apply_pattern(nodes, ITALIC_UND_RE, "em") # 4) Restore placeholders in string nodes result = [] for n in nodes: if isinstance(n, str): parts = re.split(r"(\x00PH\d+\x00)", n) for p in parts: if p == "": continue if p in placeholders: result.append(placeholders[p]) else: result.append(p) else: result.append(n) return result # --- Block parser with nested list support --- def indent_of(line): return len(line) - len(line.lstrip(" ")) def is_ol_item(line): return re.match(r"^\s*\d+\.\s+", line) def is_ul_item(line): return re.match(r"^\s*[-*]\s+", line) def strip_list_marker(line): m = re.match(r"^\s*(?:\d+\.|[-*])\s+(.*)$", line) return m.group(1) if m else line def parse_list(lines, i, base_indent): """Parse a list starting at lines[i] with base_indent. Returns (node, new_i).""" first = lines[i] is_ol = bool(is_ol_item(first)) tag = "ol" if is_ol else "ul" items = [] while i < len(lines): line = lines[i] if not line.strip(): # blank line — check if list continues if i + 1 < len(lines): nxt = lines[i + 1] if nxt.strip() and indent_of(nxt) == base_indent and (is_ol_item(nxt) or is_ul_item(nxt)): i += 1 continue break ind = indent_of(line) if ind < base_indent: break if ind > base_indent: # shouldn't happen at top — break break if not (is_ol_item(line) or is_ul_item(line)): break # same-kind check: if switching from ol→ul at same indent, break if (is_ol and is_ul_item(line) and not is_ol_item(line)) or \ (not is_ol and is_ol_item(line) and not is_ul_item(line)): break # This is a list item at base_indent text = strip_list_marker(line) i += 1 continuation_text = [] nested_children = [] # Consume continuation lines and nested lists while i < len(lines): nl = lines[i] if not nl.strip(): # peek ahead if i + 1 >= len(lines): i += 1 break nxt = lines[i + 1] if nxt.strip() and indent_of(nxt) > base_indent: i += 1 continue # end of item break ni = indent_of(nl) if ni <= base_indent: break # Nested list? if is_ol_item(nl) or is_ul_item(nl): nested, i = parse_list(lines, i, ni) nested_children.append(nested) continue # continuation line continuation_text.append(nl.strip()) i += 1 full_text = text if continuation_text: full_text = (text + " " + " ".join(continuation_text)).strip() children = parse_inline(full_text) + nested_children items.append({"tag": "li", "children": children}) return {"tag": tag, "children": items}, i def parse_blocks(md): lines = md.splitlines() nodes = [] i = 0 while i < len(lines): line = lines[i] if not line.strip(): i += 1 continue # heading m = re.match(r"^(#{1,6})\s+(.+)$", line) if m: level = len(m.group(1)) tag = "h3" if level <= 2 else "h4" nodes.append({"tag": tag, "children": parse_inline(m.group(2).strip())}) i += 1 continue # hr if re.match(r"^-{3,}\s*$", line): nodes.append({"tag": "hr"}) i += 1 continue # fenced code if line.strip().startswith("```"): i += 1 buf = [] while i < len(lines) and not lines[i].strip().startswith("```"): buf.append(lines[i]) i += 1 i += 1 nodes.append({"tag": "pre", "children": ["\n".join(buf)]}) continue # list if is_ol_item(line) or is_ul_item(line): node, i = parse_list(lines, i, indent_of(line)) nodes.append(node) continue # paragraph buf = [line] i += 1 while i < len(lines): nxt = lines[i] if not nxt.strip(): break if re.match(r"^#{1,6}\s", nxt): break if re.match(r"^-{3,}\s*$", nxt): break if is_ol_item(nxt) or is_ul_item(nxt): break if nxt.strip().startswith("```"): break buf.append(nxt) i += 1 # Preserve line breaks within paragraph using
— Telegraph supports it # Strip trailing markdown hard-break " " marker cleaned = [re.sub(r"\s+$", "", b) for b in buf] # Join with space, inserting br between lines that were originally separated inline_children = [] for idx, part in enumerate(cleaned): if idx > 0: inline_children.append({"tag": "br"}) inline_children.extend(parse_inline(part)) nodes.append({"tag": "p", "children": inline_children}) return nodes def main(): md = subprocess.check_output( ["sshpass", "-p", "1qaz!QAZ", "ssh", "-o", "StrictHostKeyChecking=no", "root@10.0.0.250", "pct exec 137 -- cat /tmp/vpn-instruction-improved.md"], text=True ) content = parse_blocks(md) # Dry-run preview if "--dry" in sys.argv: print(json.dumps(content, ensure_ascii=False, indent=2)) return data = urllib.parse.urlencode({ "access_token": ACCESS_TOKEN, "title": TITLE, "author_name": AUTHOR, "content": json.dumps(content, ensure_ascii=False), "return_content": "false", }).encode() req = urllib.request.Request(f"https://api.telegra.ph/editPage/{PATH}", data=data) with urllib.request.urlopen(req) as resp: result = json.loads(resp.read().decode()) print(json.dumps(result, ensure_ascii=False, indent=2)) if not result.get("ok"): sys.exit(1) if __name__ == "__main__": main()