aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorNguyễn Gia Phong <mcsinyx@disroot.org>2021-10-14 18:43:16 +0700
committerNguyễn Gia Phong <mcsinyx@disroot.org>2021-10-14 18:44:17 +0700
commit2f50663bc527fd2c10e60ca9764e7b0728c97422 (patch)
tree22a3246ecdb123a54da9a4bf20b6e847d18b2892 /src
downloadrsskey-2f50663bc527fd2c10e60ca9764e7b0728c97422.tar.gz
Make it work0.0.1
Diffstat (limited to 'src')
-rwxr-xr-xsrc/rsskey.py89
1 files changed, 89 insertions, 0 deletions
diff --git a/src/rsskey.py b/src/rsskey.py
new file mode 100755
index 0000000..f88bd7e
--- /dev/null
+++ b/src/rsskey.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+# RSS feed mirror on Misskey
+# Copyright (C) 2021 Nguyễn Gia Phong
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+from configparser import ConfigParser
+from contextlib import AsyncExitStack
+from functools import partial
+from re import split, sub
+
+from dateutil.parser import parse as parse_time
+from feedparser import parse as parse_feed
+from httpx import AsyncClient
+from loca import Loca
+from markdownify import markdownify as md
+from trio import open_nursery, run
+
+
+async def create(client, **note):
+ """Create the given note and return its ID."""
+ response = await client.post('notes/create', json=note)
+ return response.json()['createdNote']['id']
+
+
+async def post(job, client, link, title, summary):
+ """Post the given entry to Misskey.
+
+ In case the link was already posted, the entry shall be skipped.
+ """
+ search = await client.post('notes/search', json={'query': link,
+ 'userId': job['user']})
+ if search.json(): return
+
+ note = partial(create, client, i=job['token'], visibility='home', cw=title)
+ original = f'Original: {link}'
+ rest = '\n\n'.join((*map(partial(sub, r'\s+', ' '),
+ split(r'\s*\n{2,}\s*', md(summary).strip())),
+ original))
+ limit = int(job['limit'])
+ parent = None
+
+ while len(rest) > limit:
+ index = rest.rfind('\n\n', 0, limit+2) # split paragraphs
+ if index < 0:
+ index = rest.rfind('. ', 0, limit+1) # split sentences
+ if index < 0:
+ parent = await note(text=original, replyId=parent)
+ return
+ first, rest = rest[:index+1], rest[index+2:]
+ else:
+ first, rest = rest[:index], rest[index+2:]
+ parent = await note(text=first, replyId=parent)
+ parent = await note(text=rest, replyId=parent)
+
+
+async def mirror(nursery, job, client):
+ """Perform the given mirror job."""
+ feed = await client.get(job['source'])
+ for entry in parse_feed(feed.text)['entries']:
+ nursery.start_soon(post, job, client, entry['link'],
+ entry['title'], entry['summary'])
+
+
+async def main():
+ """Parse and run jobs."""
+ config = ConfigParser()
+ config.read(Loca().user.config()/'rsskey'/'jobs.conf')
+ async with AsyncExitStack() as stack, open_nursery() as nursery:
+ for section in config:
+ if section == 'DEFAULT': continue
+ job = config[section]
+ client = AsyncClient(base_url=job['dest'])
+ await stack.enter_async_context(client)
+ nursery.start_soon(mirror, nursery, job, client)
+
+
+if __name__ == '__main__': run(main)