Skip to content

Instantly share code, notes, and snippets.

@ookiineko
Last active October 11, 2023 09:10
Show Gist options
  • Save ookiineko/012dc790ac129bae42218830fc0a173f to your computer and use it in GitHub Desktop.
Save ookiineko/012dc790ac129bae42218830fc0a173f to your computer and use it in GitHub Desktop.
images2telegraph - A simple Telegraph gallery uploading tool with CLI

An example usage

images2telegraph.py -c path/to/config.json --author='Me' --homepage='https://example.com' \
                    Sample-Page-Path ThisIsATitle --manifest path/to/files.list.txt

note: -c path/to/config.json can be omitted if it's under the current directory

Using stdin

images2telegraph.py -c path/to/config.json Sample-Page-Path ThisIsATitle --manifest - < path/to/files.list.txt

# OR (for demonstration, please DONT abuse `cat`)
cat path/to/files.list.txt | images2telegraph.py -c path/to/config.json Sample-Page-Path ThisIsATitle --manifest -

# Another idea is to use `find`, eg. to list all files under a directory
find path/to/images/dir -type f | image2telegraph.py -c path/to/config.json Sample-Page-Path ThisIsATitle --manifest -

# note: you could use other command-line tools or scripts for sorting the file list
#       or simply do it manually if you only have a few files, and then specify the manifest file instead

Example of a config.json

{
   "access_token": "FILLME"
}

Example of a files.list.txt

path/to/image-1.png
path/to/image-2.png
path/to/image-3.png
...
path/to/image-N.png

(You should only use .jpg, .jpeg, .png and .gif files)

Technical details

  • HTML tag manipulation using dominate
  • Telegraph tags JSON conversion, Page/image uploading API using html_telegraph_poster
