From a427173739755deff9821e8bed38cfa751daf848 Mon Sep 17 00:00:00 2001 From: clerie Date: Tue, 30 Jul 2019 19:40:35 +0200 Subject: [PATCH 1/2] reworked (nearly) everything --- config.json.example | 21 ++++++++----- hashtag-spreader.py | 75 +++++++++++++++++++++++++++++---------------- 2 files changed, 61 insertions(+), 35 deletions(-) diff --git a/config.json.example b/config.json.example index 5e8b67f..a482c20 100644 --- a/config.json.example +++ b/config.json.example @@ -1,11 +1,16 @@ { - "source": { - "url": "https://chaos.social", - "token": "", - "hashtag": "gpn19" + "instances": { + "chaos.social": "secret", + "fem.social": "secret" }, - "drain": { - "url": "https://fem.social", - "token": "" - } + "spreads": [ + { + "source": "chaos.social", + "drain": "fem.social", + "hashtags": [ + "gpn19", + "cccamp19" + ] + } + ] } diff --git a/hashtag-spreader.py b/hashtag-spreader.py index 61699d0..572c2a2 100644 --- a/hashtag-spreader.py +++ b/hashtag-spreader.py @@ -1,4 +1,7 @@ +#!/usr/bin/env python3 + import json +from threading import Thread from mastodon import Mastodon, StreamListener @@ -6,35 +9,53 @@ def print_status(status): print("time: " + str(status["created_at"])) print("from: @" + status["account"]["acct"]) -with open("config.json", 'r') as f: - config = json.load(f) +class TootListener(StreamListener): + def __init__(self, source, drain): + self.source = source + self.drain = drain -if config: - chaos_social = Mastodon( - access_token = config["source"]["token"], - api_base_url = config["source"]["url"] - ) + def on_update(self, status): + print("") + print("--") + print("RECIEVE " + self.source.api_base_url + "") + print_status(status) + print("SEARCH " + self.drain.api_base_url + "") + try: + search = self.drain.search(status.url) + for s in search["statuses"]: + print_status(s) + except: + print("failed") + print("--") - fem_social = Mastodon( - access_token = config["drain"]["token"], - api_base_url = config["drain"]["url"] - ) +class HashtagSpreader(Thread): + def __init__(self, source, drain, hashtag): + Thread.__init__(self) - class TootListener(StreamListener): - def on_update(self, status): - print("") - print(" -- new status in " + config["source"]["url"] + " -- ") - print_status(status) - #print(status) - print("") - print(" -- search in " + config["drain"]["url"] + " -- ") - try: - search = fem_social.search(status.url) - for s in search["statuses"]: - print_status(s) - except: - print("failed") + self.source = source + self.drain = drain + self.hashtag = hashtag - tootListener = TootListener() + def run(self): + tootListener = TootListener(self.source, self.drain) + self.source.stream_hashtag(self.hashtag, tootListener) - chaos_social.stream_hashtag(config["source"]["hashtag"], tootListener, run_async=False, timeout=300, reconnect_async=False, reconnect_async_wait_sec=5) +if __name__ == "__main__": + instances = {} + threads = [] + + with open("config.json", 'r') as f: + config = json.load(f) + + if config: + for instance in config["instances"]: + instances[instance] = Mastodon(access_token = config["instances"][instance], api_base_url = "https://" + instance) + + for spread in config["spreads"]: + if spread["source"] in instances and spread["drain"] in instances: + for hashtag in spread["hashtags"]: + threads.append(HashtagSpreader(instances[spread["source"]], instances[spread["drain"]], hashtag)) + + + for thread in threads: + thread.start() From 498164ac2544a01a1417908dea128308c6c502f2 Mon Sep 17 00:00:00 2001 From: clerie Date: Tue, 30 Jul 2019 21:06:25 +0200 Subject: [PATCH 2/2] Documenting a bit --- README.md | 18 ++++++++++++++---- config.json.example | 4 ++-- hashtag-spreader.py | 30 ++++++++++++++++++++++-------- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 18a30c0..c5bb58b 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Mastodon Hashtag Spreader -For the #GPN19 my Mastodon instance fem.social wants to get all posts of the hashtag. Most users of this event are connected to the instance chaos.social. +For the #GPN19 my Mastodon instance [fem.social](https://fem.social) wants to get all posts of the hashtag. Most users of this event are connected to the instance [chaos.social](https://chaos.social). ## Our solution We have a script on one of our servers, streaming all posts of one hashtag from chaos.socail and putting the url of the post to the search entry of our own instance. The url will get fetched by our instace and the resulting post is added to our hashtag timeline. -## Installing -``` +## Installation +```bash pip3 install Mastodon.py git clone https://github.com/clerie/mastodon-hashtag-spreader.git cd mastodon-hashtag-spreader/ @@ -14,6 +14,16 @@ cp config.json.example config.json nano config.json ``` Edit config for your needs. -``` +```bash python3 hashtag-spreader.py ``` + +## Scopes +To get this script work, you need an application token on each instance you want to get connected to. +The needed scopes are depending on the role of the instace. + +### Source +* `read:statuses` + +### Drain +* `read:search` diff --git a/config.json.example b/config.json.example index a482c20..6957a50 100644 --- a/config.json.example +++ b/config.json.example @@ -1,7 +1,7 @@ { "instances": { - "chaos.social": "secret", - "fem.social": "secret" + "chaos.social": "access-token", + "fem.social": "access-token" }, "spreads": [ { diff --git a/hashtag-spreader.py b/hashtag-spreader.py index 572c2a2..6b879aa 100644 --- a/hashtag-spreader.py +++ b/hashtag-spreader.py @@ -10,35 +10,47 @@ def print_status(status): print("from: @" + status["account"]["acct"]) class TootListener(StreamListener): + """ + Listener class, handling incoming statuses + """ def __init__(self, source, drain): - self.source = source - self.drain = drain + self.source = source # Source instance Mastodon object + self.drain = drain # Drain instance Mastodon object def on_update(self, status): + # Just printing some stuff to make it look beautiful in terminal + # More for debugging than anything else print("") print("--") print("RECIEVE " + self.source.api_base_url + "") print_status(status) print("SEARCH " + self.drain.api_base_url + "") + + # Searching for the URL of the incoming status in our drain instance try: search = self.drain.search(status.url) for s in search["statuses"]: print_status(s) except: print("failed") + print("--") class HashtagSpreader(Thread): + """ + Thread class, streaming one hashtag from one the source instance and searching for the post URL in the drain instance + """ def __init__(self, source, drain, hashtag): - Thread.__init__(self) + Thread.__init__(self) # Configure the thread - self.source = source - self.drain = drain - self.hashtag = hashtag + self.source = source # Source instance Mastodon object + self.drain = drain # Drain instance Mastodon object + self.hashtag = hashtag # The hashtag (without #) as a string, we want to stream def run(self): tootListener = TootListener(self.source, self.drain) - self.source.stream_hashtag(self.hashtag, tootListener) + self.source.stream_hashtag(self.hashtag, tootListener) # Stream our hashtag + if __name__ == "__main__": instances = {} @@ -48,14 +60,16 @@ if __name__ == "__main__": config = json.load(f) if config: + # Create Mastodon objects for every instance for instance in config["instances"]: instances[instance] = Mastodon(access_token = config["instances"][instance], api_base_url = "https://" + instance) + # Create threads for every hashtag of a relation for spread in config["spreads"]: if spread["source"] in instances and spread["drain"] in instances: for hashtag in spread["hashtags"]: threads.append(HashtagSpreader(instances[spread["source"]], instances[spread["drain"]], hashtag)) - + # Start all threads for thread in threads: thread.start()