mirror of
https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced.git
synced 2026-08-12 17:10:37 +00:00
Compare commits
6 Commits
8f4873f2e2
...
a79a2d9ef6
| Author | SHA1 | Date | |
|---|---|---|---|
| a79a2d9ef6 | |||
|
|
9608da9b96 | ||
|
|
93b3e624d8 | ||
| 15f515c1ba | |||
| 16aa61f98e | |||
| a939003b92 |
31
.github/workflows/opencode.yml
vendored
31
.github/workflows/opencode.yml
vendored
@ -5,9 +5,16 @@ on:
|
|||||||
types: [created]
|
types: [created]
|
||||||
pull_request_review_comment:
|
pull_request_review_comment:
|
||||||
types: [created]
|
types: [created]
|
||||||
|
issues:
|
||||||
|
types: [opened]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
opencode:
|
bot:
|
||||||
if: |
|
if: |
|
||||||
contains(github.event.comment.body, ' /oc') ||
|
contains(github.event.comment.body, ' /oc') ||
|
||||||
startsWith(github.event.comment.body, '/oc') ||
|
startsWith(github.event.comment.body, '/oc') ||
|
||||||
@ -15,19 +22,33 @@ jobs:
|
|||||||
startsWith(github.event.comment.body, '/opencode')
|
startsWith(github.event.comment.body, '/opencode')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
|
||||||
contents: read
|
contents: read
|
||||||
pull-requests: read
|
|
||||||
issues: read
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Generate bot token
|
||||||
|
id: token
|
||||||
|
uses: actions/create-github-app-token@v1
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.APP_ID }}
|
||||||
|
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||||
|
owner: ${{ github.repository_owner }}
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
- name: Configure git identity
|
||||||
|
run: |
|
||||||
|
git config --global user.name "takanashi-hoshino-agent[bot]"
|
||||||
|
git config --global user.email "takanashi-hoshino-agent[bot]@users.noreply.github.com"
|
||||||
|
|
||||||
- name: Run opencode
|
- name: Run opencode
|
||||||
uses: anomalyco/opencode/github@latest
|
uses: anomalyco/opencode/github@latest
|
||||||
env:
|
env:
|
||||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||||
|
GITHUB_TOKEN: ${{ steps.token.outputs.token }}
|
||||||
with:
|
with:
|
||||||
model: opencode/deepseek-v4-flash-free
|
model: opencode/deepseek-v4-flash-free
|
||||||
|
use_github_token: true
|
||||||
|
prompt: ${{ secrets.OPENCODE_PROMPT }}
|
||||||
46
.github/workflows/pr-reviewer.yml
vendored
Normal file
46
.github/workflows/pr-reviewer.yml
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
name: opencode-review
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened, ready_for_review]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
review:
|
||||||
|
# 跳过 fork 来的 PR:GitHub 不会把 secrets 传给 fork PR 触发的工作流,
|
||||||
|
# 跑了也必然失败,所以直接跳过避免噪音(平台限制,非配置问题)。
|
||||||
|
if: github.event.pull_request.head.repo.fork == false
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Generate bot token
|
||||||
|
id: token
|
||||||
|
uses: actions/create-github-app-token@v1
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.APP_ID }}
|
||||||
|
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||||
|
owner: ${{ github.repository_owner }}
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
- name: Configure git identity
|
||||||
|
run: |
|
||||||
|
git config --global user.name "takanashi-hoshino-agent[bot]"
|
||||||
|
git config --global user.email "takanashi-hoshino-agent[bot]@users.noreply.github.com"
|
||||||
|
|
||||||
|
- name: Run opencode review
|
||||||
|
uses: anomalyco/opencode/github@latest
|
||||||
|
env:
|
||||||
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||||
|
# 用自定义 GitHub App 生成的 bot token,跟原 opencode.yml 一致,
|
||||||
|
# 评论 PR / 提交都走 takanashi-hoshino-agent[bot] 的身份。
|
||||||
|
GITHUB_TOKEN: ${{ steps.token.outputs.token }}
|
||||||
|
with:
|
||||||
|
model: opencode/deepseek-v4-flash-free
|
||||||
|
use_github_token: true
|
||||||
|
prompt: ${{ secrets.OPENCODE_PROMPT }}
|
||||||
16
interface.d.ts
vendored
16
interface.d.ts
vendored
@ -2802,6 +2802,22 @@ export function voice_upload(
|
|||||||
name: string
|
name: string
|
||||||
data: string | Buffer
|
data: string | Buffer
|
||||||
}
|
}
|
||||||
|
imgFile?: {
|
||||||
|
name: string
|
||||||
|
data: string | Buffer
|
||||||
|
}
|
||||||
|
voiceListId: string | number
|
||||||
|
coverImgId?: string | number
|
||||||
|
categoryId: string | number
|
||||||
|
secondCategoryId: string | number
|
||||||
|
description: string
|
||||||
|
songName?: string
|
||||||
|
privacy?: string | number
|
||||||
|
publishTime?: string | number
|
||||||
|
autoPublish?: string | number
|
||||||
|
autoPublishText?: string
|
||||||
|
orderNo?: string | number
|
||||||
|
composedSongs?: string
|
||||||
} & RequestBaseConfig,
|
} & RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ const { default: axios } = require('axios')
|
|||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
var xml2js = require('xml2js')
|
var xml2js = require('xml2js')
|
||||||
|
|
||||||
|
const uploadPlugin = require('../plugins/upload')
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
const { getFileExtension, readFileChunk } = require('../util/fileHelper')
|
const { getFileExtension, readFileChunk } = require('../util/fileHelper')
|
||||||
|
|
||||||
@ -19,15 +20,7 @@ function createDupkey() {
|
|||||||
return s.join('')
|
return s.join('')
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = async (query, request) => {
|
module.exports = async (query, request, dependencies = {}) => {
|
||||||
const ext = getFileExtension(query.songFile.name)
|
|
||||||
const filename =
|
|
||||||
query.songName ||
|
|
||||||
query.songFile.name
|
|
||||||
.replace('.' + ext, '')
|
|
||||||
.replace(/\s/g, '')
|
|
||||||
.replace(/\./g, '_')
|
|
||||||
|
|
||||||
if (!query.songFile) {
|
if (!query.songFile) {
|
||||||
return Promise.reject({
|
return Promise.reject({
|
||||||
status: 500,
|
status: 500,
|
||||||
@ -38,6 +31,19 @@ module.exports = async (query, request) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const axiosRequest = dependencies.axios || axios
|
||||||
|
const uploadImage = dependencies.uploadPlugin || uploadPlugin
|
||||||
|
const ext = getFileExtension(query.songFile.name)
|
||||||
|
const filename =
|
||||||
|
query.songName ||
|
||||||
|
query.songFile.name
|
||||||
|
.replace('.' + ext, '')
|
||||||
|
.replace(/\s/g, '')
|
||||||
|
.replace(/\./g, '_')
|
||||||
|
const coverImgId = query.imgFile
|
||||||
|
? (await uploadImage(query, request)).imgId
|
||||||
|
: query.coverImgId
|
||||||
|
|
||||||
const tokenRes = await request(
|
const tokenRes = await request(
|
||||||
`/api/nos/token/alloc`,
|
`/api/nos/token/alloc`,
|
||||||
{
|
{
|
||||||
@ -53,7 +59,7 @@ module.exports = async (query, request) => {
|
|||||||
|
|
||||||
const objectKey = tokenRes.body.result.objectKey.replace(/\//g, '%2F')
|
const objectKey = tokenRes.body.result.objectKey.replace(/\//g, '%2F')
|
||||||
const docId = tokenRes.body.result.docId
|
const docId = tokenRes.body.result.docId
|
||||||
const res = await axios({
|
const res = await axiosRequest({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: `https://ymusic.nos-hz.163yun.com/${objectKey}?uploads`,
|
url: `https://ymusic.nos-hz.163yun.com/${objectKey}?uploads`,
|
||||||
headers: {
|
headers: {
|
||||||
@ -94,7 +100,7 @@ module.exports = async (query, request) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const res3 = await axios({
|
const res3 = await axiosRequest({
|
||||||
method: 'put',
|
method: 'put',
|
||||||
url: `https://ymusic.nos-hz.163yun.com/${objectKey}?partNumber=${blockIndex}&uploadId=${res2.InitiateMultipartUploadResult.UploadId[0]}`,
|
url: `https://ymusic.nos-hz.163yun.com/${objectKey}?partNumber=${blockIndex}&uploadId=${res2.InitiateMultipartUploadResult.UploadId[0]}`,
|
||||||
headers: {
|
headers: {
|
||||||
@ -117,7 +123,7 @@ module.exports = async (query, request) => {
|
|||||||
}
|
}
|
||||||
completeStr += '</CompleteMultipartUpload>'
|
completeStr += '</CompleteMultipartUpload>'
|
||||||
|
|
||||||
await axios({
|
await axiosRequest({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: `https://ymusic.nos-hz.163yun.com/${objectKey}?uploadId=${res2.InitiateMultipartUploadResult.UploadId[0]}`,
|
url: `https://ymusic.nos-hz.163yun.com/${objectKey}?uploadId=${res2.InitiateMultipartUploadResult.UploadId[0]}`,
|
||||||
headers: {
|
headers: {
|
||||||
@ -128,29 +134,29 @@ module.exports = async (query, request) => {
|
|||||||
data: completeStr,
|
data: completeStr,
|
||||||
})
|
})
|
||||||
|
|
||||||
await request(
|
const voiceData = JSON.stringify([
|
||||||
`/api/voice/workbench/voice/batch/upload/preCheck`,
|
|
||||||
{
|
|
||||||
dupkey: createDupkey(),
|
|
||||||
voiceData: JSON.stringify([
|
|
||||||
{
|
{
|
||||||
name: filename,
|
name: filename,
|
||||||
autoPublish: query.autoPublish == 1 ? true : false,
|
autoPublish: query.autoPublish == 1 ? true : false,
|
||||||
autoPublishText: query.autoPublishText || '',
|
autoPublishText: query.autoPublishText || '',
|
||||||
description: query.description,
|
description: query.description,
|
||||||
voiceListId: query.voiceListId,
|
voiceListId: query.voiceListId,
|
||||||
coverImgId: query.coverImgId,
|
coverImgId,
|
||||||
dfsId: docId,
|
dfsId: docId,
|
||||||
categoryId: query.categoryId,
|
categoryId: query.categoryId,
|
||||||
secondCategoryId: query.secondCategoryId,
|
secondCategoryId: query.secondCategoryId,
|
||||||
composedSongs: query.composedSongs
|
composedSongs: query.composedSongs ? query.composedSongs.split(',') : [],
|
||||||
? query.composedSongs.split(',')
|
|
||||||
: [],
|
|
||||||
privacy: query.privacy == 1 ? true : false,
|
privacy: query.privacy == 1 ? true : false,
|
||||||
publishTime: query.publishTime || 0,
|
publishTime: query.publishTime || 0,
|
||||||
orderNo: query.orderNo || 1,
|
orderNo: query.orderNo || 1,
|
||||||
},
|
},
|
||||||
]),
|
])
|
||||||
|
|
||||||
|
await request(
|
||||||
|
`/api/voice/workbench/voice/batch/upload/preCheck`,
|
||||||
|
{
|
||||||
|
dupkey: createDupkey(),
|
||||||
|
voiceData,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...createOption(query),
|
...createOption(query),
|
||||||
@ -163,25 +169,7 @@ module.exports = async (query, request) => {
|
|||||||
`/api/voice/workbench/voice/batch/upload/v2`,
|
`/api/voice/workbench/voice/batch/upload/v2`,
|
||||||
{
|
{
|
||||||
dupkey: createDupkey(),
|
dupkey: createDupkey(),
|
||||||
voiceData: JSON.stringify([
|
voiceData,
|
||||||
{
|
|
||||||
name: filename,
|
|
||||||
autoPublish: query.autoPublish == 1 ? true : false,
|
|
||||||
autoPublishText: query.autoPublishText || '',
|
|
||||||
description: query.description,
|
|
||||||
voiceListId: query.voiceListId,
|
|
||||||
coverImgId: query.coverImgId,
|
|
||||||
dfsId: docId,
|
|
||||||
categoryId: query.categoryId,
|
|
||||||
secondCategoryId: query.secondCategoryId,
|
|
||||||
composedSongs: query.composedSongs
|
|
||||||
? query.composedSongs.split(',')
|
|
||||||
: [],
|
|
||||||
privacy: query.privacy == 1 ? true : false,
|
|
||||||
publishTime: query.publishTime || 0,
|
|
||||||
orderNo: query.orderNo || 1,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...createOption(query),
|
...createOption(query),
|
||||||
|
|||||||
@ -4371,27 +4371,33 @@ ONLINE 已发布
|
|||||||
|
|
||||||
### 播客上传声音
|
### 播客上传声音
|
||||||
|
|
||||||
说明: 可以上传声音到播客,例子在 `/public/voice_upload.html` 访问地址: <a href="/voice_upload.html" target="_blank">/voice_upload.html</a>
|
说明: 登录后调用此接口,使用`'Content-Type': 'multipart/form-data'`上传声音文件 formData(name 为`songFile`),可通过 formData(name 为`imgFile`)同时上传声音封面。例子在 `/public/voice_upload.html` 访问地址: <a href="/voice_upload.html" target="_blank">/voice_upload.html</a>
|
||||||
|
|
||||||
**接口地址:** `/voice/upload`
|
**接口地址:** `/voice/upload`
|
||||||
|
|
||||||
**必选参数:**
|
**必选参数:**
|
||||||
`voiceListId`: 播客 id
|
|
||||||
|
|
||||||
`coverImgId`: 播客封面
|
`songFile`: 声音文件
|
||||||
|
|
||||||
|
`voiceListId`: 播客 id
|
||||||
|
|
||||||
`categoryId`: 分类 id
|
`categoryId`: 分类 id
|
||||||
|
|
||||||
`secondCategoryId`:次级分类 id
|
`secondCategoryId`: 次级分类 id
|
||||||
|
|
||||||
`description`: 声音介绍
|
`description`: 声音介绍
|
||||||
|
|
||||||
**可选参数:**
|
**可选参数:**
|
||||||
|
|
||||||
|
`imgFile`: 声音封面图片文件,上传后会自动生成图片 id。与`coverImgId`同时传入时,优先使用`imgFile`
|
||||||
|
|
||||||
|
`coverImgId`: 已上传的声音封面图片 id,未传入`imgFile`时使用该值
|
||||||
|
|
||||||
`songName`: 声音名称
|
`songName`: 声音名称
|
||||||
|
|
||||||
`privacy`: 设为隐私声音,播客如果是隐私博客,则必须设为 1
|
`privacy`: 设为隐私声音,播客如果是隐私播客,则必须设为 1
|
||||||
|
|
||||||
`publishTime`:默认立即发布,定时发布的话需传入时间戳
|
`publishTime`: 默认立即发布,定时发布的话需传入时间戳
|
||||||
|
|
||||||
`autoPublish`: 是否发布动态,是则传入 1
|
`autoPublish`: 是否发布动态,是则传入 1
|
||||||
|
|
||||||
|
|||||||
@ -217,8 +217,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>选择文件</label>
|
<label for="songFile">选择声音文件</label>
|
||||||
<input type="file" name="songFile" accept="audio/*" />
|
<input id="songFile" type="file" name="songFile" accept="audio/*" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="imgFile">选择声音封面(可选)</label>
|
||||||
|
<input id="imgFile" type="file" name="imgFile" accept="image/*" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn" @click="submit">上传</button>
|
<button class="btn" @click="submit">上传</button>
|
||||||
@ -251,29 +256,36 @@
|
|||||||
methods: {
|
methods: {
|
||||||
submit() {
|
submit() {
|
||||||
console.info('submit')
|
console.info('submit')
|
||||||
const file = document.querySelector('input[type=file]').files[0]
|
const songFile = document.querySelector('input[name=songFile]').files[0]
|
||||||
if (!file) {
|
const imgFile = document.querySelector('input[name=imgFile]').files[0]
|
||||||
alert('请选择文件')
|
if (!songFile) {
|
||||||
|
alert('请选择声音文件')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.upload(file)
|
this.upload(songFile, imgFile)
|
||||||
},
|
},
|
||||||
|
|
||||||
async getData() {
|
async getData() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
try {
|
try {
|
||||||
const res = await axios({
|
const res = await axios({
|
||||||
url: `/voicelist/search?cookie=${localStorage.getItem('cookie')}`,
|
url: `/voicelist/my/created?limit=100&cookie=${localStorage.getItem(
|
||||||
|
'cookie',
|
||||||
|
)}`,
|
||||||
})
|
})
|
||||||
|
|
||||||
console.info(res.data.data)
|
console.info(res.data.data)
|
||||||
this.voicelist = res.data.data.list || []
|
this.voicelist = res.data.data.data || []
|
||||||
this.voicelist.forEach(async (i) => {
|
this.voicelist.forEach(async (i) => {
|
||||||
try {
|
try {
|
||||||
const res2 = await axios({
|
const res2 = await axios({
|
||||||
url: `/voicelist/list?voiceListId=${i.voiceListId}&limit=5`,
|
url: `/voicelist/list?voiceListId=${
|
||||||
|
i.voiceListId
|
||||||
|
}&limit=5&cookie=${localStorage.getItem('cookie')}`,
|
||||||
})
|
})
|
||||||
i.voiceListData = res2.data.data.list || []
|
i.voiceListData = res2.data.data
|
||||||
|
? res2.data.data.list || []
|
||||||
|
: []
|
||||||
console.info(res2)
|
console.info(res2)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('获取播客详情失败:', err)
|
console.error('获取播客详情失败:', err)
|
||||||
@ -286,14 +298,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
upload(file) {
|
upload(songFile, imgFile) {
|
||||||
if (!this.currentVoice) {
|
if (!this.currentVoice) {
|
||||||
alert('请先选择播客列表')
|
alert('请先选择播客列表')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var formData = new FormData()
|
var formData = new FormData()
|
||||||
formData.append('songFile', file)
|
formData.append('songFile', songFile)
|
||||||
|
if (imgFile) {
|
||||||
|
formData.append('imgFile', imgFile)
|
||||||
|
}
|
||||||
|
|
||||||
axios({
|
axios({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
@ -312,7 +327,7 @@
|
|||||||
data: formData,
|
data: formData,
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
alert(`${file.name} 上传成功`)
|
alert(`${songFile.name} 上传成功`)
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error('上传失败:', err)
|
console.error('上传失败:', err)
|
||||||
|
|||||||
129
test/voice_upload.test.js
Normal file
129
test/voice_upload.test.js
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
const assert = require('assert')
|
||||||
|
const voiceUpload = require('../module/voice_upload')
|
||||||
|
|
||||||
|
function createVoiceUploadHarness(uploadPlugin) {
|
||||||
|
const requestCalls = []
|
||||||
|
|
||||||
|
return {
|
||||||
|
requestCalls,
|
||||||
|
dependencies: {
|
||||||
|
uploadPlugin,
|
||||||
|
axios: async (options) => {
|
||||||
|
if (options.method === 'post' && options.url.endsWith('?uploads')) {
|
||||||
|
return {
|
||||||
|
data: '<InitiateMultipartUploadResult><UploadId>upload-id</UploadId></InitiateMultipartUploadResult>',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.method === 'put') {
|
||||||
|
return { headers: { etag: 'etag-1' } }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data: {} }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
request: async (uri, data, options) => {
|
||||||
|
requestCalls.push({ uri, data, options })
|
||||||
|
|
||||||
|
if (uri === '/api/nos/token/alloc') {
|
||||||
|
return {
|
||||||
|
body: {
|
||||||
|
result: {
|
||||||
|
objectKey: 'voice/audio.mp3',
|
||||||
|
docId: 'audio-doc-id',
|
||||||
|
token: 'nos-token',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri === '/api/voice/workbench/voice/batch/upload/v2') {
|
||||||
|
return { body: { data: { voiceId: 'voice-id' } } }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { body: { code: 200 } }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createVoiceUploadQuery(overrides = {}) {
|
||||||
|
const data = Buffer.from('audio')
|
||||||
|
|
||||||
|
return {
|
||||||
|
songFile: {
|
||||||
|
name: 'episode.mp3',
|
||||||
|
mimetype: 'audio/mpeg',
|
||||||
|
size: data.length,
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
voiceListId: 'voice-list-id',
|
||||||
|
categoryId: 'category-id',
|
||||||
|
secondCategoryId: 'second-category-id',
|
||||||
|
description: 'episode description',
|
||||||
|
...overrides,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('voice upload cover', () => {
|
||||||
|
it('uses the uploaded image as the cover for every voice submission', async () => {
|
||||||
|
let uploadedQuery
|
||||||
|
const harness = createVoiceUploadHarness(async (query) => {
|
||||||
|
uploadedQuery = query
|
||||||
|
return { imgId: 'uploaded-cover-id' }
|
||||||
|
})
|
||||||
|
const query = createVoiceUploadQuery({
|
||||||
|
imgFile: {
|
||||||
|
name: 'cover.jpg',
|
||||||
|
mimetype: 'image/jpeg',
|
||||||
|
data: Buffer.from('image'),
|
||||||
|
},
|
||||||
|
coverImgId: 'fallback-cover-id',
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = await voiceUpload(
|
||||||
|
query,
|
||||||
|
harness.request,
|
||||||
|
harness.dependencies,
|
||||||
|
)
|
||||||
|
const voiceCalls = harness.requestCalls.filter((call) =>
|
||||||
|
call.uri.startsWith('/api/voice/workbench/voice/batch/upload'),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.strictEqual(uploadedQuery, query)
|
||||||
|
assert.strictEqual(voiceCalls.length, 2)
|
||||||
|
voiceCalls.forEach((call) => {
|
||||||
|
const [voiceData] = JSON.parse(call.data.voiceData)
|
||||||
|
assert.strictEqual(voiceData.coverImgId, 'uploaded-cover-id')
|
||||||
|
})
|
||||||
|
assert.deepStrictEqual(result, {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
code: 200,
|
||||||
|
data: { voiceId: 'voice-id' },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('keeps using coverImgId when no image file is uploaded', async () => {
|
||||||
|
let uploaded = false
|
||||||
|
const harness = createVoiceUploadHarness(async () => {
|
||||||
|
uploaded = true
|
||||||
|
return { imgId: 'unexpected-cover-id' }
|
||||||
|
})
|
||||||
|
|
||||||
|
await voiceUpload(
|
||||||
|
createVoiceUploadQuery({ coverImgId: 'existing-cover-id' }),
|
||||||
|
harness.request,
|
||||||
|
harness.dependencies,
|
||||||
|
)
|
||||||
|
|
||||||
|
const voiceCalls = harness.requestCalls.filter((call) =>
|
||||||
|
call.uri.startsWith('/api/voice/workbench/voice/batch/upload'),
|
||||||
|
)
|
||||||
|
assert.strictEqual(uploaded, false)
|
||||||
|
voiceCalls.forEach((call) => {
|
||||||
|
const [voiceData] = JSON.parse(call.data.voiceData)
|
||||||
|
assert.strictEqual(voiceData.coverImgId, 'existing-cover-id')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user