diff --git a/kevin/.gitignore b/kevin/.gitignore new file mode 100644 index 0000000..806fbfb --- /dev/null +++ b/kevin/.gitignore @@ -0,0 +1,3 @@ +.direnv +.envrc +.history diff --git a/kevin/epub.css b/kevin/epub.css new file mode 100644 index 0000000..cb7e08e --- /dev/null +++ b/kevin/epub.css @@ -0,0 +1,3 @@ +body{margin:40px auto;max-width:650px;line-height:1.6;font-size:18px;color:#444;padding:0} +a{color:inherit;text-decoration:none} +a:hover{text-decoration:underline} diff --git a/kevin/keinverlag b/kevin/keinverlag new file mode 100755 index 0000000..cde6f39 --- /dev/null +++ b/kevin/keinverlag @@ -0,0 +1,61 @@ +#!/bin/sh + +alias to_utf8='iconv -f latin1 -t utf8' +alias curl_GET='curl -X GET -s -G' + +BASE_URL=https://www.keinverlag.de + +kv_author_id () { + if [ $# -ne 1 ]; then + echo Please call kv_author_id with an author name. >/dev/stderr + exit 1 + fi + + author_name=$1 + + curl_GET "$BASE_URL/$author_name.kv" \ + | to_utf8 \ + | sed -n 's/.*autor=\([0-9]\+\).*/\1/p' \ + | head -1 +} + +kv_text () { + if [ $# -ne 1 ]; then + echo Please call kv_text with a text ID. >/dev/stderr + exit 1 + fi + + text_id=$1 + + curl_GET "$BASE_URL/$text_id.text" \ + | to_utf8 \ + | sed -n '/

/,//p' \ + | sed 's/

.\+<\/h3>//g' \ + | pandoc -f html -t plain +} + +kv_author_texts () { + if [ $# -ne 1 ]; then + echo Please call kv_author_texts with an author ID. >/dev/stderr + exit 1 + fi + + author_id=$1 + + curl_GET "$BASE_URL/autorentexte.php" -d sortby=tnr -d start=0 -d limit=10000 -d autor="$author_id" \ + | to_utf8 \ + | sed -n 's/.*
  • .*/\1/p' +} + +case $1 in + text) + shift + kv_text "$@";; + author) + shift + for text_id in $(kv_author_texts "$(kv_author_id "$@")" | uniq); do + kv_text "$text_id" + done ;; + *) + echo >/dev/stderr "Usage: $0 text|author ID" +esac diff --git a/kevin/kevin.py b/kevin/kevin.py new file mode 100755 index 0000000..9b4cbe1 --- /dev/null +++ b/kevin/kevin.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +from argparse import ArgumentParser +from bs4 import BeautifulSoup +from typing import List +import re +import requests + + +def soup_from(response): + return BeautifulSoup(response.text, "lxml") + + +class Author: + def __init__(self, author_id: int) -> None: + response = requests.get( + "https://www.keinverlag.de/autorentexte.php", + params={"start": 0, "limit": 10000, "sortby": "tnr", "author": author_id}, + ) + soup = soup_from(response) + self.texts = [] # type: List[Text] + for text in soup.select('ul.textliste > li > a[href$=".text"]'): + # strip off the last five characters (".text") + text_id = int(text["href"][:-5]) + try: + self.texts.append(Text(text_id)) + except ValueError: + continue + + def markdown(self, *, with_type: bool = False) -> str: + name = self.texts[0].author + + def __gen(): + yield "% {}".format(name) + for text in self.texts: + yield "\n\n* * *\n\n" + yield text.markdown(with_author=False, with_type=with_type) + + return "\n".join(__gen()) + + +class Text: + def __init__(self, text_id: int) -> None: + normalization = { + 132: '"', + 147: '"', + 0x96: "--", + 0x91: "'", + 0x92: "'", + 0x97: "---", + } + text_url = "https://www.keinverlag.de/{}.text".format(text_id) + soup = soup_from(text_url) + try: + self.title = soup.select("h1 > span")[0].text.translate(normalization) + self.content = BeautifulSoup( + re.sub( + r'(([\n\r]|.)*?)', + r"_\1_", + str(soup.select(".fliesstext > span")[0]), + ), + "lxml", + ).text.translate(normalization) + self.author = soup.select("h3 > a")[2].text + self.type = soup.select("h1 ~ h3")[0].text + except IndexError: + raise ValueError("Text {} not available.".format(text_id)) + + def markdown(self, *, with_author: bool = True, with_type: bool = False) -> str: + return "#### {maybe_author}{title}{maybe_type}\n\n{content}".format( + title=self.title, + maybe_author=self.author + ": " if with_author else "", + maybe_type=" (" + self.type + ")" if with_type else "", + content="\n".join( + line + "\\" if line else "" for line in self.content.splitlines() + ), + ) + + +if __name__ == "__main__": + parser = ArgumentParser() + parser.add_argument("--type", help="Include text type", action="store_true") + subparsers = parser.add_subparsers() + + handle_text = subparsers.add_parser("text", help="Handle one text") + handle_text.add_argument("tid", help="KeinVerlag text id", type=int) + handle_text.set_defaults( + func=lambda a: print(Text(a.tid).markdown(with_type=a.type)) + ) + + handle_author = subparsers.add_parser( + "author", help="Handle all texts by an author" + ) + handle_author.add_argument("aid", help="KeinVerlag author id", type=str) + handle_author.set_defaults( + func=lambda a: print(Author(a.aid).markdown(with_type=a.type)) + ) + + args = parser.parse_args() + args.func(args) diff --git a/kevin/kevin.sh b/kevin/kevin.sh new file mode 100755 index 0000000..474f091 --- /dev/null +++ b/kevin/kevin.sh @@ -0,0 +1,2 @@ +#!/bin/sh +python3 kevin.py author "$1" | pandoc -f markdown+smart --table-of-contents --toc-depth=6 --standalone --css=epub.css -o "$2" diff --git a/kevin/shell.nix b/kevin/shell.nix new file mode 100644 index 0000000..59c35ed --- /dev/null +++ b/kevin/shell.nix @@ -0,0 +1,10 @@ +{ pkgs ? import {} }: +pkgs.mkShell { + buildInputs = with pkgs; [ + pandoc + python3Packages.beautifulsoup4 + python3Packages.requests + python3Packages.lxml + ]; + shellHook = "export HISTFILE=${toString ./.history}"; +}