initial commit
This commit is contained in:
commit
8d6448a952
160
.gitignore
vendored
Normal file
160
.gitignore
vendored
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
|
.pdm.toml
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
FROM python:buster
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
COPY requirements.txt ./
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
copy . .
|
||||||
|
|
||||||
|
CMD ["python", "./main.py"]
|
7
README.md
Normal file
7
README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# IDC - InterDimensional Cable
|
||||||
|
|
||||||
|
> The Interdimensional Cable is a cable box invented by Rick that gives access to television shows across every dimension.
|
||||||
|
|
||||||
|
Interdimensional Cable is a format consisting of short video clips that are perceived to be alien. The subreddit /r/interdimensionalcable is where enjoyers of this format find and post such videos.
|
||||||
|
|
||||||
|
This app shuffles the top /r/interdimensionalcable posts of the week (refreshed every hour) and continously plays them on https://idc.dws.rip.
|
99
main.py
Normal file
99
main.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import json
|
||||||
|
import os.path
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from flask import Flask, render_template
|
||||||
|
from flask_apscheduler import APScheduler
|
||||||
|
|
||||||
|
URL = "https://www.reddit.com/r/InterdimensionalCable/top.json?limit=100&t=week"
|
||||||
|
scheduler = APScheduler()
|
||||||
|
v = []
|
||||||
|
|
||||||
|
|
||||||
|
def getRedditData(url):
|
||||||
|
resp = requests.get(url)
|
||||||
|
text = resp.text
|
||||||
|
code = resp.status_code
|
||||||
|
while code == 429:
|
||||||
|
print("waiting for timeout to end ...")
|
||||||
|
time.sleep(2)
|
||||||
|
resp = requests.get(url)
|
||||||
|
text = resp.text
|
||||||
|
code = resp.status_code
|
||||||
|
|
||||||
|
data = json.loads(text)
|
||||||
|
token = data["data"]["after"]
|
||||||
|
ret = []
|
||||||
|
for post in data["data"]["children"]:
|
||||||
|
turl = post["data"]["url_overridden_by_dest"]
|
||||||
|
if "youtu.be" in turl:
|
||||||
|
turl = turl.replace("https://youtu.be/", "https://www.youtube.com/watch?v=")
|
||||||
|
if "m.youtube.com" in turl:
|
||||||
|
turl = turl.replace(
|
||||||
|
"https://m.youtube.com/embed", "https://www.youtube.com/watch?v="
|
||||||
|
)
|
||||||
|
turl = turl.replace("watch?v=", "embed/")
|
||||||
|
turl = turl + "?autoplay=1&enablejsapi=1"
|
||||||
|
ret.append(turl)
|
||||||
|
|
||||||
|
return ret, token
|
||||||
|
|
||||||
|
|
||||||
|
def doPages(numPages):
|
||||||
|
token = ""
|
||||||
|
vids = []
|
||||||
|
for i in range(numPages):
|
||||||
|
if i == 0:
|
||||||
|
v, t = getRedditData(URL)
|
||||||
|
vids = vids + v
|
||||||
|
token = t
|
||||||
|
time.sleep(2)
|
||||||
|
v, t = getRedditData(URL + f"&after={token}")
|
||||||
|
vids = vids + v
|
||||||
|
print(f"got page {i}...")
|
||||||
|
return vids
|
||||||
|
|
||||||
|
|
||||||
|
@scheduler.task("interval", id="do_job_1", minutes=60, misfire_grace_time=900)
|
||||||
|
def job1():
|
||||||
|
global v
|
||||||
|
doFetch = False
|
||||||
|
pexist = os.path.exists("temp.json")
|
||||||
|
|
||||||
|
if pexist:
|
||||||
|
print("temp.json exists")
|
||||||
|
sec = os.path.getmtime("temp.json")
|
||||||
|
now = time.time()
|
||||||
|
if now - sec > 600:
|
||||||
|
doFetch = True
|
||||||
|
else:
|
||||||
|
doFetch = True
|
||||||
|
|
||||||
|
if doFetch:
|
||||||
|
print("stale or no file, fetching...")
|
||||||
|
v = doPages(2)
|
||||||
|
with open("temp.json", "w") as f:
|
||||||
|
json.dump(v, f)
|
||||||
|
else:
|
||||||
|
print("fresh file, not fetching...")
|
||||||
|
with open("temp.json", "r") as f:
|
||||||
|
v = json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
scheduler.init_app(app)
|
||||||
|
scheduler.start()
|
||||||
|
job1()
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def hello_world():
|
||||||
|
global v
|
||||||
|
random.shuffle(v)
|
||||||
|
return render_template("index.html", vids=json.dumps(v))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(host="0.0.0.0", port=8080)
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
APScheduler==3.9.1
|
||||||
|
requests==2.26.0
|
||||||
|
Flask==2.0.3
|
BIN
static/favicon.ico
Normal file
BIN
static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
187
templates/index.html
Normal file
187
templates/index.html
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>InterdimensionalCable</title>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||||
|
<script
|
||||||
|
src="https://code.jquery.com/jquery-2.2.4.min.js"
|
||||||
|
integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44="
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
|
<style type="text/css">
|
||||||
|
body, html
|
||||||
|
{
|
||||||
|
margin: 0; padding: 0; height: 100%; overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content
|
||||||
|
{
|
||||||
|
position:absolute; left: 0; right: 0; bottom: 0; top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#player
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn{
|
||||||
|
border-radius: 10px;
|
||||||
|
margin:1em;
|
||||||
|
padding:1em 2em;
|
||||||
|
transition:all 125ms ease;
|
||||||
|
text-transform:uppercase;
|
||||||
|
color:#fff;
|
||||||
|
font-weight:500;
|
||||||
|
border:1px solid #d1d1d1
|
||||||
|
}
|
||||||
|
.btn:active{ transform:scale(.9) }
|
||||||
|
.btn:focus{ outline:none }
|
||||||
|
.btn:active,
|
||||||
|
.btn:hover,
|
||||||
|
.btn:focus{ color:#fff }
|
||||||
|
.btn:active:focus,
|
||||||
|
.btn:active:hover{ outline:none }
|
||||||
|
.btn:active,
|
||||||
|
.btn:active:hover,
|
||||||
|
.btn:active:focus{ transform:scale(.9) }
|
||||||
|
.btn:focus{ transform:scale(1) }
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background-color:#FF0000;
|
||||||
|
border:1px solid #FF0000;
|
||||||
|
box-shadow:0 2px 20px rgba(246,77,76,0.57)
|
||||||
|
}
|
||||||
|
.btn-danger:active{
|
||||||
|
box-shadow:0 2px 20px rgba(246,77,76,0.57)
|
||||||
|
}
|
||||||
|
.btn-danger:hover{
|
||||||
|
background-color:#ff2e2e;
|
||||||
|
border:1px solid #ff2e2e;
|
||||||
|
box-shadow:0 2px 20px rgba(246,77,76,0.67)
|
||||||
|
}
|
||||||
|
.btn-danger:active,
|
||||||
|
.btn-danger:focus,
|
||||||
|
.btn-danger:active:hover,
|
||||||
|
.btn-danger:active:focus{
|
||||||
|
border:1px solid #ff2e2e;
|
||||||
|
background-color:#ff2e2e
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#buttonl {
|
||||||
|
z-index: 101;
|
||||||
|
position: absolute;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
left: 3%;
|
||||||
|
top: 50%;
|
||||||
|
}
|
||||||
|
#buttonr {
|
||||||
|
z-index: 101;
|
||||||
|
position: absolute;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
right: 3%;
|
||||||
|
top: 50%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var tag = document.createElement('script');
|
||||||
|
|
||||||
|
tag.src = "https://www.youtube.com/iframe_api";
|
||||||
|
var firstScriptTag = document.getElementsByTagName('script')[0];
|
||||||
|
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
|
||||||
|
|
||||||
|
var idx=0;
|
||||||
|
|
||||||
|
var justHidden = false;
|
||||||
|
|
||||||
|
var videos = {{vids|safe}};
|
||||||
|
|
||||||
|
var player;
|
||||||
|
function onYouTubeIframeAPIReady() {
|
||||||
|
player = new YT.Player('player', {
|
||||||
|
events: {
|
||||||
|
'onReady': function(){},
|
||||||
|
'onStateChange': onPlayerStateChange
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPlayerStateChange(event) {
|
||||||
|
if (event.data == YT.PlayerState.ENDED) {
|
||||||
|
idx = idx + 1;
|
||||||
|
if (idx == videos.length) {
|
||||||
|
idx = 0;
|
||||||
|
}
|
||||||
|
$('#player').attr('src', videos[idx]);
|
||||||
|
player = new YT.Player('player', {
|
||||||
|
events: {
|
||||||
|
'onReady': function(){},
|
||||||
|
'onStateChange': onPlayerStateChange
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
var j;
|
||||||
|
|
||||||
|
$('#player').attr('src', videos[idx]);
|
||||||
|
$(document).mousemove(function() {
|
||||||
|
if (!justHidden) {
|
||||||
|
justHidden = false;
|
||||||
|
clearTimeout(j);
|
||||||
|
$('.btn').removeClass('hidden');
|
||||||
|
j = setTimeout('hide();', 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('#buttonl').click(function(){
|
||||||
|
idx = idx - 1;
|
||||||
|
if (idx < 0) {
|
||||||
|
idx = videos.length - 1;
|
||||||
|
}
|
||||||
|
$('#player').attr('src', videos[idx]);
|
||||||
|
player = new YT.Player('player', {
|
||||||
|
events: {
|
||||||
|
'onReady': function(){},
|
||||||
|
'onStateChange': onPlayerStateChange
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('#buttonr').click(function(){
|
||||||
|
idx = idx + 1;
|
||||||
|
if (idx == videos.length) {
|
||||||
|
idx = 0;
|
||||||
|
}
|
||||||
|
$('#player').attr('src', videos[idx]);
|
||||||
|
player = new YT.Player('player', {
|
||||||
|
events: {
|
||||||
|
'onReady': function(){},
|
||||||
|
'onStateChange': onPlayerStateChange
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function hide() {
|
||||||
|
$('.btn').addClass('hidden');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<div id="content">
|
||||||
|
<iframe
|
||||||
|
id="player"
|
||||||
|
src="https://www.youtube.com/embed/tgbNymZ7vqY?autoplay=1" frameborder="0" allow="autoplay">
|
||||||
|
</iframe>
|
||||||
|
<button id="buttonl" class="btn btn-danger hidden"><i class="fa fa-angle-left"></i></button>
|
||||||
|
<button id="buttonr" class="btn btn-danger hidden"><i class="fa fa-angle-right"></i></button>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user