Jellyfin Integration. Assisted by Claude Fable 5.

This commit is contained in:
itsericrao committed 2026-07-06 23:07:20 +08:00
1 parent b9767a7471
commit f637ba0191
32 files changed
+2802 -171

No files matched your search

+95 -5
View File
@@ -60,12 +60,49 @@
</div>
<div v-if="currentStep === 2" class="step-content">
<h2>登录音乐账号 (可选)</h2>
<p class="subtitle">登录后可播放 VIP/付费歌曲,跳过则只能播放免费歌曲</p>
<h2>连接 Jellyfin (可选)</h2>
<p class="subtitle">连接自建 Jellyfin 服务器作为主音源;也可稍后在「设置」中配置</p>
<div class="form-group">
<label>服务器地址</label>
<input v-model="jellyfin.serverUrl" placeholder="https://jellyfin.example.com" class="input" />
</div>
<div class="form-group">
<label>认证方式</label>
<select v-model="jellyfin.authMode" class="input">
<option value="userpass">账号密码</option>
<option value="apikey">API Key</option>
</select>
</div>
<template v-if="jellyfin.authMode === 'userpass'">
<div class="form-group">
<label>用户名</label>
<input v-model="jellyfin.username" placeholder="Jellyfin 用户名" class="input" />
</div>
<div class="form-group">
<label>密码</label>
<input v-model="jellyfin.password" type="password" placeholder="Jellyfin 密码" class="input" />
</div>
</template>
<template v-else>
<div class="form-group">
<label>API Key</label>
<input v-model="jellyfin.apiKey" type="password" placeholder="Jellyfin API Key" class="input" />
</div>
<div class="form-group">
<label>用户 ID</label>
<input v-model="jellyfin.userId" placeholder="该 Key 使用的用户 ID" class="input" />
</div>
</template>
<p v-if="jellyfinTestMessage" class="test-message" :class="{ ok: jellyfinTestOk }">{{ jellyfinTestMessage }}</p>
<div class="btn-row">
<button class="btn-secondary" @click="currentStep = 1">上一步</button>
<button class="btn-secondary" @click="currentStep = 3">跳过</button>
<button class="btn-primary" @click="currentStep = 3">完成登录</button>
<button class="btn-secondary" :disabled="jellyfinTesting" @click="testJellyfin">
{{ jellyfinTesting ? '测试中…' : '测试连接' }}
</button>
<button class="btn-primary" :disabled="jellyfinSaving" @click="saveJellyfinAndNext">
{{ jellyfinSaving ? '保存中…' : '保存并继续' }}
</button>
</div>
</div>
@@ -78,11 +115,11 @@
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ref, reactive } from 'vue';
import axios from 'axios';
const currentStep = ref(0);
const stepLabels = ['欢迎', 'TS 服务器', '音乐账号', '完成'];
const stepLabels = ['欢迎', 'TS 服务器', 'Jellyfin', '完成'];
const adminPassword = ref('');
const locale = ref('zh');
@@ -93,6 +130,21 @@ const nickname = ref('MusicBot');
const defaultChannel = ref('');
const channelId = ref('');
// Jellyfin — the primary music source. Optional: skipping leaves it
// configurable later in Settings.
const jellyfin = reactive({
serverUrl: '',
authMode: 'userpass' as 'userpass' | 'apikey',
username: '',
password: '',
apiKey: '',
userId: '',
});
const jellyfinTesting = ref(false);
const jellyfinSaving = ref(false);
const jellyfinTestOk = ref(false);
const jellyfinTestMessage = ref('');
async function createBotAndNext() {
try {
await axios.post('/api/bot', {
@@ -109,6 +161,36 @@ async function createBotAndNext() {
alert('Failed to create bot: ' + (err as Error).message);
}
}
async function testJellyfin() {
jellyfinTesting.value = true;
jellyfinTestMessage.value = '';
try {
const res = await axios.post('/api/auth/jellyfin/test', { ...jellyfin });
jellyfinTestOk.value = Boolean(res.data?.ok);
jellyfinTestMessage.value = res.data?.ok
? `连接成功:${res.data.serverName ?? 'Jellyfin'} ${res.data.version ?? ''}`
: `连接失败:${res.data?.error ?? '未知错误'}`;
} catch {
jellyfinTestOk.value = false;
jellyfinTestMessage.value = '测试请求失败,请检查地址后重试';
} finally {
jellyfinTesting.value = false;
}
}
async function saveJellyfinAndNext() {
jellyfinSaving.value = true;
try {
await axios.post('/api/bot/settings', { jellyfin: { ...jellyfin } });
currentStep.value = 3;
} catch {
jellyfinTestOk.value = false;
jellyfinTestMessage.value = '保存失败,请稍后在「设置」中重试';
} finally {
jellyfinSaving.value = false;
}
}
</script>
<style lang="scss" scoped>
@@ -232,6 +314,14 @@ async function createBotAndNext() {
margin-top: 32px;
}
.test-message {
font-size: 13px;
color: #e67e22;
margin-top: -6px;
&.ok { color: var(--color-online, #22c55e); }
}
.done-step {
text-align: center;
padding-top: 60px;