mirror of
https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced.git
synced 2026-08-12 08:50:38 +00:00
feat: 新增云小编抽奖接口
This commit is contained in:
parent
20d3a1a7a5
commit
64a6b9677a
24
module/middle_play_do_lottery.js
Normal file
24
module/middle_play_do_lottery.js
Normal file
@ -0,0 +1,24 @@
|
||||
// 云小编每日抽奖
|
||||
//
|
||||
// activityId:
|
||||
// 默认 6501202
|
||||
//
|
||||
// drawCount:
|
||||
// 默认 1
|
||||
//
|
||||
// checkToken:
|
||||
// 易盾反作弊 Token
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
|
||||
module.exports = (query, request) => {
|
||||
const data = {
|
||||
activityId: query.activityId || '6501202',
|
||||
drawCount: query.drawCount || '1',
|
||||
}
|
||||
return request(
|
||||
`/api/middle/play/do/lottery`,
|
||||
data,
|
||||
createOption(query, 'eapi', 'v3'),
|
||||
)
|
||||
}
|
||||
17
module/middle_play_lottery_remain_chance.js
Normal file
17
module/middle_play_lottery_remain_chance.js
Normal file
@ -0,0 +1,17 @@
|
||||
// 云小编抽奖剩余次数查询
|
||||
//
|
||||
// activityId:
|
||||
// 默认 6501202
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
|
||||
module.exports = (query, request) => {
|
||||
const data = {
|
||||
activityId: query.activityId || '6501202',
|
||||
}
|
||||
return request(
|
||||
`/api/middle/play/lottery/remain/chance`,
|
||||
data,
|
||||
createOption(query, 'eapi'),
|
||||
)
|
||||
}
|
||||
28
module/rep_ugc_exam_info_get.js
Normal file
28
module/rep_ugc_exam_info_get.js
Normal file
@ -0,0 +1,28 @@
|
||||
// 云小编考试状态
|
||||
//
|
||||
// examType:
|
||||
// 1. 歌曲曲风审核: musicalStyleEnter
|
||||
// 2. 歌曲语种审核: languageEnter
|
||||
// 3. 歌曲原唱审核: oriSingerEnter
|
||||
// 4. 情绪标签审核: emotionEnter
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
|
||||
module.exports = (query, request) => {
|
||||
if (!query.examType)
|
||||
return Promise.reject({
|
||||
status: 400,
|
||||
body: {
|
||||
code: 400,
|
||||
message: '参数不足',
|
||||
},
|
||||
})
|
||||
const data = {
|
||||
examType: query.examType,
|
||||
}
|
||||
return request(
|
||||
'/api/rep/ugc/exam/info/get',
|
||||
data,
|
||||
createOption(query, 'eapi'),
|
||||
)
|
||||
}
|
||||
32
module/rep_ugc_exam_question_single_get.js
Normal file
32
module/rep_ugc_exam_question_single_get.js
Normal file
@ -0,0 +1,32 @@
|
||||
// 云小编考试取题
|
||||
//
|
||||
// examType:
|
||||
// 1. 歌曲曲风审核: musicalStyleEnter
|
||||
// 2. 歌曲语种审核: languageEnter
|
||||
// 3. 歌曲原唱审核: oriSingerEnter
|
||||
// 4. 情绪标签审核: emotionEnter
|
||||
//
|
||||
// taskId:
|
||||
// 首次调用 rep/ugc/exam/start 获取,之后调用 rep/ugc/exam/info/get 获取
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
|
||||
module.exports = (query, request) => {
|
||||
if (!query.examType || !query.taskId)
|
||||
return Promise.reject({
|
||||
status: 400,
|
||||
body: {
|
||||
code: 400,
|
||||
message: '参数不足',
|
||||
},
|
||||
})
|
||||
const data = {
|
||||
examType: query.examType,
|
||||
taskId: query.taskId,
|
||||
}
|
||||
return request(
|
||||
'/api/rep/ugc/exam/question/single/get',
|
||||
data,
|
||||
createOption(query, 'eapi'),
|
||||
)
|
||||
}
|
||||
32
module/rep_ugc_exam_result_get.js
Normal file
32
module/rep_ugc_exam_result_get.js
Normal file
@ -0,0 +1,32 @@
|
||||
// 云小编考试结果
|
||||
//
|
||||
// examType:
|
||||
// 1. 歌曲曲风审核: musicalStyleEnter
|
||||
// 2. 歌曲语种审核: languageEnter
|
||||
// 3. 歌曲原唱审核: oriSingerEnter
|
||||
// 4. 情绪标签审核: emotionEnter
|
||||
//
|
||||
// taskId:
|
||||
// 首次调用 rep/ugc/exam/start 获取,之后调用 rep/ugc/exam/info/get 获取
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
|
||||
module.exports = (query, request) => {
|
||||
if (!query.examType || !query.taskId)
|
||||
return Promise.reject({
|
||||
status: 400,
|
||||
body: {
|
||||
code: 400,
|
||||
message: '参数不足',
|
||||
},
|
||||
})
|
||||
const data = {
|
||||
examType: query.examType,
|
||||
taskId: query.taskId,
|
||||
}
|
||||
return request(
|
||||
'/api/rep/ugc/exam/result/get',
|
||||
data,
|
||||
createOption(query, 'eapi'),
|
||||
)
|
||||
}
|
||||
24
module/rep_ugc_exam_start.js
Normal file
24
module/rep_ugc_exam_start.js
Normal file
@ -0,0 +1,24 @@
|
||||
// 云小编考试开始
|
||||
//
|
||||
// examType:
|
||||
// 1. 歌曲曲风审核: musicalStyleEnter
|
||||
// 2. 歌曲语种审核: languageEnter
|
||||
// 3. 歌曲原唱审核: oriSingerEnter
|
||||
// 4. 情绪标签审核: emotionEnter
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
|
||||
module.exports = (query, request) => {
|
||||
if (!query.examType)
|
||||
return Promise.reject({
|
||||
status: 400,
|
||||
body: {
|
||||
code: 400,
|
||||
message: '参数不足',
|
||||
},
|
||||
})
|
||||
const data = {
|
||||
examType: query.examType,
|
||||
}
|
||||
return request('/api/rep/ugc/exam/start', data, createOption(query, 'eapi'))
|
||||
}
|
||||
36
module/rep_ugc_exam_submit.js
Normal file
36
module/rep_ugc_exam_submit.js
Normal file
@ -0,0 +1,36 @@
|
||||
// 云小编考试提交
|
||||
//
|
||||
// examType:
|
||||
// 1. 歌曲曲风审核: musicalStyleEnter
|
||||
// 2. 歌曲语种审核: languageEnter
|
||||
// 3. 歌曲原唱审核: oriSingerEnter
|
||||
// 4. 情绪标签审核: emotionEnter
|
||||
//
|
||||
// taskId:
|
||||
// 首次调用 rep/ugc/exam/start 获取,之后调用 rep/ugc/exam/info/get 获取
|
||||
//
|
||||
// questionId:
|
||||
// 调用 rep/ugc/exam/question/single/get 获取
|
||||
//
|
||||
// answer:
|
||||
// A 对, B 错
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
|
||||
module.exports = (query, request) => {
|
||||
if (!query.examType || !query.taskId || !query.questionId || !query.answer)
|
||||
return Promise.reject({
|
||||
status: 400,
|
||||
body: {
|
||||
code: 400,
|
||||
message: '参数不足',
|
||||
},
|
||||
})
|
||||
const data = {
|
||||
examType: query.examType,
|
||||
taskId: query.taskId,
|
||||
questionId: query.questionId,
|
||||
answer: query.answer,
|
||||
}
|
||||
return request('/api/rep/ugc/exam/submit', data, createOption(query, 'eapi'))
|
||||
}
|
||||
@ -5604,6 +5604,104 @@ let data = encodeURIComponent(
|
||||
|
||||
**调用例子:** `/rep/ugc/user/collect-vip?activityId=5001`
|
||||
|
||||
### 云小编 - 剩余抽奖次数
|
||||
|
||||
说明: 登录后调用此接口, 获取今日云小编抽奖剩余次数
|
||||
|
||||
**可选参数:**
|
||||
|
||||
`activityId`: 活动 ID, 默认 `6501202`
|
||||
|
||||
**接口地址:** `/middle/play/lottery/remain/chance`
|
||||
|
||||
**调用例子:** `/middle/play/lottery/remain/chance?activityId=6501202`
|
||||
|
||||
### 云小编 - 每日抽奖
|
||||
|
||||
说明: 登录后调用此接口, 消耗 200 积分进行抽奖, 每日最多抽 3 次
|
||||
|
||||
> 注意: 抽奖失败也消耗每日次数, 请先调用 `/rep/ugc/user/get` 查询可用积分
|
||||
|
||||
**可选参数:**
|
||||
|
||||
`activityId`: 活动 ID, 默认 `6501202`
|
||||
|
||||
`drawCount`: 未知, 默认 `1`
|
||||
|
||||
**接口地址:** `/middle/play/do/lottery`
|
||||
|
||||
**调用例子:** `/middle/play/do/lottery?activityId=6501202&drawCount=1`
|
||||
|
||||
### 云小编 - 考试状态
|
||||
|
||||
说明: 登录后调用此接口, 查询云小编入站考试状态, 进度 (`data.process`): no: 未开始, process: 考试中, whole_exam_end: 已结束, 此时可检查 `data.hasPassExamination` 确认是否通过
|
||||
|
||||
**必选参数:**
|
||||
|
||||
`examType`: 考试类型, musicalStyleEnter: 歌曲曲风审核, languageEnter: 歌曲语种审核, oriSingerEnter: 歌曲原唱审核, emotionEnter: 情绪标签审核
|
||||
|
||||
**接口地址:** `/rep/ugc/exam/info/get`
|
||||
|
||||
**调用例子:** `/rep/ugc/exam/info/get?examType=emotionEnter`
|
||||
|
||||
### 云小编 - 考试开始
|
||||
|
||||
说明: 未考试或考试失败时调用此接口, 开始新一轮考试
|
||||
|
||||
**必选参数:**
|
||||
|
||||
`examType`: 考试类型, musicalStyleEnter: 歌曲曲风审核, languageEnter: 歌曲语种审核, oriSingerEnter: 歌曲原唱审核, emotionEnter: 情绪标签审核
|
||||
|
||||
**接口地址:** `/rep/ugc/exam/start`
|
||||
|
||||
**调用例子:** `/rep/ugc/exam/start?examType=emotionEnter`
|
||||
|
||||
### 云小编 - 考试取题
|
||||
|
||||
说明: 考试开始后调用此接口, 获取当前试题
|
||||
|
||||
**必选参数:**
|
||||
|
||||
`examType`: 考试类型, musicalStyleEnter: 歌曲曲风审核, languageEnter: 歌曲语种审核, oriSingerEnter: 歌曲原唱审核, emotionEnter: 情绪标签审核
|
||||
|
||||
`taskId`: 任务 ID, 首次调用 `/rep/ugc/exam/start` 获取,之后调用 `/rep/ugc/exam/info/get` 获取
|
||||
|
||||
**接口地址:** `/rep/ugc/exam/question/single/get`
|
||||
|
||||
**调用例子:** `/rep/ugc/exam/question/single/get?examType=emotionEnter&taskId=123456`
|
||||
|
||||
### 云小编 - 考试提交
|
||||
|
||||
说明: 取题后调用此接口, 提交作答结果
|
||||
|
||||
**必选参数:**
|
||||
|
||||
`examType`: 考试类型, musicalStyleEnter: 歌曲曲风审核, languageEnter: 歌曲语种审核, oriSingerEnter: 歌曲原唱审核, emotionEnter: 情绪标签审核
|
||||
|
||||
`taskId`: 任务 ID, 首次调用 `/rep/ugc/exam/start` 获取,之后调用 `/rep/ugc/exam/info/get` 获取
|
||||
|
||||
`questionId`: 试题 ID, 调用 `/rep/ugc/exam/question/single/get` 获取
|
||||
|
||||
`answer`: 判断结果, A: 对, B: 错
|
||||
|
||||
**接口地址:** `/rep/ugc/exam/submit`
|
||||
|
||||
**调用例子:** `/rep/ugc/exam/submit?examType=emotionEnter&taskId=123456&questionId=123456&answer=A`
|
||||
|
||||
### 云小编 - 考试结果
|
||||
|
||||
说明: 考试结束后调用此接口, 获取考试正确数与正确率
|
||||
|
||||
**必选参数:**
|
||||
|
||||
`examType`: 考试类型, musicalStyleEnter: 歌曲曲风审核, languageEnter: 歌曲语种审核, oriSingerEnter: 歌曲原唱审核, emotionEnter: 情绪标签审核
|
||||
|
||||
`taskId`: 任务 ID, 首次调用 `/rep/ugc/exam/start` 获取,之后调用 `/rep/ugc/exam/info/get` 获取
|
||||
|
||||
**接口地址:** `/rep/ugc/exam/result/get`
|
||||
|
||||
**调用例子:** `/rep/ugc/exam/result/get?examType=emotionEnter&taskId=123456`
|
||||
|
||||
### 发送/删除评论
|
||||
|
||||
说明 : 调用此接口,可发送评论或者删除评论
|
||||
|
||||
@ -99,7 +99,8 @@ curl -s {origin}/search?keywords=网易云</code></pre>
|
||||
<a href="/scrobble.html">听歌打卡示例</a> ·
|
||||
<a href="/yidun.html">获取 CheckToken</a> ·
|
||||
<a href="/free_listen.html">免费听权益</a> ·
|
||||
<a href="/ugc.html">云小编任务中心</a>
|
||||
<a href="/ugc.html">云小编任务中心</a> ·
|
||||
<a href="/ugc_lottery.html">云小编每日抽奖</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
212
public/ugc.html
212
public/ugc.html
@ -4,6 +4,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<link rel="icon" href="docs/netease.png" />
|
||||
<title>云小编任务中心 - 网易云音乐 API Enhanced</title>
|
||||
<style>
|
||||
* {
|
||||
@ -325,6 +327,10 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#userPoint {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* ---- Song Review ---- */
|
||||
.song-review {
|
||||
margin-top: 16px;
|
||||
@ -377,7 +383,7 @@
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e5e7eb;
|
||||
white-space: pre-wrap;
|
||||
max-height: 120px;
|
||||
max-height: 160px;
|
||||
overflow-y: auto;
|
||||
font-family: monospace;
|
||||
line-height: 1.6;
|
||||
@ -404,6 +410,8 @@
|
||||
<div class="quick-fill">
|
||||
<span onclick="loadLocalCookie()">读取本地 Cookie</span>
|
||||
<span onclick="clearCookie()">清除 Cookie</span>
|
||||
<span onclick="saveCookie()">保存 Cookie</span>
|
||||
<span onclick="loadAll()">一键载入</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -413,8 +421,9 @@
|
||||
<button class="btn-sm" id="userGet">获取用户详情</button>
|
||||
</div>
|
||||
<div class="info-card" id="user">
|
||||
<img alt="头像" class="avatar" id="userAvatar" />
|
||||
<img src="docs/netease.png" alt="头像" class="avatar" id="userAvatar" />
|
||||
<span class="info-text" id="userName">未登录</span>
|
||||
<span id="userPoint">0</span>
|
||||
<button class="btn-sm green" id="userSign" disabled>签到</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -436,7 +445,7 @@
|
||||
<button class="btn-sm" id="taskGet">获取任务详情</button>
|
||||
</div>
|
||||
<div class="info-card" id="task">
|
||||
<span class="info-text" id="taskInfo">[<span id="taskCount">0</span>/<span id="taskGoal">0</span>] 审核歌曲赚贡献分 (+66)</span>
|
||||
<span class="info-text" id="taskInfo">[<span id="taskCount">0</span>/<span id="taskGoal">0</span>] 审核歌曲赚贡献分 (+<span id="taskPoint">0</span>)</span>
|
||||
<button class="btn-sm" id="taskStart" disabled>去完成</button>
|
||||
</div>
|
||||
|
||||
@ -462,7 +471,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var k_cookie = 'ncm_cookie';
|
||||
@ -496,6 +504,12 @@
|
||||
showResult('已清除本地 Cookie', 'info');
|
||||
};
|
||||
|
||||
window.saveCookie = function () {
|
||||
var ck = getCookie()
|
||||
if (ck) localStorage.setItem(k_cookie, ck)
|
||||
showResult('已保存本地 Cookie', 'info');
|
||||
}
|
||||
|
||||
/* ---- Result ---- */
|
||||
function hideResult() {
|
||||
resultDiv.style.display = 'none';
|
||||
@ -547,6 +561,7 @@
|
||||
/// 用户信息 ///
|
||||
var userAvatar = document.getElementById('userAvatar');
|
||||
var userName = document.getElementById('userName');
|
||||
var userPoint = document.getElementById('userPoint');
|
||||
var userSign = document.getElementById('userSign');
|
||||
var userGet = document.getElementById('userGet');
|
||||
|
||||
@ -555,6 +570,7 @@
|
||||
var d = res.data;
|
||||
if (d.imgUrl) userAvatar.src = d.imgUrl + '?param=50y50';
|
||||
userName.textContent = d.name || '游客' + d.id;
|
||||
userPoint.textContent = d.availablePoints;
|
||||
userSign.disabled = d.signed;
|
||||
showResult('用户信息加载成功', 'success');
|
||||
});
|
||||
@ -567,6 +583,7 @@
|
||||
var d = res.data;
|
||||
userSign.disabled = d;
|
||||
showResult('签到' + (d ? '成功 ✓' : '失败 ✗'), d ? 'success' : 'error');
|
||||
if (d) userGet.click();
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -599,21 +616,36 @@
|
||||
/// 审核任务 ///
|
||||
var taskCount = document.getElementById('taskCount');
|
||||
var taskGoal = document.getElementById('taskGoal');
|
||||
var taskPoint = document.getElementById('taskPoint');
|
||||
var taskStartBtn = document.getElementById('taskStart');
|
||||
var taskGetBtn = document.getElementById('taskGet');
|
||||
|
||||
taskGetBtn.onclick = function () {
|
||||
request('/rep/ugc/activity/get', {}, function (res) {
|
||||
request('rep/ugc/activity/get', {}, function (res) {
|
||||
var d = res.data;
|
||||
taskCount.textContent = d.count;
|
||||
taskGoal.textContent = d.goalCount;
|
||||
taskStartBtn.disabled = d.activityId !== 5001;
|
||||
taskPoint.textContent = d.points;
|
||||
// 注:超过 100 可能有 bug,导致 thinktank/audit/resource/detail 获取的任务 ID 一直是 5002,目标数量也不对,暂时限制前两个任务
|
||||
taskStartBtn.disabled = d.activityId !== 5001 && d.activityId !== 5002; // 5001: 1-50, 5002: 51-100
|
||||
showResult('任务详情加载成功', 'success');
|
||||
// 任务完成,领取积分
|
||||
if (d.count >= d.goalCount) {
|
||||
request('rep/ugc/activity/collect', { activityId: d.activityId }, function (res) {
|
||||
var d = res.data;
|
||||
showResult('积分领取' + (d ? '成功,可以兑换会员了!' : '失败'), d ? 'success' : 'error');
|
||||
// 刷新任务状态
|
||||
if (d) loadAll();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
taskStartBtn.onclick = function () {
|
||||
if (!taskStartBtn.disabled) taskStart();
|
||||
if (!taskStartBtn.disabled) {
|
||||
songAudio.pause();
|
||||
examInit();
|
||||
}
|
||||
};
|
||||
|
||||
/// 歌曲审核区域 ///
|
||||
@ -627,26 +659,113 @@
|
||||
var songSkip = document.getElementById('songSkip');
|
||||
var songLyric = document.getElementById('songLyric');
|
||||
|
||||
var examType = 'emotionEnter';
|
||||
// 考试初始化、检查状态
|
||||
function examInit() {
|
||||
request('rep/ugc/exam/info/get', { examType: examType }, function (res) {
|
||||
var d = res.data;
|
||||
switch (d.process) {
|
||||
case 'whole_exam_end':
|
||||
// 通过考试
|
||||
if (d.hasPassExamination) {
|
||||
taskStart();
|
||||
return;
|
||||
}
|
||||
request('rep/ugc/exam/result/get', { examType: examType, taskId: d.taskId }, function (res) {
|
||||
var d = res.data;
|
||||
showResult('考试未通过:正确数:' + d.wrightNum + ',正确率:' + d.wrightRate, 'error');
|
||||
});
|
||||
case 'no':
|
||||
// 未通过或未开始
|
||||
request('rep/ugc/exam/start', { examType: examType }, function (res) {
|
||||
examStart(res.data.taskId);
|
||||
});
|
||||
break;
|
||||
case 'process':
|
||||
// 正在考试
|
||||
examStart(d.taskId);
|
||||
break;
|
||||
default:
|
||||
showResult('未知考试进度:' + d.process, 'error');
|
||||
return;
|
||||
}
|
||||
// 在上个信息不为异常时显示考试中提示
|
||||
if (d.process !== 'whole_exam_end' && !resultDiv.className.endsWith('error')) showResult('正在考试,请认真审题!', 'info');
|
||||
});
|
||||
}
|
||||
// 考试取题开始
|
||||
function examStart(taskId) {
|
||||
request('rep/ugc/exam/question/single/get', { examType: examType, taskId: taskId }, function (res) {
|
||||
var d = res.data;
|
||||
|
||||
songCover.src = d.coverUrl + '?param=100y100';
|
||||
songTitle.textContent = '[' + d.resId + '] ' + d.resName + ' - ' + d.artists;
|
||||
songTag.textContent = '[' + (d.auditCount + 1) + '/' + d.auditTaskCount + '] 当前结果: ' + d.questionContent + '?';
|
||||
songLyric.textContent = d.lyric ? d.transLyric ? lrctran(d.lyric, d.transLyric) : d.lyric : '[00:00.00] 暂无歌词';
|
||||
songLyric.scrollTop = 0;
|
||||
// 有时候就是没有播放链接,只能根据歌词硬审
|
||||
if (d.songUrl) {
|
||||
songAudio.src = d.songUrl;
|
||||
songAudio.play().catch(function () { /* 自动播放被浏览器阻止 */ });
|
||||
} else {
|
||||
songAudio.src = ''; // 清理播放器
|
||||
}
|
||||
songApprove.onclick = function () { if (!songApprove.disabled) examSubmit(taskId, d.questionId, 'A') };
|
||||
songReject.onclick = function () { if (!songReject.disabled) examSubmit(taskId, d.questionId, 'B') };
|
||||
songSkip.onclick = null;
|
||||
|
||||
songApprove.disabled = songReject.disabled = songSkip.disabled = true;
|
||||
song.style.display = 'block';
|
||||
|
||||
setTimeout(function () {
|
||||
songApprove.disabled = songReject.disabled = false;
|
||||
}, 3e3);
|
||||
});
|
||||
}
|
||||
// 考试提交
|
||||
function examSubmit(taskId, questionId, answer) {
|
||||
songAudio.pause();
|
||||
songApprove.disabled = songReject.disabled = songSkip.disabled = true;
|
||||
request(
|
||||
'rep/ugc/exam/submit',
|
||||
{ examType: examType, taskId: taskId, questionId: questionId, answer: answer },
|
||||
function (res) {
|
||||
var d = res.data;
|
||||
if (d.result) {
|
||||
showResult('回答正确,加载下一首...', 'success');
|
||||
} else {
|
||||
showResult('回答错误:' + d.analysis, 'error');
|
||||
}
|
||||
examInit();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务
|
||||
*/
|
||||
function taskStart() {
|
||||
song.style.display = 'none';
|
||||
request('/thinktank/audit/resource/detail', { type: '4' }, function (res) {
|
||||
request('thinktank/audit/resource/detail', { type: '4' }, function (res) {
|
||||
var d = res.data;
|
||||
|
||||
if (d.auditCount >= 50) {
|
||||
request('rep/ugc/activity/collect', { activityId: '5001' }, function (res) {
|
||||
showResult('积分领取' + (res.data ? '成功,可以兑换会员了!' : '失败'), res.data ? 'success' : 'error');
|
||||
});
|
||||
if (d.auditCount >= Number(taskGoal.textContent)) {
|
||||
showResult('当前阶段任务已完成', 'success');
|
||||
song.style.display = 'none';
|
||||
taskGetBtn.click();
|
||||
return;
|
||||
}
|
||||
// 没有链接,直接跳过
|
||||
if (!d.songUrl) {
|
||||
showResult('未返回播放链接,3 秒后自动跳过', 'info');
|
||||
setTimeout(function () { taskSubmit(d.taskId, '3') }, 3e3);
|
||||
return;
|
||||
}
|
||||
|
||||
songCover.src = d.coverUrl + '?param=100y100';
|
||||
songTitle.textContent = '[' + d.resId + '] ' + d.resName + ' - ' + d.artists;
|
||||
songTag.textContent = '[' + (d.auditCount + 1) + '/' + d.auditTaskCount + '] 当前结果: ' + d.initResult + '?';
|
||||
songLyric.textContent = (d.lyric || '') + (d.transLyric || '');
|
||||
if (!songLyric.textContent.trim()) songLyric.textContent = '[00:00.00] 暂无歌词';
|
||||
songLyric.textContent = d.lyric ? d.transLyric ? lrctran(d.lyric, d.transLyric) : d.lyric : '[00:00.00] 暂无歌词';
|
||||
songLyric.scrollTop = 0;
|
||||
songAudio.src = d.songUrl;
|
||||
songAudio.play().catch(function () { /* 自动播放被浏览器阻止 */ });
|
||||
|
||||
@ -669,6 +788,7 @@
|
||||
* @param {'1'|'2'|'3'} judgement
|
||||
*/
|
||||
function taskSubmit(taskId, judgement) {
|
||||
songAudio.pause();
|
||||
songApprove.disabled = songReject.disabled = songSkip.disabled = true;
|
||||
request(
|
||||
'thinktank/audit/resource/update',
|
||||
@ -676,7 +796,10 @@
|
||||
function (res) {
|
||||
var d = res.data;
|
||||
if (d.result) {
|
||||
showResult('审核提交成功,加载下一首...', 'success');
|
||||
showResult((d.test ? '抽查' : '审核') + '提交成功,加载下一首...', 'success');
|
||||
taskStart();
|
||||
} else if (d.test) {
|
||||
showResult('抽查失败: [' + d.dayErrorNum + '/' + d.dayErrorNumStandard + '] ' + d.errorAnalysis, 'error');
|
||||
taskStart();
|
||||
} else {
|
||||
showResult('未知状态: ' + JSON.stringify(d), 'error');
|
||||
@ -685,8 +808,63 @@
|
||||
);
|
||||
}
|
||||
|
||||
/**解析歌词时间戳和文本 */
|
||||
function lrctrim(lyrics) {
|
||||
var lines = lyrics.split('\n');
|
||||
var data = [];
|
||||
lines.forEach(function (line, index) {
|
||||
var matches = line.match(/\[(\d{2}):(\d{2}[\.:]?\d*)\]/);
|
||||
if (matches) {
|
||||
var minutes = parseInt(matches[1], 10);
|
||||
var seconds = parseFloat(matches[2].replace('.', ':')) || 0;
|
||||
var timestamp = minutes * 6e4 + seconds * 1e3;
|
||||
var text = line.replace(/\[\d{2}:\d{2}[\.:]?\d*\]/g, '').trim();
|
||||
text = text.replace(/\s\s+/g, ' '); // Replace multiple spaces with a single space
|
||||
data.push([timestamp, index, text]);
|
||||
}
|
||||
});
|
||||
data.sort(function (a, b) {
|
||||
return a[0] - b[0];
|
||||
});
|
||||
return data;
|
||||
}
|
||||
/**合并原文歌词和翻译歌词 */
|
||||
function lrctran(lyric, tlyric) {
|
||||
lyric = lrctrim(lyric);
|
||||
tlyric = lrctrim(tlyric);
|
||||
var len1 = lyric.length;
|
||||
var len2 = tlyric.length;
|
||||
var result = '';
|
||||
for (var i = 0, j = 0; i < len1 && j < len2; i++) {
|
||||
while (lyric[i][0] > tlyric[j][0] && j + 1 < len2) {
|
||||
j++;
|
||||
}
|
||||
if (lyric[i][0] === tlyric[j][0]) {
|
||||
tlyric[j][2] = tlyric[j][2].replace('/', '');
|
||||
if (tlyric[j][2]) {
|
||||
lyric[i][2] += ' (' + tlyric[j][2] + ')';
|
||||
}
|
||||
j++;
|
||||
}
|
||||
}
|
||||
for (var k = 0; k < len1; k++) {
|
||||
var t = lyric[k][0];
|
||||
result += '['.concat(
|
||||
String(Math.floor(t / 6e4)).padStart(2, '0'),
|
||||
':',
|
||||
String(Math.floor((t % 6e4) / 1e3)).padStart(2, '0'),
|
||||
'.',
|
||||
String(t % 1e3).padStart(3, '0'),
|
||||
']',
|
||||
lyric[k][2],
|
||||
'\n',
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// 一键载入 ///
|
||||
document.getElementById('loadall').onclick = function () {
|
||||
window.loadAll = function () {
|
||||
userGet.click();
|
||||
vipGet.click();
|
||||
taskGetBtn.click();
|
||||
|
||||
506
public/ugc_lottery.html
Normal file
506
public/ugc_lottery.html
Normal file
@ -0,0 +1,506 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes">
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<link rel="icon" href="docs/netease.png" />
|
||||
<title>云小编每日抽奖 - 网易云音乐 API Enhanced</title>
|
||||
<style>
|
||||
:root {
|
||||
--gold: #fdcb6e;
|
||||
--text-light: #636e72;
|
||||
--fg: #333;
|
||||
--muted: #666;
|
||||
--border: #ddd;
|
||||
--bg: #f5f5f5;
|
||||
--panel: #ffffff;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', system-ui, -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
/* align-items: center; */
|
||||
justify-content: center;
|
||||
padding: 1.5rem;
|
||||
color: var(--fg);
|
||||
background: var(--bg);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
/* 标题 */
|
||||
.header h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header p {
|
||||
margin-top: 4px;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 卡片 */
|
||||
.card {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 按钮 */
|
||||
button {
|
||||
padding: 8px 18px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
background: var(--panel);
|
||||
color: var(--fg);
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
button.primary {
|
||||
background: var(--fg);
|
||||
color: var(--panel);
|
||||
border-color: var(--fg);
|
||||
}
|
||||
|
||||
button.primary:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* 页脚 */
|
||||
.footer {
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--border);
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: var(--fg);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
border-bottom: 1px dotted var(--fg);
|
||||
}
|
||||
|
||||
.rolling-display {
|
||||
font-size: 2.4rem;
|
||||
font-weight: 800;
|
||||
color: #c4b5fd;
|
||||
min-height: 4rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
letter-spacing: 1px;
|
||||
margin: 0.5rem 0 1rem;
|
||||
word-break: break-all;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.rolling-display.idle {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 结果展示 */
|
||||
.winner-display {
|
||||
background: linear-gradient(135deg, #fff9e6, #fff3cd);
|
||||
border: 2px solid var(--gold);
|
||||
border-radius: var(--radius);
|
||||
padding: 1.5rem;
|
||||
margin: 1rem 0 1.5rem;
|
||||
animation: popIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.winner-label {
|
||||
font-size: 0.85rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.5px;
|
||||
color: #b8860b;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.winner-name {
|
||||
font-size: 2.2rem;
|
||||
font-weight: 800;
|
||||
color: #d63031;
|
||||
letter-spacing: 1px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.winner-img {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
margin: -20px;
|
||||
}
|
||||
|
||||
.confetti-row {
|
||||
margin-top: 0.5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.confetti-emoji {
|
||||
font-size: 1.8rem;
|
||||
animation: bounce 0.6s ease infinite alternate;
|
||||
}
|
||||
|
||||
.confetti-emoji:nth-child(2) {
|
||||
animation-delay: 0.15s;
|
||||
}
|
||||
|
||||
.confetti-emoji:nth-child(3) {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
@keyframes popIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.7);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
from {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 状态提示 */
|
||||
.status-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-light);
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #00b894;
|
||||
animation: pulse 1.5s ease infinite;
|
||||
}
|
||||
|
||||
.status-dot.error {
|
||||
background: #e17055;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 500px) {
|
||||
.rolling-display {
|
||||
font-size: 1.8rem;
|
||||
min-height: 3rem;
|
||||
}
|
||||
|
||||
.winner-name {
|
||||
font-size: 1.7rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- 标题 -->
|
||||
<div class="header">
|
||||
<h1>🎉 云小编每日抽奖</h1>
|
||||
<p>每次抽奖消耗 <b>200 个曲库编辑部积分</b>,每日最多可抽奖 3 次</p>
|
||||
</div>
|
||||
<!-- 抽奖 -->
|
||||
<div class="card">
|
||||
<div class="rolling-display idle" id="rollingDisplay">等待抽奖</div>
|
||||
<div id="resultContainer"></div>
|
||||
<button id="lotteryBtn" class="primary">
|
||||
<span>🎲</span> 开始抽奖
|
||||
</button>
|
||||
<button id="resetBtn">
|
||||
<span>🔄</span> 编辑令牌
|
||||
</button>
|
||||
<div class="status-bar" id="statusBar">
|
||||
<span class="status-dot" id="statusDot"></span>
|
||||
<span id="statusText">就绪,点击按钮开始抽奖</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 页脚 -->
|
||||
<div class="footer">
|
||||
<a href="/">← 返回首页</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
// ============ DOM 元素 ============
|
||||
const rollingDisplay = document.getElementById('rollingDisplay');
|
||||
const resultContainer = document.getElementById('resultContainer');
|
||||
const lotteryBtn = document.getElementById('lotteryBtn');
|
||||
const resetBtn = document.getElementById('resetBtn');
|
||||
const statusDot = document.getElementById('statusDot');
|
||||
const statusText = document.getElementById('statusText');
|
||||
|
||||
// ============ 状态 ============
|
||||
let isDrawing = false;
|
||||
let rollingTimer = null;
|
||||
|
||||
// ============ 状态栏更新 ============
|
||||
function setStatus(type, message) {
|
||||
statusText.textContent = message;
|
||||
statusDot.className = 'status-dot';
|
||||
if (type === 'error') {
|
||||
statusDot.classList.add('error');
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 重置结果 ============
|
||||
function resetResult() {
|
||||
resultContainer.innerHTML = '';
|
||||
rollingDisplay.textContent = '等待抽奖';
|
||||
rollingDisplay.className = 'rolling-display idle';
|
||||
setStatus('info', '就绪,点击按钮开始抽奖');
|
||||
}
|
||||
|
||||
// ============ 本地令牌管理 ============
|
||||
const cookieKey = 'ncm_cookie';
|
||||
let cookie = localStorage.getItem(cookieKey) || '';
|
||||
function setCookie() {
|
||||
const ck = prompt('请输入令牌:', cookie);
|
||||
if (ck !== null) {
|
||||
cookie = ck;
|
||||
localStorage.setItem(cookieKey, cookie);
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 调用后端接口 ============
|
||||
async function request(url, params) {
|
||||
try {
|
||||
url += '?timestamp=' + Date.now() + params;
|
||||
if (cookie) url += '&cookie=' + encodeURIComponent(cookie);
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
try {
|
||||
var res = await response.json();
|
||||
} catch (error) {
|
||||
console.error('数据解析错误:', error);
|
||||
throw new Error('接口请求错误: ' + response.status + ': ' + response.statusText);
|
||||
}
|
||||
|
||||
if (res.code !== 200) {
|
||||
throw new Error('接口返回错误: ' + res.code + ': ' + (res.message || '未知错误'));
|
||||
}
|
||||
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.error('接口请求失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchLotteryResult() {
|
||||
const user = await request('rep/ugc/user/get', '');
|
||||
|
||||
if ((user.data?.availablePoints ?? 0) < 200) {
|
||||
throw new Error('用户剩余积分不足,请先完成任务获取积分');
|
||||
}
|
||||
|
||||
const chance = await request('middle/play/lottery/remain/chance', '&activityId=6501202');
|
||||
|
||||
if (chance.data === 0) {
|
||||
throw new Error('今日机会已用完,明天再来吧');
|
||||
}
|
||||
|
||||
const res = await request('middle/play/do/lottery', '&activityId=6501202&drawCount=1');
|
||||
|
||||
// 根据你的后端返回格式提取中奖者名称
|
||||
const prizeMap = res.data?.prizeDetailInfoMap || {};
|
||||
for (var k in prizeMap) break; // 取第一个奖品 ID
|
||||
const prize = k ? prizeMap[k] : null;
|
||||
if (!prize) {
|
||||
throw new Error('无法解析响应数据');
|
||||
}
|
||||
|
||||
// [抽奖结果, 剩余机会, 奖品图片]
|
||||
return [prize.prizeName, res.data.restChance, prize.prizeImgList[0] || ''];
|
||||
}
|
||||
|
||||
// ============ 滚动动画 ============
|
||||
function startRollingAnimation(finalWinner, img) {
|
||||
const placeholderNames = ['🎰', '🎲', '🎯', '✨', '💫', '🌟', '🎪', '🎭'];
|
||||
const totalDuration = 2000;
|
||||
const startInterval = 50;
|
||||
const endInterval = 280;
|
||||
const startTime = Date.now();
|
||||
|
||||
rollingDisplay.className = 'rolling-display';
|
||||
resultContainer.innerHTML = '';
|
||||
|
||||
function roll() {
|
||||
const elapsed = Date.now() - startTime;
|
||||
const progress = Math.min(elapsed / totalDuration, 1.0);
|
||||
const easeOutProgress = 1 - Math.pow(1 - progress, 3);
|
||||
const currentInterval = startInterval + (endInterval - startInterval) * easeOutProgress;
|
||||
|
||||
// 随机显示占位符
|
||||
const randomPlaceholder = placeholderNames[Math.floor(Math.random() * placeholderNames.length)];
|
||||
rollingDisplay.textContent = randomPlaceholder;
|
||||
|
||||
if (progress >= 1.0) {
|
||||
// 动画结束,显示真实结果
|
||||
clearTimeout(rollingTimer);
|
||||
rollingDisplay.textContent = finalWinner;
|
||||
rollingDisplay.style.display = 'none';
|
||||
showWinnerResult(finalWinner, img);
|
||||
finishDrawing();
|
||||
return;
|
||||
}
|
||||
|
||||
rollingTimer = setTimeout(roll, currentInterval);
|
||||
}
|
||||
|
||||
roll();
|
||||
}
|
||||
|
||||
function showWinnerResult(winner, img) {
|
||||
resultContainer.innerHTML = `
|
||||
<div class="winner-display">
|
||||
<div class="winner-label">🏆 恭喜中奖</div>
|
||||
<div class="winner-name">${escapeHTML(winner)}</div>
|
||||
<img src="${img}" alt="奖品图片" class="winner-img" />
|
||||
<div class="confetti-row">
|
||||
<span class="confetti-emoji">🎉</span>
|
||||
<span class="confetti-emoji">🎊</span>
|
||||
<span class="confetti-emoji">✨</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function escapeHTML(str) {
|
||||
const div = document.createElement('div');
|
||||
div.appendChild(document.createTextNode(str));
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function finishDrawing() {
|
||||
isDrawing = false;
|
||||
lotteryBtn.disabled = false;
|
||||
resetBtn.disabled = false;
|
||||
// setStatus('info', '抽奖完成!点击重置可再次抽奖');
|
||||
}
|
||||
|
||||
// ============ 主抽奖流程 ============
|
||||
async function startLottery() {
|
||||
if (isDrawing) return;
|
||||
|
||||
// 开始前状态
|
||||
isDrawing = true;
|
||||
lotteryBtn.disabled = true;
|
||||
resetBtn.disabled = true;
|
||||
resultContainer.innerHTML = '';
|
||||
rollingDisplay.className = 'rolling-display';
|
||||
rollingDisplay.textContent = '🎰';
|
||||
rollingDisplay.style.display = 'flex';
|
||||
setStatus('info', '正在获取抽奖结果...');
|
||||
|
||||
try {
|
||||
// 调用后端接口获取中奖者
|
||||
const res = await fetchLotteryResult();
|
||||
setStatus('info', '抽奖成功,剩余次数 ' + res[1]);
|
||||
// 启动滚动动画,最终定格在中奖者
|
||||
startRollingAnimation(res[0], res[2]);
|
||||
} catch (error) {
|
||||
// 请求失败处理
|
||||
rollingDisplay.textContent = '抽奖失败';
|
||||
rollingDisplay.className = 'rolling-display idle';
|
||||
setStatus('error', '抽奖失败: ' + error.message);
|
||||
isDrawing = false;
|
||||
lotteryBtn.disabled = false;
|
||||
resetBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 事件绑定 ============
|
||||
lotteryBtn.addEventListener('click', startLottery);
|
||||
resetBtn.addEventListener('click', setCookie);
|
||||
|
||||
// 空格键快捷抽奖
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === ' ') {
|
||||
e.preventDefault();
|
||||
if (!isDrawing && !lotteryBtn.disabled) {
|
||||
startLottery();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ============ 初始化 ============
|
||||
resetResult();
|
||||
|
||||
window.demo = function (name, chance, img) {
|
||||
rollingDisplay.style.display = 'flex';
|
||||
setStatus('info', '抽奖成功,剩余次数 ' + (chance || '0'));
|
||||
startRollingAnimation(name || '演示', img || 'https://p5.music.126.net/obj/worDmcOCw6HCnzzCmsKx/61891591760/8d1c/528a/e977/10cbaa3fb39e8057be5d2560182cc5c7.png');
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user