kryten-webqueue 0.9.6__tar.gz → 0.9.7__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.9.6 → kryten_webqueue-0.9.7}/CHANGELOG.md +8 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/PKG-INFO +1 -1
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/queue/ordering.py +76 -10
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/templates/admin/schedules.html +1 -1
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/pyproject.toml +1 -1
- kryten_webqueue-0.9.7/tests/test_queue_announce.py +106 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/.github/workflows/python-publish.yml +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/.github/workflows/release.yml +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/.gitignore +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/README.md +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/config.example.json +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/deploy/kryten-webqueue.service +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/deploy/nginx-queue.conf +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/docs/IMPLEMENTATION_SPEC.md +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/docs/IMPL_API_GATE.md +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/docs/IMPL_ECONOMY.md +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/docs/IMPL_KRYTEN_PY.md +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/docs/IMPL_ROBOT.md +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/docs/PRE_PLAN_GAPS.md +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/docs/PRODUCT_PLAN.md +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/docs/SPEC_JOBS_AND_BROWSE.md +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/__init__.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/__main__.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/api_gate/__init__.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/api_gate/client.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/app.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/auth/__init__.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/auth/otp.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/auth/rate_limit.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/auth/session.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/catalog/__init__.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/catalog/db.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/catalog/images.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/catalog/mediacms.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/catalog/sync.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/config.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/integrations/__init__.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/integrations/cmsutils/__init__.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/integrations/cmsutils/_common.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/integrations/cmsutils/enrichmeta.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/integrations/cmsutils/enrichtitles.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/integrations/cmsutils/enrichtv.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/integrations/cmsutils/fetchurls.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/integrations/ytpipe/__init__.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/integrations/ytpipe/downloader.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/jobs/__init__.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/jobs/manager.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/jobs/tasks.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/playlists/__init__.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/playlists/fire.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/playlists/importer.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/playlists/scheduler.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/queue/__init__.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/queue/poller.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/queue/shadow.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/routes/__init__.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/routes/admin_catalog.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/routes/admin_jobs.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/routes/admin_playlists.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/routes/admin_queue.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/routes/admin_schedules.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/routes/auth.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/routes/catalog.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/routes/pages.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/routes/queue.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/routes/user.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/static/css/main.css +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/static/js/main.js +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/templates/admin/index.html +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/templates/admin/playlists.html +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/templates/admin/queue_mgmt.html +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/templates/auth/login.html +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/templates/base.html +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/templates/catalog/browse.html +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/templates/catalog/item_detail.html +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/templates/catalog/item_not_found.html +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/templates/queue/index.html +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/templates/user/dashboard.html +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/ws/__init__.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/ws/handler.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/ws/manager.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/tests/__init__.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/tests/test_phase1.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/tests/test_phase2_jobs.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/tests/test_phase3_jobs.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/tests/test_phase4_live_fixes.py +0 -0
- {kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/tests/test_playlist_import.py +0 -0
|
@@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.7] — 2026-06-11
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **Paid-queue chat announcement reworded.** A purchased item now announces as `"<title> added to the queue with Zcoin by <user> and is now <position>."` where `<position>` is `next` for the item immediately after the currently-playing one, or an English ordinal counting the now-playing item as first (e.g. `third`, `forty-second`, `one hundred seventh`). Position is computed relative to the currently-playing item and wraps around the playlist.
|
|
14
|
+
- **Admin queueing is no longer announced** in the channel chat (only paid placements are announced).
|
|
15
|
+
- Renamed the admin Schedules heading from “Scheduled Fires” to “Scheduled Events”.
|
|
16
|
+
|
|
9
17
|
## [0.9.6] — 2026-06-11
|
|
10
18
|
|
|
11
19
|
### Added
|
|
@@ -26,7 +26,7 @@ def _add_failure_reason(add_result: dict | None, exc: httpx.HTTPStatusError | No
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def _announcement_position(shadow, uid: int) -> int | None:
|
|
29
|
-
"""Position of the item for chat announcement.
|
|
29
|
+
"""Position of the item for chat announcement (deprecated; kept for tests).
|
|
30
30
|
|
|
31
31
|
Counting starts at the currently-playing item (position 0), so the next
|
|
32
32
|
item to play is position 1. The shadow mirrors the full CyTube playlist
|
|
@@ -39,13 +39,80 @@ def _announcement_position(shadow, uid: int) -> int | None:
|
|
|
39
39
|
return None
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
_ONES_ORDINAL = {
|
|
43
|
+
"one": "first", "two": "second", "three": "third", "five": "fifth",
|
|
44
|
+
"eight": "eighth", "nine": "ninth", "twelve": "twelfth",
|
|
45
|
+
}
|
|
46
|
+
_ONES = ["", "one", "two", "three", "four", "five", "six", "seven", "eight",
|
|
47
|
+
"nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
|
|
48
|
+
"sixteen", "seventeen", "eighteen", "nineteen"]
|
|
49
|
+
_TENS = ["", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy",
|
|
50
|
+
"eighty", "ninety"]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _cardinal_words(n: int) -> str:
|
|
54
|
+
"""English cardinal words for 1..999 (queue positions never exceed this)."""
|
|
55
|
+
if n < 20:
|
|
56
|
+
return _ONES[n]
|
|
57
|
+
if n < 100:
|
|
58
|
+
tens, ones = divmod(n, 10)
|
|
59
|
+
return _TENS[tens] + (f"-{_ONES[ones]}" if ones else "")
|
|
60
|
+
hundreds, rem = divmod(n, 100)
|
|
61
|
+
head = f"{_ONES[hundreds]} hundred"
|
|
62
|
+
return f"{head} {_cardinal_words(rem)}" if rem else head
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _to_ordinal_word(word: str) -> str:
|
|
66
|
+
"""Convert a single cardinal word to its ordinal form."""
|
|
67
|
+
if word in _ONES_ORDINAL:
|
|
68
|
+
return _ONES_ORDINAL[word]
|
|
69
|
+
if word.endswith("y"):
|
|
70
|
+
return word[:-1] + "ieth"
|
|
71
|
+
return word + "th"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _ordinal_words(n: int) -> str:
|
|
75
|
+
"""English ordinal words for n (e.g. 3 -> 'third', 42 -> 'forty-second')."""
|
|
76
|
+
cardinal = _cardinal_words(n)
|
|
77
|
+
# Convert only the final word (handles 'forty-two'->'forty-second',
|
|
78
|
+
# 'one hundred seven'->'one hundred seventh').
|
|
79
|
+
if "-" in cardinal:
|
|
80
|
+
head, last = cardinal.rsplit("-", 1)
|
|
81
|
+
return f"{head}-{_to_ordinal_word(last)}"
|
|
82
|
+
parts = cardinal.rsplit(" ", 1)
|
|
83
|
+
if len(parts) == 2:
|
|
84
|
+
return f"{parts[0]} {_to_ordinal_word(parts[1])}"
|
|
85
|
+
return _to_ordinal_word(cardinal)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def _announce_paid_queued(api_gate, shadow, *, uid: int, title: str, username: str) -> None:
|
|
89
|
+
"""Announce a paid queue placement to the channel chat.
|
|
90
|
+
|
|
91
|
+
Position is counted from the currently-playing item, wrapping around the
|
|
92
|
+
playlist (CyTube loops). The item immediately after now-playing reads
|
|
93
|
+
"next"; everything else uses an English ordinal counting the now-playing
|
|
94
|
+
item as first (so the item two slots away is "third").
|
|
95
|
+
"""
|
|
96
|
+
items = shadow.items
|
|
97
|
+
np_uid = await _now_playing_uid(api_gate, shadow)
|
|
98
|
+
np_index = None
|
|
99
|
+
item_index = None
|
|
100
|
+
for i, it in enumerate(items):
|
|
101
|
+
if it.get("uid") == np_uid:
|
|
102
|
+
np_index = i
|
|
103
|
+
if it.get("uid") == uid:
|
|
104
|
+
item_index = i
|
|
105
|
+
if item_index is None:
|
|
46
106
|
return
|
|
107
|
+
n = len(items)
|
|
108
|
+
offset = item_index if np_index is None else (item_index - np_index) % n
|
|
109
|
+
if offset <= 0:
|
|
110
|
+
return
|
|
111
|
+
position = "next" if offset == 1 else _ordinal_words(offset + 1)
|
|
47
112
|
try:
|
|
48
|
-
await api_gate.send_chat(
|
|
113
|
+
await api_gate.send_chat(
|
|
114
|
+
f"{title} added to the queue with Zcoin by {username} and is now {position}."
|
|
115
|
+
)
|
|
49
116
|
except Exception:
|
|
50
117
|
logger.warning("Failed to send queue announcement", exc_info=True)
|
|
51
118
|
|
|
@@ -238,7 +305,7 @@ async def insert_pay_queue(
|
|
|
238
305
|
)
|
|
239
306
|
|
|
240
307
|
# Announce placement to the channel
|
|
241
|
-
await
|
|
308
|
+
await _announce_paid_queued(api_gate, shadow, uid=uid, title=title, username=username)
|
|
242
309
|
|
|
243
310
|
return {"success": True, "uid": uid, "request_id": request_id}
|
|
244
311
|
|
|
@@ -351,7 +418,7 @@ async def insert_pay_playnext(
|
|
|
351
418
|
)
|
|
352
419
|
|
|
353
420
|
# Announce placement to the channel
|
|
354
|
-
await
|
|
421
|
+
await _announce_paid_queued(api_gate, shadow, uid=uid, title=title, username=username)
|
|
355
422
|
|
|
356
423
|
return {"success": True, "uid": uid, "request_id": request_id}
|
|
357
424
|
|
|
@@ -479,8 +546,7 @@ async def insert_admin_queue(
|
|
|
479
546
|
title=title, tier="admin", z_cost=0,
|
|
480
547
|
)
|
|
481
548
|
|
|
482
|
-
#
|
|
483
|
-
await _announce_queued(api_gate, shadow, uid=uid, title=title, username=username)
|
|
549
|
+
# Admin queueing is intentionally NOT announced in the channel.
|
|
484
550
|
|
|
485
551
|
return {"success": True, "uid": uid, "refunded": removed}
|
|
486
552
|
|
{kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/templates/admin/schedules.html
RENAMED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
<div class="admin-section">
|
|
13
13
|
<div class="section-head">
|
|
14
|
-
<h2>Scheduled
|
|
14
|
+
<h2>Scheduled Events</h2>
|
|
15
15
|
<button class="btn btn-primary" onclick="showScheduleModal()">+ New Schedule</button>
|
|
16
16
|
</div>
|
|
17
17
|
<div id="schedules-list">Loading…</div>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Queue chat-announcement behaviour (v0.9.7).
|
|
2
|
+
|
|
3
|
+
Covers the English-ordinal position words, the paid-queue announcement message
|
|
4
|
+
format/position (counted from now-playing, wrapping), and that admin queueing
|
|
5
|
+
is not announced.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from kryten_webqueue.queue import ordering
|
|
11
|
+
from kryten_webqueue.queue.ordering import _ordinal_words, _announce_paid_queued
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _FakeApiGate:
|
|
15
|
+
def __init__(self, np_uid=None):
|
|
16
|
+
self.sent = []
|
|
17
|
+
self._np_uid = np_uid
|
|
18
|
+
|
|
19
|
+
async def get_now_playing(self):
|
|
20
|
+
return {"uid": self._np_uid} if self._np_uid is not None else None
|
|
21
|
+
|
|
22
|
+
async def send_chat(self, message):
|
|
23
|
+
self.sent.append(message)
|
|
24
|
+
return {"success": True}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _FakeShadow:
|
|
28
|
+
def __init__(self, items, now_playing=None):
|
|
29
|
+
self._items = items
|
|
30
|
+
self.now_playing = now_playing
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def items(self):
|
|
34
|
+
return self._items
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.mark.parametrize("n,word", [
|
|
38
|
+
(2, "second"),
|
|
39
|
+
(3, "third"),
|
|
40
|
+
(4, "fourth"),
|
|
41
|
+
(5, "fifth"),
|
|
42
|
+
(8, "eighth"),
|
|
43
|
+
(9, "ninth"),
|
|
44
|
+
(11, "eleventh"),
|
|
45
|
+
(12, "twelfth"),
|
|
46
|
+
(20, "twentieth"),
|
|
47
|
+
(21, "twenty-first"),
|
|
48
|
+
(42, "forty-second"),
|
|
49
|
+
(53, "fifty-third"),
|
|
50
|
+
(100, "one hundredth"),
|
|
51
|
+
(107, "one hundred seventh"),
|
|
52
|
+
(123, "one hundred twenty-third"),
|
|
53
|
+
])
|
|
54
|
+
def test_ordinal_words(n, word):
|
|
55
|
+
assert _ordinal_words(n) == word
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _items(*uids):
|
|
59
|
+
return [{"uid": u, "title": f"Item {u}"} for u in uids]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
async def test_paid_announcement_next():
|
|
63
|
+
# now-playing uid=10 at index 0; the new item uid=11 is immediately next.
|
|
64
|
+
api = _FakeApiGate(np_uid=10)
|
|
65
|
+
shadow = _FakeShadow(_items(10, 11))
|
|
66
|
+
await _announce_paid_queued(api, shadow, uid=11, title="Airplane (1980)", username="Hollis")
|
|
67
|
+
assert api.sent == [
|
|
68
|
+
"Airplane (1980) added to the queue with Zcoin by Hollis and is now next."
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def test_paid_announcement_third():
|
|
73
|
+
# now-playing uid=10 at index 0; new item is two slots away -> "third".
|
|
74
|
+
api = _FakeApiGate(np_uid=10)
|
|
75
|
+
shadow = _FakeShadow(_items(10, 99, 11))
|
|
76
|
+
await _announce_paid_queued(api, shadow, uid=11, title="Airplane (1980)", username="Hollis")
|
|
77
|
+
assert api.sent == [
|
|
78
|
+
"Airplane (1980) added to the queue with Zcoin by Hollis and is now third."
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def test_paid_announcement_wraps_around():
|
|
83
|
+
# now-playing is uid=99 at index 2; list wraps: after 99 comes 10 (next),
|
|
84
|
+
# then 11 (third).
|
|
85
|
+
api = _FakeApiGate(np_uid=99)
|
|
86
|
+
shadow = _FakeShadow(_items(10, 11, 99))
|
|
87
|
+
await _announce_paid_queued(api, shadow, uid=11, title="X", username="U")
|
|
88
|
+
assert api.sent == ["X added to the queue with Zcoin by U and is now third."]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
async def test_admin_queue_not_announced(monkeypatch):
|
|
92
|
+
"""insert_admin_queue must never call the announcement helper."""
|
|
93
|
+
called = False
|
|
94
|
+
|
|
95
|
+
async def _fail(*a, **k):
|
|
96
|
+
nonlocal called
|
|
97
|
+
called = True
|
|
98
|
+
|
|
99
|
+
monkeypatch.setattr(ordering, "_announce_paid_queued", _fail)
|
|
100
|
+
# Source check: the admin path has no announce call (the helper is only wired
|
|
101
|
+
# to the paid paths). This guards against a regression re-adding it.
|
|
102
|
+
import inspect
|
|
103
|
+
src = inspect.getsource(ordering.insert_admin_queue)
|
|
104
|
+
assert "_announce_paid_queued" not in src
|
|
105
|
+
assert "_announce_queued" not in src
|
|
106
|
+
assert called is False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/integrations/cmsutils/__init__.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/integrations/cmsutils/_common.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/integrations/cmsutils/enrichmeta.py
RENAMED
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/integrations/cmsutils/enrichtv.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/integrations/cmsutils/fetchurls.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/integrations/ytpipe/__init__.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/integrations/ytpipe/downloader.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/templates/admin/playlists.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/templates/admin/queue_mgmt.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/templates/catalog/browse.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/templates/catalog/item_detail.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.9.6 → kryten_webqueue-0.9.7}/kryten_webqueue/templates/user/dashboard.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|