├── .gitignore ├── hurley ├── __init__.py ├── podcasts.py ├── templates │ ├── about.html │ ├── index.html │ ├── privacy.html │ └── base.html ├── dijkstra.py └── server.py ├── run.py ├── hurley.wsgi ├── README.md └── data.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .venv 3 | -------------------------------------------------------------------------------- /hurley/__init__.py: -------------------------------------------------------------------------------- 1 | from hurley.server import app 2 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from hurley.server import app 4 | 5 | if __name__ == '__main__': 6 | app.run(host='0.0.0.0') 7 | -------------------------------------------------------------------------------- /hurley.wsgi: -------------------------------------------------------------------------------- 1 | import sys 2 | import site 3 | 4 | sdir = "/home/alexwlchan/.virtualenvs/hurley/local/lib/python2.7/site-packages" 5 | site.addsitedir(sdir) 6 | 7 | # Expand Python classes path with your app's path 8 | sys.path.insert(0, "/var/www/six-degrees-of-myke/hurley") 9 | 10 | from hurley import app 11 | 12 | # Put logging code (and imports) here ... 13 | 14 | # Initialize WSGI app object 15 | application = app 16 | -------------------------------------------------------------------------------- /hurley/podcasts.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import json 3 | 4 | from hurley.dijkstra import Graph 5 | 6 | import os 7 | with open("/var/www/six-degrees-of-myke/hurley/data.json") as f: 8 | data = json.loads(f.read()) 9 | 10 | pairings = set() 11 | podcasters = set() 12 | for value in data.values(): 13 | for person in value: 14 | podcasters.add(person) 15 | for i in itertools.permutations(value, 2): 16 | pairings.add(i) 17 | pairings = list(pairings) 18 | 19 | graph = Graph(pairings) 20 | 21 | 22 | def find_links(result): 23 | pairings = [] 24 | for i in xrange(len(result) - 1): 25 | pair = list(result)[i:i+2] 26 | for podcast, podcasters in data.iteritems(): 27 | if all(item in podcasters for item in list(pair)): 28 | pairings.append((pair[0], pair[1], podcast)) 29 | return pairings 30 | -------------------------------------------------------------------------------- /hurley/templates/about.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
9 | This started as a joke in episode #15 of Cortex, a podcast on the Relay.FM network. 10 |
11 | 12 |13 | The hosts were joking about the Six Degrees of Kevin Bacon, and how Myke is the Kevin Bacon of the podcasting world. 14 | He founded his own podcast network, and co-hosts many of the shows. 15 | They were wondering how many connections you could find from Myke to other popular podcasters. 16 |
17 | 18 |19 | Lots of people were making static charts, but those are hard to follow and don't scale to many podcasters. 20 | I made this site as a bit of fun while cooking dinner. 21 |
22 | 23 |24 | It serves no purpose, it's just a bit of fun. I hope it makes you smile. :-) 25 |
26 | 27 | {% endblock %} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Six Degrees of Myke Hurley](http://six-degrees-of-myke.net/) 2 | 3 | This is a quick Flask app for finding links between podcasters, a la the [Six Degrees of Kevin Bacon](https://en.wikipedia.org/wiki/Six_Degrees_of_Kevin_Bacon) game. It's a bit of nonsense that I hope will make people smile. 4 | 5 | For the slightly longer description, see the [about page](http://six-degrees-of-myke.net/about). 6 | 7 | ### The code 8 | 9 | This is just a simple Flask app that turns a list of podcasts and hosts into a graph, then uses [Dijkstra's algorithm](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm) to find the shortest path between two hosts (if one exists). 10 | The code for Dijkstra is taken from [Rosetta code](http://rosettacode.org/wiki/Dijkstra%27s_algorithm), because I threw this together in a hurry. 11 | 12 | Most of this is quite messy because I threw it together quickly. It's just a bit of fun, not a serious project. 13 | 14 | ### Contributing 15 | 16 | If you want to expand the list of podcasts, you should edit `data.json`. Each podcast gets an entry of the form `name: [list of hosts]`; for example, 17 | 18 | ```json 19 | "Hello Internet": [ 20 | "Brady Haran", 21 | "CGP Grey" 22 | ] 23 | ``` 24 | 25 | Please keep the host names alphabetically ordered, and likewise the podcasts. 26 | 27 | Currently I have all the Relay.FM and 5by5 shows, plus a few others. More are always welcome. :-) 28 | -------------------------------------------------------------------------------- /hurley/dijkstra.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | This does the heavy lifting; it uses Djiksta's algorithm to find the 4 | shortest path between podcast hosts. 5 | 6 | Heavily cribbed from http://rosettacode.org/wiki/Dijkstra%27s_algorithm#Python 7 | """ 8 | 9 | from collections import namedtuple, deque 10 | import itertools 11 | import json 12 | from pprint import pprint as pp 13 | 14 | 15 | inf = float('inf') 16 | Edge = namedtuple('Edge', ['start', 'end']) 17 | 18 | 19 | class Graph(): 20 | def __init__(self, edges): 21 | self.edges = edges2 = [Edge(*edge) for edge in edges] 22 | self.vertices = set(sum(([e.start, e.end] for e in edges2), [])) 23 | 24 | def dijkstra(self, source, dest): 25 | assert source in self.vertices 26 | dist = {vertex: inf for vertex in self.vertices} 27 | previous = {vertex: None for vertex in self.vertices} 28 | dist[source] = 0 29 | q = self.vertices.copy() 30 | neighbours = {vertex: set() for vertex in self.vertices} 31 | for start, end in self.edges: 32 | neighbours[start].add((end, 1)) 33 | # pp(neighbours) 34 | 35 | while q: 36 | u = min(q, key=lambda vertex: dist[vertex]) 37 | q.remove(u) 38 | if dist[u] == inf or u == dest: 39 | break 40 | for v, cost in neighbours[u]: 41 | alt = dist[u] + cost 42 | if alt < dist[v]: # Relax (u,v,a) 43 | dist[v] = alt 44 | previous[v] = u 45 | # pp(previous) 46 | s, u = deque(), dest 47 | while previous[u]: 48 | s.appendleft(u) 49 | u = previous[u] 50 | s.appendleft(u) 51 | return s 52 | -------------------------------------------------------------------------------- /hurley/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from flask import Flask, render_template 4 | from flask.ext.wtf import Form 5 | from wtforms import SelectField 6 | 7 | from hurley.podcasts import pairings, podcasters, graph, find_links 8 | 9 | 10 | app = Flask(__name__) 11 | key = 'no really tis must dosidhksjdfgnvkyjrhmstbgnemr,test secret key' 12 | app.config['WTF_CSRF_ENABLED'] = True 13 | app.config['SECRET_KEY'] = key 14 | 15 | 16 | MYCACHE = {} 17 | 18 | 19 | class PodcastForm(Form): 20 | choices = [("", "")] + [(p, p) for p in sorted(podcasters)] 21 | src = SelectField('src', choices=choices) 22 | dst = SelectField('dst', choices=choices) 23 | 24 | 25 | @app.route('/', methods=['GET', 'POST']) 26 | def hello_world(): 27 | form = PodcastForm() 28 | result = None 29 | print MYCACHE 30 | if form.validate_on_submit() and form.src.data and form.dst.data: 31 | pairings = MYCACHE.setdefault((form.src.data, form.dst.data), 32 | graph.dijkstra(form.src.data, 33 | form.dst.data)) 34 | if form.src.data == form.dst.data: 35 | result = ['identical'] 36 | elif len(pairings) == 1: 37 | result = ["disconnected"] 38 | else: 39 | result = find_links(pairings) 40 | 41 | return render_template('index.html', 42 | form=form, 43 | src=form.src.data, 44 | dst=form.dst.data, 45 | result=result) 46 | 47 | 48 | @app.route('/about') 49 | def about(): 50 | return render_template('about.html', title="About") 51 | 52 | 53 | @app.route('/privacy') 54 | def privacy(): 55 | return render_template('privacy.html', title="Privacy") 56 | -------------------------------------------------------------------------------- /hurley/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 21 | 22 | {% if result %} 23 |{{ src }} and {{ dst }} are separated by {{ result|count }} podcasts:
33 |{{ r[0] }} and {{ r[1] }} are both on {{ r[2] }}.
I use Piwik to provide basic analytics for six-degrees-of-myke.net.
Piwik is a piece of software that records data about people who visit the site. It installs a cookie to record basic tracking data.
11 | 12 |For a full list, see What data does Piwik track? on the Piwik support site.
15 | 16 |I only record the default information collected by the Piwik JavaScript tracker. I don’t use any of the optional add-ons.
17 | 18 |I can't see which podcast pairs you're looking up.
19 | 20 |I’m only interested in two bits of information:
23 | 24 |Mostly this is a vanity thing – I just like seeing how many people are visiting my sites.
30 | 31 |I use the self-hosted version of Piwik, which means the data is stored only on my server. It’s not shared with Piwik or other third-parties (such as advertisers). I don’t run advertising on the site, tracking-enabled or otherwise.
32 | 33 |Quoting from the Piwik Privacy page:
36 | 37 |38 |42 | 43 |Opting out of all data collection
39 | 40 |To opt-out of all web tracking technologies (including Google Analytics and Piwik), using an extension such as Noscript or Ghostery is the safest way: these browser extensions will disable all the known JavaScript trackers and ensure that your browser does not send a request to external tracking servers. If you wish to browse the Internet without your IP address being tracked at all, please consider using Tor Browser which will automatically connect you to the secured and anonymous Tor network.
41 |
If you don’t want to install a browser plugin, you can use the opt-out option below. This will set an opt-out cookie in your browser, which tells Piwik to ignore your visits:
44 | 45 | 46 | 47 | 48 | {% endblock %} -------------------------------------------------------------------------------- /hurley/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 21 | 22 | 23 | 24 |What is this nonsense? • Source on GitHub • Privacy policy
107 | 108 |Made by @alexwlchan.
109 | 110 |