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