Temp. fix for Gemini URIs; Add base for Mastodon support

This commit is contained in:
octospacc 2022-06-30 18:50:18 +02:00
parent 28dbca3ea3
commit e0777bd143
9 changed files with 4094 additions and 9 deletions

View File

@ -21,6 +21,7 @@ Feel free to experiment with all of this stuff!
- [html2gmi](https://github.com/LukeEmmet/html2gmi)
## Features roadmap
- [ ] ActivityPub support (Feed + embedded comments)
- [ ] Polished Gemtext generation
- [x] Autodetection of pages and posts
- [x] Info for posts shown on their page

View File

@ -23,6 +23,7 @@ except ModuleNotFoundError:
from Libs import htmlmin
from Libs.bs4 import BeautifulSoup
from Modules.ActivityPub import *
from Modules.Gemini import *
from Modules.Pug import *
from Modules.Utils import *
@ -315,8 +316,9 @@ def DelTmp():
for Ext in Extensions['Pages']:
for File in Path('public').rglob('*.{}'.format(Ext)):
os.remove(File)
for File in Path('public').rglob('*.tmp'):
os.remove(File)
for Dir in ('public', 'public.gmi'):
for File in Path(Dir).rglob('*.tmp'):
os.remove(File)
def RevSort(List):
List.sort()
@ -480,10 +482,17 @@ def Main(Args, FeedEntries):
if Args.GemtextOut:
GemtextCompileList(Pages)
#HTML2Gemtext(
# Pages=Pages,
# SiteName=SiteName,
# SiteTagline=SiteTagline)
"""
MastodonSession = MastodonGetSession(
Args.MastodonURL if Args.MastodonURL else '',
Args.MastodonToken if Args.MastodonToken else '')
MastodonPosts = MastodonGetPostsFromUserID(
MastodonSession,
MastodonGetMyID(MastodonSession))
for i in MastodonPosts:
print(i['uri'], i['content'])
"""
DelTmp()
os.system("cp -R Assets/* public/")
@ -503,6 +512,8 @@ if __name__ == '__main__':
Parser.add_argument('--ContextParts', type=str)
Parser.add_argument('--MarkdownExts', type=str)
Parser.add_argument('--ReservedPaths', type=str)
Parser.add_argument('--MastodonURL', type=str)
Parser.add_argument('--MastodonToken', type=str)
Args = Parser.parse_args()
try:

30
Source/Libs/bs4/LICENSE Normal file
View File

@ -0,0 +1,30 @@
Beautiful Soup is made available under the MIT license:
Copyright (c) 2004-2022 Leonard Richardson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Beautiful Soup incorporates code from the html5lib library, which is
also made available under the MIT license. Copyright (c) 2006-2013
James Graham and other contributors
Beautiful Soup depends on the soupsieve library, which is also made
available under the MIT license. Copyright (c) 2018 Isaac Muse

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Lorenz Diener / Mastodon.py contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
from .Mastodon import Mastodon, AttribAccessDict, MastodonError, MastodonVersionError, MastodonIllegalArgumentError, MastodonIOError, MastodonFileNotFoundError, MastodonNetworkError, MastodonAPIError, MastodonNotFoundError, MastodonUnauthorizedError, MastodonRatelimitError, MastodonMalformedEventError, MastodonServerError, MastodonInternalServerError, MastodonBadGatewayError, MastodonServiceUnavailableError, MastodonGatewayTimeoutError
from .streaming import StreamListener, CallbackStreamListener
__all__ = ['Mastodon', 'AttribAccessDict', 'StreamListener', 'CallbackStreamListener', 'MastodonError', 'MastodonVersionError', 'MastodonIllegalArgumentError', 'MastodonIOError', 'MastodonFileNotFoundError', 'MastodonNetworkError', 'MastodonAPIError', 'MastodonNotFoundError', 'MastodonUnauthorizedError', 'MastodonRatelimitError', 'MastodonMalformedEventError',
'MastodonServerError', 'MastodonInternalServerError', 'MastodonBadGatewayError', 'MastodonServiceUnavailableError', 'MastodonGatewayTimeoutError']

View File

@ -0,0 +1,190 @@
"""
Handlers for the Streaming API:
https://github.com/tootsuite/mastodon/blob/master/docs/Using-the-API/Streaming-API.md
"""
import json
import six
from . import Mastodon
from .Mastodon import MastodonMalformedEventError, MastodonNetworkError, MastodonReadTimeout
from requests.exceptions import ChunkedEncodingError, ReadTimeout
class StreamListener(object):
"""Callbacks for the streaming API. Create a subclass, override the on_xxx
methods for the kinds of events you're interested in, then pass an instance
of your subclass to Mastodon.user_stream(), Mastodon.public_stream(), or
Mastodon.hashtag_stream()."""
def on_update(self, status):
"""A new status has appeared! 'status' is the parsed JSON dictionary
describing the status."""
pass
def on_notification(self, notification):
"""A new notification. 'notification' is the parsed JSON dictionary
describing the notification."""
pass
def on_abort(self, err):
"""There was a connection error, read timeout or other error fatal to
the streaming connection. The exception object about to be raised
is passed to this function for reference.
Note that the exception will be raised properly once you return from this
function, so if you are using this handler to reconnect, either never
return or start a thread and then catch and ignore the exception.
"""
pass
def on_delete(self, status_id):
"""A status has been deleted. status_id is the status' integer ID."""
pass
def on_conversation(self, conversation):
"""A direct message (in the direct stream) has been received. conversation
contains the resulting conversation dict."""
pass
def handle_heartbeat(self):
"""The server has sent us a keep-alive message. This callback may be
useful to carry out periodic housekeeping tasks, or just to confirm
that the connection is still open."""
pass
def handle_stream(self, response):
"""
Handles a stream of events from the Mastodon server. When each event
is received, the corresponding .on_[name]() method is called.
response; a requests response object with the open stream for reading.
"""
event = {}
line_buffer = bytearray()
try:
for chunk in response.iter_content(chunk_size = 1):
if chunk:
for chunk_part in chunk:
chunk_part = bytearray([chunk_part])
if chunk_part == b'\n':
try:
line = line_buffer.decode('utf-8')
except UnicodeDecodeError as err:
exception = MastodonMalformedEventError("Malformed UTF-8")
self.on_abort(exception)
six.raise_from(
exception,
err
)
if line == '':
self._dispatch(event)
event = {}
else:
event = self._parse_line(line, event)
line_buffer = bytearray()
else:
line_buffer.extend(chunk_part)
except ChunkedEncodingError as err:
exception = MastodonNetworkError("Server ceased communication.")
self.on_abort(exception)
six.raise_from(
exception,
err
)
except MastodonReadTimeout as err:
exception = MastodonReadTimeout("Timed out while reading from server."),
self.on_abort(exception)
six.raise_from(
exception,
err
)
def _parse_line(self, line, event):
if line.startswith(':'):
self.handle_heartbeat()
else:
try:
key, value = line.split(': ', 1)
except:
exception = MastodonMalformedEventError("Malformed event.")
self.on_abort(exception)
raise exception
# According to the MDN spec, repeating the 'data' key
# represents a newline(!)
if key in event:
event[key] += '\n' + value
else:
event[key] = value
return event
def _dispatch(self, event):
try:
name = event['event']
data = event['data']
payload = json.loads(data, object_hook = Mastodon._Mastodon__json_hooks)
except KeyError as err:
exception = MastodonMalformedEventError('Missing field', err.args[0], event)
self.on_abort(exception)
six.raise_from(
exception,
err
)
except ValueError as err:
# py2: plain ValueError
# py3: json.JSONDecodeError, a subclass of ValueError
exception = MastodonMalformedEventError('Bad JSON', data)
self.on_abort(exception)
six.raise_from(
exception,
err
)
handler_name = 'on_' + name
try:
handler = getattr(self, handler_name)
except AttributeError as err:
exception = MastodonMalformedEventError('Bad event type', name)
self.on_abort(exception)
six.raise_from(
exception,
err
)
else:
handler(payload)
class CallbackStreamListener(StreamListener):
"""
Simple callback stream handler class.
Can optionally additionally send local update events to a separate handler.
"""
def __init__(self, update_handler = None, local_update_handler = None, delete_handler = None, notification_handler = None, conversation_handler = None):
super(CallbackStreamListener, self).__init__()
self.update_handler = update_handler
self.local_update_handler = local_update_handler
self.delete_handler = delete_handler
self.notification_handler = notification_handler
self.conversation_handler = conversation_handler
def on_update(self, status):
if self.update_handler != None:
self.update_handler(status)
try:
if self.local_update_handler != None and not "@" in status["account"]["acct"]:
self.local_update_handler(status)
except Exception as err:
six.raise_from(
MastodonMalformedEventError('received bad update', status),
err
)
def on_delete(self, deleted_id):
if self.delete_handler != None:
self.delete_handler(deleted_id)
def on_notification(self, notification):
if self.notification_handler != None:
self.notification_handler(notification)
def on_conversation(self, conversation):
if self.conversation_handler != None:
self.conversation_handler(conversation)

View File

@ -0,0 +1,27 @@
""" ================================= |
| This file is part of |
| staticoso |
| Just a simple Static Site Generator |
| |
| Licensed under the AGPLv3 license |
| Copyright (C) 2022, OctoSpacc |
| ================================= """
from Libs.mastodon import Mastodon
from Modules.Utils import *
def MastodonGetSession(MastodonURL, MastodonToken):
return Mastodon(
api_base_url=MastodonURL,
access_token=MastodonToken)
def MastodonGetMyID(Session):
return Session.me()['id']
def MastodonGetPostsFromUserID(Session, UserID):
return Session.account_statuses(
UserID,
exclude_replies=True)
def MastodonDoPost(Session):
pass # mastodon.toot('Tooting from python using #mastodonpy !')

View File

@ -22,11 +22,13 @@ OpenTags = (
'img')
def GemtextCompileList(Pages):
Cmd = ''
for File, Content, Titles, Meta, HTMLContent, Description, Image in Pages:
Src = 'public/{}.html.tmp'.format(StripExt(File))
WriteFile(Src, HTMLContent)
Src = 'public.gmi/{}.html.tmp'.format(StripExt(File))
WriteFile(Src, HTMLContent.replace('.html', '.gmi')) # TODO: Adjust links properly..
Dst = 'public.gmi/{}.gmi'.format(StripExt(File))
os.system('cat {} | html2gmi > {}'.format(Src, Dst))
Cmd += 'cat "{}" | html2gmi > "{}"; '.format(Src, Dst)
os.system(Cmd)
def FindEarliest(Str, Items):
Pos, Item = 0, ''