Compare commits
4 Commits
1cce00e971
...
2353f894ff
Author | SHA1 | Date | |
---|---|---|---|
2353f894ff | |||
2e79f946c3 | |||
8391ca049f | |||
ea5ae49bc9 |
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -126,6 +126,7 @@
|
||||
"taza",
|
||||
"teruteru",
|
||||
"timestamptz",
|
||||
"tlyric",
|
||||
"toolsmp",
|
||||
"tsconfigs",
|
||||
"tview",
|
||||
|
932
package-lock.json
generated
932
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@ -42,26 +42,26 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^3.1.8",
|
||||
"@astrojs/mdx": "^3.1.9",
|
||||
"@astrojs/rss": "^4.0.9",
|
||||
"@zeabur/astro-adapter": "^1.0.6",
|
||||
"astro": "^4.16.7",
|
||||
"drizzle-orm": "^0.35.3",
|
||||
"astro": "^4.16.8",
|
||||
"drizzle-orm": "^0.36.0",
|
||||
"fuse.js": "^7.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.5.0",
|
||||
"marked": "^14.1.3",
|
||||
"pg": "^8.13.0",
|
||||
"pg": "^8.13.1",
|
||||
"qrcode-svg": "^1.1.0",
|
||||
"ultrahtml": "^1.5.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@napi-rs/canvas": "^0.1.58",
|
||||
"@types/lodash": "^4.17.12",
|
||||
"@napi-rs/canvas": "^0.1.59",
|
||||
"@types/lodash": "^4.17.13",
|
||||
"@types/luxon": "^3.4.2",
|
||||
"@types/node": "^22.7.9",
|
||||
"@types/node": "^22.8.6",
|
||||
"@types/pg": "^8.11.10",
|
||||
"@types/qrcode-svg": "^1.1.5",
|
||||
"@types/unist": "^3.0.3",
|
||||
@ -83,6 +83,6 @@
|
||||
},
|
||||
"packageManager": "npm@10.9.0",
|
||||
"engines": {
|
||||
"node": "22.10.0"
|
||||
"node": "23.1.0"
|
||||
}
|
||||
}
|
||||
|
4
src/components/player/netease/config.js
Normal file
4
src/components/player/netease/config.js
Normal file
@ -0,0 +1,4 @@
|
||||
const net_ease_anonymous_token =
|
||||
'de91e1f8119d32e01cc73efcb82c0a30c9137e8d4f88dbf5e3d7bf3f28998f21add2bc8204eeee5e56c0bbb8743574b46ca2c10c35dc172199bef9bf4d60ecdeab066bb4dc737d1c3324751bcc9aaf44c3061cd18d77b7a0';
|
||||
|
||||
export { net_ease_anonymous_token };
|
60
src/components/player/netease/crypto.js
Normal file
60
src/components/player/netease/crypto.js
Normal file
@ -0,0 +1,60 @@
|
||||
import { Buffer } from 'node:buffer';
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
const iv = Buffer.from('0102030405060708');
|
||||
const presetKey = Buffer.from('0CoJUm6Qyw8W8jud');
|
||||
const linuxapiKey = Buffer.from('rFgB&h#%2?^eDg:Q');
|
||||
const base62 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
const publicKey =
|
||||
'-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB\n-----END PUBLIC KEY-----';
|
||||
const eapiKey = 'e82ckenh8dichen8';
|
||||
|
||||
const aesEncrypt = (buffer, mode, key, iv) => {
|
||||
const cipher = crypto.createCipheriv(`aes-128-${mode}`, key, iv);
|
||||
return Buffer.concat([cipher.update(buffer), cipher.final()]);
|
||||
};
|
||||
|
||||
const rsaEncrypt = (buffer, key) => {
|
||||
return crypto.publicEncrypt(
|
||||
{ key: key, padding: crypto.constants.RSA_NO_PADDING },
|
||||
Buffer.concat([Buffer.alloc(128 - buffer.length), buffer]),
|
||||
);
|
||||
};
|
||||
|
||||
const weapi = (object) => {
|
||||
const text = JSON.stringify(object);
|
||||
const secretKey = crypto.randomBytes(16).map((n) => base62.charAt(n % 62).charCodeAt());
|
||||
return {
|
||||
params: aesEncrypt(
|
||||
Buffer.from(aesEncrypt(Buffer.from(text), 'cbc', presetKey, iv).toString('base64')),
|
||||
'cbc',
|
||||
secretKey,
|
||||
iv,
|
||||
).toString('base64'),
|
||||
encSecKey: rsaEncrypt(secretKey.reverse(), publicKey).toString('hex'),
|
||||
};
|
||||
};
|
||||
|
||||
const linuxapi = (object) => {
|
||||
const text = JSON.stringify(object);
|
||||
return {
|
||||
eparams: aesEncrypt(Buffer.from(text), 'ecb', linuxapiKey, '').toString('hex').toUpperCase(),
|
||||
};
|
||||
};
|
||||
|
||||
const eapi = (url, object) => {
|
||||
const text = typeof object === 'object' ? JSON.stringify(object) : object;
|
||||
const message = `nobody${url}use${text}md5forencrypt`;
|
||||
const digest = crypto.createHash('md5').update(message).digest('hex');
|
||||
const data = `${url}-36cd479b6b5-${text}-36cd479b6b5-${digest}`;
|
||||
return {
|
||||
params: aesEncrypt(Buffer.from(data), 'ecb', eapiKey, '').toString('hex').toUpperCase(),
|
||||
};
|
||||
};
|
||||
|
||||
const decrypt = (cipherBuffer) => {
|
||||
const decipher = crypto.createDecipheriv('aes-128-ecb', eapiKey, '');
|
||||
return Buffer.concat([decipher.update(cipherBuffer), decipher.final()]);
|
||||
};
|
||||
|
||||
export default { weapi, linuxapi, eapi, decrypt };
|
35
src/components/player/netease/song.js
Normal file
35
src/components/player/netease/song.js
Normal file
@ -0,0 +1,35 @@
|
||||
import { map_song_list, request } from './util.js';
|
||||
|
||||
export const get_song_url = async (id, cookie = '') => {
|
||||
const data = {
|
||||
ids: `[${id}]`,
|
||||
level: 'standard',
|
||||
encodeType: 'flac',
|
||||
};
|
||||
|
||||
const res = await request('POST', 'https://interface.music.163.com/eapi/song/enhance/player/url/v1', data, {
|
||||
crypto: 'eapi',
|
||||
url: '/api/song/enhance/player/url/v1',
|
||||
cookie: {},
|
||||
});
|
||||
const url = res.data[0]?.url?.replace('http://', 'https://');
|
||||
|
||||
return url || `https://music.163.com/song/media/outer/url?id=${id}.mp3`;
|
||||
};
|
||||
|
||||
export const get_song_info = async (id, cookie = '') => {
|
||||
const ids = [id];
|
||||
const data = {
|
||||
c: `[${ids.map((id) => `{"id":${id}}`).join(',')}]`,
|
||||
};
|
||||
let res = await request('POST', 'https://music.163.com/api/v3/song/detail', data, {
|
||||
crypto: 'weapi',
|
||||
});
|
||||
|
||||
if (!res.songs) {
|
||||
throw res;
|
||||
}
|
||||
|
||||
res = map_song_list(res);
|
||||
return res;
|
||||
};
|
181
src/components/player/netease/util.js
Normal file
181
src/components/player/netease/util.js
Normal file
File diff suppressed because one or more lines are too long
@ -1,3 +1,5 @@
|
||||
import { get_song_info, get_song_url } from '@/components/player/netease/song';
|
||||
|
||||
export type Song = {
|
||||
name: string;
|
||||
artist: string;
|
||||
@ -17,19 +19,11 @@ const song = async (props: MusicPlayerProps): Promise<Song> => {
|
||||
const { netease, song } = props;
|
||||
|
||||
if (netease) {
|
||||
// Fix the UNABLE_TO_GET_ISSUER_CERT_LOCALLY issue for the mirror site.
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
|
||||
// https://github.com/injahow/meting-api
|
||||
const data = await fetch(`https://api.injahow.cn/meting/?type=song&id=${netease}`)
|
||||
.then((response) => response.json())
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
return [emptySong];
|
||||
});
|
||||
const info = await get_song_info(netease);
|
||||
const url = await get_song_url(netease);
|
||||
|
||||
// Check the return result.
|
||||
return { name: data[0].name, artist: data[0].artist, url: data[0].url, pic: data[0].pic };
|
||||
return { name: info[0].title, artist: info[0].author, url: url, pic: info[0].pic };
|
||||
}
|
||||
|
||||
if (song) {
|
||||
|
@ -19,9 +19,9 @@ summary: 网易云音乐一直是一个值得吐槽的地方,比如,它常
|
||||
|
||||
<MusicPlayer netease={812400} />
|
||||
|
||||
周一时听到一首名为 **[「PLANET」](http://music.163.com/#/song?id=812400)** 的歌曲,前奏就十分抓耳,当时就立刻点击下载,可惜这歌竟然要付费。生在天朝,仗着自己从事 IT 相关的工作,最大的好处便是能免费搞到一些数字出版物。但我在谷歌、百度上查找甚久,竟找不到此曲的 320 Kbps 版本,不禁有些沮丧。
|
||||
周一时听到一首名为[「PLANET」](http://music.163.com/#/song?id=812400)的歌曲,前奏就十分抓耳,当时就立刻点击下载,可惜这歌竟然要付费。生在天朝,仗着自己从事 IT 相关的工作,最大的好处便是能免费搞到一些数字出版物。但我在谷歌、百度上查找甚久,竟找不到此曲的 320 Kbps 版本,不禁有些沮丧。
|
||||
|
||||
在网易的「チーズ牛丼」(沙拉牛肉)上找到一篇关于「PLANET」的创作组合**「ラムジ」**的[介绍](http://music.163.com/#/topic?id=17639053)。 22 岁的山下佑树在过生日的时候,只因想要唱点什么东西来到东京,和报复不得施展的井上慎二郎桑如童话般在雨中相遇,并开始了长达 8 年的演艺事业。
|
||||
在网易的「チーズ牛丼」(沙拉牛肉)上找到一篇关于「PLANET」的创作组合 **「ラムジ」** 的[介绍](http://music.163.com/#/topic?id=17639053)。 22 岁的山下佑树在过生日的时候,只因想要唱点什么东西来到东京,和报复不得施展的井上慎二郎桑如童话般在雨中相遇,并开始了长达 8 年的演艺事业。
|
||||
|
||||
> 当時、井上慎二郎に初めて出会ったとき、雨に濡れて震える子羊のような容姿だった為、当時の山下のイメージから羊を連想し、アニメの子羊キャラクターからラムジと命名。
|
||||
>
|
||||
@ -49,7 +49,7 @@ summary: 网易云音乐一直是一个值得吐槽的地方,比如,它常
|
||||
|
||||
所以说,测试代码,再怎么变,逃离不了上面的编程方法,即使是别的编程语言,亦是如此。我很费解的是,在苏宁,看到我的同事写测试的时候,写的是一个 `main` 方法,用 `System.out.print` 输出结果人肉判断。因为上线有单测覆盖率要求,竟写出了针对实体 `Getter` `Setter` 方法的测试代码。这种测试代码,有存在的意义么?
|
||||
|
||||
有时我觉得,编程就是一种体力活,想好了怎么设计,后面的编码加测试,纯粹是一种[肝][moegirl]的状态。有时候又觉得很多代码纯粹是套路(模板代码),只是因为某些需要不得不这么写。正如标题所言,总得给自己留下点什么。我希望给自己留下的,是那些有意思的设计思路,而不是那些**「垃圾代码」**。
|
||||
有时我觉得,编程就是一种体力活,想好了怎么设计,后面的编码加测试,纯粹是一种[肝][moegirl]的状态。有时候又觉得很多代码纯粹是套路(模板代码),只是因为某些需要不得不这么写。正如标题所言,总得给自己留下点什么。我希望给自己留下的,是那些有意思的设计思路,而不是那些 **「垃圾代码」**。
|
||||
|
||||
![我的英雄学院](/images/2019/05/2019050902332168.jpg)
|
||||
|
||||
|
@ -23,7 +23,7 @@ if (pageNum < 1) {
|
||||
<!-- This is the layout for listing all the blogs by using pagination. -->
|
||||
<BaseLayout title={pageNum > 1 ? `第 ${pageNum} 页` : undefined}>
|
||||
<div class="px-lg-2 px-xxl-5 py-3 py-md-4 py-xxl-5">
|
||||
<FeaturePosts {posts} />
|
||||
{pageNum === 1 && <FeaturePosts {posts} />}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<PostCards {pageNum} {posts} />
|
||||
|
Loading…
Reference in New Issue
Block a user