Skip to content

Commit 1cedbcb

Browse files
committed
Add support for images, stylesheets and html
1 parent 60f5a02 commit 1cedbcb

File tree

3 files changed

+98
-13
lines changed

3 files changed

+98
-13
lines changed

twitchio/ext/overlays/core.py

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
class Node:
4848
def __init__(self, data: NodeDataT) -> None:
4949
self._raw = data
50-
self._type: Literal["text", "image", "audio", "html"] = data.get("type", "text")
50+
self._type: Literal["text", "image", "audio", "html", "stylesheet"] = data.get("type", "text")
5151

5252
element = data.get("element")
5353
self._element = HTMLElement(element) if element else None
@@ -57,7 +57,7 @@ def __init__(self, data: NodeDataT) -> None:
5757
self._location = data.get("location")
5858

5959
@property
60-
def type(self) -> Literal["text", "image", "audio", "html"]:
60+
def type(self) -> Literal["text", "image", "audio", "html", "stylesheet"]:
6161
return self._type
6262

6363
@property
@@ -76,13 +76,18 @@ def html_class(self) -> str | None:
7676
def location(self) -> str | None:
7777
return self._location
7878

79+
@property
80+
def raw(self) -> str | None:
81+
return self._raw.get("raw")
82+
7983

8084
class Overlay:
8185
VERSION: ClassVar[int] = 0
8286

8387
def __init__(self, data: OverlayDataT | None = None, *, duration: int = 5000) -> None:
8488
self._nodes: list[Node] = []
8589
self._audio: Node | None = None
90+
self._stylesheet: Node | None = None
8691

8792
data = data or {}
8893
nodes = data.get("nodes", [])
@@ -93,6 +98,22 @@ def __init__(self, data: OverlayDataT | None = None, *, duration: int = 5000) ->
9398

9499
self._duration = data.get("delay") or duration
95100

101+
@property
102+
def nodes(self) -> list[Node]:
103+
return self._nodes.copy()
104+
105+
@property
106+
def audio(self) -> Node | None:
107+
return self._audio
108+
109+
@property
110+
def stylesheet(self) -> Node | None:
111+
return self._stylesheet
112+
113+
@property
114+
def duration(self) -> int:
115+
return self._duration
116+
96117
def add_text(
97118
self,
98119
text: str,
@@ -120,26 +141,74 @@ def add_text(
120141

121142
return self
122143

123-
def add_image(self) -> ...: ...
144+
def add_image(
145+
self,
146+
file: str | os.PathLike[str],
147+
*,
148+
html_class: str | None = None,
149+
html_id: str | None = None,
150+
) -> Self:
151+
data: NodeDataT = {}
124152

125-
def add_html(self) -> ...: ...
153+
uri: str = f"media/{file}" if not str(file).startswith(("http://", "https://")) else str(file)
154+
data["type"] = "image"
155+
data["location"] = str(file)
156+
data["html_class"] = html_class
157+
data["html_id"] = html_id
158+
159+
id_ = f'id="{html_id}" ' if html_id else ""
160+
class_ = f'class="{html_class}" ' if html_class else ""
161+
data["raw"] = f"<img {id_}{class_}src='{html.escape(uri)}'></img>"
126162

127-
def set_audio(self, file: str | os.PathLike[str]) -> Self:
163+
node = Node(data)
164+
self._nodes.append(node)
165+
166+
return self
167+
168+
def add_html(self, html: str) -> Self:
169+
data: NodeDataT = {}
170+
171+
data["type"] = "html"
172+
data["raw"] = html
173+
node = Node(data)
174+
175+
self._nodes.append(node)
176+
return self
177+
178+
def set_audio(self, file: str | os.PathLike[str], *, duration: int | None = None, loop: bool = False) -> Self:
128179
data: NodeDataT = {}
129180
identifier: str = secrets.token_urlsafe(4)
130181

131-
uri = f"media/{file}" if not str(file).startswith(("http://", "https://")) else file
182+
uri: str = f"media/{file}" if not str(file).startswith(("http://", "https://")) else str(file)
183+
loop_ = "loop" if loop else ""
184+
132185
data["type"] = "audio"
133186
data["location"] = str(file)
134187
data["html_id"] = identifier
135-
data["raw"] = f"<audio id='{identifier}'><source src='{uri}'></audio>"
188+
data["raw"] = f"<audio id='{identifier}' {loop_}><source src='{html.escape(uri)}'></audio>"
189+
190+
if duration:
191+
self.set_duration(duration)
136192

137193
node = Node(data)
138194
self._audio = node
139195

140196
return self
141197

142-
def set_stylesheet(self) -> ...: ...
198+
def set_stylesheet(self, file: str | os.PathLike[str]) -> Self:
199+
data: NodeDataT = {}
200+
identifier: str = secrets.token_urlsafe(4)
201+
202+
uri: str = f"media/{file}" if not str(file).startswith(("http://", "https://")) else str(file)
203+
data["type"] = "stylesheet"
204+
data["location"] = str(file)
205+
data["html_id"] = identifier
206+
data["raw"] = f"<link id='{identifier}' rel='stylesheet' href='{html.escape(uri)}'>"
207+
208+
node = Node(data)
209+
self._stylesheet = node
210+
211+
return self
143212

144213
def set_duration(self, value: int, /) -> Self:
145214
self._duration = value
@@ -153,7 +222,10 @@ def build(self) -> OverlayDataT:
153222
nodes.append(node._raw)
154223

155224
if self._audio:
156-
nodes.append(self._audio._raw)
225+
nodes.insert(0, self._audio._raw)
226+
227+
if self._stylesheet:
228+
nodes.insert(0, self._stylesheet._raw)
157229

158230
return data
159231

twitchio/ext/overlays/types_.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030

3131
class NodeDataT(TypedDict, total=False):
32-
type: Literal["text", "image", "audio", "html"]
32+
type: Literal["text", "image", "audio", "html", "stylesheet"]
3333
element: HTMLElementT | None
3434
html_id: str | None
3535
html_class: str | None

twitchio/web/html.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,25 +65,38 @@
6565
sock.addEventListener("message", (e) => {{
6666
const data = JSON.parse(e.data);
6767
let nodes = data["nodes"];
68-
let delay = data["duration"];
68+
let delay = data["delay"];
69+
let ssId = null;
6970
7071
let container = document.getElementById("container");
7172
7273
for (let node of nodes) {{
73-
console.log(node["raw"]);
74+
if (node["type"] === "stylesheet") {{
75+
document.head.insertAdjacentHTML("beforeend", node["raw"]);
76+
ssId = node["html_id"];
77+
continue;
78+
}};
79+
7480
container.insertAdjacentHTML("beforeend", node["raw"]);
7581
7682
if (node["type"] === "audio") {{
7783
let audio = document.getElementById(node["html_id"]);
7884
audio.play();
7985
}};
8086
}};
87+
88+
setTimeout(clearOverlay, delay, ssId);
8189
}});
8290
}};
8391
84-
const clearOverlay = () => {{
92+
const clearOverlay = (stylesheet) => {{
8593
let container = document.getElementById("container");
8694
container.innerHTML = "";
95+
96+
if (stylesheet !== null) {{
97+
let el = document.getElementById(stylesheet);
98+
el.remove();
99+
}};
87100
}};
88101
89102
const clearReconnect = () => {{

0 commit comments

Comments
 (0)