kryten-webqueue 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kryten_webqueue-0.1.1/.github/workflows/python-publish.yml +75 -0
- kryten_webqueue-0.1.1/.github/workflows/release.yml +125 -0
- kryten_webqueue-0.1.1/.gitignore +79 -0
- kryten_webqueue-0.1.1/PKG-INFO +127 -0
- kryten_webqueue-0.1.1/README.md +101 -0
- kryten_webqueue-0.1.1/config.example.json +27 -0
- kryten_webqueue-0.1.1/deploy/kryten-webqueue.service +23 -0
- kryten_webqueue-0.1.1/deploy/nginx-queue.conf +49 -0
- kryten_webqueue-0.1.1/docs/IMPLEMENTATION_SPEC.md +1847 -0
- kryten_webqueue-0.1.1/docs/IMPL_API_GATE.md +341 -0
- kryten_webqueue-0.1.1/docs/IMPL_ECONOMY.md +489 -0
- kryten_webqueue-0.1.1/docs/IMPL_KRYTEN_PY.md +335 -0
- kryten_webqueue-0.1.1/docs/IMPL_ROBOT.md +193 -0
- kryten_webqueue-0.1.1/docs/PRE_PLAN_GAPS.md +793 -0
- kryten_webqueue-0.1.1/docs/PRODUCT_PLAN.md +1110 -0
- kryten_webqueue-0.1.1/kryten_webqueue/__init__.py +0 -0
- kryten_webqueue-0.1.1/kryten_webqueue/__main__.py +10 -0
- kryten_webqueue-0.1.1/kryten_webqueue/api_gate/__init__.py +0 -0
- kryten_webqueue-0.1.1/kryten_webqueue/api_gate/client.py +113 -0
- kryten_webqueue-0.1.1/kryten_webqueue/app.py +184 -0
- kryten_webqueue-0.1.1/kryten_webqueue/auth/__init__.py +0 -0
- kryten_webqueue-0.1.1/kryten_webqueue/auth/otp.py +10 -0
- kryten_webqueue-0.1.1/kryten_webqueue/auth/rate_limit.py +29 -0
- kryten_webqueue-0.1.1/kryten_webqueue/auth/session.py +40 -0
- kryten_webqueue-0.1.1/kryten_webqueue/catalog/__init__.py +0 -0
- kryten_webqueue-0.1.1/kryten_webqueue/catalog/db.py +562 -0
- kryten_webqueue-0.1.1/kryten_webqueue/catalog/images.py +114 -0
- kryten_webqueue-0.1.1/kryten_webqueue/catalog/sync.py +96 -0
- kryten_webqueue-0.1.1/kryten_webqueue/config.py +46 -0
- kryten_webqueue-0.1.1/kryten_webqueue/playlists/__init__.py +0 -0
- kryten_webqueue-0.1.1/kryten_webqueue/playlists/fire.py +71 -0
- kryten_webqueue-0.1.1/kryten_webqueue/playlists/importer.py +92 -0
- kryten_webqueue-0.1.1/kryten_webqueue/playlists/scheduler.py +72 -0
- kryten_webqueue-0.1.1/kryten_webqueue/queue/__init__.py +0 -0
- kryten_webqueue-0.1.1/kryten_webqueue/queue/ordering.py +186 -0
- kryten_webqueue-0.1.1/kryten_webqueue/queue/poller.py +43 -0
- kryten_webqueue-0.1.1/kryten_webqueue/queue/shadow.py +116 -0
- kryten_webqueue-0.1.1/kryten_webqueue/routes/__init__.py +0 -0
- kryten_webqueue-0.1.1/kryten_webqueue/routes/admin_playlists.py +98 -0
- kryten_webqueue-0.1.1/kryten_webqueue/routes/admin_queue.py +64 -0
- kryten_webqueue-0.1.1/kryten_webqueue/routes/admin_schedules.py +129 -0
- kryten_webqueue-0.1.1/kryten_webqueue/routes/auth.py +83 -0
- kryten_webqueue-0.1.1/kryten_webqueue/routes/catalog.py +44 -0
- kryten_webqueue-0.1.1/kryten_webqueue/routes/pages.py +82 -0
- kryten_webqueue-0.1.1/kryten_webqueue/routes/queue.py +144 -0
- kryten_webqueue-0.1.1/kryten_webqueue/routes/user.py +35 -0
- kryten_webqueue-0.1.1/kryten_webqueue/static/css/main.css +470 -0
- kryten_webqueue-0.1.1/kryten_webqueue/static/js/main.js +26 -0
- kryten_webqueue-0.1.1/kryten_webqueue/templates/admin/index.html +98 -0
- kryten_webqueue-0.1.1/kryten_webqueue/templates/auth/login.html +69 -0
- kryten_webqueue-0.1.1/kryten_webqueue/templates/base.html +41 -0
- kryten_webqueue-0.1.1/kryten_webqueue/templates/catalog/browse.html +105 -0
- kryten_webqueue-0.1.1/kryten_webqueue/templates/queue/index.html +126 -0
- kryten_webqueue-0.1.1/kryten_webqueue/templates/user/dashboard.html +87 -0
- kryten_webqueue-0.1.1/kryten_webqueue/ws/__init__.py +0 -0
- kryten_webqueue-0.1.1/kryten_webqueue/ws/handler.py +59 -0
- kryten_webqueue-0.1.1/kryten_webqueue/ws/manager.py +57 -0
- kryten_webqueue-0.1.1/pyproject.toml +45 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
name: Publish Python Package to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_call:
|
|
5
|
+
release:
|
|
6
|
+
types: [published]
|
|
7
|
+
push:
|
|
8
|
+
tags:
|
|
9
|
+
- 'kryten-webqueue-v*'
|
|
10
|
+
- 'v*'
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
id-token: write
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
release-build:
|
|
18
|
+
name: Build distribution packages
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout repository
|
|
23
|
+
uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- name: Install uv
|
|
26
|
+
uses: astral-sh/setup-uv@v4
|
|
27
|
+
with:
|
|
28
|
+
version: "latest"
|
|
29
|
+
|
|
30
|
+
- name: Set up Python
|
|
31
|
+
uses: actions/setup-python@v5
|
|
32
|
+
with:
|
|
33
|
+
python-version: "3.12"
|
|
34
|
+
|
|
35
|
+
- name: Build release distributions
|
|
36
|
+
run: |
|
|
37
|
+
uv build
|
|
38
|
+
echo "📦 Built packages:"
|
|
39
|
+
ls -lh dist/
|
|
40
|
+
|
|
41
|
+
- name: Upload distributions
|
|
42
|
+
uses: actions/upload-artifact@v4
|
|
43
|
+
with:
|
|
44
|
+
name: release-dists
|
|
45
|
+
path: dist/
|
|
46
|
+
|
|
47
|
+
pypi-publish:
|
|
48
|
+
name: Publish to PyPI
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
needs:
|
|
51
|
+
- release-build
|
|
52
|
+
environment:
|
|
53
|
+
name: pypi
|
|
54
|
+
permissions:
|
|
55
|
+
id-token: write
|
|
56
|
+
|
|
57
|
+
steps:
|
|
58
|
+
- name: Retrieve release distributions
|
|
59
|
+
uses: actions/download-artifact@v4
|
|
60
|
+
with:
|
|
61
|
+
name: release-dists
|
|
62
|
+
path: dist/
|
|
63
|
+
|
|
64
|
+
- name: Publish release distributions to PyPI
|
|
65
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
66
|
+
with:
|
|
67
|
+
packages-dir: dist/
|
|
68
|
+
skip-existing: true
|
|
69
|
+
attestations: false
|
|
70
|
+
|
|
71
|
+
- name: Success notification
|
|
72
|
+
if: success()
|
|
73
|
+
run: |
|
|
74
|
+
echo "✅ Successfully published to PyPI!"
|
|
75
|
+
echo "🔗 Package URL: https://pypi.org/project/kryten-webqueue/"
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
name: Release Automation
|
|
2
|
+
|
|
3
|
+
# Automatically create GitHub releases when version in pyproject.toml changes
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
paths:
|
|
9
|
+
- 'pyproject.toml'
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
pull-requests: read
|
|
15
|
+
id-token: write
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
create-release:
|
|
19
|
+
name: Create Release
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
with:
|
|
25
|
+
fetch-depth: 0 # Full history for changelog
|
|
26
|
+
|
|
27
|
+
- name: Read version from pyproject.toml
|
|
28
|
+
id: version
|
|
29
|
+
run: |
|
|
30
|
+
VERSION=$(grep -m1 'version = "' pyproject.toml | cut -d '"' -f 2)
|
|
31
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
32
|
+
echo "tag=v$VERSION" >> $GITHUB_OUTPUT
|
|
33
|
+
echo "📦 Version: $VERSION"
|
|
34
|
+
|
|
35
|
+
- name: Check if tag exists
|
|
36
|
+
id: check_tag
|
|
37
|
+
run: |
|
|
38
|
+
git fetch --tags
|
|
39
|
+
if git rev-parse "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then
|
|
40
|
+
echo "exists=true" >> $GITHUB_OUTPUT
|
|
41
|
+
echo "ℹ️ Tag ${{ steps.version.outputs.tag }} already exists"
|
|
42
|
+
else
|
|
43
|
+
echo "exists=false" >> $GITHUB_OUTPUT
|
|
44
|
+
echo "✨ Tag ${{ steps.version.outputs.tag }} will be created"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
- name: Generate changelog
|
|
48
|
+
id: changelog
|
|
49
|
+
if: steps.check_tag.outputs.exists == 'false'
|
|
50
|
+
run: |
|
|
51
|
+
echo "Generating changelog..."
|
|
52
|
+
|
|
53
|
+
# Get previous tag
|
|
54
|
+
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
|
|
55
|
+
|
|
56
|
+
if [ -z "$PREV_TAG" ]; then
|
|
57
|
+
echo "📝 First release - generating full changelog"
|
|
58
|
+
COMMITS=$(git log --pretty=format:"- %s (%h)" --no-merges)
|
|
59
|
+
else
|
|
60
|
+
echo "📝 Generating changelog since $PREV_TAG"
|
|
61
|
+
COMMITS=$(git log ${PREV_TAG}..HEAD --pretty=format:"- %s (%h)" --no-merges)
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Save changelog to file
|
|
65
|
+
cat > changelog.md << EOF
|
|
66
|
+
## What's Changed
|
|
67
|
+
|
|
68
|
+
${COMMITS}
|
|
69
|
+
|
|
70
|
+
**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREV_TAG}...${{ steps.version.outputs.tag }}
|
|
71
|
+
EOF
|
|
72
|
+
|
|
73
|
+
cat changelog.md
|
|
74
|
+
|
|
75
|
+
- name: Create Git tag
|
|
76
|
+
if: steps.check_tag.outputs.exists == 'false'
|
|
77
|
+
run: |
|
|
78
|
+
git config user.name "github-actions[bot]"
|
|
79
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
80
|
+
git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}"
|
|
81
|
+
git push origin "${{ steps.version.outputs.tag }}"
|
|
82
|
+
|
|
83
|
+
- name: Create GitHub Release
|
|
84
|
+
if: steps.check_tag.outputs.exists == 'false'
|
|
85
|
+
env:
|
|
86
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
87
|
+
run: |
|
|
88
|
+
gh release create "${{ steps.version.outputs.tag }}" \
|
|
89
|
+
--title "Release ${{ steps.version.outputs.tag }}" \
|
|
90
|
+
--notes-file changelog.md
|
|
91
|
+
|
|
92
|
+
- name: Skip release creation
|
|
93
|
+
if: steps.check_tag.outputs.exists == 'true'
|
|
94
|
+
run: |
|
|
95
|
+
echo "ℹ️ Release ${{ steps.version.outputs.tag }} already exists - skipping"
|
|
96
|
+
|
|
97
|
+
publish-to-pypi:
|
|
98
|
+
name: Publish to PyPI
|
|
99
|
+
needs: [create-release]
|
|
100
|
+
uses: ./.github/workflows/python-publish.yml
|
|
101
|
+
permissions:
|
|
102
|
+
id-token: write
|
|
103
|
+
contents: read
|
|
104
|
+
|
|
105
|
+
notify-release:
|
|
106
|
+
name: Notify Release Created
|
|
107
|
+
runs-on: ubuntu-latest
|
|
108
|
+
needs: [create-release, publish-to-pypi]
|
|
109
|
+
|
|
110
|
+
steps:
|
|
111
|
+
- uses: actions/checkout@v4
|
|
112
|
+
|
|
113
|
+
- name: Read version from pyproject.toml
|
|
114
|
+
id: version
|
|
115
|
+
run: |
|
|
116
|
+
VERSION=$(grep -m1 'version = "' pyproject.toml | cut -d '"' -f 2)
|
|
117
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
118
|
+
echo "tag=v$VERSION" >> $GITHUB_OUTPUT
|
|
119
|
+
|
|
120
|
+
- name: Send notification
|
|
121
|
+
run: |
|
|
122
|
+
echo "📢 Release notification"
|
|
123
|
+
echo "Version: ${{ steps.version.outputs.tag }}"
|
|
124
|
+
echo "Repository: ${{ github.repository }}"
|
|
125
|
+
echo "Release URL: https://github.com/${{ github.repository }}/releases/tag/${{ steps.version.outputs.tag }}"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
pip-wheel-metadata/
|
|
20
|
+
share/python-wheels/
|
|
21
|
+
*.egg-info/
|
|
22
|
+
.installed.cfg
|
|
23
|
+
*.egg
|
|
24
|
+
MANIFEST
|
|
25
|
+
|
|
26
|
+
# Virtual environments
|
|
27
|
+
.venv/
|
|
28
|
+
venv/
|
|
29
|
+
ENV/
|
|
30
|
+
env/
|
|
31
|
+
|
|
32
|
+
# PyCharm
|
|
33
|
+
.idea/
|
|
34
|
+
|
|
35
|
+
# VSCode
|
|
36
|
+
.vscode/
|
|
37
|
+
|
|
38
|
+
# pytest
|
|
39
|
+
.pytest_cache/
|
|
40
|
+
htmlcov/
|
|
41
|
+
.coverage
|
|
42
|
+
.coverage.*
|
|
43
|
+
coverage.xml
|
|
44
|
+
*.cover
|
|
45
|
+
.hypothesis/
|
|
46
|
+
|
|
47
|
+
# mypy
|
|
48
|
+
.mypy_cache/
|
|
49
|
+
.dmypy.json
|
|
50
|
+
dmypy.json
|
|
51
|
+
|
|
52
|
+
# Logs
|
|
53
|
+
*.log
|
|
54
|
+
logs/
|
|
55
|
+
|
|
56
|
+
# Configuration files (keep examples only)
|
|
57
|
+
config.json
|
|
58
|
+
config-*.json
|
|
59
|
+
!config.example.json
|
|
60
|
+
|
|
61
|
+
# Database files
|
|
62
|
+
*.db
|
|
63
|
+
*.db-wal
|
|
64
|
+
*.db-shm
|
|
65
|
+
|
|
66
|
+
# OS
|
|
67
|
+
.DS_Store
|
|
68
|
+
Thumbs.db
|
|
69
|
+
|
|
70
|
+
# Backup files
|
|
71
|
+
*~
|
|
72
|
+
*.bak
|
|
73
|
+
*.swp
|
|
74
|
+
|
|
75
|
+
# Test output
|
|
76
|
+
test-output/
|
|
77
|
+
|
|
78
|
+
# uv
|
|
79
|
+
uv.lock
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kryten-webqueue
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Netflix/Tubi-style catalog browser and pay-to-play queue management for CyTube
|
|
5
|
+
Author: grobertson
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Requires-Dist: aiosqlite>=0.20
|
|
9
|
+
Requires-Dist: apscheduler>=3.10
|
|
10
|
+
Requires-Dist: fastapi>=0.115
|
|
11
|
+
Requires-Dist: httpx>=0.27
|
|
12
|
+
Requires-Dist: jinja2>=3.1
|
|
13
|
+
Requires-Dist: pillow>=10.0
|
|
14
|
+
Requires-Dist: pydantic>=2.0
|
|
15
|
+
Requires-Dist: pyjwt>=2.8
|
|
16
|
+
Requires-Dist: uvicorn[standard]>=0.30
|
|
17
|
+
Requires-Dist: websockets>=12.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: black>=24.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: httpx; extra == 'dev'
|
|
21
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# kryten-webqueue
|
|
28
|
+
|
|
29
|
+
Netflix/Tubi-style catalog browser and pay-to-play queue management for CyTube.
|
|
30
|
+
|
|
31
|
+
## Architecture
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
Browser → nginx (queue.dropsugar.co) → kryten-webqueue (:2010) → kryten-api-gate (:24444) → NATS services
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- **HTTP-only** — webqueue never touches NATS directly
|
|
38
|
+
- **SQLite** for all local state (catalog, OTPs, queue shadow, playlists, schedules)
|
|
39
|
+
- **WebSocket** for real-time queue updates
|
|
40
|
+
- **Polling** api-gate every 3 seconds for playlist state
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Install
|
|
46
|
+
python -m venv .venv
|
|
47
|
+
.venv/bin/pip install -e .
|
|
48
|
+
|
|
49
|
+
# Configure
|
|
50
|
+
cp config.example.json /etc/kryten-webqueue/config.json
|
|
51
|
+
# Edit config.json with your secrets
|
|
52
|
+
|
|
53
|
+
# Run
|
|
54
|
+
WQ_CONFIG=/etc/kryten-webqueue/config.json python -m kryten_webqueue
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
See `config.example.json` for all options. Required secrets:
|
|
60
|
+
|
|
61
|
+
| Key | Description |
|
|
62
|
+
|-----|-------------|
|
|
63
|
+
| `secret_key` | Random string for JWT signing (≥32 chars) |
|
|
64
|
+
| `api_gate_token` | Bearer token for kryten-api-gate |
|
|
65
|
+
| `mediacms_token` | API token for MediaCMS catalog sync |
|
|
66
|
+
|
|
67
|
+
## Deployment
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Install service
|
|
71
|
+
sudo cp deploy/kryten-webqueue.service /etc/systemd/system/
|
|
72
|
+
sudo systemctl daemon-reload
|
|
73
|
+
sudo systemctl enable --now kryten-webqueue
|
|
74
|
+
|
|
75
|
+
# nginx
|
|
76
|
+
sudo cp deploy/nginx-queue.conf /etc/nginx/sites-available/queue.dropsugar.co
|
|
77
|
+
sudo ln -s /etc/nginx/sites-available/queue.dropsugar.co /etc/nginx/sites-enabled/
|
|
78
|
+
sudo nginx -t && sudo systemctl reload nginx
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## API Endpoints
|
|
82
|
+
|
|
83
|
+
### Auth
|
|
84
|
+
- `POST /auth/otp/request` — Request OTP (delivered via PM)
|
|
85
|
+
- `POST /auth/otp/verify` — Verify OTP, get session cookie
|
|
86
|
+
- `POST /auth/logout` — Clear session
|
|
87
|
+
- `GET /auth/me` — Current user info
|
|
88
|
+
|
|
89
|
+
### Catalog
|
|
90
|
+
- `GET /catalog/browse` — Paginated browse (with category filter)
|
|
91
|
+
- `GET /catalog/search?q=...` — Full-text search
|
|
92
|
+
- `GET /catalog/item/{token}` — Item detail
|
|
93
|
+
- `GET /catalog/categories` — List categories
|
|
94
|
+
|
|
95
|
+
### Queue
|
|
96
|
+
- `GET /queue/state` — Current queue state
|
|
97
|
+
- `POST /queue/add` — Add item (FIFO pay-queue)
|
|
98
|
+
- `POST /queue/playnext` — Add as play-next (premium)
|
|
99
|
+
- `GET /queue/preview?friendly_token=...&tier=...` — Cost preview
|
|
100
|
+
- `GET /queue/history` — User's queue history
|
|
101
|
+
|
|
102
|
+
### User
|
|
103
|
+
- `GET /user/balance` — Economy balance
|
|
104
|
+
- `GET /user/transactions` — Transaction history
|
|
105
|
+
- `GET /user/profile` — Profile info
|
|
106
|
+
|
|
107
|
+
### Admin (rank ≥ 3)
|
|
108
|
+
- `GET/POST/PUT/DELETE /admin/playlists/...` — Saved playlists CRUD
|
|
109
|
+
- `GET/POST/PUT/DELETE /admin/schedules/...` — Schedule CRUD
|
|
110
|
+
- `POST /admin/schedules/{id}/fire` — Manual fire
|
|
111
|
+
- `POST /admin/queue/clear` — Clear queue (refunds pay items)
|
|
112
|
+
- `DELETE /admin/queue/{uid}` — Remove item
|
|
113
|
+
- `POST /admin/queue/sync-now` — Trigger catalog sync
|
|
114
|
+
|
|
115
|
+
### WebSocket
|
|
116
|
+
- `ws://host/ws` — Real-time queue updates (auth via session cookie)
|
|
117
|
+
|
|
118
|
+
## Dependencies
|
|
119
|
+
|
|
120
|
+
- Python ≥ 3.12
|
|
121
|
+
- kryten-api-gate ≥ 0.3.6
|
|
122
|
+
- kryten-py ≥ 0.16.1 (upstream, not a direct dependency)
|
|
123
|
+
- kryten-economy ≥ 0.8.11 (upstream, not a direct dependency)
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
Proprietary — DropSugar / Q&A
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# kryten-webqueue
|
|
2
|
+
|
|
3
|
+
Netflix/Tubi-style catalog browser and pay-to-play queue management for CyTube.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Browser → nginx (queue.dropsugar.co) → kryten-webqueue (:2010) → kryten-api-gate (:24444) → NATS services
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- **HTTP-only** — webqueue never touches NATS directly
|
|
12
|
+
- **SQLite** for all local state (catalog, OTPs, queue shadow, playlists, schedules)
|
|
13
|
+
- **WebSocket** for real-time queue updates
|
|
14
|
+
- **Polling** api-gate every 3 seconds for playlist state
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Install
|
|
20
|
+
python -m venv .venv
|
|
21
|
+
.venv/bin/pip install -e .
|
|
22
|
+
|
|
23
|
+
# Configure
|
|
24
|
+
cp config.example.json /etc/kryten-webqueue/config.json
|
|
25
|
+
# Edit config.json with your secrets
|
|
26
|
+
|
|
27
|
+
# Run
|
|
28
|
+
WQ_CONFIG=/etc/kryten-webqueue/config.json python -m kryten_webqueue
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Configuration
|
|
32
|
+
|
|
33
|
+
See `config.example.json` for all options. Required secrets:
|
|
34
|
+
|
|
35
|
+
| Key | Description |
|
|
36
|
+
|-----|-------------|
|
|
37
|
+
| `secret_key` | Random string for JWT signing (≥32 chars) |
|
|
38
|
+
| `api_gate_token` | Bearer token for kryten-api-gate |
|
|
39
|
+
| `mediacms_token` | API token for MediaCMS catalog sync |
|
|
40
|
+
|
|
41
|
+
## Deployment
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Install service
|
|
45
|
+
sudo cp deploy/kryten-webqueue.service /etc/systemd/system/
|
|
46
|
+
sudo systemctl daemon-reload
|
|
47
|
+
sudo systemctl enable --now kryten-webqueue
|
|
48
|
+
|
|
49
|
+
# nginx
|
|
50
|
+
sudo cp deploy/nginx-queue.conf /etc/nginx/sites-available/queue.dropsugar.co
|
|
51
|
+
sudo ln -s /etc/nginx/sites-available/queue.dropsugar.co /etc/nginx/sites-enabled/
|
|
52
|
+
sudo nginx -t && sudo systemctl reload nginx
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## API Endpoints
|
|
56
|
+
|
|
57
|
+
### Auth
|
|
58
|
+
- `POST /auth/otp/request` — Request OTP (delivered via PM)
|
|
59
|
+
- `POST /auth/otp/verify` — Verify OTP, get session cookie
|
|
60
|
+
- `POST /auth/logout` — Clear session
|
|
61
|
+
- `GET /auth/me` — Current user info
|
|
62
|
+
|
|
63
|
+
### Catalog
|
|
64
|
+
- `GET /catalog/browse` — Paginated browse (with category filter)
|
|
65
|
+
- `GET /catalog/search?q=...` — Full-text search
|
|
66
|
+
- `GET /catalog/item/{token}` — Item detail
|
|
67
|
+
- `GET /catalog/categories` — List categories
|
|
68
|
+
|
|
69
|
+
### Queue
|
|
70
|
+
- `GET /queue/state` — Current queue state
|
|
71
|
+
- `POST /queue/add` — Add item (FIFO pay-queue)
|
|
72
|
+
- `POST /queue/playnext` — Add as play-next (premium)
|
|
73
|
+
- `GET /queue/preview?friendly_token=...&tier=...` — Cost preview
|
|
74
|
+
- `GET /queue/history` — User's queue history
|
|
75
|
+
|
|
76
|
+
### User
|
|
77
|
+
- `GET /user/balance` — Economy balance
|
|
78
|
+
- `GET /user/transactions` — Transaction history
|
|
79
|
+
- `GET /user/profile` — Profile info
|
|
80
|
+
|
|
81
|
+
### Admin (rank ≥ 3)
|
|
82
|
+
- `GET/POST/PUT/DELETE /admin/playlists/...` — Saved playlists CRUD
|
|
83
|
+
- `GET/POST/PUT/DELETE /admin/schedules/...` — Schedule CRUD
|
|
84
|
+
- `POST /admin/schedules/{id}/fire` — Manual fire
|
|
85
|
+
- `POST /admin/queue/clear` — Clear queue (refunds pay items)
|
|
86
|
+
- `DELETE /admin/queue/{uid}` — Remove item
|
|
87
|
+
- `POST /admin/queue/sync-now` — Trigger catalog sync
|
|
88
|
+
|
|
89
|
+
### WebSocket
|
|
90
|
+
- `ws://host/ws` — Real-time queue updates (auth via session cookie)
|
|
91
|
+
|
|
92
|
+
## Dependencies
|
|
93
|
+
|
|
94
|
+
- Python ≥ 3.12
|
|
95
|
+
- kryten-api-gate ≥ 0.3.6
|
|
96
|
+
- kryten-py ≥ 0.16.1 (upstream, not a direct dependency)
|
|
97
|
+
- kryten-economy ≥ 0.8.11 (upstream, not a direct dependency)
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
Proprietary — DropSugar / Q&A
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"channel": "Q_A",
|
|
3
|
+
"host": "0.0.0.0",
|
|
4
|
+
"port": 2010,
|
|
5
|
+
"secret_key": "CHANGE_ME_long_random_string",
|
|
6
|
+
"session_ttl_hours": 24,
|
|
7
|
+
|
|
8
|
+
"api_gate_url": "http://127.0.0.1:24444",
|
|
9
|
+
"api_gate_token": "CHANGE_ME",
|
|
10
|
+
|
|
11
|
+
"mediacms_url": "https://www.dropsugar.com",
|
|
12
|
+
"mediacms_token": "CHANGE_ME",
|
|
13
|
+
|
|
14
|
+
"tmdb_api_key": "",
|
|
15
|
+
"omdb_api_key": "",
|
|
16
|
+
|
|
17
|
+
"db_path": "/var/lib/kryten-webqueue/webqueue.db",
|
|
18
|
+
|
|
19
|
+
"image_dir": "/var/lib/kryten-webqueue/images",
|
|
20
|
+
"placeholder_dir": "/var/lib/kryten-webqueue/images/placeholders",
|
|
21
|
+
|
|
22
|
+
"catalog_sync_interval_hours": 4,
|
|
23
|
+
"pre_fire_lock_minutes_default": 15,
|
|
24
|
+
"state_poll_interval_sec": 3.0,
|
|
25
|
+
|
|
26
|
+
"prometheus_port": 28292
|
|
27
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=kryten-webqueue - CyTube Queue Management
|
|
3
|
+
After=network.target
|
|
4
|
+
|
|
5
|
+
[Service]
|
|
6
|
+
Type=simple
|
|
7
|
+
User=kryten
|
|
8
|
+
Group=kryten
|
|
9
|
+
WorkingDirectory=/opt/kryten-webqueue
|
|
10
|
+
Environment=WQ_CONFIG=/etc/kryten-webqueue/config.json
|
|
11
|
+
ExecStart=/opt/kryten-webqueue/.venv/bin/python -m kryten_webqueue
|
|
12
|
+
Restart=always
|
|
13
|
+
RestartSec=5
|
|
14
|
+
|
|
15
|
+
# Hardening
|
|
16
|
+
NoNewPrivileges=true
|
|
17
|
+
ProtectSystem=strict
|
|
18
|
+
ProtectHome=true
|
|
19
|
+
ReadWritePaths=/var/lib/kryten-webqueue
|
|
20
|
+
PrivateTmp=true
|
|
21
|
+
|
|
22
|
+
[Install]
|
|
23
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
server {
|
|
2
|
+
listen 443 ssl http2;
|
|
3
|
+
server_name queue.dropsugar.co;
|
|
4
|
+
|
|
5
|
+
ssl_certificate /etc/letsencrypt/live/queue.dropsugar.co/fullchain.pem;
|
|
6
|
+
ssl_certificate_key /etc/letsencrypt/live/queue.dropsugar.co/privkey.pem;
|
|
7
|
+
|
|
8
|
+
# WebSocket upgrade
|
|
9
|
+
location /ws {
|
|
10
|
+
proxy_pass http://127.0.0.1:2010;
|
|
11
|
+
proxy_http_version 1.1;
|
|
12
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
13
|
+
proxy_set_header Connection "upgrade";
|
|
14
|
+
proxy_set_header Host $host;
|
|
15
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
16
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
17
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
18
|
+
proxy_read_timeout 86400;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# API + pages
|
|
22
|
+
location / {
|
|
23
|
+
proxy_pass http://127.0.0.1:2010;
|
|
24
|
+
proxy_set_header Host $host;
|
|
25
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
26
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
27
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Static assets - serve directly for performance
|
|
31
|
+
location /static/ {
|
|
32
|
+
alias /opt/kryten-webqueue/kryten_webqueue/static/;
|
|
33
|
+
expires 7d;
|
|
34
|
+
add_header Cache-Control "public, immutable";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Cover art images
|
|
38
|
+
location /images/ {
|
|
39
|
+
alias /var/lib/kryten-webqueue/images/;
|
|
40
|
+
expires 30d;
|
|
41
|
+
add_header Cache-Control "public, immutable";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
server {
|
|
46
|
+
listen 80;
|
|
47
|
+
server_name queue.dropsugar.co;
|
|
48
|
+
return 301 https://$server_name$request_uri;
|
|
49
|
+
}
|