summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Mitterdorfer <daniel.mitterdorfer@gmail.com>2016-07-16 06:29:47 +0200
committerDaniel Mitterdorfer <daniel.mitterdorfer@gmail.com>2016-07-16 06:29:47 +0200
commit64f34314e1e60a9c073fb4001af399f2177fc59b (patch)
treee60dc36b9e9ea291e57620100e904abbaf85ac76
downloadsniffa-64f34314e1e60a9c073fb4001af399f2177fc59b.tar.gz
sniffa-64f34314e1e60a9c073fb4001af399f2177fc59b.tar.bz2
sniffa-64f34314e1e60a9c073fb4001af399f2177fc59b.zip
Create minimalistic implementation and docs
-rw-r--r--README.md95
-rwxr-xr-xsniffa.py108
2 files changed, 203 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6c74686
--- /dev/null
+++ b/README.md
@@ -0,0 +1,95 @@
+sniffa
+======
+
+sniffa is a small utility that allows you to watch Discuss forums for keywords.
+
+Every time it is invoked, it checks for new posts matching the keywords and creates a notification in the Mac OS X notification bar.
+
+# Requirements
+
+* Mac OS X 10.8 or later: As it uses Mac OS X notifications, sniffa works only on Mac OS X 10.8 or later.
+* Python 3
+* certifi: Install with `pip3 install certifi`
+* pync: Install with `pip3 install pync`
+
+# Installation
+
+Ensure that all prerequisites are installed, then copy `sniffa.py` to any directory, e.g. `~/bin` and run `chmod u+x sniffa.py`.
+
+# Usage
+
+sniffa can be used to query multiple Discuss forums. The keywords and the ids of all already seen posts are maintained in a file `~/.sniffa/watch-$(FORUM_NAME).ini`, where `$(FORUM_NAME)` is a name that you can choose to identify this forum.
+
+## Example
+
+Consider you want to watch for the keywords "Rally" and "JMeter" in the Elastic Discuss forum at https://discuss.elastic.co.
+
+1. Create `~/.sniffa/watch-elastic.ini`
+2. Add the following lines:
+
+```
+[sniffa.domain]
+url = https://discuss.elastic.co
+
+[Rally]
+
+[JMeter]
+```
+
+Now invoke sniffa: ``python3 sniffa.py elastic``. It will load the watches file for the forum named "elastic", check for new posts (which will be a lot at the first time) and show a notice for each of them in the Mac OS X notification bar.
+
+## Automatic regular invocation
+
+Quite likely you don't want to invoke sniffa manually every time you want to check for new posts. Therefore, you can install sniffa as a launch agent.
+
+Create a new file in `~/Library/LaunchAgents/org.github.sniffa.plist` with the following contents:
+
+```plist
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Label</key>
+ <string>org.github.sniffa</string>
+
+ <key>ProgramArguments</key>
+ <array>
+ <string>~/bin/sniffa.py</string>
+ <string>elastic</string>
+ </array>
+
+ <key>Nice</key>
+ <integer>1</integer>
+
+ <key>StartInterval</key>
+ <integer>7200</integer>
+
+ <key>RunAtLoad</key>
+ <true/>
+
+ <key>StandardErrorPath</key>
+ <string>~/.sniffa/sniffa-elastic.err.log</string>
+
+ <key>StandardOutPath</key>
+ <string>~/.sniffa/sniffa-elastic.out.log</string>
+</dict>
+</plist>
+```
+
+Change the paths depending on the install location of sniffa and also your domain parameter.
+
+With this plist file, sniffa will check the Elastic Discuss forum every two hours (7200 seconds) for new posts.
+
+Finally register the launch agent with Mac OS X:
+
+```
+launchctl load ~/Library/LaunchAgents/org.github.sniffa.plist
+```
+
+After a restart, Mac OS X will pick up the plist file automatically.
+
+If you are interested in more details about launch agents, check [Alvin Alexander's blog post about plist files](http://alvinalexander.com/mac-os-x/mac-osx-startup-crontab-launchd-jobs) (on which this description is based).
+
+# License
+
+'sniffa' is distributed under the terms of the [Apache Software Foundation license, version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html).
diff --git a/sniffa.py b/sniffa.py
new file mode 100755
index 0000000..b1e8b18
--- /dev/null
+++ b/sniffa.py
@@ -0,0 +1,108 @@
+#!/usr/local/bin/python3
+
+#
+# Note: Ideally the shebang line would contain /usr/bin/env python3 instead of the
+# hardcoded path but it seems the user's environment is not inherited by
+# LaunchAgents (at least not on El Capitan).
+#
+
+import os
+import sys
+import errno
+import json
+import configparser
+import urllib3
+import urllib.parse
+import datetime
+
+import certifi
+import pync
+
+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)
+
+ for post in posts:
+ topic = topics_by_id[post["topic_id"]]
+ pync.Notifier.notify(topic["title"], title="New post mentioning '%s'" % keyword,
+ open="%s/t/%s/%d" % (domain, topic["slug"], topic["id"]), group=str(topic["id"]))
+
+ with open(watches_file, "w") as f:
+ config.write(f)
+ print("Finished")
+
+if __name__ == "__main__":
+ main()
bgstack15