commit 86729cc95e7f5ff6c502de20b2685f190eee8910 from: Aleksey Ryndin date: Tue Jan 28 11:05:01 2025 UTC Add first worked version commit - a58fea8282cec6d0d2c2301d91ae5dd72c46a8f1 commit + 86729cc95e7f5ff6c502de20b2685f190eee8910 blob - a0990367ef8b03c70c29d285e22ef85907e1d0b7 blob + f6bd4a8274f8b4773299c3a8cb594f0afb7a5773 --- README +++ README @@ -1 +1,83 @@ -TBD +# Агрегатор "Катастеризм" ("Katasterismos") + +Агрегатор русскоязычных gemini-публикаций "Катастеризм" ("Katasterismos"), отображающий перекрестные ссылки между публикациями. + + +## Работающий экземпляр агрегатора + +Результат ежедневной работы агрегатора доступен по следующему адресу: + +=> gemini://pub.phreedom.club/~katasterismos/ + +На этой странице собраны публикации за последние восемь недель. + + +## Идея агрегатора + +Идея была нагло украдена из ветки обсуждений "Реквестирую русскоязычный аналог Cosmos" на GSR: +=> gemini://geminispace.ru/s/74/ Реквестирую русскоязычный аналог Cosmos - агрегатор, который составляет треды из ссылающихся на кого-то ещё посты. + + +## Исходный код + +Код агрегатора написан на Python3 и доступен в git-репозитории: +``` +ssh://anonymous@got.any-key.press/katasterismos +``` +=> https://got.any-key.press/?action=summary&path=katasterismos.git Web интерфейс к git репозиторию + +=> gemini://any-key.press/tt/welcome.gmi Сделано в соответствии с духом Тривиальных Технологий + + +## Ленты публикаций агрегатора + +Ниже приведены ссылки, на которые подписан проект "Катастеризм". + +Критерии коллекции: +* русскоязычные содержимое (хотя бы частично) +* содержимое соответствует формату "Subscribing to Gemini pages" + +=> gemini://geminiprotocol.net/docs/companion/subscription.gmi Subscribing to Gemini pages + +Вы всегда можете помочь актуализировать этот список: напишите письмо на электронную почту +``` +katasterismos@to.any-key.press +``` +=> mailto:katasterismos@to.any-key.press + +### [FEEDS] Ленты публикаций, за которыми следит агрегатор + +=> gemini://academia.fzrw.info/ru/blog +=> gemini://academia.fzrw.info/ru/encyclopedia +=> gemini://academia.fzrw.info/ru/publications +=> gemini://alexey.shpakovsky.ru/rulog/ +=> gemini://any-key.press/vgi/atom2gemfeed/?gemini%3A%2F%2Fany-key.press%2Fatom.xml +=> gemini://armitage.flounder.online/gemlog/ +=> gemini://any-key.press/vgi/atom2gemfeed/?gemini%3A%2F%2Fbasnja.ru%2Fatom.xml +=> gemini://bbs.geminispace.org/s/russian?feed +=> gemini://byzoni.org/gemlog.gmi +=> gemini://causa-arcana.com/ru/blog/feed.gmi +=> gemini://gemini.quietplace.xyz/~razzlom/gemlog/ +=> gemini://gemlog.blue/users/3550/ +=> gemini://gemlog.blue/users/DaVINCIs23/ +=> gemini://gemlog.blue/users/KindFoxie/ +=> gemini://gemlog.blue/users/abrbus/ +=> gemini://gemlog.blue/users/antcating/ +=> gemini://gemlog.blue/users/cu8wllwp/ +=> gemini://gemlog.blue/users/freedom/ +=> gemini://gemlog.blue/users/musu_pilseta/ +=> gemini://gemlog.stargrave.org/ +=> gemini://hugeping.ru/ +=> gemini://any-key.press/vgi/atom2gemfeed/?gemini%3A%2F%2Fhugeping.ru%2Fmicro%2Fatom.xml +=> gemini://karabas.flounder.online/gemlog +=> gemini://kotobank.ch/~merlin/feed_ru.gmi +=> gemini://muu-online.ru/ +=> gemini://parthen.smol.pub/ +=> gemini://polyserv.xyz/ +=> gemini://pub.phreedom.club/~kornilovnet/glog/ +=> gemini://pub.phreedom.club/~tolstoevsky/glog/ +=> gemini://sn4il.site/ +=> gemini://spline-online.ru/ +=> gemini://sysrq.in/ru/gemlog/ +=> gemini://tilde.team/~runation/Post/post.gmi +=> gemini://topotun.dynu.com/blog/ blob - 65779c1903b343d11ea7f8e40bdf4c4379061414 (mode 644) blob + /dev/null --- feeds.gmi +++ /dev/null @@ -1,51 +0,0 @@ -# Катастеризм. Подписки - -Ниже приведены ссылки, на которые подписан проект "Катастеризм". - -Критерии коллекции: -* русскоязычные ссылки -* содержимое соответствует формату "Subscribing to Gemini pages" - -=> gemini://geminiprotocol.net/docs/companion/subscription.gmi Subscribing to Gemini pages - -Если вы хотите изменить этот список, пожалуйста, напишите письмо на электронную почту -``` -katasterismos@to.any-key.press -``` - -## Коллекция подписок - -=> gemini://academia.fzrw.info/ru/blog -=> gemini://academia.fzrw.info/ru/encyclopedia -=> gemini://academia.fzrw.info/ru/publications -=> gemini://alexey.shpakovsky.ru/rulog/ -=> gemini://any-key.press/vgi/atom2gemfeed/?gemini%3A%2F%2Fany-key.press%2Fatom.xml -=> gemini://armitage.flounder.online/gemlog/ -=> gemini://any-key.press/vgi/atom2gemfeed/?gemini%3A%2F%2Fbasnja.ru%2Fatom.xml -=> gemini://bbs.geminispace.org/s/russian?feed -=> gemini://byzoni.org/gemlog.gmi -=> gemini://causa-arcana.com/ru/blog/feed.gmi -=> gemini://gemini.quietplace.xyz/~razzlom/gemlog/ -=> gemini://gemlog.blue/users/3550/ -=> gemini://gemlog.blue/users/DaVINCIs23/ -=> gemini://gemlog.blue/users/KindFoxie/ -=> gemini://gemlog.blue/users/abrbus/ -=> gemini://gemlog.blue/users/antcating/ -=> gemini://gemlog.blue/users/cu8wllwp/ -=> gemini://gemlog.blue/users/freedom/ -=> gemini://gemlog.blue/users/musu_pilseta/ -=> gemini://gemlog.stargrave.org/ -=> gemini://hugeping.ru/ -=> gemini://any-key.press/vgi/atom2gemfeed/?gemini%3A%2F%2Fhugeping.ru%2Fmicro%2Fatom.xml -=> gemini://karabas.flounder.online/gemlog -=> gemini://kotobank.ch/~merlin/feed_ru.gmi -=> gemini://muu-online.ru/ -=> gemini://parthen.smol.pub/ -=> gemini://polyserv.xyz/ -=> gemini://pub.phreedom.club/~kornilovnet/glog/ -=> gemini://pub.phreedom.club/~tolstoevsky/glog/ -=> gemini://sn4il.site/ -=> gemini://spline-online.ru/ -=> gemini://sysrq.in/ru/gemlog/ -=> gemini://tilde.team/~runation/Post/post.gmi -=> gemini://topotun.dynu.com/blog/ blob - /dev/null blob + 51cf5e91dd283ac2b99f2a20bf51e149c488eb70 (mode 644) --- /dev/null +++ index.gmi @@ -0,0 +1,77 @@ +# Агрегатор "Катастеризм" ("Katasterismos") + +Агрегатор русскоязычных gemini-публикаций "Катастеризм" ("Katasterismos"), отображающий перекрестные ссылки между публикациями. + +## Работающий экземпляр агрегатора + +Результат ежедневной работы агрегатора доступен по следующему адресу: + +=> gemini://pub.phreedom.club/~katasterismos/ + +## Идея агрегатора + +Идея была нагло украдена из ветки обсуждений "Реквестирую русскоязычный аналог Cosmos" на GSR: +=> gemini://geminispace.ru/s/74/ Реквестирую русскоязычный аналог Cosmos - агрегатор, который составляет треды из ссылающихся на кого-то ещё посты. + +## Исходный код + +Код агрегатора написан на Python3 и доступен в git-репозитории: +``` +ssh://anonymous@got.any-key.press/katasterismos +``` +=> https://got.any-key.press/?action=summary&path=katasterismos.git Web интерфейс к git репозиторию + +=> gemini://any-key.press/tt/welcome.gmi Сделано в соответствии с духом Тривиальных Технологий + +## Ленты публикаций агрегатора + +Ниже приведены ссылки, на которые подписан проект "Катастеризм". + +Критерии коллекции: +* русскоязычные содержимое (хотя бы частично) +* содержимое соответствует формату "Subscribing to Gemini pages" + +=> gemini://geminiprotocol.net/docs/companion/subscription.gmi Subscribing to Gemini pages + +Вы всегда можете помочь актуализировать этот список: напишите письмо на электронную почту +``` +katasterismos@to.any-key.press +``` +=> mailto:katasterismos@to.any-key.press + +### [LINKS] Ленты публикаций, за которыми следит агрегатор + +=> gemini://academia.fzrw.info/ru/blog +=> gemini://academia.fzrw.info/ru/encyclopedia +=> gemini://academia.fzrw.info/ru/publications +=> gemini://alexey.shpakovsky.ru/rulog/ +=> gemini://any-key.press/vgi/atom2gemfeed/?gemini%3A%2F%2Fany-key.press%2Fatom.xml +=> gemini://armitage.flounder.online/gemlog/ +=> gemini://any-key.press/vgi/atom2gemfeed/?gemini%3A%2F%2Fbasnja.ru%2Fatom.xml +=> gemini://bbs.geminispace.org/s/russian?feed +=> gemini://byzoni.org/gemlog.gmi +=> gemini://causa-arcana.com/ru/blog/feed.gmi +=> gemini://gemini.quietplace.xyz/~razzlom/gemlog/ +=> gemini://gemlog.blue/users/3550/ +=> gemini://gemlog.blue/users/DaVINCIs23/ +=> gemini://gemlog.blue/users/KindFoxie/ +=> gemini://gemlog.blue/users/abrbus/ +=> gemini://gemlog.blue/users/antcating/ +=> gemini://gemlog.blue/users/cu8wllwp/ +=> gemini://gemlog.blue/users/freedom/ +=> gemini://gemlog.blue/users/musu_pilseta/ +=> gemini://gemlog.stargrave.org/ +=> gemini://hugeping.ru/ +=> gemini://any-key.press/vgi/atom2gemfeed/?gemini%3A%2F%2Fhugeping.ru%2Fmicro%2Fatom.xml +=> gemini://karabas.flounder.online/gemlog +=> gemini://kotobank.ch/~merlin/feed_ru.gmi +=> gemini://muu-online.ru/ +=> gemini://parthen.smol.pub/ +=> gemini://polyserv.xyz/ +=> gemini://pub.phreedom.club/~kornilovnet/glog/ +=> gemini://pub.phreedom.club/~tolstoevsky/glog/ +=> gemini://sn4il.site/ +=> gemini://spline-online.ru/ +=> gemini://sysrq.in/ru/gemlog/ +=> gemini://tilde.team/~runation/Post/post.gmi +=> gemini://topotun.dynu.com/blog/ blob - bb0516d44da55d09e45a46b4c3c12e9694433c14 blob + 01e0fa00f9401e52306626d36d62acf90487bd63 --- katasterismos.py +++ katasterismos.py @@ -1,5 +1,5 @@ from dataclasses import dataclass, field -from datetime import date +from datetime import date, datetime, timedelta from email.message import Message from pathlib import Path from socket import create_connection, gaierror @@ -40,7 +40,7 @@ def get(url): context.check_hostname = False context.verify_mode = CERT_NONE try: - with create_connection((parsed.hostname, parsed.port or 1965)) as raw_s: + with create_connection((parsed.hostname, parsed.port or 1965), timeout=15.) as raw_s: with context.wrap_socket(raw_s, server_hostname=parsed.hostname) as s: s.sendall((url + '\r\n').encode("UTF-8")) fp = s.makefile("rb") @@ -53,7 +53,7 @@ def get(url): if not status.startswith("2"): message = f"Unexpected answer: {splitted[0]}" if len(splitted) == 2: - message += f"({splitted[1]})" + message += f" ({splitted[1]})" raise FeedError(url=url, message=message) mime = splitted[1].lower() if len(splitted) == 2 else "text/gemini" if not mime.startswith("text/gemini"): @@ -62,15 +62,15 @@ def get(url): m = Message() m['content-type'] = mime return fp.read().decode(m.get_param('charset') or "UTF-8", errors='ignore') - except gaierror as error: + except OSError as error: raise FeedError(url=url, message=str(error)) except SSLError as error: raise FeedError(url=url, message=str(error)) -def parse_feed(url, feed_body, feeds): +def parse_feed(url, body, feeds): _feed = Feed(url=url) - for line in feed_body.splitlines(): + for line in body.splitlines(): if line.startswith("#") and line[1:2] != "#" and not _feed.title: _feed.title = line[1:].strip() elif line.startswith("=>"): @@ -90,23 +90,76 @@ def parse_feed(url, feed_body, feeds): feed=_feed ) -def daily(feeds_gmi): + +def iterate_links(body): + for line in body.splitlines(): + if line.startswith("=>"): + splitted = line[2:].strip().split(maxsplit=1) + if splitted: + yield splitted[0] + + +def daily(index_gmi): feeds = {} errors = [] header_2_passes = False - for line in feeds_gmi.read_text(encoding="utf8").splitlines(): - if line.startswith("##"): + for line in index_gmi.read_text(encoding="utf8").splitlines(): + if line.startswith("### [FEEDS]"): header_2_passes = True elif header_2_passes and line.startswith("=>"): try: url = line[2:].strip() parse_feed(url, get(url), feeds) - raise NotImplementedError(len(feeds), feeds, len(errors), errors) except FeedError as error: errors.append(error) + dt_now = datetime.now() + date_limit = (dt_now - timedelta(weeks=8)).date() + rv = [ + f"# Katasterismos ({date_limit.isoformat()} - {dt_now.date().isoformat()})", + "", + 'Агрегатор русскоязычных gemini-публикаций "Катастеризм" ("Katasterismos"), отображающий перекрестные ссылки между публикациями.', + "", + "=> about.gmi Подробности", + ] + current_date = None + for entry in reversed( + sorted( + [entry for url, entry in feeds.items() if entry.updated >= date_limit], + key=lambda entry: entry.updated, + ) + ): + if current_date != entry.updated: + current_date = entry.updated + rv.append("") + rv.append("") + rv.append(f"## {current_date.isoformat()}") + + rv.append("") + rv.append(f"=> {entry.url} {entry.feed.title} {entry.updated.isoformat()} {entry.title}") + for link in iterate_links(get(entry.url)): + url = urljoin(entry.url, link) + linked_to = feeds.get(url) + if linked_to: + rv.append(f"=> {url} ↳ {linked_to.feed.title} {linked_to.updated.isoformat()} {linked_to.title}") + + + if errors: + rv.append("") + rv.append("") + rv.append("## Errors") + for error in errors: + rv.append("") + rv.append(f"=> {error.args[0]}") + rv.append("```") + rv.append(error.args[1]) + rv.append("```") + + print("\r\n".join(rv)) + + if __name__ == '__main__': from argparse import ArgumentParser parser = ArgumentParser()