From ed7b7b9f04688aff7b4eb70c02b29c3fd035b1d8 Mon Sep 17 00:00:00 2001 From: Izumiko Date: Thu, 23 Jan 2025 02:16:15 +0800 Subject: [PATCH 1/3] fix: dandanplay api signature --- cf_worker.js | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/cf_worker.js b/cf_worker.js index 5b94f8b..850abf7 100644 --- a/cf_worker.js +++ b/cf_worker.js @@ -31,6 +31,16 @@ function handleOptions(request) { } } +async function generateSignature(appId, timestamp, path, appSecret) { + const data = appId + timestamp + path + appSecret; + const encoder = new TextEncoder(); + const dataBuffer = encoder.encode(data); + const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashBase64 = btoa(String.fromCharCode.apply(null, hashArray)); + return hashBase64; +} + async function handleRequest(request) { let response; if (request.method === 'OPTIONS') { @@ -48,9 +58,10 @@ async function handleRequest(request) { return Forbidden(tUrlObj); } + const body = request.method === 'POST' ? await request.json() : null; + // dandanplay login, compute sigh hash with appId and appSecret if (request.method === 'POST' && tUrlObj.pathname === '/api/v2/login') { - let body = await request.json(); if (body.userName.length == 0 || body.password.length == 0) { return new Response('{"error": "用户名或密码不能为空"}', { status: 400, @@ -65,22 +76,22 @@ async function handleRequest(request) { body.hash = Array.from(new Uint8Array(hash)) .map((b) => b.toString(16).padStart(2, '0')) .join(''); - - response = await fetch(url, { - headers: request.headers, - body: JSON.stringify(body), - method: request.method, - }); - response = new Response(await response.body, response); - response.headers.set('Access-Control-Allow-Origin', '*'); - response.headers.set('Access-Control-Allow-Methods', 'GET, HEAD, POST, OPTIONS'); - - return response; } + // handle dandanplay api auth + const timeStamp = Math.round(new Date().getTime() / 1000); + const apiPath = tUrlObj.pathname; + const signature = await generateSignature(appId, timeStamp, apiPath, appSecret); + response = await fetch(url, { - headers: request.headers, - body: request.body, + headers: { + ...request.headers, + 'X-AppId': appId, + 'X-Signature': signature, + 'X-Timestamp': timeStamp, + 'X-Auth': '1', + }, + body: body ? JSON.stringify(body) : null, method: request.method, }); response = new Response(await response.body, response); From aadd9f69cd88907217e36dfb7bb5d7ca8c57932f Mon Sep 17 00:00:00 2001 From: Izumiko Date: Thu, 23 Jan 2025 02:23:13 +0800 Subject: [PATCH 2/3] chore: Tampermonkey detection and LocalCors are useless api need signature, so worker proxy is always needed. --- ede.js | 52 +++++++++++----------------------------------------- 1 file changed, 11 insertions(+), 41 deletions(-) diff --git a/ede.js b/ede.js index b93a53d..4c4dbf9 100644 --- a/ede.js +++ b/ede.js @@ -3,13 +3,12 @@ // @description Jellyfin弹幕插件 // @namespace https://github.com/RyoLee // @author RyoLee -// @version 1.51 +// @version 1.52 // @copyright 2022, RyoLee (https://github.com/RyoLee) // @license MIT; https://raw.githubusercontent.com/Izumiko/jellyfin-danmaku/jellyfin/LICENSE // @icon https://github.githubassets.com/pinned-octocat.svg // @updateURL https://cdn.jsdelivr.net/gh/Izumiko/jellyfin-danmaku@gh-pages/ede.user.js // @downloadURL https://cdn.jsdelivr.net/gh/Izumiko/jellyfin-danmaku@gh-pages/ede.user.js -// @grant GM_xmlhttpRequest // @connect * // @match *://*/*/web/index.html // @match *://*/web/index.html @@ -24,16 +23,9 @@ return; } // ------ configs start------ - const isInTampermonkey = !(typeof GM_xmlhttpRequest === 'undefined'); - const isLocalCors = (!isInTampermonkey && document.currentScript?.src) ? new URL(document.currentScript?.src).searchParams.has("noCors") : false; const corsProxy = 'https://ddplay-api.930524.xyz/cors/'; - const apiPrefix = isInTampermonkey - ? 'https://api.dandanplay.net' - : isLocalCors - ? `${window.location.origin}/ddplay-api` - : corsProxy + 'https://api.dandanplay.net'; - // const apiPrefix = 'https://api.930524.xyz'; - const authPrefix = isLocalCors ? apiPrefix : corsProxy + 'https://api.dandanplay.net'; // 在Worker上计算Hash + const apiPrefix = corsProxy + 'https://api.dandanplay.net'; + const authPrefix = corsProxy + 'https://api.dandanplay.net'; // 在Worker上计算Hash let ddplayStatus = JSON.parse(localStorage.getItem('ddplayStatus')) || { isLogin: false, token: '', tokenExpire: 0 }; const check_interval = 200; // 0:当前状态关闭 1:当前状态打开 @@ -871,36 +863,14 @@ } function makeGetRequest(url) { - if (isInTampermonkey) { - return new Promise((resolve, reject) => { - GM_xmlhttpRequest({ - method: "GET", - url: url, - headers: { - "Accept-Encoding": "gzip,br", - "Accept": "application/json" - }, - onload: function (response) { - response.json = () => Promise.resolve(JSON.parse(response.responseText)); - response.text = () => Promise.resolve(response.responseText); - response.ok = response.status >= 200 && response.status < 300; - resolve(response); - }, - onerror: function (error) { - reject(error); - } - }); - }); - } else { - return fetch(url, { - method: 'GET', - headers: { - "Accept-Encoding": "gzip,br", - "Accept": "application/json", - "User-Agent": navigator.userAgent - } - }); - } + return fetch(url, { + method: 'GET', + headers: { + "Accept-Encoding": "gzip,br", + "Accept": "application/json", + "User-Agent": navigator.userAgent + } + }); } async function getEpisodeInfo(is_auto = true) { From 0784f848afc694957ed8140913be63190a3255c0 Mon Sep 17 00:00:00 2001 From: Izumiko Date: Thu, 23 Jan 2025 02:33:49 +0800 Subject: [PATCH 3/3] chore: update readme --- README.md | 47 ++--------------------------------------------- 1 file changed, 2 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 12fcc3a..080c5bc 100644 --- a/README.md +++ b/README.md @@ -64,34 +64,10 @@ ```conf proxy_set_header Accept-Encoding ""; -sub_filter '' ''; +sub_filter '' ''; sub_filter_once on; ``` -若需要本地代理弹弹play API,不使用CF Worker代理,则加入新的 location 块,否则删除上面网址中的`?noCors=1`: -```conf -location /ddplay-api/ { - proxy_pass https://api.dandanplay.net; - proxy_set_header Host $host; - - # example.com 根据自己的域名设置,或直接设为* - add_header Access-Control-Allow-Origin "example.com"; - add_header Access-Control-Allow-Methods "GET, POST, OPTIONS"; - add_header Access-Control-Allow-Headers "Origin, Content-Type, Accept, Authorization"; -} - -location /ddplay-api/api/v2/login { - rewrite ^/ddplay-api/api/v2/login(.*)$ /cors/https://api.dandanplay.net/api/v2/login$1 break; - proxy_pass https://ddplay-api.930524.xyz; - proxy_set_header Host $host; - - # example.com 根据自己的域名设置,或直接设为* - add_header Access-Control-Allow-Origin "example.com"; - add_header Access-Control-Allow-Methods "POST, OPTIONS"; - add_header Access-Control-Allow-Headers "Origin, Content-Type, Accept, Authorization"; -} -``` - - [`完整示例`](https://github.com/Izumiko/jellyfin-danmaku/issues/8) #### 2.2 Caddy @@ -109,31 +85,12 @@ example.com { filter { path /web/.* search_pattern - replacement "" + replacement "" content_type text/html } reverse_proxy localhost:8096 { header_up Accept-Encoding identity } - - # 若需要本地代理弹弹play API,不使用CF Worker代理,则加入下面两个handle_path,否则删除上面网址中的 ?noCors=1 - handle_path /ddplay-api/* { - reverse_proxy https://api.dandanplay.net { - header_up Host {upstream_hostport} - header_down Access-Control-Allow-Origin "example.com" - header_down Access-Control-Allow-Methods "GET, POST, OPTIONS" - header_down Access-Control-Allow-Headers "Origin, Content-Type, Accept, Authorization" - } - } - handle_path /ddplay-api/api/v2/login* { - rewrite * /cors/https://api.dandanplay.net/api/v2/login{http.request.uri.path} - reverse_proxy https://ddplay-api.930524.xyz { - header_up Host {upstream_hostport} - header_down Access-Control-Allow-Origin "example.com" - header_down Access-Control-Allow-Methods "POST, OPTIONS" - header_down Access-Control-Allow-Headers "Origin, Content-Type, Accept, Authorization" - } - } } ```