From c45825922a9c3373456685cb466558e1961df1e9 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 22 Nov 2024 22:11:41 +0000
Subject: [PATCH 01/51] Bump tornado from 6.4.1 to 6.4.2
Bumps [tornado](https://github.com/tornadoweb/tornado) from 6.4.1 to 6.4.2.
- [Changelog](https://github.com/tornadoweb/tornado/blob/v6.4.2/docs/releases.rst)
- [Commits](https://github.com/tornadoweb/tornado/compare/v6.4.1...v6.4.2)
---
updated-dependencies:
- dependency-name: tornado
dependency-type: direct:production
...
Signed-off-by: dependabot[bot]
---
requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/requirements.txt b/requirements.txt
index b966f5ce..387e0caa 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,4 +3,4 @@ cachetools==5.3.1
circuitbreaker==2.0.0
pycryptodome==3.19.1
sqlalchemy==2.0.19
-tornado==6.4.1
+tornado==6.4.2
From faf272c8a76f62be2033267659bdb0daeb5e1c71 Mon Sep 17 00:00:00 2001
From: John Smith
Date: Sat, 23 Nov 2024 17:31:03 +0800
Subject: [PATCH 02/51] =?UTF-8?q?=E6=95=B4=E7=90=86=E5=89=8D=E7=AB=AF?=
=?UTF-8?q?=E9=83=A8=E5=88=86=E7=BB=84=E4=BB=B6CSS?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../components/ChatRenderer/TextMessage.vue | 6 ++---
frontend/src/layout/Sidebar.vue | 10 +++++---
frontend/src/layout/index.vue | 23 ++++++++-----------
3 files changed, 20 insertions(+), 19 deletions(-)
diff --git a/frontend/src/components/ChatRenderer/TextMessage.vue b/frontend/src/components/ChatRenderer/TextMessage.vue
index 266adbc7..393b7acf 100644
--- a/frontend/src/components/ChatRenderer/TextMessage.vue
+++ b/frontend/src/components/ChatRenderer/TextMessage.vue
@@ -84,12 +84,12 @@ export default {
}
-
diff --git a/frontend/src/layout/index.vue b/frontend/src/layout/index.vue
index 60e94f1e..aef30714 100644
--- a/frontend/src/layout/index.vue
+++ b/frontend/src/layout/index.vue
@@ -12,7 +12,7 @@
-
+
@@ -51,8 +51,8 @@ export default {
}
-
From e27955b8b5e31ca69258f8b9341617665d7dcfa9 Mon Sep 17 00:00:00 2001
From: John Smith
Date: Sat, 23 Nov 2024 17:32:38 +0800
Subject: [PATCH 03/51] =?UTF-8?q?=E5=8E=9F=E7=89=88YouTube=E6=A0=B7?=
=?UTF-8?q?=E5=BC=8F=E6=94=BE=E5=85=A5@layer=EF=BC=8C=E8=87=AA=E5=AE=9A?=
=?UTF-8?q?=E4=B9=89=E6=A0=B7=E5=BC=8F=E4=B8=8D=E7=94=A8=E5=8A=A0!importan?=
=?UTF-8?q?t=E4=BA=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/src/assets/css/youtube/yt-html.css | 4 ++++
frontend/src/assets/css/youtube/yt-icon.css | 4 ++++
frontend/src/assets/css/youtube/yt-img-shadow.css | 4 ++++
.../assets/css/youtube/yt-live-chat-author-badge-renderer.css | 4 ++++
frontend/src/assets/css/youtube/yt-live-chat-author-chip.css | 4 ++++
.../assets/css/youtube/yt-live-chat-item-list-renderer.css | 4 ++++
.../css/youtube/yt-live-chat-membership-item-renderer.css | 4 ++++
.../assets/css/youtube/yt-live-chat-paid-message-renderer.css | 4 ++++
frontend/src/assets/css/youtube/yt-live-chat-renderer.css | 4 ++++
.../assets/css/youtube/yt-live-chat-text-message-renderer.css | 4 ++++
.../yt-live-chat-ticker-paid-message-item-renderer.css | 4 ++++
.../src/assets/css/youtube/yt-live-chat-ticker-renderer.css | 4 ++++
12 files changed, 48 insertions(+)
diff --git a/frontend/src/assets/css/youtube/yt-html.css b/frontend/src/assets/css/youtube/yt-html.css
index 75dc6fce..1c4a78ac 100644
--- a/frontend/src/assets/css/youtube/yt-html.css
+++ b/frontend/src/assets/css/youtube/yt-html.css
@@ -1,3 +1,5 @@
+@layer yt {
+
html:not(.style-scope) {
--yt-live-chat-background-color: hsl(0, 0%, 100%);
--yt-live-chat-action-panel-background-color: hsla(0, 0%, 93.3%, .4);
@@ -360,3 +362,5 @@ html:not(.style-scope) {
--layout-fixed-left_-_left: 0;
;
}
+
+}
diff --git a/frontend/src/assets/css/youtube/yt-icon.css b/frontend/src/assets/css/youtube/yt-icon.css
index c21acf27..6af70c6e 100644
--- a/frontend/src/assets/css/youtube/yt-icon.css
+++ b/frontend/src/assets/css/youtube/yt-icon.css
@@ -1,3 +1,5 @@
+@layer yt {
+
canvas.yt-icon, caption.yt-icon, center.yt-icon, cite.yt-icon, code.yt-icon, dd.yt-icon, del.yt-icon, dfn.yt-icon, div.yt-icon, dl.yt-icon, dt.yt-icon, em.yt-icon, embed.yt-icon, fieldset.yt-icon, font.yt-icon, form.yt-icon, h1.yt-icon, h2.yt-icon, h3.yt-icon, h4.yt-icon, h5.yt-icon, h6.yt-icon, hr.yt-icon, i.yt-icon, iframe.yt-icon, img.yt-icon, ins.yt-icon, kbd.yt-icon, label.yt-icon, legend.yt-icon, li.yt-icon, menu.yt-icon, object.yt-icon, ol.yt-icon, p.yt-icon, pre.yt-icon, q.yt-icon, s.yt-icon, samp.yt-icon, small.yt-icon, span.yt-icon, strike.yt-icon, strong.yt-icon, sub.yt-icon, sup.yt-icon, table.yt-icon, tbody.yt-icon, td.yt-icon, tfoot.yt-icon, th.yt-icon, thead.yt-icon, tr.yt-icon, tt.yt-icon, u.yt-icon, ul.yt-icon, var.yt-icon {
margin: 0;
padding: 0;
@@ -28,3 +30,5 @@ yt-icon, .yt-icon-container.yt-icon {
yt-icon.external-container {
display: none !important;
}
+
+}
diff --git a/frontend/src/assets/css/youtube/yt-img-shadow.css b/frontend/src/assets/css/youtube/yt-img-shadow.css
index e4ea828e..eac9967d 100644
--- a/frontend/src/assets/css/youtube/yt-img-shadow.css
+++ b/frontend/src/assets/css/youtube/yt-img-shadow.css
@@ -1,3 +1,5 @@
+@layer yt {
+
canvas.yt-img-shadow, caption.yt-img-shadow, center.yt-img-shadow, cite.yt-img-shadow, code.yt-img-shadow, dd.yt-img-shadow, del.yt-img-shadow, dfn.yt-img-shadow, div.yt-img-shadow, dl.yt-img-shadow, dt.yt-img-shadow, em.yt-img-shadow, embed.yt-img-shadow, fieldset.yt-img-shadow, font.yt-img-shadow, form.yt-img-shadow, h1.yt-img-shadow, h2.yt-img-shadow, h3.yt-img-shadow, h4.yt-img-shadow, h5.yt-img-shadow, h6.yt-img-shadow, hr.yt-img-shadow, i.yt-img-shadow, iframe.yt-img-shadow, img.yt-img-shadow, ins.yt-img-shadow, kbd.yt-img-shadow, label.yt-img-shadow, legend.yt-img-shadow, li.yt-img-shadow, menu.yt-img-shadow, object.yt-img-shadow, ol.yt-img-shadow, p.yt-img-shadow, pre.yt-img-shadow, q.yt-img-shadow, s.yt-img-shadow, samp.yt-img-shadow, small.yt-img-shadow, span.yt-img-shadow, strike.yt-img-shadow, strong.yt-img-shadow, sub.yt-img-shadow, sup.yt-img-shadow, table.yt-img-shadow, tbody.yt-img-shadow, td.yt-img-shadow, tfoot.yt-img-shadow, th.yt-img-shadow, thead.yt-img-shadow, tr.yt-img-shadow, tt.yt-img-shadow, u.yt-img-shadow, ul.yt-img-shadow, var.yt-img-shadow {
margin: 0;
padding: 0;
@@ -66,3 +68,5 @@ img.yt-img-shadow {
max-width: 100%;
border-radius: none;
}
+
+}
diff --git a/frontend/src/assets/css/youtube/yt-live-chat-author-badge-renderer.css b/frontend/src/assets/css/youtube/yt-live-chat-author-badge-renderer.css
index 9a0b8409..59013081 100644
--- a/frontend/src/assets/css/youtube/yt-live-chat-author-badge-renderer.css
+++ b/frontend/src/assets/css/youtube/yt-live-chat-author-badge-renderer.css
@@ -1,3 +1,5 @@
+@layer yt {
+
canvas.yt-live-chat-author-badge-renderer, caption.yt-live-chat-author-badge-renderer, center.yt-live-chat-author-badge-renderer, cite.yt-live-chat-author-badge-renderer, code.yt-live-chat-author-badge-renderer, dd.yt-live-chat-author-badge-renderer, del.yt-live-chat-author-badge-renderer, dfn.yt-live-chat-author-badge-renderer, div.yt-live-chat-author-badge-renderer, dl.yt-live-chat-author-badge-renderer, dt.yt-live-chat-author-badge-renderer, em.yt-live-chat-author-badge-renderer, embed.yt-live-chat-author-badge-renderer, fieldset.yt-live-chat-author-badge-renderer, font.yt-live-chat-author-badge-renderer, form.yt-live-chat-author-badge-renderer, h1.yt-live-chat-author-badge-renderer, h2.yt-live-chat-author-badge-renderer, h3.yt-live-chat-author-badge-renderer, h4.yt-live-chat-author-badge-renderer, h5.yt-live-chat-author-badge-renderer, h6.yt-live-chat-author-badge-renderer, hr.yt-live-chat-author-badge-renderer, i.yt-live-chat-author-badge-renderer, iframe.yt-live-chat-author-badge-renderer, img.yt-live-chat-author-badge-renderer, ins.yt-live-chat-author-badge-renderer, kbd.yt-live-chat-author-badge-renderer, label.yt-live-chat-author-badge-renderer, legend.yt-live-chat-author-badge-renderer, li.yt-live-chat-author-badge-renderer, menu.yt-live-chat-author-badge-renderer, object.yt-live-chat-author-badge-renderer, ol.yt-live-chat-author-badge-renderer, p.yt-live-chat-author-badge-renderer, pre.yt-live-chat-author-badge-renderer, q.yt-live-chat-author-badge-renderer, s.yt-live-chat-author-badge-renderer, samp.yt-live-chat-author-badge-renderer, small.yt-live-chat-author-badge-renderer, span.yt-live-chat-author-badge-renderer, strike.yt-live-chat-author-badge-renderer, strong.yt-live-chat-author-badge-renderer, sub.yt-live-chat-author-badge-renderer, sup.yt-live-chat-author-badge-renderer, table.yt-live-chat-author-badge-renderer, tbody.yt-live-chat-author-badge-renderer, td.yt-live-chat-author-badge-renderer, tfoot.yt-live-chat-author-badge-renderer, th.yt-live-chat-author-badge-renderer, thead.yt-live-chat-author-badge-renderer, tr.yt-live-chat-author-badge-renderer, tt.yt-live-chat-author-badge-renderer, u.yt-live-chat-author-badge-renderer, ul.yt-live-chat-author-badge-renderer, var.yt-live-chat-author-badge-renderer {
margin: 0;
padding: 0;
@@ -34,3 +36,5 @@ img.yt-live-chat-author-badge-renderer, yt-icon.yt-live-chat-author-badge-render
width: 16px;
height: 16px;
}
+
+}
diff --git a/frontend/src/assets/css/youtube/yt-live-chat-author-chip.css b/frontend/src/assets/css/youtube/yt-live-chat-author-chip.css
index 2ce7ed44..ff3dbab4 100644
--- a/frontend/src/assets/css/youtube/yt-live-chat-author-chip.css
+++ b/frontend/src/assets/css/youtube/yt-live-chat-author-chip.css
@@ -1,3 +1,5 @@
+@layer yt {
+
canvas.yt-live-chat-author-chip, caption.yt-live-chat-author-chip, center.yt-live-chat-author-chip, cite.yt-live-chat-author-chip, code.yt-live-chat-author-chip, dd.yt-live-chat-author-chip, del.yt-live-chat-author-chip, dfn.yt-live-chat-author-chip, div.yt-live-chat-author-chip, dl.yt-live-chat-author-chip, dt.yt-live-chat-author-chip, em.yt-live-chat-author-chip, embed.yt-live-chat-author-chip, fieldset.yt-live-chat-author-chip, font.yt-live-chat-author-chip, form.yt-live-chat-author-chip, h1.yt-live-chat-author-chip, h2.yt-live-chat-author-chip, h3.yt-live-chat-author-chip, h4.yt-live-chat-author-chip, h5.yt-live-chat-author-chip, h6.yt-live-chat-author-chip, hr.yt-live-chat-author-chip, i.yt-live-chat-author-chip, iframe.yt-live-chat-author-chip, img.yt-live-chat-author-chip, ins.yt-live-chat-author-chip, kbd.yt-live-chat-author-chip, label.yt-live-chat-author-chip, legend.yt-live-chat-author-chip, li.yt-live-chat-author-chip, menu.yt-live-chat-author-chip, object.yt-live-chat-author-chip, ol.yt-live-chat-author-chip, p.yt-live-chat-author-chip, pre.yt-live-chat-author-chip, q.yt-live-chat-author-chip, s.yt-live-chat-author-chip, samp.yt-live-chat-author-chip, small.yt-live-chat-author-chip, span.yt-live-chat-author-chip, strike.yt-live-chat-author-chip, strong.yt-live-chat-author-chip, sub.yt-live-chat-author-chip, sup.yt-live-chat-author-chip, table.yt-live-chat-author-chip, tbody.yt-live-chat-author-chip, td.yt-live-chat-author-chip, tfoot.yt-live-chat-author-chip, th.yt-live-chat-author-chip, thead.yt-live-chat-author-chip, tr.yt-live-chat-author-chip, tt.yt-live-chat-author-chip, u.yt-live-chat-author-chip, ul.yt-live-chat-author-chip, var.yt-live-chat-author-chip {
margin: 0;
padding: 0;
@@ -62,3 +64,5 @@ yt-live-chat-author-chip[is-highlighted] #chip-badges.yt-live-chat-author-chip y
#chip-badges.yt-live-chat-author-chip yt-live-chat-author-badge-renderer.yt-live-chat-author-chip:last-of-type {
margin-right: -2px;
}
+
+}
diff --git a/frontend/src/assets/css/youtube/yt-live-chat-item-list-renderer.css b/frontend/src/assets/css/youtube/yt-live-chat-item-list-renderer.css
index 7eaad8b4..078270c4 100644
--- a/frontend/src/assets/css/youtube/yt-live-chat-item-list-renderer.css
+++ b/frontend/src/assets/css/youtube/yt-live-chat-item-list-renderer.css
@@ -1,3 +1,5 @@
+@layer yt {
+
canvas.yt-live-chat-item-list-renderer, caption.yt-live-chat-item-list-renderer, center.yt-live-chat-item-list-renderer, cite.yt-live-chat-item-list-renderer, code.yt-live-chat-item-list-renderer, dd.yt-live-chat-item-list-renderer, del.yt-live-chat-item-list-renderer, dfn.yt-live-chat-item-list-renderer, div.yt-live-chat-item-list-renderer, dl.yt-live-chat-item-list-renderer, dt.yt-live-chat-item-list-renderer, em.yt-live-chat-item-list-renderer, embed.yt-live-chat-item-list-renderer, fieldset.yt-live-chat-item-list-renderer, font.yt-live-chat-item-list-renderer, form.yt-live-chat-item-list-renderer, h1.yt-live-chat-item-list-renderer, h2.yt-live-chat-item-list-renderer, h3.yt-live-chat-item-list-renderer, h4.yt-live-chat-item-list-renderer, h5.yt-live-chat-item-list-renderer, h6.yt-live-chat-item-list-renderer, hr.yt-live-chat-item-list-renderer, i.yt-live-chat-item-list-renderer, iframe.yt-live-chat-item-list-renderer, img.yt-live-chat-item-list-renderer, ins.yt-live-chat-item-list-renderer, kbd.yt-live-chat-item-list-renderer, label.yt-live-chat-item-list-renderer, legend.yt-live-chat-item-list-renderer, li.yt-live-chat-item-list-renderer, menu.yt-live-chat-item-list-renderer, object.yt-live-chat-item-list-renderer, ol.yt-live-chat-item-list-renderer, p.yt-live-chat-item-list-renderer, pre.yt-live-chat-item-list-renderer, q.yt-live-chat-item-list-renderer, s.yt-live-chat-item-list-renderer, samp.yt-live-chat-item-list-renderer, small.yt-live-chat-item-list-renderer, span.yt-live-chat-item-list-renderer, strike.yt-live-chat-item-list-renderer, strong.yt-live-chat-item-list-renderer, sub.yt-live-chat-item-list-renderer, sup.yt-live-chat-item-list-renderer, table.yt-live-chat-item-list-renderer, tbody.yt-live-chat-item-list-renderer, td.yt-live-chat-item-list-renderer, tfoot.yt-live-chat-item-list-renderer, th.yt-live-chat-item-list-renderer, thead.yt-live-chat-item-list-renderer, tr.yt-live-chat-item-list-renderer, tt.yt-live-chat-item-list-renderer, u.yt-live-chat-item-list-renderer, ul.yt-live-chat-item-list-renderer, var.yt-live-chat-item-list-renderer {
margin: 0;
padding: 0;
@@ -138,3 +140,5 @@ yt-live-chat-paid-sticker-renderer.yt-live-chat-item-list-renderer {
yt-live-chat-paid-sticker-renderer.yt-live-chat-item-list-renderer[dashboard-money-feed] {
padding: 8px 16px;
}
+
+}
diff --git a/frontend/src/assets/css/youtube/yt-live-chat-membership-item-renderer.css b/frontend/src/assets/css/youtube/yt-live-chat-membership-item-renderer.css
index 6a1c2caa..b09a8cdd 100644
--- a/frontend/src/assets/css/youtube/yt-live-chat-membership-item-renderer.css
+++ b/frontend/src/assets/css/youtube/yt-live-chat-membership-item-renderer.css
@@ -1,3 +1,5 @@
+@layer yt {
+
#timestamp.yt-live-chat-membership-item-renderer {
display: var(--yt-live-chat-item-timestamp-display, inline);
margin: var(--yt-live-chat-item-timestamp-margin, 0 8px 0 0);
@@ -360,3 +362,5 @@ yt-live-chat-membership-item-renderer[dashboard-money-feed] #menu.yt-live-chat-m
margin-top: 8px;
background: linear-gradient(to right, transparent, var(--yt-live-chat-background-color) 40%);
}
+
+}
diff --git a/frontend/src/assets/css/youtube/yt-live-chat-paid-message-renderer.css b/frontend/src/assets/css/youtube/yt-live-chat-paid-message-renderer.css
index 639d609d..a6c51f10 100644
--- a/frontend/src/assets/css/youtube/yt-live-chat-paid-message-renderer.css
+++ b/frontend/src/assets/css/youtube/yt-live-chat-paid-message-renderer.css
@@ -1,3 +1,5 @@
+@layer yt {
+
canvas.yt-live-chat-paid-message-renderer, caption.yt-live-chat-paid-message-renderer, center.yt-live-chat-paid-message-renderer, cite.yt-live-chat-paid-message-renderer, code.yt-live-chat-paid-message-renderer, dd.yt-live-chat-paid-message-renderer, del.yt-live-chat-paid-message-renderer, dfn.yt-live-chat-paid-message-renderer, div.yt-live-chat-paid-message-renderer, dl.yt-live-chat-paid-message-renderer, dt.yt-live-chat-paid-message-renderer, em.yt-live-chat-paid-message-renderer, embed.yt-live-chat-paid-message-renderer, fieldset.yt-live-chat-paid-message-renderer, font.yt-live-chat-paid-message-renderer, form.yt-live-chat-paid-message-renderer, h1.yt-live-chat-paid-message-renderer, h2.yt-live-chat-paid-message-renderer, h3.yt-live-chat-paid-message-renderer, h4.yt-live-chat-paid-message-renderer, h5.yt-live-chat-paid-message-renderer, h6.yt-live-chat-paid-message-renderer, hr.yt-live-chat-paid-message-renderer, i.yt-live-chat-paid-message-renderer, iframe.yt-live-chat-paid-message-renderer, img.yt-live-chat-paid-message-renderer, ins.yt-live-chat-paid-message-renderer, kbd.yt-live-chat-paid-message-renderer, label.yt-live-chat-paid-message-renderer, legend.yt-live-chat-paid-message-renderer, li.yt-live-chat-paid-message-renderer, menu.yt-live-chat-paid-message-renderer, object.yt-live-chat-paid-message-renderer, ol.yt-live-chat-paid-message-renderer, p.yt-live-chat-paid-message-renderer, pre.yt-live-chat-paid-message-renderer, q.yt-live-chat-paid-message-renderer, s.yt-live-chat-paid-message-renderer, samp.yt-live-chat-paid-message-renderer, small.yt-live-chat-paid-message-renderer, span.yt-live-chat-paid-message-renderer, strike.yt-live-chat-paid-message-renderer, strong.yt-live-chat-paid-message-renderer, sub.yt-live-chat-paid-message-renderer, sup.yt-live-chat-paid-message-renderer, table.yt-live-chat-paid-message-renderer, tbody.yt-live-chat-paid-message-renderer, td.yt-live-chat-paid-message-renderer, tfoot.yt-live-chat-paid-message-renderer, th.yt-live-chat-paid-message-renderer, thead.yt-live-chat-paid-message-renderer, tr.yt-live-chat-paid-message-renderer, tt.yt-live-chat-paid-message-renderer, u.yt-live-chat-paid-message-renderer, ul.yt-live-chat-paid-message-renderer, var.yt-live-chat-paid-message-renderer {
margin: 0;
padding: 0;
@@ -378,3 +380,5 @@ yt-live-chat-paid-message-renderer[show-footer-divider] #footer.yt-live-chat-pai
yt-live-chat-paid-message-renderer[is-user-editable] #footer.yt-live-chat-paid-message-renderer:not(:empty) {
padding-top: 8px;
}
+
+}
diff --git a/frontend/src/assets/css/youtube/yt-live-chat-renderer.css b/frontend/src/assets/css/youtube/yt-live-chat-renderer.css
index 5e805f6c..0bc15fc5 100644
--- a/frontend/src/assets/css/youtube/yt-live-chat-renderer.css
+++ b/frontend/src/assets/css/youtube/yt-live-chat-renderer.css
@@ -1,3 +1,5 @@
+@layer yt {
+
yt-live-chat-renderer, yt-live-chat-item-list-renderer #item-scroller {
height: 100%;
}
@@ -250,3 +252,5 @@ yt-live-chat-renderer[collapsed] .hide-on-collapse.yt-live-chat-renderer {
background-color: rgba(0, 0, 0, 0.60);
}
}
+
+}
diff --git a/frontend/src/assets/css/youtube/yt-live-chat-text-message-renderer.css b/frontend/src/assets/css/youtube/yt-live-chat-text-message-renderer.css
index bf1ed283..12679ab8 100644
--- a/frontend/src/assets/css/youtube/yt-live-chat-text-message-renderer.css
+++ b/frontend/src/assets/css/youtube/yt-live-chat-text-message-renderer.css
@@ -1,3 +1,5 @@
+@layer yt {
+
canvas.yt-live-chat-text-message-renderer, caption.yt-live-chat-text-message-renderer, center.yt-live-chat-text-message-renderer, cite.yt-live-chat-text-message-renderer, code.yt-live-chat-text-message-renderer, dd.yt-live-chat-text-message-renderer, del.yt-live-chat-text-message-renderer, dfn.yt-live-chat-text-message-renderer, div.yt-live-chat-text-message-renderer, dl.yt-live-chat-text-message-renderer, dt.yt-live-chat-text-message-renderer, em.yt-live-chat-text-message-renderer, embed.yt-live-chat-text-message-renderer, fieldset.yt-live-chat-text-message-renderer, font.yt-live-chat-text-message-renderer, form.yt-live-chat-text-message-renderer, h1.yt-live-chat-text-message-renderer, h2.yt-live-chat-text-message-renderer, h3.yt-live-chat-text-message-renderer, h4.yt-live-chat-text-message-renderer, h5.yt-live-chat-text-message-renderer, h6.yt-live-chat-text-message-renderer, hr.yt-live-chat-text-message-renderer, i.yt-live-chat-text-message-renderer, iframe.yt-live-chat-text-message-renderer, img.yt-live-chat-text-message-renderer, ins.yt-live-chat-text-message-renderer, kbd.yt-live-chat-text-message-renderer, label.yt-live-chat-text-message-renderer, legend.yt-live-chat-text-message-renderer, li.yt-live-chat-text-message-renderer, menu.yt-live-chat-text-message-renderer, object.yt-live-chat-text-message-renderer, ol.yt-live-chat-text-message-renderer, p.yt-live-chat-text-message-renderer, pre.yt-live-chat-text-message-renderer, q.yt-live-chat-text-message-renderer, s.yt-live-chat-text-message-renderer, samp.yt-live-chat-text-message-renderer, small.yt-live-chat-text-message-renderer, span.yt-live-chat-text-message-renderer, strike.yt-live-chat-text-message-renderer, strong.yt-live-chat-text-message-renderer, sub.yt-live-chat-text-message-renderer, sup.yt-live-chat-text-message-renderer, table.yt-live-chat-text-message-renderer, tbody.yt-live-chat-text-message-renderer, td.yt-live-chat-text-message-renderer, tfoot.yt-live-chat-text-message-renderer, th.yt-live-chat-text-message-renderer, thead.yt-live-chat-text-message-renderer, tr.yt-live-chat-text-message-renderer, tt.yt-live-chat-text-message-renderer, u.yt-live-chat-text-message-renderer, ul.yt-live-chat-text-message-renderer, var.yt-live-chat-text-message-renderer {
margin: 0;
padding: 0;
@@ -230,3 +232,5 @@ yt-live-chat-text-message-renderer[is-dimmed] #message.yt-live-chat-text-message
yt-live-chat-text-message-renderer[is-dimmed]::before {
background: var(--yt-live-chat-error-message-color, #f44336);
}
+
+}
diff --git a/frontend/src/assets/css/youtube/yt-live-chat-ticker-paid-message-item-renderer.css b/frontend/src/assets/css/youtube/yt-live-chat-ticker-paid-message-item-renderer.css
index 4844b66c..d6b41918 100644
--- a/frontend/src/assets/css/youtube/yt-live-chat-ticker-paid-message-item-renderer.css
+++ b/frontend/src/assets/css/youtube/yt-live-chat-ticker-paid-message-item-renderer.css
@@ -1,3 +1,5 @@
+@layer yt {
+
canvas.yt-live-chat-ticker-paid-message-item-renderer, caption.yt-live-chat-ticker-paid-message-item-renderer, center.yt-live-chat-ticker-paid-message-item-renderer, cite.yt-live-chat-ticker-paid-message-item-renderer, code.yt-live-chat-ticker-paid-message-item-renderer, dd.yt-live-chat-ticker-paid-message-item-renderer, del.yt-live-chat-ticker-paid-message-item-renderer, dfn.yt-live-chat-ticker-paid-message-item-renderer, div.yt-live-chat-ticker-paid-message-item-renderer, dl.yt-live-chat-ticker-paid-message-item-renderer, dt.yt-live-chat-ticker-paid-message-item-renderer, em.yt-live-chat-ticker-paid-message-item-renderer, embed.yt-live-chat-ticker-paid-message-item-renderer, fieldset.yt-live-chat-ticker-paid-message-item-renderer, font.yt-live-chat-ticker-paid-message-item-renderer, form.yt-live-chat-ticker-paid-message-item-renderer, h1.yt-live-chat-ticker-paid-message-item-renderer, h2.yt-live-chat-ticker-paid-message-item-renderer, h3.yt-live-chat-ticker-paid-message-item-renderer, h4.yt-live-chat-ticker-paid-message-item-renderer, h5.yt-live-chat-ticker-paid-message-item-renderer, h6.yt-live-chat-ticker-paid-message-item-renderer, hr.yt-live-chat-ticker-paid-message-item-renderer, i.yt-live-chat-ticker-paid-message-item-renderer, iframe.yt-live-chat-ticker-paid-message-item-renderer, img.yt-live-chat-ticker-paid-message-item-renderer, ins.yt-live-chat-ticker-paid-message-item-renderer, kbd.yt-live-chat-ticker-paid-message-item-renderer, label.yt-live-chat-ticker-paid-message-item-renderer, legend.yt-live-chat-ticker-paid-message-item-renderer, li.yt-live-chat-ticker-paid-message-item-renderer, menu.yt-live-chat-ticker-paid-message-item-renderer, object.yt-live-chat-ticker-paid-message-item-renderer, ol.yt-live-chat-ticker-paid-message-item-renderer, p.yt-live-chat-ticker-paid-message-item-renderer, pre.yt-live-chat-ticker-paid-message-item-renderer, q.yt-live-chat-ticker-paid-message-item-renderer, s.yt-live-chat-ticker-paid-message-item-renderer, samp.yt-live-chat-ticker-paid-message-item-renderer, small.yt-live-chat-ticker-paid-message-item-renderer, span.yt-live-chat-ticker-paid-message-item-renderer, strike.yt-live-chat-ticker-paid-message-item-renderer, strong.yt-live-chat-ticker-paid-message-item-renderer, sub.yt-live-chat-ticker-paid-message-item-renderer, sup.yt-live-chat-ticker-paid-message-item-renderer, table.yt-live-chat-ticker-paid-message-item-renderer, tbody.yt-live-chat-ticker-paid-message-item-renderer, td.yt-live-chat-ticker-paid-message-item-renderer, tfoot.yt-live-chat-ticker-paid-message-item-renderer, th.yt-live-chat-ticker-paid-message-item-renderer, thead.yt-live-chat-ticker-paid-message-item-renderer, tr.yt-live-chat-ticker-paid-message-item-renderer, tt.yt-live-chat-ticker-paid-message-item-renderer, u.yt-live-chat-ticker-paid-message-item-renderer, ul.yt-live-chat-ticker-paid-message-item-renderer, var.yt-live-chat-ticker-paid-message-item-renderer {
margin: 0;
padding: 0;
@@ -78,3 +80,5 @@ yt-img-shadow.yt-live-chat-ticker-paid-message-item-renderer {
yt-live-chat-ticker-paid-message-item-renderer[is-deleted] #author-photo.yt-live-chat-ticker-paid-message-item-renderer {
display: none;
}
+
+}
diff --git a/frontend/src/assets/css/youtube/yt-live-chat-ticker-renderer.css b/frontend/src/assets/css/youtube/yt-live-chat-ticker-renderer.css
index 029720d1..598a207b 100644
--- a/frontend/src/assets/css/youtube/yt-live-chat-ticker-renderer.css
+++ b/frontend/src/assets/css/youtube/yt-live-chat-ticker-renderer.css
@@ -1,3 +1,5 @@
+@layer yt {
+
canvas.yt-live-chat-ticker-renderer, caption.yt-live-chat-ticker-renderer, center.yt-live-chat-ticker-renderer, cite.yt-live-chat-ticker-renderer, code.yt-live-chat-ticker-renderer, dd.yt-live-chat-ticker-renderer, del.yt-live-chat-ticker-renderer, dfn.yt-live-chat-ticker-renderer, div.yt-live-chat-ticker-renderer, dl.yt-live-chat-ticker-renderer, dt.yt-live-chat-ticker-renderer, em.yt-live-chat-ticker-renderer, embed.yt-live-chat-ticker-renderer, fieldset.yt-live-chat-ticker-renderer, font.yt-live-chat-ticker-renderer, form.yt-live-chat-ticker-renderer, h1.yt-live-chat-ticker-renderer, h2.yt-live-chat-ticker-renderer, h3.yt-live-chat-ticker-renderer, h4.yt-live-chat-ticker-renderer, h5.yt-live-chat-ticker-renderer, h6.yt-live-chat-ticker-renderer, hr.yt-live-chat-ticker-renderer, i.yt-live-chat-ticker-renderer, iframe.yt-live-chat-ticker-renderer, img.yt-live-chat-ticker-renderer, ins.yt-live-chat-ticker-renderer, kbd.yt-live-chat-ticker-renderer, label.yt-live-chat-ticker-renderer, legend.yt-live-chat-ticker-renderer, li.yt-live-chat-ticker-renderer, menu.yt-live-chat-ticker-renderer, object.yt-live-chat-ticker-renderer, ol.yt-live-chat-ticker-renderer, p.yt-live-chat-ticker-renderer, pre.yt-live-chat-ticker-renderer, q.yt-live-chat-ticker-renderer, s.yt-live-chat-ticker-renderer, samp.yt-live-chat-ticker-renderer, small.yt-live-chat-ticker-renderer, span.yt-live-chat-ticker-renderer, strike.yt-live-chat-ticker-renderer, strong.yt-live-chat-ticker-renderer, sub.yt-live-chat-ticker-renderer, sup.yt-live-chat-ticker-renderer, table.yt-live-chat-ticker-renderer, tbody.yt-live-chat-ticker-renderer, td.yt-live-chat-ticker-renderer, tfoot.yt-live-chat-ticker-renderer, th.yt-live-chat-ticker-renderer, thead.yt-live-chat-ticker-renderer, tr.yt-live-chat-ticker-renderer, tt.yt-live-chat-ticker-renderer, u.yt-live-chat-ticker-renderer, ul.yt-live-chat-ticker-renderer, var.yt-live-chat-ticker-renderer {
margin: 0;
padding: 0;
@@ -63,3 +65,5 @@ yt-icon.yt-live-chat-ticker-renderer {
padding: 4px;
width: 24px;
}
+
+}
From 3976aa79ede44ff05c4df959f9f552d32d030b7a Mon Sep 17 00:00:00 2001
From: John Smith
Date: Sat, 23 Nov 2024 18:49:27 +0800
Subject: [PATCH 04/51] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=A0=B7=E5=BC=8F?=
=?UTF-8?q?=E7=94=9F=E6=88=90=E5=99=A8=E9=A2=84=E8=A7=88=E7=9A=84=E6=80=A7?=
=?UTF-8?q?=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/src/views/Room.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/views/Room.vue b/frontend/src/views/Room.vue
index a43a2079..a79d24b6 100644
--- a/frontend/src/views/Room.vue
+++ b/frontend/src/views/Room.vue
@@ -242,7 +242,7 @@ export default {
let { type, data } = event.data
switch (type) {
case 'roomSetCustomStyle':
- this.customStyleElement.innerText = data.css
+ this.customStyleElement.textContent = data.css
break
case 'roomStartClient':
if (this.chatClient) {
From 1244210bfe2a6309f315f721a3cf05da164fe624 Mon Sep 17 00:00:00 2001
From: John Smith
Date: Sun, 24 Nov 2024 18:06:41 +0800
Subject: [PATCH 05/51] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=A0=B7=E5=BC=8F?=
=?UTF-8?q?=E7=94=9F=E6=88=90=E5=99=A8=E7=95=8C=E9=9D=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/package.json | 2 +-
frontend/src/lang/zh.js | 6 +-
frontend/src/views/StyleGenerator/Legacy.vue | 726 +++++++++---------
.../src/views/StyleGenerator/LineLike.vue | 634 +++++++--------
frontend/src/views/StyleGenerator/index.vue | 10 +-
5 files changed, 706 insertions(+), 672 deletions(-)
diff --git a/frontend/package.json b/frontend/package.json
index 762016f6..94006fdc 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "blivechat",
- "version": "1.9.3",
+ "version": "1.10.0-dev",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
diff --git a/frontend/src/lang/zh.js b/frontend/src/lang/zh.js
index 9a58247a..8dfe91fd 100644
--- a/frontend/src/lang/zh.js
+++ b/frontend/src/lang/zh.js
@@ -79,8 +79,8 @@ export default {
legacy: '经典',
lineLike: '仿微信',
- light: '明亮',
- dark: '黑暗',
+ light: '亮色',
+ dark: '暗色',
outlines: '描边',
showOutlines: '显示描边',
@@ -150,7 +150,7 @@ export default {
fadeOutTime: '淡出时间(毫秒)',
slide: '滑动',
reverseSlide: '反向滑动',
- playAnimation: '播放动画',
+ playAnimation: '生成消息',
result: '结果',
copy: '复制',
diff --git a/frontend/src/views/StyleGenerator/Legacy.vue b/frontend/src/views/StyleGenerator/Legacy.vue
index 865b3c75..52e25a13 100644
--- a/frontend/src/views/StyleGenerator/Legacy.vue
+++ b/frontend/src/views/StyleGenerator/Legacy.vue
@@ -1,360 +1,378 @@
-
+
- {{ $t('stylegen.outlines') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('stylegen.avatars') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('stylegen.userNames') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('stylegen.messages') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('stylegen.time') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('stylegen.backgrounds') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('stylegen.scAndNewMember') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('stylegen.animation') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ {{ $t('stylegen.outlines') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('stylegen.avatars') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('stylegen.userNames') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('stylegen.messages') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('stylegen.time') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('stylegen.backgrounds') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('stylegen.scAndNewMember') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('stylegen.animation') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
From 79f070fc26066a6a1191c2a340398bc01181c116 Mon Sep 17 00:00:00 2001
From: John Smith
Date: Thu, 3 Apr 2025 19:58:45 +0800
Subject: [PATCH 19/51] =?UTF-8?q?web=E6=8E=A5=E5=8F=A3=E6=8D=A2=E4=B8=80?=
=?UTF-8?q?=E4=B8=AA=E5=88=9D=E5=A7=8B=E5=8C=96=E6=88=BF=E9=97=B4=E7=9A=84?=
=?UTF-8?q?=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
api/chat.py | 4 ++--
blivedm | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/api/chat.py b/api/chat.py
index 9bd979ec..64bc3457 100644
--- a/api/chat.py
+++ b/api/chat.py
@@ -375,8 +375,8 @@ async def _get_room_info(room_id) -> Tuple[int, int]:
logger.warning('room=%d _get_room_info failed: %s', room_id, data['message'])
return room_id, 0
- room_info = data['data']['room_info']
- return room_info['room_id'], room_info['uid']
+ data = data['data']
+ return data['room_id'], data['uid']
async def _get_server_host_list_and_token(self, room_id) -> Tuple[dict, Optional[str]]:
try:
diff --git a/blivedm b/blivedm
index 30fcd4bf..a45ee8f6 160000
--- a/blivedm
+++ b/blivedm
@@ -1 +1 @@
-Subproject commit 30fcd4bf6275d0921a6e38d53f95d3f85cae570f
+Subproject commit a45ee8f6774064978ba062621475cb78d6e27df8
From 7b91cbae3f1de6ee4541630b97cb2486f8f26623 Mon Sep 17 00:00:00 2001
From: John Smith
Date: Fri, 4 Apr 2025 23:26:12 +0800
Subject: [PATCH 20/51] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E6=B7=BB=E5=8A=A0?=
=?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=87=AA=E5=AE=9A=E4=B9=89=E6=A8=A1=E6=9D=BF?=
=?UTF-8?q?=E5=88=97=E8=A1=A8=E7=9A=84=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.dockerignore | 2 +
api/main.py | 63 +++++++++++++++++++++++++++
data/custom_public/templates/.gitkeep | 0
3 files changed, 65 insertions(+)
create mode 100644 data/custom_public/templates/.gitkeep
diff --git a/.dockerignore b/.dockerignore
index 2fc3c4ef..7321a3e1 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -24,6 +24,8 @@ data/*
!data/custom_public/
data/custom_public/*
!data/custom_public/README.txt
+!data/custom_public/templates/
+data/custom_public/templates/*
!data/emoticons/
data/emoticons/*
!data/plugins/
diff --git a/api/main.py b/api/main.py
index 92085769..e9314f49 100644
--- a/api/main.py
+++ b/api/main.py
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
import asyncio
import hashlib
+import json
import logging
import os
+import cachetools
import tornado.web
+import yarl
import api.base
import config
@@ -15,6 +18,10 @@
EMOTICON_UPLOAD_PATH = os.path.join(config.DATA_PATH, 'emoticons')
EMOTICON_BASE_URL = '/emoticons'
CUSTOM_PUBLIC_PATH = os.path.join(config.DATA_PATH, 'custom_public')
+TEMPLATE_PATH = os.path.join(CUSTOM_PUBLIC_PATH, 'templates')
+TEMPLATE_BASE_URL = '/custom_public/templates'
+
+_templates_cache = cachetools.TTLCache(1, 10)
class StaticHandler(tornado.web.StaticFileHandler):
@@ -100,6 +107,61 @@ def _save_file(body, client):
return f'{EMOTICON_BASE_URL}/{filename}'
+class TemplatesHandler(api.base.ApiHandler):
+ async def get(self):
+ templates = _templates_cache.get('templates', None)
+ if templates is None:
+ templates = await asyncio.get_running_loop().run_in_executor(None, self._get_templates)
+ _templates_cache['templates'] = templates
+
+ self.set_header('Cache-Control', 'private, max-age=10')
+ self.write({'templates': templates})
+
+ @staticmethod
+ def _get_templates():
+ template_ids = []
+ try:
+ with os.scandir(TEMPLATE_PATH) as it:
+ for entry in it:
+ if entry.is_dir() and os.path.isfile(os.path.join(entry.path, 'template.json')):
+ template_ids.append(entry.name)
+ except OSError:
+ logger.exception('Failed to discover templates:')
+ return []
+ if not template_ids:
+ return []
+
+ templates = []
+ for template_id in template_ids:
+ try:
+ config_path = os.path.join(TEMPLATE_PATH, template_id, 'template.json')
+ with open(config_path, encoding='utf-8') as f:
+ cfg = json.load(f)
+ if not isinstance(cfg, dict):
+ raise TypeError(f'Config type error, type={type(cfg)}')
+
+ url_str = str(cfg.get('url', ''))
+ url = yarl.URL(url_str)
+ if not url.absolute:
+ # 相对于模板目录
+ base_url = yarl.URL(f'{TEMPLATE_BASE_URL}/{template_id}/')
+ url = base_url.join(url)
+ url_str = str(url)
+
+ template = {
+ 'id': template_id,
+ 'name': str(cfg.get('name', '')),
+ 'version': str(cfg.get('version', '')),
+ 'author': str(cfg.get('author', '')),
+ 'description': str(cfg.get('description', '')),
+ 'url': url_str,
+ }
+ templates.append(template)
+ except (OSError, json.JSONDecodeError, TypeError, ValueError):
+ logger.exception('template_id=%s failed to load config:', template_id)
+ return templates
+
+
class NoCacheStaticFileHandler(tornado.web.StaticFileHandler):
def set_extra_headers(self, path):
self.set_header('Cache-Control', 'no-cache')
@@ -110,6 +172,7 @@ def set_extra_headers(self, path):
(r'/api/endpoints', ServiceDiscoveryHandler),
(r'/api/ping', PingHandler),
(r'/api/emoticon', UploadEmoticonHandler),
+ (r'/api/templates', TemplatesHandler),
]
# 通配的放在最后
LAST_ROUTES = [
diff --git a/data/custom_public/templates/.gitkeep b/data/custom_public/templates/.gitkeep
new file mode 100644
index 00000000..e69de29b
From 22d80e3af2dda7b1a9ca7eab326e17cb99a8a142 Mon Sep 17 00:00:00 2001
From: John Smith
Date: Sun, 6 Apr 2025 16:23:52 +0800
Subject: [PATCH 21/51] =?UTF-8?q?=E5=89=8D=E7=AB=AF=E6=B7=BB=E5=8A=A0?=
=?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E6=A8=A1=E6=9D=BF=E9=85=8D=E7=BD=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/src/api/main.js | 4 +
frontend/src/lang/en.js | 7 +
frontend/src/lang/ja.js | 7 +
frontend/src/lang/zh.js | 7 +
frontend/src/views/Home/TemplateSelect.vue | 177 ++++++++++++++++++
.../src/views/{Home.vue => Home/index.vue} | 6 +
6 files changed, 208 insertions(+)
create mode 100644 frontend/src/views/Home/TemplateSelect.vue
rename frontend/src/views/{Home.vue => Home/index.vue} (98%)
diff --git a/frontend/src/api/main.js b/frontend/src/api/main.js
index 4fd822a3..bec9d781 100644
--- a/frontend/src/api/main.js
+++ b/frontend/src/api/main.js
@@ -9,3 +9,7 @@ export async function uploadEmoticon(file) {
body.set('file', file)
return (await axios.post('/api/emoticon', body)).data
}
+
+export async function getTemplates() {
+ return (await axios.get('/api/templates')).data
+}
diff --git a/frontend/src/lang/en.js b/frontend/src/lang/en.js
index 8b1bb4c2..f9bd4349 100644
--- a/frontend/src/lang/en.js
+++ b/frontend/src/lang/en.js
@@ -65,6 +65,13 @@ export default {
addEmoticon: 'Add emote',
emoticonFileTooLarge: 'File size is too large. Max size is 1MB',
+ template: 'Custom HTML Templates',
+ templateDefaultTitle: 'Default',
+ templateDefaultDescription: 'A YouTube-style template that can be styled with custom CSS. Choose this if you are unfamiliar with custom HTML templates, otherwise your custom CSS will not take effect',
+ templateCustomUrlTitle: 'Enter template URL',
+ templateCustomUrlDescription: 'For templates not listed, you can manually enter the URL provided by the author here',
+ author: 'Author: ',
+
urlTooLong: 'The room URL is too long, and will be truncated by Livehime (but not by OBS)',
roomUrlUpdated: 'The room URL is updated. Remember to copy it again',
roomUrl: 'Room URL',
diff --git a/frontend/src/lang/ja.js b/frontend/src/lang/ja.js
index dddf0ec8..957c4f0a 100644
--- a/frontend/src/lang/ja.js
+++ b/frontend/src/lang/ja.js
@@ -65,6 +65,13 @@ export default {
addEmoticon: 'スタンプを追加',
emoticonFileTooLarge: 'ファイルサイズが大きすぎます。最大サイズは1MBです',
+ template: 'カスタムHTMLテンプレート',
+ templateDefaultTitle: 'デフォルト',
+ templateDefaultDescription: 'YouTube風のテンプレートで、カスタムCSSでスタイルを変更できます。カスタムHTMLテンプレートの機能に慣れていない場合はこれを選択してください。そうでない場合、カスタムCSSが適用されない',
+ templateCustomUrlTitle: 'テンプレートURLを入力',
+ templateCustomUrlDescription: 'リストにないテンプレートは、作者が提供するURLをここで手動で入力できます',
+ author: '作者:',
+
urlTooLong: 'ルームのURLが長すぎて、直播姬によって切り詰められます(ただし、OBSでは切り詰められません)',
roomUrlUpdated: 'ルームのURLが更新されました。再度コピーすることをお忘れなく',
roomUrl: 'ルームのURL',
diff --git a/frontend/src/lang/zh.js b/frontend/src/lang/zh.js
index 8dfe91fd..2cf83554 100644
--- a/frontend/src/lang/zh.js
+++ b/frontend/src/lang/zh.js
@@ -65,6 +65,13 @@ export default {
addEmoticon: '添加表情',
emoticonFileTooLarge: '文件尺寸太大,最大1MB',
+ template: '自定义HTML模板',
+ templateDefaultTitle: '默认',
+ templateDefaultDescription: '仿YouTube风格的模板,可以用自定义CSS修改样式。如果你不了解自定义HTML模板的功能,就选择这个,否则自定义CSS不会生效',
+ templateCustomUrlTitle: '输入模板URL',
+ templateCustomUrlDescription: '没有列出的模板,也可以在这里手动输入作者提供的URL',
+ author: '作者:',
+
urlTooLong: '房间URL太长了,会被直播姬截断(OBS不会)',
roomUrlUpdated: '房间URL已更新,记得重新复制',
roomUrl: '房间URL',
diff --git a/frontend/src/views/Home/TemplateSelect.vue b/frontend/src/views/Home/TemplateSelect.vue
new file mode 100644
index 00000000..cfdeda05
--- /dev/null
+++ b/frontend/src/views/Home/TemplateSelect.vue
@@ -0,0 +1,177 @@
+
+
+
+
+
+
+
{{ $t('home.templateDefaultTitle') }}
+
+
{{ $t('home.templateDefaultDescription') }}
+
+
+
+
+
+
+
{{ $t('home.templateCustomUrlTitle') }}
+
+
{{ $t('home.templateCustomUrlDescription') }}
+
+
+
+
+
+
+
+
+
+
{{ template.name }}
+ {{ template.version }}
+ {{ $t('home.author') }}{{ template.author }}
+
+
{{ template.description }}
+
URL: {{ template.url }}
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home/index.vue
similarity index 98%
rename from frontend/src/views/Home.vue
rename to frontend/src/views/Home/index.vue
index 7ccf991a..2957112d 100644
--- a/frontend/src/views/Home.vue
+++ b/frontend/src/views/Home/index.vue
@@ -217,6 +217,10 @@
{{$t('home.addEmoticon')}}
+
+
+
+
@@ -249,12 +253,14 @@
import _ from 'lodash'
import download from 'downloadjs'
+import TemplateSelect from './TemplateSelect'
import { mergeConfig } from '@/utils'
import * as mainApi from '@/api/main'
import * as chatConfig from '@/api/chatConfig'
export default {
name: 'Home',
+ components: { TemplateSelect },
data() {
return {
serverConfig: {
From 8c3dfa902ca58cc409c6eb079e923be32a233243 Mon Sep 17 00:00:00 2001
From: John Smith
Date: Sun, 6 Apr 2025 18:35:10 +0800
Subject: [PATCH 22/51] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=AE=9A?=
=?UTF-8?q?=E4=B9=89=E6=A8=A1=E6=9D=BF=E7=BC=A9=E7=95=A5=E5=9B=BE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
api/main.py | 62 +++++++++++++---------
frontend/src/views/Help.vue | 20 ++-----
frontend/src/views/Home/TemplateSelect.vue | 51 +++++++++++++++---
3 files changed, 87 insertions(+), 46 deletions(-)
diff --git a/api/main.py b/api/main.py
index e9314f49..0b9ecb1d 100644
--- a/api/main.py
+++ b/api/main.py
@@ -117,8 +117,7 @@ async def get(self):
self.set_header('Cache-Control', 'private, max-age=10')
self.write({'templates': templates})
- @staticmethod
- def _get_templates():
+ def _get_templates(self):
template_ids = []
try:
with os.scandir(TEMPLATE_PATH) as it:
@@ -134,33 +133,48 @@ def _get_templates():
templates = []
for template_id in template_ids:
try:
- config_path = os.path.join(TEMPLATE_PATH, template_id, 'template.json')
- with open(config_path, encoding='utf-8') as f:
- cfg = json.load(f)
- if not isinstance(cfg, dict):
- raise TypeError(f'Config type error, type={type(cfg)}')
-
- url_str = str(cfg.get('url', ''))
- url = yarl.URL(url_str)
- if not url.absolute:
- # 相对于模板目录
- base_url = yarl.URL(f'{TEMPLATE_BASE_URL}/{template_id}/')
- url = base_url.join(url)
- url_str = str(url)
-
- template = {
- 'id': template_id,
- 'name': str(cfg.get('name', '')),
- 'version': str(cfg.get('version', '')),
- 'author': str(cfg.get('author', '')),
- 'description': str(cfg.get('description', '')),
- 'url': url_str,
- }
+ template = self._load_template_config(template_id)
templates.append(template)
except (OSError, json.JSONDecodeError, TypeError, ValueError):
logger.exception('template_id=%s failed to load config:', template_id)
return templates
+ @staticmethod
+ def _load_template_config(template_id):
+ config_path = os.path.join(TEMPLATE_PATH, template_id, 'template.json')
+ with open(config_path, encoding='utf-8') as f:
+ cfg = json.load(f)
+ if not isinstance(cfg, dict):
+ raise TypeError(f'Config type error, type={type(cfg)}')
+
+ # 相对于模板目录
+ base_url = yarl.URL(f'{TEMPLATE_BASE_URL}/{template_id}/')
+
+ def ensure_abs_url(url_str_):
+ url_ = yarl.URL(url_str_)
+ if not url_.absolute:
+ url_ = base_url.join(url_)
+ url_str_ = str(url_)
+ return url_str_
+
+ thumbnail_url_str = str(cfg.get('thumbnail', ''))
+ if thumbnail_url_str != '':
+ thumbnail_url_str = ensure_abs_url(thumbnail_url_str)
+
+ url_str = str(cfg.get('url', ''))
+ url_str = ensure_abs_url(url_str)
+
+ template = {
+ 'id': template_id,
+ 'name': str(cfg.get('name', '')),
+ 'version': str(cfg.get('version', '')),
+ 'author': str(cfg.get('author', '')),
+ 'description': str(cfg.get('description', '')),
+ 'thumbnail': thumbnail_url_str,
+ 'url': url_str,
+ }
+ return template
+
class NoCacheStaticFileHandler(tornado.web.StaticFileHandler):
def set_extra_headers(self, path):
diff --git a/frontend/src/views/Help.vue b/frontend/src/views/Help.vue
index cb8c7cab..c2fb36aa 100644
--- a/frontend/src/views/Help.vue
+++ b/frontend/src/views/Help.vue
@@ -2,15 +2,15 @@
{{ $t('help.help') }}
{{ $t('help.p1_1') }} https://play-live.bilibili.com/ {{ $t('help.p1_2') }}
-
+
{{ $t('help.p2') }}
-
+
{{ $t('help.p3') }}
-
+
{{ $t('help.p4') }}
-
+
{{ $t('help.p5') }}
-
+
--------------------------------------------------------------------------------------------------------
使用前必看:注意事项和常见问题
喜欢的话可以推荐给别人 _(:з」∠)_
@@ -22,13 +22,3 @@ export default {
name: 'Help'
}
-
-
diff --git a/frontend/src/views/Home/TemplateSelect.vue b/frontend/src/views/Home/TemplateSelect.vue
index cfdeda05..8e56d711 100644
--- a/frontend/src/views/Home/TemplateSelect.vue
+++ b/frontend/src/views/Home/TemplateSelect.vue
@@ -6,7 +6,11 @@
{{ $t('home.templateDefaultTitle') }}
-
{{ $t('home.templateDefaultDescription') }}
+
+
+
{{ $t('home.templateDefaultDescription') }}
+
+
@@ -15,7 +19,11 @@
{{ $t('home.templateCustomUrlTitle') }}
- {{ $t('home.templateCustomUrlDescription') }}
+
+
+
{{ $t('home.templateCustomUrlDescription') }}
+
+
@@ -29,8 +37,13 @@
{{ template.version }}
{{ $t('home.author') }}{{ template.author }}
- {{ template.description }}
- URL: {{ template.url }}
+
+
+
+
{{ template.description }}
+
URL: {{ template.url }}
+
+
@@ -140,13 +153,12 @@ export default {
}
.card-body {
- max-height: 200px;
display: flex;
flex-flow: column nowrap;
+ gap: 1em;
}
.title-line {
- margin-block-end: 1em;
flex: none;
display: flex;
flex-flow: row nowrap;
@@ -168,10 +180,35 @@ export default {
color: #606266;
}
+.description-line {
+ flex: none;
+ display: flex;
+ flex-flow: row nowrap;
+ gap: 1em;
+ max-height: 300px;
+}
+
+.thumbnail {
+ flex: none;
+ width: 400px;
+ height: 300px;
+}
+
.description {
- margin-block-end: 1em;
flex: auto;
overflow-y: auto;
text-wrap: wrap;
+ word-wrap: break-word;
+}
+
+@media only screen and (max-width: 992px) {
+ .description-line {
+ flex-wrap: wrap;
+ max-height: unset;
+ }
+
+ .thumbnail {
+ max-width: 100%;
+ }
}
From d2990ba8d8f14dcb0c3fd81a0e92ff1f65cae364 Mon Sep 17 00:00:00 2001
From: John Smith
Date: Sun, 13 Apr 2025 17:01:01 +0800
Subject: [PATCH 23/51] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=AE=9A?=
=?UTF-8?q?=E4=B9=89=E6=A8=A1=E6=9D=BFSDK?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/.eslintignore | 1 +
frontend/src/blcsdk.js | 312 ++++++++++++++++++++++++++++++++++++
frontend/src/views/Room.vue | 89 ++++++++--
3 files changed, 392 insertions(+), 10 deletions(-)
create mode 100644 frontend/src/blcsdk.js
diff --git a/frontend/.eslintignore b/frontend/.eslintignore
index 96be2c3a..e684bb40 100644
--- a/frontend/.eslintignore
+++ b/frontend/.eslintignore
@@ -1,2 +1,3 @@
brotli_decode.js
pronunciation/dict*.js
+blcsdk.js
diff --git a/frontend/src/blcsdk.js b/frontend/src/blcsdk.js
new file mode 100644
index 00000000..22f017a2
--- /dev/null
+++ b/frontend/src/blcsdk.js
@@ -0,0 +1,312 @@
+/** @module blcsdk */
+
+(function(root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ define([], factory)
+ } else {
+ root.blcsdk = factory()
+ }
+}(typeof self !== 'undefined' ? self : this, function() {
+ const exports = {}
+
+ const VERSION = '1.0.0'
+ /**
+ * 取SDK版本
+ * @returns {string} "1.0.0"
+ */
+ exports.getVersion = () => VERSION
+
+ // 初始化消息的Promise {promise, resolve, reject}
+ let initPromise = null
+ // 初始化消息,包含版本、配置等信息
+ let initMsg = null
+
+ /**
+ * 消息处理器
+ * @type {MsgHandler}
+ */
+ let msgHandler = null
+ /**
+ * 用户设置的消息处理器
+ * @type {MsgHandler}
+ */
+ let rawMsgHandler = null
+
+ /**
+ * @typedef InitOptions
+ * @property {boolean} noMsgDelay 去掉消息延迟,但会导致消息不平滑
+ */
+
+ /**
+ * 初始化SDK
+ *
+ * 在调用除了setMsgHandler以外的其他接口之前必须先调用这个
+ */
+ async function init(
+ /** @type {?InitOptions} */
+ {noMsgDelay = false} = {}
+ ) {
+ if (initPromise) {
+ throw new Error('Cannot call init() again')
+ }
+ // initPromise = Promise.withResolvers()
+ initPromise = {}
+ initPromise.promise = new Promise((resolve, reject) => {
+ initPromise.resolve = resolve
+ initPromise.reject = reject
+ })
+
+ if (window.parent === window) {
+ initPromise.reject(new Error('No parent window'))
+ return initPromise.promise
+ }
+
+ msgHandler = noMsgDelay ? new SdkMsgHandler() : new SmoothedSdkMsgHandler()
+ window.addEventListener('message', onWindowMessage)
+
+ // 连接blivechat
+ blcSendMsg('blcTemplateConnect')
+ setTimeout(() => initPromise.reject(new Error('Timed out waiting for blcInit message')), 10 * 1000)
+
+ // 等待初始化消息
+ initMsg = await initPromise.promise
+ console.debug('blcsdk initialized, init_msg=', initMsg)
+ }
+ exports.init = init
+
+ /**
+ * 设置消息处理器
+ * @param {?MsgHandler} handler 消息处理器
+ */
+ function setMsgHandler(handler) {
+ rawMsgHandler = handler
+ }
+ exports.setMsgHandler = setMsgHandler
+
+ /**
+ * 取blivechat前端版本
+ * @returns {string} "v1.10.0-dev"
+ */
+ function getBlcVersion() {
+ if (!initMsg) {
+ throw new Error('Please call init() first')
+ }
+ return initMsg.blcVersion
+ }
+ exports.getBlcVersion = getBlcVersion
+
+ /**
+ * 取blivechat前端用的SDK版本。是父窗口用的版本,不是这个包的getVersion返回值"
+ * @returns {string} "1.0.0"
+ */
+ function getBlcSdkVersion() {
+ if (!initMsg) {
+ throw new Error('Please call init() first')
+ }
+ return initMsg.sdkVersion
+ }
+ exports.getBlcSdkVersion = getBlcSdkVersion
+
+ /**
+ * @typedef Config
+ * @property {boolean} showGiftName 显示礼物名
+ * @property {boolean} mergeSimilarDanmaku 合并相似弹幕
+ * @property {boolean} mergeGift 合并礼物
+ * @property {number} maxNumber 最大弹幕数
+ */
+
+ /**
+ * 取blivechat前端房间部分配置
+ * @returns {Config}
+ */
+ function getConfig() {
+ if (!initMsg) {
+ throw new Error('Please call init() first')
+ }
+ return Object.freeze(initMsg.config)
+ }
+ exports.getConfig = getConfig
+
+ function blcSendMsg(type, data = null) {
+ if (window.parent === window) {
+ return
+ }
+ let msg = { type, data }
+ window.parent.postMessage(msg, '*')
+ }
+
+ function onWindowMessage(event) {
+ if (event.source !== window.parent) {
+ return
+ }
+
+ let { type, data } = event.data
+ switch (type) {
+ case 'blcInit':
+ initPromise.resolve(data)
+ break
+ case 'blcAddMsg':
+ msgHandler.addMsg(data)
+ break
+ case 'blcDelMsgs':
+ msgHandler.delMsgs(data.ids)
+ break
+ case 'blcUpdateMsg':
+ msgHandler.updateMsg(data.id, data.newValuesObj)
+ break
+ }
+ }
+
+ /** 模板消息处理器接口 */
+ class MsgHandler {
+ /**
+ * 添加消息
+ * @param {Object} msg
+ */
+ addMsg(msg) {}
+
+ /**
+ * 删除消息,主要用于撤回醒目留言
+ * @param {string[]} ids 要删除的消息ID
+ */
+ delMsgs(ids) {}
+
+ /**
+ * 更新消息字段,主要用于更新翻译结果
+ * @param {string} id 要更新的消息ID
+ * @param {Object} newValuesObj 字段和对应的新值
+ */
+ updateMsg(id, newValuesObj) {}
+ }
+ exports.MsgHandler = MsgHandler
+
+ class SdkMsgHandler extends MsgHandler {
+ addMsg(msg) { this._callRawHandler('addMsg', msg) }
+ delMsgs(ids) { this._callRawHandler('delMsgs', ids) }
+ updateMsg(id, newValuesObj) { this._callRawHandler('updateMsg', id, newValuesObj) }
+ _callRawHandler(...args) { doCallRawHandler(...args) }
+ }
+
+ function doCallRawHandler(funcName, ...args) {
+ if (!rawMsgHandler) {
+ return
+ }
+ try {
+ let func = rawMsgHandler[funcName]
+ return func.call(rawMsgHandler, ...args)
+ } catch (e) {
+ console.error(e)
+ }
+ }
+
+ // 发送消息时间间隔范围
+ const MSG_MIN_INTERVAL = 80
+ const MSG_MAX_INTERVAL = 1000
+
+ class SmoothedSdkMsgHandler extends SdkMsgHandler {
+ constructor() {
+ super()
+ // 消息队列
+ this._queue = []
+ // 消费消息队列的定时器ID
+ this._emitSmoothedMsgTimerId = null
+ // 最近进队列的时间间隔,用来估计下次进队列的时间
+ this._enqueueIntervals = []
+ // 上次进队列的时间
+ this._lastEnqueueTime = null
+ // 估计的下次进队列时间间隔
+ this._estimatedEnqueueInterval = null
+
+ this._boundEmitSmoothedMsgs = this._emitSmoothedMsgs.bind(this)
+ }
+
+ _callRawHandler(funcName, ...args) {
+ let msg = {funcName, args}
+ this._enqueueMsg(msg)
+ }
+
+ _enqueueMsg(msg) {
+ // 估计进队列时间间隔
+ if (!this._lastEnqueueTime) {
+ this._lastEnqueueTime = new Date()
+ } else {
+ let curTime = new Date()
+ let interval = curTime - this._lastEnqueueTime
+ // 真实的进队列时间间隔模式大概是这样:2500, 300, 300, 300, 2500, 300, ...
+ // B站消息有缓冲,会一次发多条消息。这里把波峰视为发送了一次真实的WS消息,所以要过滤掉间隔太小的
+ if (interval > 1000 || this._enqueueIntervals.length < 5) {
+ this._enqueueIntervals.push(interval)
+ if (this._enqueueIntervals.length > 5) {
+ this._enqueueIntervals.splice(0, this._enqueueIntervals.length - 5)
+ }
+ // 这边估计得尽量大,只要不太早把消息缓冲发完就是平滑的。有MESSAGE_MAX_INTERVAL保底,不会让消息延迟太大
+ // 其实可以用单调队列求最大值,偷懒不写了
+ this._estimatedEnqueueInterval = Math.max(...this._enqueueIntervals)
+ }
+ // 上次入队时间还是要设置,否则会太早把消息缓冲发完,然后较长时间没有新消息
+ this._lastEnqueueTime = curTime
+ }
+
+ this._queue.push(msg)
+
+ if (!this._emitSmoothedMsgTimerId) {
+ this._emitSmoothedMsgTimerId = setTimeout(this._boundEmitSmoothedMsgs)
+ }
+ }
+
+ _emitSmoothedMsgs() {
+ this._emitSmoothedMsgTimerId = null
+ if (this._queue.length <= 0) {
+ return
+ }
+
+ // 估计的下次进队列剩余时间
+ let estimatedNextEnqueueRemainTime = 10 * 1000
+ if (this._estimatedEnqueueInterval) {
+ estimatedNextEnqueueRemainTime = Math.max(this._lastEnqueueTime - new Date() + this._estimatedEnqueueInterval, 1)
+ }
+ // 计算发送的消息数,保证在下次进队列之前发完
+ // 下次进队列之前应该发多少条消息
+ let shouldEmitNum = Math.max(this._queue.length, 0)
+ // 下次进队列之前最多能发多少次
+ let maxCanEmitCount = estimatedNextEnqueueRemainTime / MSG_MIN_INTERVAL
+ // 这次发多少条消息
+ let numToEmit
+ if (shouldEmitNum < maxCanEmitCount) {
+ // 队列中消息数很少,每次发1条也能发完
+ numToEmit = 1
+ } else {
+ // 每次发1条以上,保证按最快速度能发完
+ numToEmit = Math.ceil(shouldEmitNum / maxCanEmitCount)
+ }
+
+ // 发消息
+ let msgs = this._queue.splice(0, numToEmit)
+ for (let msg of msgs) {
+ doCallRawHandler(msg.funcName, ...msg.args)
+ }
+
+ if (this._queue.length <= 0) {
+ return
+ }
+ // 消息没发完,计算下次发消息时间
+ let sleepTime
+ if (numToEmit === 1) {
+ // 队列中消息数很少,随便定个[MESSAGE_MIN_INTERVAL, MESSAGE_MAX_INTERVAL]的时间
+ sleepTime = estimatedNextEnqueueRemainTime / this._queue.length
+ sleepTime *= 0.5 + Math.random()
+ if (sleepTime > MSG_MAX_INTERVAL) {
+ sleepTime = MSG_MAX_INTERVAL
+ } else if (sleepTime < MSG_MIN_INTERVAL) {
+ sleepTime = MSG_MIN_INTERVAL
+ }
+ } else {
+ // 按最快速度发
+ sleepTime = MSG_MIN_INTERVAL
+ }
+ this._emitSmoothedMsgTimerId = window.setTimeout(this._boundEmitSmoothedMsgs, sleepTime)
+ }
+ }
+
+ return exports
+}))
diff --git a/frontend/src/views/Room.vue b/frontend/src/views/Room.vue
index 3a5c66a2..1c21c17e 100644
--- a/frontend/src/views/Room.vue
+++ b/frontend/src/views/Room.vue
@@ -16,23 +16,62 @@ import * as chatModels from '@/api/chat/models'
import ChatRenderer from '@/components/ChatRenderer'
import * as constants from '@/components/ChatRenderer/constants'
+class DefaultRenderer {
+ constructor(rendererVm) {
+ this.addMessage = rendererVm.addMessage
+ this.delMessages = rendererVm.delMessages
+ this.updateMessage = rendererVm.updateMessage
+ this.mergeSimilarText = rendererVm.mergeSimilarText
+ this.mergeSimilarGift = rendererVm.mergeSimilarGift
+ }
+
+ destroy() {
+ let dummyFunc = () => {}
+ this.addMessage = dummyFunc
+ this.delMessages = dummyFunc
+ this.updateMessage = dummyFunc
+ this.mergeSimilarText = dummyFunc
+ this.mergeSimilarGift = dummyFunc
+ }
+}
+
+const BLC_SDK_VERSION = '1.0.0'
+
class CustomTemplateRenderer {
- constructor(templateIframe) {
- this.templateIframe = templateIframe
+ constructor(templateIframe, config) {
+ this._templateIframe = templateIframe
+ this._config = config
+
+ this._enabledSendMessageToTemplate = (type, data) => {
+ let msg = { type, data }
+ templateIframe.contentWindow.postMessage(msg, '*')
+ }
+ this._sendMessageToTemplate = () => {}
+
+ this._boundOnWindowMessage = this._onWindowMessage.bind(this)
+ window.addEventListener('message', this._boundOnWindowMessage)
+ }
+
+ destroy() {
+ window.removeEventListener('message', this._boundOnWindowMessage)
+
+ let dummyFunc = () => {}
+ this._enabledSendMessageToTemplate = dummyFunc
+ this._sendMessageToTemplate = dummyFunc
}
addMessage(message) {
- this.sendMessageToTemplate('blcAddMsg', message)
+ this._sendMessageToTemplate('blcAddMsg', message)
}
delMessages(ids) {
let data = { ids }
- this.sendMessageToTemplate('blcDelMsgs', data)
+ this._sendMessageToTemplate('blcDelMsgs', data)
}
updateMessage(id, newValuesObj) {
let data = { id, newValuesObj }
- this.sendMessageToTemplate('blcUpdateMsg', data)
+ this._sendMessageToTemplate('blcUpdateMsg', data)
}
mergeSimilarText() {
@@ -43,9 +82,31 @@ class CustomTemplateRenderer {
return false
}
- sendMessageToTemplate(cmd, data) {
- let msg = { cmd, data }
- this.templateIframe.contentWindow.postMessage(msg, '*')
+ _onWindowMessage(event) {
+ if (event.source !== this._templateIframe.contentWindow) {
+ return
+ }
+
+ let { type } = event.data
+ switch (type) {
+ case 'blcTemplateConnect': {
+ this._sendMessageToTemplate = this._enabledSendMessageToTemplate
+
+ // 发送初始化消息
+ let initData = {
+ blcVersion: process.env.APP_VERSION,
+ sdkVersion: BLC_SDK_VERSION,
+ config: {
+ showGiftName: this._config.showGiftName,
+ mergeSimilarDanmaku: this._config.mergeSimilarDanmaku,
+ mergeGift: this._config.mergeGift,
+ maxNumber: this._config.maxNumber,
+ }
+ }
+ this._sendMessageToTemplate('blcInit', initData)
+ break
+ }
+ }
}
}
@@ -78,7 +139,7 @@ export default {
textEmoticons: [], // 官方的文本表情(后端配置的)
pronunciationConverter: null,
- customStyleElement, // 仅用于样式生成器中预览样式
+ customStyleElement, // 仅用于样式生成器中预览样式和使用自定义模板时
presetCssLinkElement: null,
renderer: null,
@@ -124,7 +185,11 @@ export default {
}
},
mounted() {
- this.renderer = !this.useCustomTemplate ? this.$refs.renderer : new CustomTemplateRenderer(this.$refs.templateIframe)
+ if (this.useCustomTemplate) {
+ this.renderer = new CustomTemplateRenderer(this.$refs.templateIframe, this.config)
+ } else {
+ this.renderer = new DefaultRenderer(this.$refs.renderer)
+ }
if (document.visibilityState === 'visible') {
if (this.roomKeyValue === null) {
@@ -152,6 +217,10 @@ export default {
if (this.presetCssLinkElement) {
document.head.removeChild(this.presetCssLinkElement)
}
+
+ if (this.renderer) {
+ this.renderer.destroy()
+ }
},
methods: {
onVisibilityChange() {
From 491dbb4fd96200f2e76007bd41746b3a5aa35f82 Mon Sep 17 00:00:00 2001
From: John Smith
Date: Sun, 13 Apr 2025 17:31:10 +0800
Subject: [PATCH 24/51] =?UTF-8?q?=E5=89=8D=E7=AB=AFrichContent=E6=94=B9?=
=?UTF-8?q?=E5=90=8D=E4=B8=BAcontentParts?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../components/ChatRenderer/TextMessage.vue | 12 ++---
.../src/components/ChatRenderer/constants.js | 14 +++---
.../src/components/ChatRenderer/index.vue | 4 +-
frontend/src/views/Room.vue | 48 +++++++++----------
4 files changed, 39 insertions(+), 39 deletions(-)
diff --git a/frontend/src/components/ChatRenderer/TextMessage.vue b/frontend/src/components/ChatRenderer/TextMessage.vue
index 393b7acf..39135dd8 100644
--- a/frontend/src/components/ChatRenderer/TextMessage.vue
+++ b/frontend/src/components/ChatRenderer/TextMessage.vue
@@ -9,10 +9,10 @@
:isInMemberMessage="false" :authorName="authorName" :authorType="authorType" :privilegeType="privilegeType"
>
-
- {{ content.text }}
+
+ {{ content.text }}
-
人民币 汇率
const EXCHANGE_RATE = 7
@@ -173,15 +173,15 @@ export function getShowContent(message) {
return message.content
}
-export function getShowRichContent(message) {
- let richContent = [...message.richContent]
+export function getShowContentParts(message) {
+ let contentParts = [...message.contentParts]
if (message.translation) {
- richContent.push({
- type: CONTENT_TYPE_TEXT,
+ contentParts.push({
+ type: CONTENT_PART_TYPE_TEXT,
text: `(${message.translation})`
})
}
- return richContent
+ return contentParts
}
export function getGiftShowContent(message, showGiftName) {
diff --git a/frontend/src/components/ChatRenderer/index.vue b/frontend/src/components/ChatRenderer/index.vue
index fd78e0b4..973cce3c 100644
--- a/frontend/src/components/ChatRenderer/index.vue
+++ b/frontend/src/components/ChatRenderer/index.vue
@@ -17,7 +17,7 @@
:authorName="message.authorName"
:authorType="message.authorType"
:privilegeType="message.privilegeType"
- :richContent="getShowRichContent(message)"
+ :contentParts="getShowContentParts(message)"
:repeated="message.repeated"
>
Date: Sun, 13 Apr 2025 20:46:02 +0800
Subject: [PATCH 25/51] =?UTF-8?q?=E5=89=8D=E7=AB=AF=E6=B8=B2=E6=9F=93?=
=?UTF-8?q?=E5=99=A8=E6=B6=88=E6=81=AF=E6=B7=BB=E5=8A=A0=E6=9B=B4=E5=A4=9A?=
=?UTF-8?q?=E5=AD=97=E6=AE=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/api/chat/ChatClientDirectOpenLive.js | 26 +++++++-
frontend/src/api/chat/ChatClientDirectWeb.js | 61 +++++++++++++++++--
frontend/src/api/chat/ChatClientRelay.js | 4 +-
frontend/src/api/chat/models.js | 44 +++++++++++++
.../src/components/ChatRenderer/index.vue | 4 +-
frontend/src/views/Room.vue | 33 ++++++++--
6 files changed, 157 insertions(+), 15 deletions(-)
diff --git a/frontend/src/api/chat/ChatClientDirectOpenLive.js b/frontend/src/api/chat/ChatClientDirectOpenLive.js
index 79069c1d..c11155a5 100644
--- a/frontend/src/api/chat/ChatClientDirectOpenLive.js
+++ b/frontend/src/api/chat/ChatClientDirectOpenLive.js
@@ -262,6 +262,9 @@ export default class ChatClientDirectOpenLive extends ChatClientOfficialBase {
medalLevel: data.fans_medal_wearing_status ? data.fans_medal_level : 0,
id: data.msg_id,
emoticon: emoticon,
+ // 给模板用的字段
+ uid: data.open_id,
+ medalName: data.fans_medal_wearing_status ? data.fans_medal_name : '',
})
this.msgHandler.onAddText(data)
}
@@ -277,7 +280,14 @@ export default class ChatClientDirectOpenLive extends ChatClientOfficialBase {
totalCoin: data.paid ? totalCoin : 0,
totalFreeCoin: !data.paid ? totalCoin : 0,
giftName: data.gift_name,
- num: data.gift_num
+ num: data.gift_num,
+ // 给模板用的字段
+ giftId: data.gift_id,
+ giftIconUrl: data.gift_icon,
+ uid: data.open_id,
+ privilegeType: data.guard_level,
+ medalLevel: data.fans_medal_wearing_status ? data.fans_medal_level : 0,
+ medalName: data.fans_medal_wearing_status ? data.fans_medal_name : '',
})
this.msgHandler.onAddGift(data)
}
@@ -289,7 +299,14 @@ export default class ChatClientDirectOpenLive extends ChatClientOfficialBase {
avatarUrl: chat.processAvatarUrl(data.user_info.uface),
timestamp: data.timestamp,
authorName: data.user_info.uname,
- privilegeType: data.guard_level
+ privilegeType: data.guard_level,
+ // 给模板用的字段
+ num: data.guard_num,
+ unit: data.guard_unit,
+ total_coin: data.price * data.guard_num,
+ uid: data.user_info.open_id,
+ medalLevel: data.fans_medal_wearing_status ? data.fans_medal_level : 0,
+ medalName: data.fans_medal_wearing_status ? data.fans_medal_name : '',
})
this.msgHandler.onAddMember(data)
}
@@ -303,6 +320,11 @@ export default class ChatClientDirectOpenLive extends ChatClientOfficialBase {
authorName: data.uname,
price: data.rmb,
content: data.message,
+ // 给模板用的字段
+ uid: data.open_id,
+ privilegeType: data.guard_level,
+ medalLevel: data.fans_medal_wearing_status ? data.fans_medal_level : 0,
+ medalName: data.fans_medal_wearing_status ? data.fans_medal_name : '',
})
this.msgHandler.onAddSuperChat(data)
}
diff --git a/frontend/src/api/chat/ChatClientDirectWeb.js b/frontend/src/api/chat/ChatClientDirectWeb.js
index 8bb5bbde..daf7be86 100644
--- a/frontend/src/api/chat/ChatClientDirectWeb.js
+++ b/frontend/src/api/chat/ChatClientDirectWeb.js
@@ -76,12 +76,14 @@ export default class ChatClientDirectWeb extends ChatClientOfficialBase {
avatarUrl = await chat.getAvatarUrl(uid, authorName)
}
- let roomId, medalLevel
+ let medalRoomId, medalLevel, medalName
if (info[3]) {
- roomId = info[3][3]
+ medalRoomId = info[3][3]
medalLevel = info[3][0]
+ medalName = info[3][1]
} else {
- roomId = medalLevel = 0
+ medalRoomId = medalLevel = 0
+ medalName = ''
}
let uid = info[2][0]
@@ -127,8 +129,11 @@ export default class ChatClientDirectWeb extends ChatClientOfficialBase {
authorLevel: info[4][0],
isNewbie: info[2][5] < 10000,
isMobileVerified: Boolean(info[2][6]),
- medalLevel: roomId === this.roomId ? medalLevel : 0,
+ medalLevel: medalRoomId === this.roomId ? medalLevel : 0,
emoticon: info[0][13].url || null,
+ // 给模板用的字段
+ uid: info[2][0] ? info[2][0].toString() : authorName,
+ medalName: medalRoomId === this.roomId ? medalName : '',
})
this.msgHandler.onAddText(data)
}
@@ -136,6 +141,17 @@ export default class ChatClientDirectWeb extends ChatClientOfficialBase {
sendGiftCallback(command) {
let data = command.data
let isPaidGift = data.coin_type === 'gold'
+
+ let medalRuid, medalLevel, medalName
+ if (data.medal_info) {
+ medalRuid = data.medal_info.target_id
+ medalLevel = data.medal_info.medal_level
+ medalName = data.medal_info.medal_name
+ } else {
+ medalRuid = medalLevel = 0
+ medalName = ''
+ }
+
data = new chatModels.AddGiftMsg({
avatarUrl: chat.processAvatarUrl(data.face),
timestamp: data.timestamp,
@@ -143,7 +159,14 @@ export default class ChatClientDirectWeb extends ChatClientOfficialBase {
totalCoin: isPaidGift ? data.total_coin : 0,
totalFreeCoin: !isPaidGift ? data.total_coin : 0,
giftName: data.giftName,
- num: data.num
+ num: data.num,
+ // 给模板用的字段
+ giftId: data.giftId,
+ giftIconUrl: data.gift_info.img_basic,
+ uid: data.uid ? data.uid.toString() : data.uname,
+ privilegeType: data.guard_level,
+ medalLevel: medalRuid === this.roomOwnerUid ? medalLevel : 0,
+ medalName: medalRuid === this.roomOwnerUid ? medalName : '',
})
this.msgHandler.onAddGift(data)
}
@@ -161,18 +184,41 @@ export default class ChatClientDirectWeb extends ChatClientOfficialBase {
start_time: timestamp,
guard_level: guardLevel,
},
+ pay_info: {
+ num,
+ unit,
+ price,
+ },
} = data
data = new chatModels.AddMemberMsg({
avatarUrl: await chat.getAvatarUrl(uid, username),
timestamp: timestamp,
authorName: username,
privilegeType: guardLevel,
+ // 给模板用的字段
+ num: num,
+ unit: unit,
+ total_coin: price * num,
+ uid: uid ? uid.toString() : username,
+ medalLevel: 0,
+ medalName: '',
})
this.msgHandler.onAddMember(data)
}
superChatMessageCallback(command) {
let data = command.data
+
+ let medalRoomId, medalLevel, medalName
+ if (data.medal_info) {
+ medalRoomId = data.medal_info.anchor_roomid
+ medalLevel = data.medal_info.medal_level
+ medalName = data.medal_info.medal_name
+ } else {
+ medalRoomId = medalLevel = 0
+ medalName = ''
+ }
+
data = new chatModels.AddSuperChatMsg({
id: data.id.toString(),
avatarUrl: chat.processAvatarUrl(data.user_info.face),
@@ -180,6 +226,11 @@ export default class ChatClientDirectWeb extends ChatClientOfficialBase {
authorName: data.user_info.uname,
price: data.price,
content: data.message,
+ // 给模板用的字段
+ uid: data.uid ? data.uid.toString() : data.user_info.uname,
+ privilegeType: data.user_info.guard_level,
+ medalLevel: medalRoomId === this.roomId ? medalLevel : 0,
+ medalName: medalRoomId === this.roomId ? medalName : '',
})
this.msgHandler.onAddSuperChat(data)
}
diff --git a/frontend/src/api/chat/ChatClientRelay.js b/frontend/src/api/chat/ChatClientRelay.js
index e5cfde19..4cf26aa7 100644
--- a/frontend/src/api/chat/ChatClientRelay.js
+++ b/frontend/src/api/chat/ChatClientRelay.js
@@ -180,7 +180,9 @@ export default class ChatClientRelay {
medalLevel: data[10],
id: data[11],
translation: data[12],
- emoticon: emoticon
+ emoticon: emoticon,
+ uid: data[16],
+ medalName: data[17],
})
this.msgHandler.onAddText(data)
break
diff --git a/frontend/src/api/chat/models.js b/frontend/src/api/chat/models.js
index 96d7e2a1..e275d7f1 100644
--- a/frontend/src/api/chat/models.js
+++ b/frontend/src/api/chat/models.js
@@ -18,6 +18,9 @@ export class AddTextMsg {
id = getUuid4Hex(),
translation = '',
emoticon = null,
+ // 给模板用的字段
+ uid = '',
+ medalName = '',
} = {}) {
this.avatarUrl = avatarUrl
this.timestamp = timestamp
@@ -33,6 +36,9 @@ export class AddTextMsg {
this.id = id
this.translation = translation
this.emoticon = emoticon
+ // 给模板用的字段
+ this.uid = uid
+ this.medalName = medalName
}
}
@@ -46,6 +52,13 @@ export class AddGiftMsg {
totalFreeCoin = 0,
giftName = '',
num = 1,
+ // 给模板用的字段
+ giftId = 0,
+ giftIconUrl = '',
+ uid = '',
+ privilegeType = 0,
+ medalLevel = 0,
+ medalName = '',
} = {}) {
this.id = id
this.avatarUrl = avatarUrl
@@ -55,6 +68,13 @@ export class AddGiftMsg {
this.totalFreeCoin = totalFreeCoin
this.giftName = giftName
this.num = num
+ // 给模板用的字段
+ this.giftId = giftId
+ this.giftIconUrl = giftIconUrl
+ this.uid = uid
+ this.privilegeType = privilegeType
+ this.medalLevel = medalLevel
+ this.medalName = medalName
}
}
@@ -65,12 +85,26 @@ export class AddMemberMsg {
timestamp = new Date().getTime() / 1000,
authorName = '',
privilegeType = 1,
+ // 给模板用的字段
+ num = 1,
+ unit = '月',
+ total_coin = 0,
+ uid = '',
+ medalLevel = 0,
+ medalName = '',
} = {}) {
this.id = id
this.avatarUrl = avatarUrl
this.timestamp = timestamp
this.authorName = authorName
this.privilegeType = privilegeType
+ // 给模板用的字段
+ this.num = num
+ this.unit = unit
+ this.totalCoin = total_coin
+ this.uid = uid
+ this.medalLevel = medalLevel
+ this.medalName = medalName
}
}
@@ -83,6 +117,11 @@ export class AddSuperChatMsg {
price = 0,
content = '',
translation = '',
+ // 给模板用的字段
+ uid = '',
+ privilegeType = 0,
+ medalLevel = 0,
+ medalName = '',
} = {}) {
this.id = id
this.avatarUrl = avatarUrl
@@ -91,6 +130,11 @@ export class AddSuperChatMsg {
this.price = price
this.content = content
this.translation = translation
+ // 给模板用的字段
+ this.uid = uid
+ this.privilegeType = privilegeType
+ this.medalLevel = medalLevel
+ this.medalName = medalName
}
}
diff --git a/frontend/src/components/ChatRenderer/index.vue b/frontend/src/components/ChatRenderer/index.vue
index 973cce3c..09a6a730 100644
--- a/frontend/src/components/ChatRenderer/index.vue
+++ b/frontend/src/components/ChatRenderer/index.vue
@@ -192,7 +192,7 @@ export default {
}
return false
},
- mergeSimilarGift(authorName, price, _freePrice, giftName, num) {
+ mergeSimilarGift(authorName, price, _totalFreeCoin, giftName, num) {
for (let message of this.iterRecentMessages(5)) {
if (
message.type === constants.MESSAGE_TYPE_GIFT
@@ -201,7 +201,7 @@ export default {
) {
this.updateMessage(message.id, { $add: {
price: price,
- // freePrice: freePrice, // 暂时没用到
+ // totalFreeCoin: totalFreeCoin, // 暂时没用到
num: num
} })
return true
diff --git a/frontend/src/views/Room.vue b/frontend/src/views/Room.vue
index c329096d..8fba7555 100644
--- a/frontend/src/views/Room.vue
+++ b/frontend/src/views/Room.vue
@@ -396,7 +396,11 @@ export default {
contentParts: contentParts,
privilegeType: data.privilegeType,
repeated: 1,
- translation: data.translation
+ translation: data.translation,
+ // 给模板用的字段
+ uid: data.uid,
+ medalLevel: data.medalLevel,
+ medalName: data.medalName,
}
this.renderer.addMessage(message)
},
@@ -420,9 +424,16 @@ export default {
authorName: data.authorName,
authorNamePronunciation: this.getPronunciation(data.authorName),
price: price,
- // freePrice: data.totalFreeCoin, // 暂时没用到
giftName: data.giftName,
- num: data.num
+ num: data.num,
+ // 给模板用的字段
+ totalFreeCoin: data.totalFreeCoin,
+ giftId: data.giftId,
+ giftIconUrl: data.giftIconUrl,
+ uid: data.uid,
+ privilegeType: data.privilegeType,
+ medalLevel: data.medalLevel,
+ medalName: data.medalName,
}
this.renderer.addMessage(message)
},
@@ -439,7 +450,14 @@ export default {
authorName: data.authorName,
authorNamePronunciation: this.getPronunciation(data.authorName),
privilegeType: data.privilegeType,
- title: this.$t('chat.membershipTitle')
+ title: this.$t('chat.membershipTitle'),
+ // 给模板用的字段
+ num: data.num,
+ unit: data.unit,
+ price: data.totalCoin / 1000,
+ uid: data.uid,
+ medalLevel: data.medalLevel,
+ medalName: data.medalName,
}
this.renderer.addMessage(message)
},
@@ -460,7 +478,12 @@ export default {
price: data.price,
time: new Date(data.timestamp * 1000),
content: data.content.trim(),
- translation: data.translation
+ translation: data.translation,
+ // 给模板用的字段
+ uid: data.uid,
+ privilegeType: data.privilegeType,
+ medalLevel: data.medalLevel,
+ medalName: data.medalName,
}
this.renderer.addMessage(message)
},
From 7ff3c9e646dfb173958d81be1a2073a53f7143fc Mon Sep 17 00:00:00 2001
From: John Smith
Date: Mon, 14 Apr 2025 00:33:31 +0800
Subject: [PATCH 26/51] =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E6=A8=A1?=
=?UTF-8?q?=E6=9D=BFSDK=E6=B7=BB=E5=8A=A0=E6=9E=9A=E4=B8=BE=E5=92=8C?=
=?UTF-8?q?=E6=B6=88=E6=81=AF=E7=B1=BB=E5=9E=8B=E6=B3=A8=E8=A7=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/src/blcsdk.js | 179 +++++++++++++++++++++++++++++++++++-
frontend/src/views/Room.vue | 5 +
2 files changed, 180 insertions(+), 4 deletions(-)
diff --git a/frontend/src/blcsdk.js b/frontend/src/blcsdk.js
index 22f017a2..4355f7e0 100644
--- a/frontend/src/blcsdk.js
+++ b/frontend/src/blcsdk.js
@@ -14,7 +14,10 @@
* 取SDK版本
* @returns {string} "1.0.0"
*/
- exports.getVersion = () => VERSION
+ function getVersion() {
+ return VERSION
+ }
+ exports.getVersion = getVersion
// 初始化消息的Promise {promise, resolve, reject}
let initPromise = null
@@ -40,7 +43,7 @@
/**
* 初始化SDK
*
- * 在调用除了setMsgHandler以外的其他接口之前必须先调用这个
+ * 在调用除了{@link setMsgHandler}以外的其他接口之前必须先调用这个
*/
async function init(
/** @type {?InitOptions} */
@@ -96,7 +99,7 @@
exports.getBlcVersion = getBlcVersion
/**
- * 取blivechat前端用的SDK版本。是父窗口用的版本,不是这个包的getVersion返回值"
+ * 取blivechat前端用的SDK版本。是父窗口用的版本,不是这个包的{@link getVersion}返回值
* @returns {string} "1.0.0"
*/
function getBlcSdkVersion() {
@@ -161,7 +164,7 @@
class MsgHandler {
/**
* 添加消息
- * @param {Object} msg
+ * @param {AnyDisplayMsg} msg
*/
addMsg(msg) {}
@@ -308,5 +311,173 @@
}
}
+ /**
+ * 消息类型
+ * @enum {number}
+ */
+ const MsgType = Object.freeze({
+ /** 评论 @see TextMsg */
+ TEXT: 0,
+ /** 礼物 @see GiftMsg */
+ GIFT: 1,
+ /** 上舰 @see MemberMsg */
+ MEMBER: 2,
+ /** 醒目留言 @see SuperChatMsg */
+ SUPER_CHAT: 3,
+ })
+ exports.MsgType = MsgType
+
+ /**
+ * 作者类型
+ * @enum {number}
+ */
+ const AuthorType = Object.freeze({
+ NORMAL: 0,
+ /** 舰队 */
+ MEMBER: 1,
+ /** 房管 */
+ ADMIN: 2,
+ /** 主播 */
+ OWNER: 3,
+ })
+ exports.AuthorType = AuthorType
+
+ /**
+ * 舰队等级。因为历史原因,消息里的字段名叫`privilegeType`
+ * @enum {number}
+ */
+ const GuardLevel = Object.freeze({
+ NONE: 0,
+ /** 总督 */
+ LV3: 1,
+ /** 提督 */
+ LV2: 2,
+ /** 舰长 */
+ LV1: 3,
+ })
+ exports.GuardLevel = GuardLevel
+
+ /**
+ * 一段内容的类型
+ * @enum {number}
+ */
+ const ContentPartType = Object.freeze({
+ /** 文本 */
+ TEXT: 0,
+ /** 图片 */
+ IMAGE: 1,
+ })
+ exports.ContentPartType = ContentPartType
+
+ //
+ // 以下只用于类型注解,运行时没什么用
+ //
+
+ /**
+ * 用于显示的消息
+ * @typedef {TextMsg | GiftMsg | MemberMsg | SuperChatMsg} AnyDisplayMsg
+ */
+
+ /**
+ * 评论消息。因为历史原因叫TextMsg,实际上会包含表情图片
+ * @typedef TextMsg
+ * @property {string} id 消息ID
+ * @property {MsgType} type 消息类型
+ * @property {string} avatarUrl 用户头像URL
+ * @property {Date} time 时间
+ * @property {string} authorName 用户名
+ * @property {AuthorType} authorType 用户类型
+ * @property {string} content 纯文本表示的内容
+ * @property {AnyContentPart[]} contentParts 解析后的内容,包括文本、图片
+ * @property {GuardLevel} privilegeType 舰队等级
+ * @property {string} translation 内容的翻译,刚添加时一般是空的,之后通过更新消息赋值
+ * @property {string} uid 用户Open ID或ID,使用房间ID连接时不保证是唯一的
+ * @property {number} medalLevel 勋章等级,如果没戴当前房间勋章则为0
+ * @property {string} medalName 勋章名,如果没戴当前房间勋章则为空字符串
+ */
+ exports.TextMsg = /** @type {TextMsg} */ (undefined)
+
+ /**
+ * 一段内容
+ * @typedef {TextContentPart | ImageContentPart} AnyContentPart
+ */
+ /**
+
+ * 一段文本内容
+ * @typedef TextContentPart
+ * @property {ContentPartType} type 内容类型
+ * @property {string} text 内容
+ */
+
+ /**
+ * 一段图片内容
+ * @typedef ImageContentPart
+ * @property {ContentPartType} type 内容类型
+ * @property {string} text 纯文本表示的内容
+ * @property {string} url 图片URL
+ * @property {number} width 宽度,加载失败则为0
+ * @property {number} height 高度,加载失败则为0
+ */
+
+ /**
+ * 礼物消息
+ * @typedef GiftMsg
+ * @property {string} id 消息ID
+ * @property {MsgType} type 消息类型
+ * @property {string} avatarUrl 用户头像URL
+ * @property {Date} time 时间
+ * @property {string} authorName 用户名
+ * @property {string} authorNamePronunciation 用户名读音
+ * @property {number} price 总价(元),免费礼物则为0
+ * @property {string} giftName 礼物名
+ * @property {number} num 数量
+ * @property {number} totalFreeCoin 免费礼物总价(银瓜子数),付费礼物则为0
+ * @property {number} giftId 礼物ID
+ * @property {string} giftIconUrl 礼物图标URL
+ * @property {string} uid 用户Open ID或ID,使用房间ID连接时不保证是唯一的
+ * @property {GuardLevel} privilegeType 舰队等级
+ * @property {number} medalLevel 勋章等级,如果没戴当前房间勋章则为0
+ * @property {string} medalName 勋章名,如果没戴当前房间勋章则为空字符串
+ */
+ exports.GiftMsg = /** @type {GiftMsg} */ (undefined)
+
+ /**
+ * 上舰消息
+ * @typedef MemberMsg
+ * @property {string} id 消息ID
+ * @property {MsgType} type 消息类型
+ * @property {string} avatarUrl 用户头像URL
+ * @property {Date} time 时间
+ * @property {string} authorName 用户名
+ * @property {string} authorNamePronunciation 用户名读音
+ * @property {GuardLevel} privilegeType 舰队等级
+ * @property {number} num 数量
+ * @property {string} unit 单位("月")
+ * @property {number} price 总价(元)
+ * @property {string} uid 用户Open ID或ID,使用房间ID连接时不保证是唯一的
+ * @property {number} medalLevel 勋章等级,如果没戴当前房间勋章则为0
+ * @property {string} medalName 勋章名,如果没戴当前房间勋章则为空字符串
+ */
+ exports.MemberMsg = /** @type {MemberMsg} */ (undefined)
+
+ /**
+ * 醒目留言消息
+ * @typedef SuperChatMsg
+ * @property {string} id 消息ID
+ * @property {MsgType} type 消息类型
+ * @property {string} avatarUrl 用户头像URL
+ * @property {Date} time 时间
+ * @property {string} authorName 用户名
+ * @property {string} authorNamePronunciation 用户名读音
+ * @property {number} price 价格(元)
+ * @property {string} content 内容
+ * @property {string} translation 内容的翻译,刚添加时一般是空的,之后通过更新消息赋值
+ * @property {string} uid 用户Open ID或ID,使用房间ID连接时不保证是唯一的
+ * @property {GuardLevel} privilegeType 舰队等级
+ * @property {number} medalLevel 勋章等级,如果没戴当前房间勋章则为0
+ * @property {string} medalName 勋章名,如果没戴当前房间勋章则为空字符串
+ */
+ exports.SuperChatMsg = /** @type {SuperChatMsg} */ (undefined)
+
return exports
}))
diff --git a/frontend/src/views/Room.vue b/frontend/src/views/Room.vue
index 8fba7555..808bd21e 100644
--- a/frontend/src/views/Room.vue
+++ b/frontend/src/views/Room.vue
@@ -15,6 +15,7 @@ import * as chat from '@/api/chat'
import * as chatModels from '@/api/chat/models'
import ChatRenderer from '@/components/ChatRenderer'
import * as constants from '@/components/ChatRenderer/constants'
+/** @import * as blcsdk from '@/blcsdk' */
class DefaultRenderer {
constructor(rendererVm) {
@@ -385,6 +386,7 @@ export default {
if (this.mergeSimilarText(data.content)) {
return
}
+ /** @type {typeof blcsdk.TextMsg} */
let message = {
id: data.id,
type: constants.MESSAGE_TYPE_TEXT,
@@ -416,6 +418,7 @@ export default {
if (price < this.config.minGiftPrice) { // 丢人
return
}
+ /** @type {typeof blcsdk.GiftMsg} */
let message = {
id: data.id,
type: constants.MESSAGE_TYPE_GIFT,
@@ -442,6 +445,7 @@ export default {
if (!this.config.showGift || !this.filterNewMemberMessage(data)) {
return
}
+ /** @type {typeof blcsdk.MemberMsg} */
let message = {
id: data.id,
type: constants.MESSAGE_TYPE_MEMBER,
@@ -469,6 +473,7 @@ export default {
if (data.price < this.config.minGiftPrice) { // 丢人
return
}
+ /** @type {typeof blcsdk.SuperChatMsg} */
let message = {
id: data.id,
type: constants.MESSAGE_TYPE_SUPER_CHAT,
From 9f7cdac674492818da4e54c668384983222792e6 Mon Sep 17 00:00:00 2001
From: John Smith
Date: Tue, 15 Apr 2025 23:33:17 +0800
Subject: [PATCH 27/51] =?UTF-8?q?=E5=89=8D=E7=AB=AF=E6=B5=8B=E8=AF=95?=
=?UTF-8?q?=E6=88=BF=E9=97=B4=E6=94=AF=E6=8C=81=E6=9B=B4=E5=A4=9A=E6=B6=88?=
=?UTF-8?q?=E6=81=AF=E5=92=8C=E5=AD=97=E6=AE=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/src/api/chat/ChatClientTest.js | 83 +++++++++++++++++++---
frontend/src/views/Home/TemplateSelect.vue | 4 +-
frontend/src/views/Room.vue | 4 +-
3 files changed, 76 insertions(+), 15 deletions(-)
diff --git a/frontend/src/api/chat/ChatClientTest.js b/frontend/src/api/chat/ChatClientTest.js
index 6bc7cdfd..972c93c9 100644
--- a/frontend/src/api/chat/ChatClientTest.js
+++ b/frontend/src/api/chat/ChatClientTest.js
@@ -63,7 +63,13 @@ const EMOTICONS = [
'lipu',
'huangdou_xihuan',
'sakaban_jiayu_yutou',
-].map(name => `/static/img/emoticons/${name}.png`)
+].map(name => `${window.location.origin}/static/img/emoticons/${name}.png`)
+
+const TRANSLATIONS = [
+ '这是翻译',
+ 'これは翻訳です',
+ 'blah blah blah',
+]
const AUTHOR_TYPES = [
{ weight: 10, value: constants.AUTHOR_TYPE_NORMAL },
@@ -72,6 +78,10 @@ const AUTHOR_TYPES = [
{ weight: 1, value: constants.AUTHOR_TYPE_OWNER }
]
+const GUARD_LEVEL_TO_PRICE = [
+ 0, 19998, 1998, 198
+]
+
function randGuardInfo() {
let authorType = randomChoose(AUTHOR_TYPES)
let privilegeType
@@ -85,14 +95,28 @@ function randGuardInfo() {
return { authorType, privilegeType }
}
+const MEDAL_NAMES = [
+ 'ikun',
+ 'K学姐',
+ '小孤独',
+ 'Go学长',
+ '不登校',
+]
+
+function randMedalInfo() {
+ let medalLevel = randInt(1, 4) <= 1 ? 0 : randInt(1, 40)
+ let medalName = medalLevel === 0 ? '' : randomChoose(MEDAL_NAMES)
+ return { medalLevel, medalName }
+}
+
const GIFT_INFO_LIST = [
- { giftName: '辣条', totalFreeCoin: 1000, num: 10 },
- { giftName: 'B坷垃', totalCoin: 9900 },
- { giftName: '礼花', totalCoin: 28000 },
- { giftName: '花式夸夸', totalCoin: 39000 },
- { giftName: '天空之翼', totalCoin: 100000 },
- { giftName: '摩天大楼', totalCoin: 450000 },
- { giftName: '小电视飞船', totalCoin: 1245000 }
+ { giftId: 1, giftName: '粉丝团灯牌', totalFreeCoin: 200, num: 10, giftIconUrl: '//s1.hdslb.com/bfs/live/e051dfd4557678f8edcac4993ed00a0935cbd9cc.png' },
+ { giftId: 2, giftName: '可爱捏', totalCoin: 9900, giftIconUrl: '//s1.hdslb.com/bfs/live/6dab14826b531c731521345e00d6b56a6708a449.png' },
+ { giftId: 3, giftName: '花式夸夸', totalCoin: 29900, giftIconUrl: '//s1.hdslb.com/bfs/live/28186596880db45a7b843f17d6ebb70feeac06f9.png' },
+ { giftId: 4, giftName: '情书', totalCoin: 52000, num: 10, giftIconUrl: '//s1.hdslb.com/bfs/live/14dafbf217618f0931c08897e0b3eefc00d0da22.png' },
+ { giftId: 5, giftName: '极速超跑', totalCoin: 100000, giftIconUrl: '//s1.hdslb.com/bfs/live/27b9734d1a5f77ea6fc94957e3fcbeb55505c6b9.png' },
+ { giftId: 6, giftName: '为你摘星', totalCoin: 520000, giftIconUrl: '//s1.hdslb.com/bfs/live/5bd584b6fdfb03d66de56102e775582fb29ceab7.png' },
+ { giftId: 7, giftName: '次元之城', totalCoin: 1245000, giftIconUrl: '//s1.hdslb.com/bfs/live/cdae8136b1ee767609aeec688bca8124651d4d01.png' }
]
const SC_PRICES = [
@@ -108,13 +132,14 @@ const MESSAGE_GENERATORS = [
type: constants.MESSAGE_TYPE_TEXT,
message: new chatModels.AddTextMsg({
...randGuardInfo(),
+ ...randMedalInfo(),
authorName: randomChoose(NAMES),
content: randomChoose(CONTENTS),
isGiftDanmaku: randInt(1, 10) <= 1,
authorLevel: randInt(1, 60),
isNewbie: randInt(1, 10) <= 1,
isMobileVerified: randInt(1, 10) <= 9,
- medalLevel: randInt(0, 40),
+ translation: randInt(1, 10) <= 1 ? randomChoose(TRANSLATIONS) : '',
})
}
}
@@ -127,11 +152,11 @@ const MESSAGE_GENERATORS = [
type: constants.MESSAGE_TYPE_TEXT,
message: new chatModels.AddTextMsg({
...randGuardInfo(),
+ ...randMedalInfo(),
authorName: randomChoose(NAMES),
authorLevel: randInt(1, 60),
isNewbie: randInt(1, 10) <= 1,
isMobileVerified: randInt(1, 10) <= 9,
- medalLevel: randInt(0, 40),
emoticon: randomChoose(EMOTICONS),
})
}
@@ -144,8 +169,10 @@ const MESSAGE_GENERATORS = [
return {
type: constants.MESSAGE_TYPE_GIFT,
message: new chatModels.AddGiftMsg({
+ ...randMedalInfo(),
...randomChoose(GIFT_INFO_LIST),
authorName: randomChoose(NAMES),
+ privilegeType: randInt(0, 3),
})
}
}
@@ -157,9 +184,12 @@ const MESSAGE_GENERATORS = [
return {
type: constants.MESSAGE_TYPE_SUPER_CHAT,
message: new chatModels.AddSuperChatMsg({
+ ...randMedalInfo(),
authorName: randomChoose(NAMES),
price: randomChoose(SC_PRICES),
content: randomChoose(CONTENTS),
+ translation: randInt(1, 10) <= 1 ? randomChoose(TRANSLATIONS) : '',
+ privilegeType: randInt(0, 3),
})
}
}
@@ -168,11 +198,14 @@ const MESSAGE_GENERATORS = [
{
weight: 1,
value() {
+ let privilegeType = randInt(1, 3)
return {
type: constants.MESSAGE_TYPE_MEMBER,
message: new chatModels.AddMemberMsg({
+ ...randMedalInfo(),
authorName: randomChoose(NAMES),
- privilegeType: randInt(1, 3)
+ privilegeType: privilegeType,
+ total_coin: GUARD_LEVEL_TO_PRICE[privilegeType] * 1000,
})
}
}
@@ -250,6 +283,7 @@ export default class ChatClientTest {
switch (type) {
case constants.MESSAGE_TYPE_TEXT:
this.msgHandler.onAddText(message)
+ this.maybeTranslate(message)
break
case constants.MESSAGE_TYPE_GIFT:
this.msgHandler.onAddGift(message)
@@ -259,7 +293,34 @@ export default class ChatClientTest {
break
case constants.MESSAGE_TYPE_SUPER_CHAT:
this.msgHandler.onAddSuperChat(message)
+ this.maybeTranslate(message)
+ this.maybeDeleteSc(message)
break
}
}
+
+ maybeTranslate(message) {
+ if (message.translation || randInt(1, 4) <= 1) {
+ return
+ }
+ window.setTimeout(() => {
+ let translateMessage = new chatModels.UpdateTranslationMsg({
+ id: message.id,
+ translation: randomChoose(TRANSLATIONS),
+ })
+ this.msgHandler.onUpdateTranslation(translateMessage)
+ }, randInt(1000, 3000))
+ }
+
+ maybeDeleteSc(message) {
+ if (randInt(1, 5) <= 4) {
+ return
+ }
+ window.setTimeout(() => {
+ let deleteMessage = new chatModels.DelSuperChatMsg({
+ ids: [message.id],
+ })
+ this.msgHandler.onDelSuperChat(deleteMessage)
+ }, randInt(1000, 3000))
+ }
}
diff --git a/frontend/src/views/Home/TemplateSelect.vue b/frontend/src/views/Home/TemplateSelect.vue
index 8e56d711..17ecfeb1 100644
--- a/frontend/src/views/Home/TemplateSelect.vue
+++ b/frontend/src/views/Home/TemplateSelect.vue
@@ -190,8 +190,8 @@ export default {
.thumbnail {
flex: none;
- width: 400px;
- height: 300px;
+ width: 190px;
+ height: 105px;
}
.description {
diff --git a/frontend/src/views/Room.vue b/frontend/src/views/Room.vue
index 808bd21e..bcd34e40 100644
--- a/frontend/src/views/Room.vue
+++ b/frontend/src/views/Room.vue
@@ -398,7 +398,7 @@ export default {
contentParts: contentParts,
privilegeType: data.privilegeType,
repeated: 1,
- translation: data.translation,
+ translation: this.config.autoTranslate ? data.translation : '',
// 给模板用的字段
uid: data.uid,
medalLevel: data.medalLevel,
@@ -483,7 +483,7 @@ export default {
price: data.price,
time: new Date(data.timestamp * 1000),
content: data.content.trim(),
- translation: data.translation,
+ translation: this.config.autoTranslate ? data.translation : '',
// 给模板用的字段
uid: data.uid,
privilegeType: data.privilegeType,
From 11234c8e29bdd103cf7c9a5124c7a0040429b6fa Mon Sep 17 00:00:00 2001
From: John Smith
Date: Wed, 16 Apr 2025 22:21:47 +0800
Subject: [PATCH 28/51] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B5=8B=E8=AF=95?=
=?UTF-8?q?=E6=B6=88=E6=81=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/src/api/chat/ChatClientTest.js | 3 +++
frontend/src/views/Home/index.vue | 3 ++-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/frontend/src/api/chat/ChatClientTest.js b/frontend/src/api/chat/ChatClientTest.js
index 972c93c9..759d7f29 100644
--- a/frontend/src/api/chat/ChatClientTest.js
+++ b/frontend/src/api/chat/ChatClientTest.js
@@ -35,6 +35,7 @@ const CONTENTS = [
'让我看看',
'卑鄙的外乡人',
'我不做人了,JOJO',
+ '想吃广东菜✋😭✋',
'已经没有什么好怕的了',
'你这猴子,真令我欢喜',
'[dog]文本[比心]表情[喝彩]',
@@ -55,6 +56,8 @@ const CONTENTS = [
'DU↗DU→DU↗DU↓ Max Verstappen',
'Farewell, ashen one. May the flame guide thee',
'Hey Vergil, your portal opening days are over. Give me the Yamato',
+ '',
+ '
',
]
const EMOTICONS = [
diff --git a/frontend/src/views/Home/index.vue b/frontend/src/views/Home/index.vue
index 2957112d..8c2282bc 100644
--- a/frontend/src/views/Home/index.vue
+++ b/frontend/src/views/Home/index.vue
@@ -390,7 +390,8 @@ export default {
getUnvalidatedRoomUrl(isTestRoom) {
// 重要的字段放在前面,因为如果被截断就连接不了房间了
let frontFields = {
- roomKeyType: this.form.roomKeyType
+ roomKeyType: this.form.roomKeyType,
+ templateUrl: this.form.templateUrl,
}
let backFields = {
lang: this.$i18n.locale,
From 8dfb40662c681108fd89876f1630137ab6f47dfd Mon Sep 17 00:00:00 2001
From: John Smith
Date: Sat, 19 Apr 2025 15:24:48 +0800
Subject: [PATCH 29/51] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=AE=9A?=
=?UTF-8?q?=E4=B9=89=E6=A8=A1=E6=9D=BF=E5=B8=AE=E5=8A=A9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 5 +++--
frontend/package.json | 2 +-
frontend/src/lang/en.js | 22 +++++++++++++++++-----
frontend/src/lang/ja.js | 14 +++++++++++++-
frontend/src/lang/zh.js | 14 ++++++++++++--
frontend/src/views/Home/TemplateSelect.vue | 8 ++++++++
6 files changed, 54 insertions(+), 11 deletions(-)
diff --git a/README.md b/README.md
index efd1adfd..a2872c12 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,8 @@
* 支持自动翻译弹幕、醒目留言到日语,可以在后台配置翻译目标语言
* 支持标注打赏用户名的读音,可选拼音或日文假名
* 支持配置自定义表情,不需要开通B站官方表情
-* 支持插件开发
+* 支持[自定义HTML模板](https://github.com/xfgryujk/blivechat/wiki/%E8%87%AA%E5%AE%9A%E4%B9%89HTML%E6%A8%A1%E6%9D%BF)
+* 支持[插件开发](https://github.com/xfgryujk/blivechat/wiki/%E6%8F%92%E4%BB%B6%E7%B3%BB%E7%BB%9F)
## 使用方法
@@ -105,7 +106,7 @@
服务器配置文件在`data/config.ini`,可以配置数据库和允许自动翻译等,编辑后要重启生效
-**自建服务器时强烈建议不使用加载器**,否则可能因为各种原因加载不出来
+**自建服务器时注意要删除loader_url配置**,否则加载不了房间页面
## 常用链接
diff --git a/frontend/package.json b/frontend/package.json
index 94006fdc..55cffc73 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "blivechat",
- "version": "1.10.0-dev",
+ "version": "1.10.0-beta",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
diff --git a/frontend/src/lang/en.js b/frontend/src/lang/en.js
index f9bd4349..6cd35217 100644
--- a/frontend/src/lang/en.js
+++ b/frontend/src/lang/en.js
@@ -66,8 +66,20 @@ export default {
emoticonFileTooLarge: 'File size is too large. Max size is 1MB',
template: 'Custom HTML Templates',
+ templateHelp: 'Help',
+ templateHelpContent: `\
+Custom HTML templates allow complete customization of the room page, including DOM structure and CSS styles.
+ Templates may be developed by third-party authors, and their security and quality are the responsibility of the template author.
+ You can find some published templates on GitHub Discussions
+To install a template: Put the extracted template directory into the "data/custom_public/templates" directory, then wait
+ about 10 seconds and refresh the webpage. Alternatively, you can directly enter the template URL to use an online template
+Note: After modifying the template setting, the custom CSS in OBS will not take effect
+Template Development Documentation
+`,
templateDefaultTitle: 'Default',
- templateDefaultDescription: 'A YouTube-style template that can be styled with custom CSS. Choose this if you are unfamiliar with custom HTML templates, otherwise your custom CSS will not take effect',
+ templateDefaultDescription: 'A YouTube-style template that can be styled with custom CSS. Choose this if you are unfamiliar with custom HTML templates, otherwise your custom CSS in OBS will not take effect',
templateCustomUrlTitle: 'Enter template URL',
templateCustomUrlDescription: 'For templates not listed, you can manually enter the URL provided by the author here',
author: 'Author: ',
@@ -193,12 +205,12 @@ export default {
be developed by third-party authors, and the security and quality are the responsibility of the plugin author. You can
find some published plugins in GitHub Discussions
-Plugin installation method: Put the extracted plugin directory into the "data/plugins" directory, then restart blivechat
+To install a plugin: Put the extracted plugin directory into the "data/plugins" directory, then restart blivechat
Notes: Most plugins require enabling the "Relay messages by the server" option and connecting to the room
in order to receive messages
-Introducing video
-
- Plugin development documentation
+Introducing Video
+Plugin Development Documentation
`,
author: 'Author: ',
disabledByServer: 'Administration for plugins is disabled by the server',
diff --git a/frontend/src/lang/ja.js b/frontend/src/lang/ja.js
index 957c4f0a..ce2156cf 100644
--- a/frontend/src/lang/ja.js
+++ b/frontend/src/lang/ja.js
@@ -66,8 +66,20 @@ export default {
emoticonFileTooLarge: 'ファイルサイズが大きすぎます。最大サイズは1MBです',
template: 'カスタムHTMLテンプレート',
+ templateHelp: 'ヘルプ',
+ templateHelpContent: `\
+カスタムHTMLテンプレートを使用すると、DOM構造やCSSスタイルを含むフロントエンドページを完全にカスタマイズできます。
+ テンプレートはサードパーティの作者によって開発される場合があり、そのセキュリティと品質はテンプレート作者の責任となります。
+ 公開されているテンプレートはGitHub Discussionsで入手できます
+テンプレートのインストール方法:解凍したテンプレートディレクトリを「data/custom_public/templates」フォルダに配置し、
+ 約10秒待ってからウェブページを更新してください。また、テンプレートURLを直接入力してオンラインテンプレートを使用することもできます
+注意:カスタムテンプレートを変更した場合、OBS内のカスタムCSSは反映されません
+テンプレート開発ドキュメント
+`,
templateDefaultTitle: 'デフォルト',
- templateDefaultDescription: 'YouTube風のテンプレートで、カスタムCSSでスタイルを変更できます。カスタムHTMLテンプレートの機能に慣れていない場合はこれを選択してください。そうでない場合、カスタムCSSが適用されない',
+ templateDefaultDescription: 'YouTube風のテンプレートで、カスタムCSSでスタイルを変更できます。カスタムHTMLテンプレートの機能に慣れていない場合はこれを選択してください。そうでない場合、OBS内のカスタムCSSは反映されません',
templateCustomUrlTitle: 'テンプレートURLを入力',
templateCustomUrlDescription: 'リストにないテンプレートは、作者が提供するURLをここで手動で入力できます',
author: '作者:',
diff --git a/frontend/src/lang/zh.js b/frontend/src/lang/zh.js
index 2cf83554..b2ef7610 100644
--- a/frontend/src/lang/zh.js
+++ b/frontend/src/lang/zh.js
@@ -66,8 +66,18 @@ export default {
emoticonFileTooLarge: '文件尺寸太大,最大1MB',
template: '自定义HTML模板',
+ templateHelp: '帮助',
+ templateHelpContent: `\
+自定义HTML模板可以完全自定义房间页面,包括DOM结构、CSS样式。模板可能由第三方作者开发,其安全性和质量由模板作者负责。
+ 你可以在GitHub Discussions获取一些已发布的模板
+模板安装方法:把解压后的模板目录放到“data/custom_public/templates”目录,然后等待约10秒后刷新网页。
+ 另外你也可以直接输入模板URL来使用在线模板
+注意:修改自定义模板设置后,OBS中的自定义CSS将不会生效
+模板开发文档
+`,
templateDefaultTitle: '默认',
- templateDefaultDescription: '仿YouTube风格的模板,可以用自定义CSS修改样式。如果你不了解自定义HTML模板的功能,就选择这个,否则自定义CSS不会生效',
+ templateDefaultDescription: '仿YouTube风格的模板,可以用自定义CSS修改样式。如果你不了解自定义HTML模板的功能,就选择这个,否则OBS中的自定义CSS不会生效',
templateCustomUrlTitle: '输入模板URL',
templateCustomUrlDescription: '没有列出的模板,也可以在这里手动输入作者提供的URL',
author: '作者:',
@@ -193,7 +203,7 @@ export default {
你可以在GitHub Discussions获取一些已发布的插件
插件安装方法:把解压后的插件目录放到“data/plugins”目录,然后重启blivechat
-注意事项:大部分插件需要开启“通过服务器转发消息”,并且连接到房间,才能接收消息
+注意:大部分插件需要开启“通过服务器转发消息”,并且连接到房间,才能接收消息
介绍视频
插件开发文档
`,
diff --git a/frontend/src/views/Home/TemplateSelect.vue b/frontend/src/views/Home/TemplateSelect.vue
index 17ecfeb1..8e13a5ac 100644
--- a/frontend/src/views/Home/TemplateSelect.vue
+++ b/frontend/src/views/Home/TemplateSelect.vue
@@ -1,5 +1,12 @@
+
+ {{$t('home.templateHelp')}}
+
+
+
+
+
@@ -66,6 +73,7 @@ export default {
INDEX_DEFAULT,
INDEX_CUSTOM,
+ isHelpVisible: false,
templates: [],
customUrl: '',
}
From edd3636ecefc7fa9de628bbdc1fd46b07ebeab47 Mon Sep 17 00:00:00 2001
From: John Smith
Date: Wed, 23 Apr 2025 22:45:14 +0800
Subject: [PATCH 30/51] =?UTF-8?q?=E4=BC=98=E5=8C=96docker=E9=95=9C?=
=?UTF-8?q?=E5=83=8F=EF=BC=8C=E5=89=8D=E7=AB=AF=E4=B8=8D=E5=AD=98=E5=88=B0?=
=?UTF-8?q?=E5=8D=B7=E9=87=8C=EF=BC=8C=E9=BB=98=E8=AE=A4=E5=8E=BB=E6=8E=89?=
=?UTF-8?q?=E5=8A=A0=E8=BD=BD=E5=99=A8URL?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Dockerfile | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index 3cdf36ba..11fb9f8a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -18,7 +18,7 @@ RUN npm run build
# 准备后端
#
-FROM python:3.12.7-bookworm
+FROM python:3.12.10-bookworm
ARG BASE_PATH='/root/blivechat'
ARG EXT_DATA_PATH='/mnt/data'
WORKDIR "${BASE_PATH}"
@@ -30,18 +30,17 @@ RUN pip3 install --no-cache-dir -i https://mirrors.aliyun.com/pypi/simple -r req
# 数据目录
COPY . ./
-RUN mkdir -p "${EXT_DATA_PATH}/frontend/dist" \
+RUN sed 's/^host =.*$/host = 0.0.0.0/; s/^loader_url =.*$/loader_url =/' data/config.example.ini >> data/config.ini
+RUN mkdir -p "${EXT_DATA_PATH}" \
&& mv data "${EXT_DATA_PATH}/data" \
&& ln -s "${EXT_DATA_PATH}/data" data \
&& mv log "${EXT_DATA_PATH}/log" \
- && ln -s "${EXT_DATA_PATH}/log" log \
- && ln -s "${EXT_DATA_PATH}/frontend/dist" frontend/dist
+ && ln -s "${EXT_DATA_PATH}/log" log
# 编译好的前端
-COPY --from=builder "${BASE_PATH}/frontend/dist" "${EXT_DATA_PATH}/frontend/dist/"
+COPY --from=builder "${BASE_PATH}/frontend/dist" frontend/dist
# 运行
VOLUME "${EXT_DATA_PATH}"
EXPOSE 12450
ENTRYPOINT ["python3", "main.py"]
-CMD ["--host", "0.0.0.0", "--port", "12450"]
From cbfcb01b17357b4a2895ca2f29dbd58fec896c5d Mon Sep 17 00:00:00 2001
From: John Smith
Date: Wed, 23 Apr 2025 22:47:17 +0800
Subject: [PATCH 31/51] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC?=
=?UTF-8?q?=E5=8F=B7v1.10.0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/package.json | 2 +-
update.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/frontend/package.json b/frontend/package.json
index 55cffc73..a712e05e 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "blivechat",
- "version": "1.10.0-beta",
+ "version": "1.10.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
diff --git a/update.py b/update.py
index c84cf6be..0129cf24 100644
--- a/update.py
+++ b/update.py
@@ -6,7 +6,7 @@
import utils.async_io
import utils.request
-VERSION = 'v1.9.3'
+VERSION = 'v1.10.0'
def check_update():
From 8afb18bd7cd60f2bf631dde285e42dc8d16c322f Mon Sep 17 00:00:00 2001
From: John Smith
Date: Sat, 3 May 2025 11:25:23 +0800
Subject: [PATCH 32/51] =?UTF-8?q?=E4=BB=BF=E5=BE=AE=E4=BF=A1=E9=A3=8E?=
=?UTF-8?q?=E6=A0=BC=E6=A0=B7=E5=BC=8F=E7=94=9F=E6=88=90=E5=99=A8=E7=A6=81?=
=?UTF-8?q?=E6=AD=A2=E9=80=89=E8=83=8C=E6=99=AF=E7=9A=84=E4=B8=8D=E9=80=8F?=
=?UTF-8?q?=E6=98=8E=E5=BA=A6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/package.json | 2 +-
frontend/src/views/StyleGenerator/LineLike.vue | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/frontend/package.json b/frontend/package.json
index a712e05e..e0d5fa72 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "blivechat",
- "version": "1.10.0",
+ "version": "1.10.1-dev",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
diff --git a/frontend/src/views/StyleGenerator/LineLike.vue b/frontend/src/views/StyleGenerator/LineLike.vue
index 78f0d822..c821db1d 100644
--- a/frontend/src/views/StyleGenerator/LineLike.vue
+++ b/frontend/src/views/StyleGenerator/LineLike.vue
@@ -175,24 +175,24 @@
-
+
-
+
-
+
-
+
From 6ddf29e76908e8d7525f57bb52e711e4ebcce0b9 Mon Sep 17 00:00:00 2001
From: John Smith
Date: Sat, 3 May 2025 17:41:38 +0800
Subject: [PATCH 33/51] =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E6=A8=A1?=
=?UTF-8?q?=E6=9D=BFSDK=E5=8D=87=E7=BA=A7=E5=88=B0v1.0.1=EF=BC=8C=E6=94=AF?=
=?UTF-8?q?=E6=8C=81=E6=B3=A8=E5=85=A5OBS=E4=B8=AD=E7=9A=84=E8=87=AA?=
=?UTF-8?q?=E5=AE=9A=E4=B9=89CSS?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/src/blcsdk.js | 48 ++++++++++++++++++++++++++++++-------
frontend/src/views/Room.vue | 44 ++++++++++++++++++++++++++++++----
2 files changed, 78 insertions(+), 14 deletions(-)
diff --git a/frontend/src/blcsdk.js b/frontend/src/blcsdk.js
index 4355f7e0..3306c701 100644
--- a/frontend/src/blcsdk.js
+++ b/frontend/src/blcsdk.js
@@ -9,7 +9,7 @@
}(typeof self !== 'undefined' ? self : this, function() {
const exports = {}
- const VERSION = '1.0.0'
+ const VERSION = '1.0.1'
/**
* 取SDK版本
* @returns {string} "1.0.0"
@@ -38,8 +38,13 @@
/**
* @typedef InitOptions
* @property {boolean} noMsgDelay 去掉消息延迟,但会导致消息不平滑
+ * @property {boolean} noCssInjection 不注入OBS的自定义CSS和blivechat服务器预设CSS
*/
+ let initOptions = {
+ noCssInjection: false,
+ }
+
/**
* 初始化SDK
*
@@ -47,7 +52,7 @@
*/
async function init(
/** @type {?InitOptions} */
- {noMsgDelay = false} = {}
+ {noMsgDelay = false, noCssInjection = false} = {}
) {
if (initPromise) {
throw new Error('Cannot call init() again')
@@ -64,6 +69,8 @@
return initPromise.promise
}
+ initOptions.noCssInjection = noCssInjection
+
msgHandler = noMsgDelay ? new SdkMsgHandler() : new SmoothedSdkMsgHandler()
window.addEventListener('message', onWindowMessage)
@@ -73,7 +80,7 @@
// 等待初始化消息
initMsg = await initPromise.promise
- console.debug('blcsdk initialized, init_msg=', initMsg)
+ console.debug('blcsdk initialized, initMsg=', initMsg)
}
exports.init = init
@@ -145,21 +152,44 @@
let { type, data } = event.data
switch (type) {
- case 'blcInit':
- initPromise.resolve(data)
- break
case 'blcAddMsg':
msgHandler.addMsg(data)
break
+ case 'blcUpdateMsg':
+ msgHandler.updateMsg(data.id, data.newValuesObj)
+ break
case 'blcDelMsgs':
msgHandler.delMsgs(data.ids)
break
- case 'blcUpdateMsg':
- msgHandler.updateMsg(data.id, data.newValuesObj)
+
+ case 'blcInit':
+ initPromise.resolve(data)
+ break
+ case 'blcInjectCss':
+ injectCss(data)
break
}
}
+ function injectCss({injectCssUrls = [], injectCss = ''}) {
+ if (initOptions.noCssInjection) {
+ return
+ }
+
+ for (let url of injectCssUrls) {
+ let el = document.createElement('link')
+ el.rel = 'stylesheet'
+ el.href = url
+ document.head.appendChild(el)
+ }
+
+ if (injectCss !== '') {
+ let el = document.createElement('style')
+ el.textContent = injectCss
+ document.head.appendChild(el)
+ }
+ }
+
/** 模板消息处理器接口 */
class MsgHandler {
/**
@@ -307,7 +337,7 @@
// 按最快速度发
sleepTime = MSG_MIN_INTERVAL
}
- this._emitSmoothedMsgTimerId = window.setTimeout(this._boundEmitSmoothedMsgs, sleepTime)
+ this._emitSmoothedMsgTimerId = setTimeout(this._boundEmitSmoothedMsgs, sleepTime)
}
}
diff --git a/frontend/src/views/Room.vue b/frontend/src/views/Room.vue
index bcd34e40..3b77ba56 100644
--- a/frontend/src/views/Room.vue
+++ b/frontend/src/views/Room.vue
@@ -36,7 +36,10 @@ class DefaultRenderer {
}
}
-const BLC_SDK_VERSION = '1.0.0'
+const BLC_SDK_VERSION = '1.0.1'
+const PRESET_CSS_URL = '/custom_public/preset.css'
+// 有这个特征字符串的style元素则注入到模板里
+const OBS_CSS_SIGN = 'blc-inject-into-template'
class CustomTemplateRenderer {
constructor(templateIframe, config) {
@@ -91,8 +94,6 @@ class CustomTemplateRenderer {
let { type } = event.data
switch (type) {
case 'blcTemplateConnect': {
- this._sendMessageToTemplate = this._enabledSendMessageToTemplate
-
// 发送初始化消息
let initData = {
blcVersion: process.env.APP_VERSION,
@@ -104,11 +105,44 @@ class CustomTemplateRenderer {
maxNumber: this._config.maxNumber,
}
}
+ this._sendMessageToTemplate = this._enabledSendMessageToTemplate
this._sendMessageToTemplate('blcInit', initData)
+
+ this._injectCss()
break
}
}
}
+
+ async _injectCss() {
+ if (document.readyState !== 'complete') {
+ // OBS的自定义CSS在load事件之后注入
+ await new Promise(resolve => {
+ window.addEventListener('load', () => {
+ window.setTimeout(resolve, 100)
+ })
+ })
+ }
+
+ let injectCssUrls = []
+ if (this._config.importPresetCss) {
+ injectCssUrls.push(window.location.origin + PRESET_CSS_URL)
+ }
+
+ let injectCssArr = []
+ for (let el of document.querySelectorAll('style')) {
+ if (el.textContent.indexOf(OBS_CSS_SIGN) !== -1) {
+ injectCssArr.push(el.textContent)
+ }
+ }
+
+ if (injectCssUrls.length !== 0 || injectCssArr.length !== 0) {
+ this._sendMessageToTemplate('blcInjectCss', {
+ injectCssUrls: injectCssUrls,
+ injectCss: injectCssArr.join('\n\n'),
+ })
+ }
+ }
}
export default {
@@ -238,10 +272,10 @@ export default {
this.pronunciationConverter = new pronunciation.PronunciationConverter()
this.pronunciationConverter.loadDict(this.config.giftUsernamePronunciation)
}
- if (this.config.importPresetCss) {
+ if (this.config.importPresetCss && !this.useCustomTemplate) {
this.presetCssLinkElement = document.createElement('link')
this.presetCssLinkElement.rel = 'stylesheet'
- this.presetCssLinkElement.href = '/custom_public/preset.css'
+ this.presetCssLinkElement.href = PRESET_CSS_URL
document.head.appendChild(this.presetCssLinkElement)
}
From f6bd831f0c006c64cbafa1f6832f92e55d812f80 Mon Sep 17 00:00:00 2001
From: John Smith
Date: Sun, 4 May 2025 22:53:34 +0800
Subject: [PATCH 34/51] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9C=89=E6=97=B6?=
=?UTF-8?q?=E5=80=99=E6=B3=A8=E5=85=A5OBS=E4=B8=AD=E7=9A=84=E8=87=AA?=
=?UTF-8?q?=E5=AE=9A=E4=B9=89CSS=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE?=
=?UTF-8?q?=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/src/views/Room.vue | 59 ++++++++++++++++++++++++++++++-------
1 file changed, 49 insertions(+), 10 deletions(-)
diff --git a/frontend/src/views/Room.vue b/frontend/src/views/Room.vue
index 3b77ba56..39d37d40 100644
--- a/frontend/src/views/Room.vue
+++ b/frontend/src/views/Room.vue
@@ -54,9 +54,17 @@ class CustomTemplateRenderer {
this._boundOnWindowMessage = this._onWindowMessage.bind(this)
window.addEventListener('message', this._boundOnWindowMessage)
+
+ this._connected = false
+ this._styleObserver = null
}
destroy() {
+ if (this._styleObserver) {
+ this._styleObserver.disconnect()
+ this._styleObserver = null
+ }
+
window.removeEventListener('message', this._boundOnWindowMessage)
let dummyFunc = () => {}
@@ -94,6 +102,12 @@ class CustomTemplateRenderer {
let { type } = event.data
switch (type) {
case 'blcTemplateConnect': {
+ if (this._connected) {
+ console.warn('模板重复连接')
+ break
+ }
+ this._connected = true
+
// 发送初始化消息
let initData = {
blcVersion: process.env.APP_VERSION,
@@ -114,16 +128,7 @@ class CustomTemplateRenderer {
}
}
- async _injectCss() {
- if (document.readyState !== 'complete') {
- // OBS的自定义CSS在load事件之后注入
- await new Promise(resolve => {
- window.addEventListener('load', () => {
- window.setTimeout(resolve, 100)
- })
- })
- }
-
+ _injectCss() {
let injectCssUrls = []
if (this._config.importPresetCss) {
injectCssUrls.push(window.location.origin + PRESET_CSS_URL)
@@ -142,6 +147,40 @@ class CustomTemplateRenderer {
injectCss: injectCssArr.join('\n\n'),
})
}
+
+ // OBS的自定义CSS可能在之后注入,再监听一段时间。OBS和直播姬都是注入到head的,如果有其他软件不是再改吧
+ this._styleObserver = new MutationObserver(this._onDomMutate.bind(this))
+ this._styleObserver.observe(document.head, { childList: true })
+ window.setTimeout(() => {
+ if (this._styleObserver) {
+ this._styleObserver.disconnect()
+ this._styleObserver = null
+ }
+ }, 30 * 1000)
+ }
+
+ _onDomMutate(mutations) {
+ let injectCssArr = []
+ for (let mutation of mutations) {
+ if (mutation.type !== 'childList') {
+ continue
+ }
+ for (let el of mutation.addedNodes) {
+ if (el.nodeName !== 'STYLE') {
+ continue
+ }
+ if (el.textContent.indexOf(OBS_CSS_SIGN) !== -1) {
+ injectCssArr.push(el.textContent)
+ }
+ }
+ }
+
+ if (injectCssArr.length !== 0) {
+ this._sendMessageToTemplate('blcInjectCss', {
+ injectCssUrls: [],
+ injectCss: injectCssArr.join('\n\n'),
+ })
+ }
}
}
From 540c3a6046fb86443753b6c923e7ac63ffe8e6cb Mon Sep 17 00:00:00 2001
From: John Smith
Date: Sun, 4 May 2025 23:52:33 +0800
Subject: [PATCH 35/51] =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E7=94=9F=E6=88=90?=
=?UTF-8?q?=E5=99=A8=E6=94=AF=E6=8C=81=E9=80=89=E6=8B=A9=E6=9C=AC=E5=9C=B0?=
=?UTF-8?q?=E5=AD=97=E4=BD=93?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/src/lang/en.js | 1 +
frontend/src/lang/ja.js | 1 +
frontend/src/lang/zh.js | 1 +
.../src/views/StyleGenerator/FontSelect.vue | 20 ++++++++++++++++--
frontend/src/views/StyleGenerator/fonts.js | 21 +++++++++++++++++++
5 files changed, 42 insertions(+), 2 deletions(-)
diff --git a/frontend/src/lang/en.js b/frontend/src/lang/en.js
index 6cd35217..6d845908 100644
--- a/frontend/src/lang/en.js
+++ b/frontend/src/lang/en.js
@@ -117,6 +117,7 @@ export default {
recentFonts: 'Recent fonts',
presetFonts: 'Preset fonts',
networkFonts: 'Network fonts',
+ localFonts: 'Local fonts',
fontSize: 'Font size',
lineHeight: 'Line height (0 for default)',
normalColor: 'Normal color',
diff --git a/frontend/src/lang/ja.js b/frontend/src/lang/ja.js
index ce2156cf..7271cba8 100644
--- a/frontend/src/lang/ja.js
+++ b/frontend/src/lang/ja.js
@@ -117,6 +117,7 @@ export default {
recentFonts: '最近のフォント',
presetFonts: 'プリセットフォント',
networkFonts: 'ネットワークフォント',
+ localFonts: 'ローカルフォント',
fontSize: 'フォントサイズ',
lineHeight: '行の高さ(0はデフォルト)',
normalColor: 'ノーマルの色',
diff --git a/frontend/src/lang/zh.js b/frontend/src/lang/zh.js
index b2ef7610..88ab5c2a 100644
--- a/frontend/src/lang/zh.js
+++ b/frontend/src/lang/zh.js
@@ -115,6 +115,7 @@ export default {
recentFonts: '最近使用的字体',
presetFonts: '预设字体',
networkFonts: '网络字体',
+ localFonts: '本地字体',
fontSize: '字体尺寸',
lineHeight: '行高(0为默认)',
normalColor: '普通颜色',
diff --git a/frontend/src/views/StyleGenerator/FontSelect.vue b/frontend/src/views/StyleGenerator/FontSelect.vue
index 4403be01..495aa7c7 100644
--- a/frontend/src/views/StyleGenerator/FontSelect.vue
+++ b/frontend/src/views/StyleGenerator/FontSelect.vue
@@ -1,6 +1,6 @@
-
+
+
+
+
@@ -45,6 +49,7 @@ export default {
recentFonts: this.getRecentFonts(), // 这里只作为缓存,以localStorage为准
PRESET_FONTS: fonts.PRESET_FONTS,
NETWORK_FONTS: fonts.NETWORK_FONTS,
+ localFonts: [],
innerValue: [],
}
@@ -68,10 +73,21 @@ export default {
this.addRecentFont(font)
}
},
+ onSelectVisibleChange(visible) {
+ if (!visible) {
+ return
+ }
+ this.updateRecentFonts()
+ // 在这里更新第一次下拉时不会显示本地字体,但没什么好办法
+ this.updateLocalFonts()
+ },
+
updateRecentFonts() {
this.recentFonts = this.getRecentFonts()
},
-
+ async updateLocalFonts() {
+ this.localFonts = await fonts.getLocalFonts()
+ },
getRecentFonts() {
return common.fontsStrToArr(window.localStorage.recentFonts || '')
},
diff --git a/frontend/src/views/StyleGenerator/fonts.js b/frontend/src/views/StyleGenerator/fonts.js
index f60d5455..9b116e74 100644
--- a/frontend/src/views/StyleGenerator/fonts.js
+++ b/frontend/src/views/StyleGenerator/fonts.js
@@ -46,3 +46,24 @@ export const NETWORK_FONTS = [
'ZCOOL XiaoWei',
'Zhi Mang Xing',
]
+
+let localFontsPromise = null
+
+export async function getLocalFonts() {
+ if (!localFontsPromise) {
+ localFontsPromise = doGetLocalFonts()
+ }
+ return localFontsPromise
+}
+
+async function doGetLocalFonts() {
+ try {
+ return (await window.queryLocalFonts()).map(fontData => {
+ // 理论上应该用family,但fullName可读性更好
+ return fontData.fullName
+ })
+ } catch (e) {
+ console.error(e)
+ return []
+ }
+}
From bd3c4cdaba360f98a17ca1206cb6068266fd6a24 Mon Sep 17 00:00:00 2001
From: John Smith
Date: Wed, 7 May 2025 23:55:41 +0800
Subject: [PATCH 36/51] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AD=97=E4=BD=93?=
=?UTF-8?q?=E4=B8=8B=E6=8B=89=E6=A1=86=E7=9A=84=E9=AB=98=E5=BA=A6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/src/views/StyleGenerator/FontSelect.vue | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/frontend/src/views/StyleGenerator/FontSelect.vue b/frontend/src/views/StyleGenerator/FontSelect.vue
index 495aa7c7..8dd6d538 100644
--- a/frontend/src/views/StyleGenerator/FontSelect.vue
+++ b/frontend/src/views/StyleGenerator/FontSelect.vue
@@ -118,6 +118,12 @@ export default {
}
+
+