2018-03-17 23:06:43 +01:00
|
|
|
'''Mastodon interface.'''
|
|
|
|
|
|
|
|
from os import umask
|
|
|
|
|
2018-04-03 02:09:25 +02:00
|
|
|
import click
|
2018-03-17 23:06:43 +01:00
|
|
|
from lxml.etree import HTML # pylint: disable=no-name-in-module
|
|
|
|
import mastodon
|
|
|
|
from youtube_dl.utils import DownloadError
|
|
|
|
|
|
|
|
from fediplay.queue import Queue
|
|
|
|
|
|
|
|
Mastodon = mastodon.Mastodon
|
|
|
|
|
2018-07-20 17:25:41 +02:00
|
|
|
|
|
|
|
def api_base_url(instance):
|
|
|
|
'''Create an API base url from an instance name.'''
|
|
|
|
|
|
|
|
return 'https://' + instance
|
|
|
|
|
2018-03-17 23:06:43 +01:00
|
|
|
class StreamListener(mastodon.StreamListener):
|
|
|
|
'''Listens to a Mastodon timeline and adds links the given Queue.'''
|
|
|
|
|
|
|
|
def __init__(self, queue):
|
|
|
|
self.queue = queue
|
|
|
|
|
|
|
|
def on_update(self, status):
|
|
|
|
tags = extract_tags(status)
|
|
|
|
if 'fediplay' in tags:
|
|
|
|
links = extract_links(status)
|
|
|
|
for link in links:
|
|
|
|
try:
|
2018-04-03 02:09:25 +02:00
|
|
|
click.echo('==> Trying {}'.format(link))
|
2018-03-17 23:06:43 +01:00
|
|
|
self.queue.add(link)
|
|
|
|
return
|
|
|
|
except DownloadError:
|
|
|
|
pass
|
|
|
|
|
2018-07-20 17:25:41 +02:00
|
|
|
def register(instance, clientcred):
|
2018-03-17 23:06:43 +01:00
|
|
|
'''Register fediplay to a Mastodon server and save the client credentials.'''
|
|
|
|
|
2018-07-20 17:25:41 +02:00
|
|
|
saved_umask = umask(0o77)
|
2018-04-03 03:46:14 +02:00
|
|
|
Mastodon.create_app('fediplay',
|
|
|
|
scopes=['read'],
|
2018-07-20 17:25:41 +02:00
|
|
|
api_base_url=api_base_url(instance),
|
|
|
|
to_file=clientcred)
|
|
|
|
umask(saved_umask)
|
|
|
|
|
|
|
|
def build_client(instance, clientcred, usercred=None):
|
|
|
|
'''Builds a Mastodon client.'''
|
|
|
|
|
|
|
|
return Mastodon(client_id=clientcred, access_token=usercred, api_base_url=api_base_url(instance))
|
|
|
|
|
|
|
|
def get_auth_request_url(client):
|
|
|
|
'''Gets an authorization request URL from a Mastodon instance.'''
|
|
|
|
|
|
|
|
return client.auth_request_url(scopes=['read'])
|
2018-03-17 23:06:43 +01:00
|
|
|
|
2018-07-20 17:25:41 +02:00
|
|
|
def login(client, grant_code, usercred):
|
2018-03-17 23:06:43 +01:00
|
|
|
'''Log in to a Mastodon server and save the user credentials.'''
|
|
|
|
|
2018-07-20 17:25:41 +02:00
|
|
|
saved_umask = umask(0o77)
|
|
|
|
client.log_in(code=grant_code, scopes=['read'], to_file=usercred)
|
|
|
|
umask(saved_umask)
|
2018-03-17 23:06:43 +01:00
|
|
|
|
2018-07-20 17:25:41 +02:00
|
|
|
def stream(instance, clientcred, usercred):
|
2018-03-17 23:06:43 +01:00
|
|
|
'''Stream statuses and add them to a queue.'''
|
|
|
|
|
2018-07-20 17:25:41 +02:00
|
|
|
url = api_base_url(instance)
|
|
|
|
client = Mastodon(client_id=clientcred, access_token=usercred, api_base_url=url)
|
2018-03-17 23:06:43 +01:00
|
|
|
listener = StreamListener(Queue())
|
2018-07-20 17:25:41 +02:00
|
|
|
click.echo('==> Streaming from {}'.format(url))
|
2018-03-17 23:06:43 +01:00
|
|
|
client.stream_user(listener)
|
|
|
|
|
|
|
|
def extract_tags(toot):
|
|
|
|
'''Extract tags from a toot.'''
|
|
|
|
|
|
|
|
return [tag['name'] for tag in toot['tags']]
|
|
|
|
|
|
|
|
def link_is_internal(link):
|
|
|
|
'''Determines if a link is internal to the Mastodon instance.'''
|
|
|
|
|
|
|
|
classes = link.attrib.get('class', '').split(' ')
|
|
|
|
if classes:
|
|
|
|
return 'mention' in classes
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
def extract_links(toot):
|
|
|
|
'''Extract all external links from a toot.'''
|
|
|
|
|
|
|
|
html = HTML(toot['content'])
|
|
|
|
all_links = html.cssselect('a')
|
|
|
|
return [link.attrib['href'] for link in all_links if not link_is_internal(link)]
|