#!/usr/bin/env python3
"""
images2telegraph - A simple Telegraph gallery uploading tool with CLI
"""
import json
import os.path
import os
import traceback
import argparse
from dataclasses import dataclass
from typing import (Iterable, Optional)
from argparse import (ArgumentParser, FileType)
from requests.exceptions import RequestException
import rich
from dominate import tags
from html_telegraph_poster import TelegraphPoster
from html_telegraph_poster.errors import Error
from html_telegraph_poster.utils import DocumentPreprocessor
SCRIPT_URL = ('https://gist.github.com/ookiineko/'
'012dc790ac129bae42218830fc0a173f')
ALLOWED_EXTS = ('jpg', 'jpeg', 'png', 'gif')
@dataclass
class PageSpec:
"""
just a handy data structure for passing many args
"""
title: Optional[str]
author: Optional[str]
homepage: Optional[str]
html: str
@dataclass
class UploadOpt:
"""
options for upload()
"""
jobs: int
before: bool
dry_run: bool
def _check_path(path: str):
"""
err if path is not a valid file (doesn't exist or is a dir)
"""
assert os.path.isfile(path) and os.access(path, os.R_OK), \
f'path {path!r} is invalid or not readable'
def _check_ext(path: str):
"""
err if file extension is not allowed by Telegraph server
"""
ext = path.rsplit('.', 1)[-1]
assert ext in ALLOWED_EXTS, f'extension {ext!r} is ' \
f'not allowed in path {path!r}'
def _get_clean_lines(lines: str) -> Iterable[str]:
"""
strip the beginning and trailing whitespace
chars and filter empty lines
"""
return filter(bool, (line.strip() for line in lines.split('\n')))
def generate_html(manifest: Iterable[str], title: str) -> str:
"""
generate a fixed shitty HTML layout for the gallery
from the image files
note: local path will be uploaded and converted to URLs
pointing to Telegraph server later in upload()
"""
# prepare figs
figs = []
idx = -1
for idx, path in enumerate(manifest):
_check_path(path)
_check_ext(path)
with tags.figure() as fig:
# attach img to fig
tags.img(src=path)
figs.append(fig)
count = idx + 1
# add figcaption for each fig
for idx, fig in enumerate(figs):
with fig:
with tags.figcaption():
tags.i(f'Page {idx + 1} of {count} - {title}')
# construct the gallery layout
with tags.h3() as prologue:
tags.i(f'(Total {count} page(s) to read)')
root_elems = [
prologue
]
# added padding between each fig
for fig in figs:
root_elems.append(fig)
with tags.p() as pad_para:
tags.br()
root_elems.append(pad_para)
with tags.h4() as epilogue:
with tags.i('This gallery is automatically generated with '):
tags.a('images2telegraph', href=SCRIPT_URL)
root_elems.append(epilogue)
# render html
return '\n'.join(
root_elem.render(pretty=True) for root_elem in root_elems)
def __dummy(*_args, **_kwargs):
pass
def upload(access_token: str, page_path: str, spec: PageSpec,
opts: UploadOpt) -> Optional[PageSpec]:
"""
upload a page to Telegraph
"""
poster = TelegraphPoster(use_api=True, access_token=access_token)
# get the old page info and content (if requested)
old_page: Optional[dict] = None
try:
old_page: dict = poster.get_page(page_path,
return_content=opts.before)
except KeyError:
pass
if old_page is None:
raise Error(f'page path {page_path!r} does not exist')
res = None
if opts.before:
res = PageSpec(title=old_page['title'],
author=old_page.get('author_name'),
homepage=old_page.get('author_url'),
html=old_page['html'])
if not opts.dry_run:
# upload images
pre_proc = DocumentPreprocessor(spec.html)
# HACK: buggy function??
# pylint: disable=protected-access
old_mla_func = pre_proc._make_links_absolute
pre_proc._make_links_absolute = __dummy
pre_proc.upload_all_images(max_workers=opts.jobs)
pre_proc._make_links_absolute = old_mla_func
# get new HTML with remote image URLs
html = pre_proc.get_processed_html()
# do the stuff
poster.edit(title=spec.title, author=spec.author, text=html,
author_url=spec.homepage, path=page_path)
return res
def _main(args: argparse.Namespace):
"""
main
"""
try:
# read file inputs
with args.config as file:
cdict = json.loads(file.read())
with args.manifest as file:
manifest = _get_clean_lines(file.read())
except json.JSONDecodeError:
traceback.print_exc()
parser.error('config file does not valid JSON str')
except OSError:
traceback.print_exc()
parser.error('unable to read due to above err')
else:
# check config
assert isinstance(cdict, dict), 'config is not a dict'
assert (access_token := cdict.get('access_token')) is not None, \
'access_token is not in config'
assert isinstance(access_token, str), 'access_token is not a str'
# check other args
assert args.jobs > 0, f'illegal job number \'{args.jobs}\''
# do the stuff
html = generate_html(manifest, args.title)
try:
old_spec = upload(access_token, args.page_path,
PageSpec(title=args.title, author=args.author,
homepage=args.homepage, html=html),
UploadOpt(args.jobs, args.before, args.debug))
except RequestException:
traceback.print_exc()
parser.error('failed due to above network error')
except Error:
traceback.print_exc()
parser.error('failed due to above err')
else:
if args.before:
rich.print(old_spec)
if __name__ == '__main__':
parser = ArgumentParser('images2telegraph',
description='A simple program that generates '
'a gallery page from a set of images '
'and uploads it to Telegraph')
parser.add_argument('--config', '-c',
type=FileType('r'), default='config.json',
help='path to the configuration JSON '
'file (default is \'config.json\')')
# pylint: disable=consider-using-with, unspecified-encoding
parser.add_argument('--manifest', '-m',
type=FileType('r'), default=open(os.devnull, 'r'),
help='path to a text file where each line contains '
'the path to an image file (use a dash "-" to '
'read from stdin, default is a dummy file')
parser.add_argument('--jobs', '-j', type=int, default=3,
help='thread number for uploading images to '
'Telegraph server (default is 3)')
parser.add_argument('--before', '-b', action='store_true',
help='print the previous page info and content before '
'overwriting (default is disabled)')
parser.add_argument('--debug', '-d', action='store_true',
help='to enable debugging mode, aka dry run '
'(default is disabled)')
parser.add_argument('--author', '-a', type=str,
default=None, help='desired author name (cleared '
'if not specified)')
parser.add_argument('--homepage', '-u', type=str,
default=None, help='desired author url (cleared '
'if not specified)')
parser.add_argument('page_path', type=str,
help='path that refers to a previously created '
'Telegraph page to work with')
parser.add_argument('title', type=str,
help='desired page title (required)')
_main(parser.parse_args())
rich
dominate
html-telegraph-poster
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment