mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-01 20:16:36 +08:00
fa41936095
后端: - /api/reader/media 签名媒体代理:HMAC-SHA256(jwtSecret, bookID|url) 防开放 中转滥用;免 JWT(audio/img 元素带不了头)但需有效签名+书籍存在 - 携书源级请求头与 Referer(防盗链)、Range 透传(音频拖动进度 206)、 m3u8 播放列表分片/密钥地址改写为签名代理(hls.js 无感续播) - 音频/图片地址按最终页面 URL 绝对化,imageStyle 透传; GetContentForBook 将 tracks/images 统一改写为代理地址 前端: - ReaderAudioPanel(仿 ReadAloudDialog):hls.js 播 m3u8 / <audio> 直链, 上一章/播放暂停/下一章、进度条拖动、倍速 0.75–2x、按秒记忆进度、 播完自动下一章、点背景呼出菜单 - ReaderComic:上下滚动 / 左右单页双模式(复用翻页/滚动切换)、 图片懒加载+失败占位、滚动模式左右点击滚屏/中间呼菜单、 单页模式点击区翻页、菜单进度条按图片序号、按图片序号记忆进度 - 菜单进度条按内容类型适配(文本页/音频章节/漫画图片序号)
139 lines
4.3 KiB
Go
139 lines
4.3 KiB
Go
// Package handler wires the HTTP routes to the service container.
|
|
//
|
|
// All routes are mounted under /api/* so the frontend dev-server can proxy a
|
|
// single prefix.
|
|
package handler
|
|
|
|
import (
|
|
"github.com/gin-gonic/gin"
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/truewhile/MeBox/internal/config"
|
|
"github.com/truewhile/MeBox/internal/middleware"
|
|
"github.com/truewhile/MeBox/internal/service"
|
|
)
|
|
|
|
// Register attaches every API route to the engine.
|
|
func Register(r *gin.Engine, cfg *config.Config, log *zap.Logger, svc *service.Container) {
|
|
api := r.Group("/api")
|
|
api.Use(middleware.GzipAPI())
|
|
{
|
|
api.GET("/health", healthCheck)
|
|
api.HEAD("/health", healthCheck)
|
|
api.GET("/version", versionInfo)
|
|
api.GET("/public/ui-config", publicUIConfigHandler(svc))
|
|
|
|
// STRM 播放端点:strm 文件内容指向这里,Emby/Infuse 直接请求(无 JWT)。
|
|
api.GET("/strm/play/:provider/:file", strmPlayHandler(svc))
|
|
api.HEAD("/strm/play/:provider/:file", strmPlayHandler(svc))
|
|
// 阅读媒体代理:音频流/漫画图片,鉴权靠 HMAC 签名(audio/img 元素带不了 JWT)。
|
|
api.GET("/reader/media", readerMediaProxyHandler(svc))
|
|
// 115 中继/CloudDrive 授权回跳(authorization_id 会话 + 共享密钥校验)
|
|
api.POST("/strm/oauth/callback", strm115OAuthCallbackHandler(svc))
|
|
api.GET("/strm/oauth/callback", strm115OAuthCallbackHandler(svc))
|
|
|
|
registerPublicAuthRoutes(api, svc, log)
|
|
|
|
registerAuthenticatedRoutes(api, cfg, svc)
|
|
|
|
registerAdminRoutes(api, cfg, svc)
|
|
|
|
registerAPIConfigRoutes(api, cfg, svc)
|
|
|
|
// Emby/Jellyfin compatibility shim — routes mounted at /emby/* AND
|
|
// the root path so Infuse / Yamby / Hills / Senplayer 都能自动连接。
|
|
}
|
|
|
|
registerEmbyRoutes(r, cfg.Secrets.JWTSecret, svc)
|
|
}
|
|
|
|
func healthCheck(c *gin.Context) {
|
|
c.JSON(200, gin.H{"status": "ok"})
|
|
}
|
|
|
|
func versionInfo(c *gin.Context) {
|
|
c.JSON(200, gin.H{"name": "MeBox", "version": "0.1.0"})
|
|
}
|
|
|
|
// ─── 权限 Handler 包装 ────────────────────────────────────────────────────────
|
|
|
|
func getMyPermissionsHandler(svc *service.Container) gin.HandlerFunc {
|
|
h := NewPermissionHandler(svc, svc.Log)
|
|
return h.GetMyPermissions
|
|
}
|
|
|
|
// ─── API Config Handler 包装 ───────────────────────────────────────────────────
|
|
|
|
func listApiConfigsHandler(svc *service.Container) gin.HandlerFunc {
|
|
h := NewApiConfigHandler(svc, svc.Log)
|
|
return h.ListApiConfigs
|
|
}
|
|
|
|
func listProvidersHandler(svc *service.Container) gin.HandlerFunc {
|
|
h := NewApiConfigHandler(svc, svc.Log)
|
|
return h.ListProviders
|
|
}
|
|
|
|
func getApiConfigHandler(svc *service.Container) gin.HandlerFunc {
|
|
h := NewApiConfigHandler(svc, svc.Log)
|
|
return h.GetApiConfig
|
|
}
|
|
|
|
func getEffectiveConfigHandler(svc *service.Container) gin.HandlerFunc {
|
|
h := NewApiConfigHandler(svc, svc.Log)
|
|
return h.GetEffectiveConfig
|
|
}
|
|
|
|
func upsertApiConfigHandler(svc *service.Container) gin.HandlerFunc {
|
|
h := NewApiConfigHandler(svc, svc.Log)
|
|
return h.UpsertApiConfig
|
|
}
|
|
|
|
func deleteApiConfigHandler(svc *service.Container) gin.HandlerFunc {
|
|
h := NewApiConfigHandler(svc, svc.Log)
|
|
return h.DeleteApiConfig
|
|
}
|
|
|
|
func testApiConfigHandler(svc *service.Container) gin.HandlerFunc {
|
|
h := NewApiConfigHandler(svc, svc.Log)
|
|
return h.TestApiConfig
|
|
}
|
|
|
|
// ─── SSE Handler ──────────────────────────────────────────────────────────────
|
|
|
|
func sseHandler(svc *service.Container) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// 获取 SSE Hub
|
|
hub := svc.SSEHub
|
|
|
|
// 设置 SSE 响应头
|
|
c.Header("Content-Type", "text/event-stream")
|
|
c.Header("Cache-Control", "no-cache")
|
|
c.Header("Connection", "keep-alive")
|
|
c.Header("X-Accel-Buffering", "no")
|
|
|
|
// 订阅事件流
|
|
client := hub.Subscribe()
|
|
defer hub.Unsubscribe(client)
|
|
|
|
// 发送初始连接成功事件
|
|
c.SSEvent("connected", gin.H{"status": "ok"})
|
|
c.Writer.Flush()
|
|
|
|
// 持续发送事件直到客户端断开连接
|
|
clientGone := c.Request.Context().Done()
|
|
for {
|
|
select {
|
|
case <-clientGone:
|
|
return
|
|
case event, ok := <-client.Ch:
|
|
if !ok {
|
|
return
|
|
}
|
|
c.SSEvent(event.Type, event.Payload)
|
|
c.Writer.Flush()
|
|
}
|
|
}
|
|
}
|
|
}
|