Initial repod server commit with basic Bassdrive support
commit
622b90aeaa
|
@ -0,0 +1,6 @@
|
||||||
|
*.pyc
|
||||||
|
bin/
|
||||||
|
lib/
|
||||||
|
share/
|
||||||
|
include/
|
||||||
|
.idea/workspace.xml
|
|
@ -0,0 +1 @@
|
||||||
|
repod
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<resourceExtensions />
|
||||||
|
<wildcardResourcePatterns>
|
||||||
|
<entry name="!?*.java" />
|
||||||
|
<entry name="!?*.form" />
|
||||||
|
<entry name="!?*.class" />
|
||||||
|
<entry name="!?*.groovy" />
|
||||||
|
<entry name="!?*.scala" />
|
||||||
|
<entry name="!?*.flex" />
|
||||||
|
<entry name="!?*.kt" />
|
||||||
|
<entry name="!?*.clj" />
|
||||||
|
<entry name="!?*.aj" />
|
||||||
|
</wildcardResourcePatterns>
|
||||||
|
<annotationProcessing>
|
||||||
|
<profile default="true" name="Default" enabled="false">
|
||||||
|
<processorPath useClasspath="true" />
|
||||||
|
</profile>
|
||||||
|
</annotationProcessing>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<component name="CopyrightManager">
|
||||||
|
<settings default="" />
|
||||||
|
</component>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding">
|
||||||
|
<file url="PROJECT" charset="UTF-8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
|
<type id="django" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
|
||||||
|
<OptionsSetting value="true" id="Add" />
|
||||||
|
<OptionsSetting value="true" id="Remove" />
|
||||||
|
<OptionsSetting value="true" id="Checkout" />
|
||||||
|
<OptionsSetting value="true" id="Update" />
|
||||||
|
<OptionsSetting value="true" id="Status" />
|
||||||
|
<OptionsSetting value="true" id="Edit" />
|
||||||
|
<ConfirmationsSetting value="0" id="Add" />
|
||||||
|
<ConfirmationsSetting value="0" id="Remove" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="Python 3.5.1 (C:\Users\Bradlee Speice\Anaconda3\python.exe)" project-jdk-type="Python SDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
<component name="masterDetails">
|
||||||
|
<states>
|
||||||
|
<state key="ProjectJDKs.UI">
|
||||||
|
<settings>
|
||||||
|
<last-edited>Python 3.5.1 (C:\Users\Bradlee Speice\Anaconda3\python.exe)</last-edited>
|
||||||
|
<splitter-proportions>
|
||||||
|
<option name="proportions">
|
||||||
|
<list>
|
||||||
|
<option value="0.2" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</splitter-proportions>
|
||||||
|
</settings>
|
||||||
|
</state>
|
||||||
|
</states>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/repod.iml" filepath="$PROJECT_DIR$/repod.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,4 @@
|
||||||
|
pyramid>=1.6.1
|
||||||
|
feedgen>=0.3.2
|
||||||
|
PyYAML>=3.11
|
||||||
|
requests>=2.9.1
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Format: (all args are passed to __init__ as kwargs
|
||||||
|
#
|
||||||
|
# <mountpoint>:
|
||||||
|
# class: <feed_class>
|
||||||
|
# args:
|
||||||
|
# key: value
|
||||||
|
subfactory-show:
|
||||||
|
package: bassdrive
|
||||||
|
class: BassdriveFeed
|
||||||
|
args:
|
||||||
|
url: http://archives.bassdrivearchive.com/1%20-%20Monday/Subfactory%20Show%20-%20DJ%20Spim/
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
|
@ -0,0 +1,29 @@
|
||||||
|
"""
|
||||||
|
Given a configuration file, set up everything needed to kick
|
||||||
|
off the server.
|
||||||
|
"""
|
||||||
|
from importlib import import_module
|
||||||
|
import yaml
|
||||||
|
from pyramid.config import Configurator
|
||||||
|
|
||||||
|
# Needed for import_module call
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
import modules
|
||||||
|
|
||||||
|
# Hardcoded, need to look up from users $HOME later
|
||||||
|
file_loc = 'example_conf.yaml'
|
||||||
|
|
||||||
|
|
||||||
|
def build_configuration(conf=file_loc) -> Configurator:
|
||||||
|
with open(conf) as conf_file:
|
||||||
|
conf_dict = yaml.load(conf_file)
|
||||||
|
server_conf = Configurator()
|
||||||
|
for mountpoint, feed in conf_dict.items():
|
||||||
|
feed_package = import_module('modules.' + feed['package'])
|
||||||
|
feed_class = getattr(feed_package, feed['class'])
|
||||||
|
feed_instance = feed_class(**feed['args'])
|
||||||
|
|
||||||
|
server_conf.add_route(mountpoint, '/' + mountpoint + '/')
|
||||||
|
server_conf.add_view(feed_instance.view, route_name=mountpoint)
|
||||||
|
|
||||||
|
return server_conf
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Format: (all args are passed to __init__ as kwargs
|
||||||
|
#
|
||||||
|
# <mountpoint>:
|
||||||
|
# class: <feed_class>
|
||||||
|
# args:
|
||||||
|
# key: value
|
||||||
|
subfactory-show:
|
||||||
|
package: bassdrive
|
||||||
|
class: BassdriveFeed
|
||||||
|
args:
|
||||||
|
url: http://archives.bassdrivearchive.com/1%20-%20Monday/Subfactory%20Show%20-%20DJ%20Spim/
|
|
@ -0,0 +1,74 @@
|
||||||
|
"""
|
||||||
|
Podcast provider for the Bassdrive Archives
|
||||||
|
"""
|
||||||
|
from html.parser import HTMLParser
|
||||||
|
from urllib.parse import unquote
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from feedgen.feed import FeedGenerator
|
||||||
|
|
||||||
|
from podcast import BasePodcast
|
||||||
|
|
||||||
|
|
||||||
|
class BassdriveParser(HTMLParser):
|
||||||
|
links = []
|
||||||
|
record_link_text = False
|
||||||
|
link_url = ''
|
||||||
|
|
||||||
|
def handle_starttag(self, tag, attrs):
|
||||||
|
href = ''
|
||||||
|
for attr, val in attrs:
|
||||||
|
if attr == 'href':
|
||||||
|
href = val
|
||||||
|
|
||||||
|
if tag == 'a' and href.find('mp3') != -1:
|
||||||
|
self.record_link_text = True
|
||||||
|
self.link_url = href
|
||||||
|
|
||||||
|
def handle_data(self, data):
|
||||||
|
if self.record_link_text:
|
||||||
|
print(self.link_url)
|
||||||
|
self.links.append((data, self.link_url))
|
||||||
|
self.record_link_text = False
|
||||||
|
|
||||||
|
def get_links(self):
|
||||||
|
# Reverse to sort in descending date order
|
||||||
|
self.links.reverse()
|
||||||
|
return self.links
|
||||||
|
|
||||||
|
|
||||||
|
class BassdriveFeed(BasePodcast):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
print(kwargs)
|
||||||
|
self.url = kwargs['url']
|
||||||
|
# Get the title and DJ while handling trailing slash
|
||||||
|
url_pretty = unquote(self.url)
|
||||||
|
elems = filter(lambda x: x, url_pretty.split('/'))
|
||||||
|
self.title, self.dj = list(elems)[-1].split(' - ')
|
||||||
|
|
||||||
|
def build_feed(self):
|
||||||
|
"Build the feed given our existing URL"
|
||||||
|
# Get all the episodes
|
||||||
|
page_content = str(requests.get(self.url).content)
|
||||||
|
parser = BassdriveParser()
|
||||||
|
parser.feed(page_content)
|
||||||
|
links = parser.get_links()
|
||||||
|
|
||||||
|
# And turn them into something usable
|
||||||
|
fg = FeedGenerator()
|
||||||
|
fg.load_extension('podcast')
|
||||||
|
fg.id(self.url)
|
||||||
|
fg.title(self.title)
|
||||||
|
fg.description(self.title)
|
||||||
|
fg.author({'name': self.dj})
|
||||||
|
fg.language('en')
|
||||||
|
fg.link({'href': self.url, 'rel': 'alternate'})
|
||||||
|
|
||||||
|
for link in links:
|
||||||
|
fe = fg.add_entry()
|
||||||
|
fe.author({'name': self.dj})
|
||||||
|
fe.title(link[0])
|
||||||
|
fe.description(link[0])
|
||||||
|
fe.enclosure(self.url + link[1], 0, 'audio/mpeg')
|
||||||
|
|
||||||
|
return fg
|
|
@ -0,0 +1,16 @@
|
||||||
|
"""
|
||||||
|
Base skeleton for what needs to be implemented by a podcast provider
|
||||||
|
"""
|
||||||
|
from feedgen.feed import FeedGenerator
|
||||||
|
from pyramid.response import Response
|
||||||
|
|
||||||
|
class BasePodcast():
|
||||||
|
def build_feed(self) -> FeedGenerator:
|
||||||
|
"Return a list of all episodes, in descending date order"
|
||||||
|
pass
|
||||||
|
|
||||||
|
def view(self, request):
|
||||||
|
fg = self.build_feed()
|
||||||
|
response = Response(fg.rss_str(pretty=True))
|
||||||
|
response.content_type = 'application/rss+xml'
|
||||||
|
return response
|
|
@ -0,0 +1,10 @@
|
||||||
|
from wsgiref.simple_server import make_server
|
||||||
|
from conf_parser import build_configuration
|
||||||
|
|
||||||
|
def start_server():
|
||||||
|
app = build_configuration().make_wsgi_app()
|
||||||
|
server = make_server('0.0.0.0', 8080, app)
|
||||||
|
server.serve_forever()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
start_server()
|
Loading…
Reference in New Issue