#!/usr/bin/env python3 # File: sniffa.py # Location: https://gitlab.com/bgstack15/sniffa # Author: danielmitterdorfer # SPDX-License-Identifer: GPL-3.0 # Startdate: 2016-07-16 # Title: Discourse sniffer # Purpose: Send alerts for saved searches on Discourse forum websites # History: # 2022-01-07 forked by bgstack15 to send discord messages # Homepage: https://github.com/danielmitterdorfer/sniffa # Usage: # ./sniffa.py aoe2 # Reference: # https://meta.discourse.org/t/watching-and-sending-notifications-for-keywords/59286 # https://stackoverflow.com/questions/62731561/discord-send-message-only-from-python-app-to-discord-channel-one-way-communic/62731999#62731999 # https://discordpy.readthedocs.io/en/latest/api.html#discord.Webhook.send # Improve: # Documentation: See README.md # Dependencies: # req-fedora: python3-certifi # req-pip3: discord # A webhook given to me by discord "server" admin import os, sys, errno, json, configparser, urllib3, urllib.parse, datetime, certifi, requests from discord import Webhook, RequestsWebhookAdapter DOMAIN_SECTION_KEY = "sniffa.domain" def ensure_dir(directory): try: os.makedirs(directory) except OSError as exception: if exception.errno != errno.EEXIST: raise def creation_date(item): return datetime.datetime.strptime(item["created_at"], "%Y-%m-%dT%H:%M:%S.%fZ").timestamp() def main(): if not len(sys.argv) == 2: print("usage: %s domain_key" % sys.argv[0], file=sys.stderr) exit(1) domain_key = sys.argv[1] http = urllib3.PoolManager(cert_reqs="CERT_REQUIRED", ca_certs=certifi.where()) watches_dir = "%s/.sniffa" % os.getenv("HOME") watches_file = "%s/watches-%s.ini" % (watches_dir, domain_key) ensure_dir(watches_dir) config = configparser.ConfigParser() config.read(watches_file) if not DOMAIN_SECTION_KEY in config or not "url" in config[DOMAIN_SECTION_KEY]: print("Invalid configuration in [%s]" % watches_file, file=sys.stderr) print("", file=sys.stderr) print("Please add a domain to query according to the following example in [%s]" % watches_file, file=sys.stderr) print("", file=sys.stderr) print(" [%s]" % DOMAIN_SECTION_KEY, file=sys.stderr) print(" url=https://discuss.example.org", file=sys.stderr) print("", file=sys.stderr) exit(1) domain = config[DOMAIN_SECTION_KEY]["url"] print("Checking for new posts on %s" % domain_key) for keyword in config.sections(): if keyword == DOMAIN_SECTION_KEY: continue if "ids" in config[keyword]: ids = config[keyword]["ids"] if len(ids) > 0: known_ids = set([int(id) for id in config[keyword]["ids"].split(",")]) else: known_ids = set() else: known_ids = set() query_string = urllib.parse.quote_plus("%s order:latest" % keyword) r = http.request("GET", "%s/search?q=%s" % (domain, query_string), headers={"Accept": "application/json"}) results = json.loads(r.data.decode("utf-8")) topics_by_id = {} for topic in results["topics"]: topics_by_id[topic["id"]] = topic posts = [] for post in results["posts"]: post_id = post["id"] if post_id not in known_ids: known_ids.add(post_id) posts.append(post) config[keyword]["ids"] = ",".join([str(id) for id in known_ids]) sorted(posts, key=creation_date, reverse=True) if len(posts) > 0: webhook = Webhook.from_url(config[DOMAIN_SECTION_KEY]["webhook"], adapter=RequestsWebhookAdapter()) for post in posts: topic = topics_by_id[post["topic_id"]] message = f"New post mentioning \"{keyword}\". Url=\"{domain}/t/{topic['slug']}/{topic['id']}\". group={str(topic['id'])}." print(message) webhook.send(f"{domain}/t/{topic['slug']}/{topic['id']}", username=config[DOMAIN_SECTION_KEY]["discord_user"]) with open(watches_file, "w") as f: config.write(f) print("Finished") if __name__ == "__main__": main()