Skip to content

Commit 3ec9d99

Browse files
committed
feat: 中二 query
1 parent bb9021d commit 3ec9d99

File tree

9 files changed

+172
-21
lines changed

9 files changed

+172
-21
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@
2424
2525
}
2626
},
27-
"packageManager": "pnpm@9.13.2+sha512.88c9c3864450350e65a33587ab801acf946d7c814ed1134da4a924f6df5a2120fd36b46aab68f7cd1d413149112d53c7db3a4136624cfd00ff1846a0c6cef48a"
27+
"packageManager": "pnpm@9.15.2"
2828
}

packages/botcore/src/botBuilder.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import admin from './modules/admin';
1313
import exportUserMusic from './modules/exportUserMusic';
1414
import musicSearchChu from './modules/musicSearchChu';
1515
import levelScores from './modules/levelScores';
16+
import scoreQueryChu from './modules/scoreQueryChu';
1617

1718
interface BuilderEnvBase<T extends BotTypes> {
1819
bot: Bot<T>,
@@ -37,7 +38,7 @@ export const buildBot = <T extends BotTypes>(env: BuilderEnvBase<T>) => {
3738
)
3839
} satisfies BuilderEnv<T>;
3940

40-
for (const attachHandlers of [callbackQuery, help, bind, scoreQuery, plateProgress, levelProgress, levelScores, /* levelConstTable, */ b50, exportUserMusic, musicSearchChu, musicSearch, admin]) {
41+
for (const attachHandlers of [callbackQuery, help, bind, scoreQueryChu, scoreQuery, plateProgress, levelProgress, levelScores, /* levelConstTable, */ b50, exportUserMusic, musicSearchChu, musicSearch, admin]) {
4142
attachHandlers(passEnv);
4243
}
4344
};

packages/botcore/src/modules/musicSearchChu.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ export default <T extends BotTypes>({ bot, env, getContext, musicToFile }: Build
1616
const answer = event.answer()
1717
.withCacheTime(60);
1818
await Promise.all(results.map(async song => {
19-
if (musicToFile[song.id]) {
20-
answer.addAudioResult(`chu:${song.id}` || song.title, musicToFile[song.id])
21-
.setText(song.display);
22-
} else if (song.coverUrl) {
19+
// if (musicToFile[song.id]) {
20+
// answer.addAudioResult(`chu:${song.id}` || song.title, musicToFile[song.id])
21+
// .setText(song.display);
22+
// } else
23+
if (song.coverUrl) {
2324
answer.addPhotoResult(`chu:${song.id}` || song.title, song.coverUrl)
2425
.setTitle(song.title)
2526
.setText(song.display);
@@ -37,17 +38,17 @@ export default <T extends BotTypes>({ bot, env, getContext, musicToFile }: Build
3738

3839
const msgTitle = song.display.substring(0, song.display.indexOf('\n'));
3940
const msgText = song.display.substring(song.display.indexOf('\n') + 1).trim();
40-
if (musicToFile[song.id]) {
41-
req.addAudio(musicToFile[song.id]);
42-
} else if (song.coverUrl) {
43-
req
44-
.addPhoto(song.coverUrl)
45-
.setTemplatedMessage(MESSAGE_TEMPLATE.MusicInfo, {
46-
title: msgTitle,
47-
content: msgText,
48-
image: song.coverUrl
49-
});
50-
}
41+
// if (musicToFile[song.id]) {
42+
// req.addAudio(musicToFile[song.id]);
43+
// } else if (song.coverUrl) {
44+
req
45+
.addPhoto(song.coverUrl)
46+
.setTemplatedMessage(MESSAGE_TEMPLATE.MusicInfo, {
47+
title: msgTitle,
48+
content: msgText,
49+
image: song.coverUrl
50+
});
51+
// }
5152

5253
await req.setText(song.display).dispatch();
5354
};
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import _ from 'lodash';
2+
import { CHU_LEVEL_EMOJI, FC, LEVEL_EMOJI, Song } from '@clansty/maibot-types/src';
3+
import { chusanRating, computeRa } from '@clansty/maibot-utils';
4+
import { BotTypes, MessageButtonSwitchInline } from '@clansty/maibot-firm';
5+
import { BuilderEnv } from '../botBuilder';
6+
import { ChuniSong } from '@clansty/maibot-types';
7+
8+
export default <T extends BotTypes>({ bot, env, getContext, musicToFile }: BuilderEnv<T>) => {
9+
bot.registerInlineQuery(/^ ?query-?chu (.+)/i, async (event) => {
10+
const ctx = getContext(event);
11+
const profile = await ctx.getCurrentProfile(false);
12+
if (!profile) {
13+
await event.answer()
14+
.withStartButton('请绑定用户', 'bind')
15+
.isPersonal()
16+
.dispatch();
17+
return true;
18+
}
19+
20+
const query = event.match[1].trim().toLowerCase();
21+
if (query === '') {
22+
await event.answer()
23+
.withCacheTime(86400)
24+
.dispatch();
25+
}
26+
const results = ChuniSong.search(query);
27+
const userMusic = await profile.getChuniUserMusic(results);
28+
const answer = event.answer()
29+
.isPersonal();
30+
for (const song of results) {
31+
const userScores = userMusic.filter(it => it.musicId === song.id);
32+
if (!userScores.length) continue;
33+
_.sortBy(userScores, it => it.level);
34+
35+
const message = [song.id + '. ' + song.title, ''];
36+
for (const userScore of userScores) {
37+
const chart = song.notes[userScore.level];
38+
message.push(`${CHU_LEVEL_EMOJI[userScore.level]} ${chart.lv.toFixed(1)} ` +
39+
`${userScore.scoreMax.toLocaleString('zh-CN')} = ${chusanRating(chart.lv, userScore.scoreMax)} ${userScore.isAllJustice ? 'AJ' : ''} ${userScore.isFullCombo ? 'FC' : ''}`);
40+
}
41+
42+
// if (musicToFile[song.id]) {
43+
// // 有音频
44+
// answer.addAudioResult(song.dxId?.toString() || song.title, musicToFile[song.id])
45+
// .setText(message.join('\n'))
46+
// .addButtons(new MessageButtonSwitchInline('歌曲详情', song.id.toString()));
47+
// } else {
48+
answer.addPhotoResult(song.id?.toString() || song.title, song.coverUrl)
49+
.setTitle(song.title)
50+
.setText(message.join('\n'))
51+
.addButtons(new MessageButtonSwitchInline('歌曲详情', 'chu ' + song.id.toString()));
52+
// }
53+
}
54+
55+
await answer.dispatch();
56+
return true;
57+
});
58+
59+
bot.registerCommand(['querychu', 'qchu'], async (event) => {
60+
const ctx = getContext(event);
61+
const profile = await ctx.getCurrentProfile();
62+
const kw = event.params.join(' ').trim();
63+
if (!kw) {
64+
await event.reply()
65+
.setText('请输入关键词')
66+
.dispatch();
67+
return true;
68+
}
69+
70+
const results = ChuniSong.search(kw);
71+
72+
if (!results.length) {
73+
await event.reply()
74+
.setText('找不到匹配的歌')
75+
.dispatch();
76+
return true;
77+
}
78+
const userMusic = await profile.getChuniUserMusic(results);
79+
for (const song of results) {
80+
const userScores = userMusic.filter(it => it.musicId === song.id || it.musicId === song.id + 1e4);
81+
if (!userScores.length) continue;
82+
_.sortBy(userScores, it => it.level);
83+
84+
const message = [song.id + '. ' + song.title, ''];
85+
for (const userScore of userScores) {
86+
const chart = song.notes[userScore.level];
87+
message.push(`${CHU_LEVEL_EMOJI[userScore.level]} ${chart.lv.toFixed(1)} ` +
88+
`${userScore.scoreMax.toLocaleString('zh-CN')} = ${chusanRating(chart.lv, userScore.scoreMax)} ${userScore.isAllJustice ? 'AJ' : ''} ${userScore.isFullCombo ? 'FC' : ''}`);
89+
}
90+
91+
const reply = event.reply()
92+
.setText(message.join('\n'))
93+
.addButtons(new MessageButtonSwitchInline('歌曲详情', 'chu ' + song.id.toString()));
94+
// if (musicToFile[song.id]) {
95+
// reply.addAudio(musicToFile[song.id]);
96+
// } else {
97+
reply.addPhoto(song.coverUrl);
98+
// }
99+
await reply.dispatch();
100+
return true;
101+
}
102+
await event.reply()
103+
.setText(`共找到 ${results.length} 个结果:\n\n` +
104+
results.map(song => `${song.title} ${song.id ? '' : '(ID 缺失)'}`).join('\n') +
105+
// 如果有 ID 缺失就不一定没玩过了
106+
(results.some(it => !it.id) ? '' : '\n\n可惜你都没玩过'))
107+
.dispatch();
108+
});
109+
}

packages/clients/src/UserProfile.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BUDDIES_LOGO, BUDDIES_PLUS_LOGO, GameVariantPlateMusicList, MaiVersion, PLATE_MUSIC_LIST_145, PLATE_MUSIC_LIST_CN, PLATE_MUSIC_LIST_JP, Regions, Env, Song, UserPreviewSummary, UserProfileDto, PRISM_LOGO, PLATE_MUSIC_LIST_150 } from '@clansty/maibot-types';
1+
import { BUDDIES_LOGO, BUDDIES_PLUS_LOGO, GameVariantPlateMusicList, MaiVersion, PLATE_MUSIC_LIST_145, PLATE_MUSIC_LIST_CN, PLATE_MUSIC_LIST_JP, Regions, Env, Song, UserPreviewSummary, UserProfileDto, PRISM_LOGO, PLATE_MUSIC_LIST_150, ChuniSong } from '@clansty/maibot-types';
22
import { UserSource } from './UserSource';
33
import AquaDxLegacy from './AquaDxLegacy';
44
import SdgbProxied from './SdgbProxied';
@@ -162,4 +162,24 @@ export class UserProfile {
162162
async getNameplate() {
163163
return this.client.getNameplate(this.userId);
164164
}
165+
166+
async getChuniUserMusic(musicIdList: (number | ChuniSong)[]) {
167+
const convertedList = [] as number[];
168+
for (const music of musicIdList) {
169+
if (music instanceof ChuniSong) {
170+
convertedList.push(music.id);
171+
} else {
172+
convertedList.push(music);
173+
}
174+
}
175+
return this.client.getChuniUserMusic(this.userId, convertedList);
176+
}
177+
178+
async getChuniUserRating() {
179+
return this.client.getChuniUserRating(this.userId);
180+
}
181+
182+
async getChuniUserPreview() {
183+
return this.client.getChuniUserPreview(this.userId);
184+
}
165185
}

packages/types/src/ChuniSong.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { CHU_LEVEL_EMOJI as LEVEL_EMOJI } from './consts';
44
import { MaiVersion } from './types';
55
import { ASSET_TYPE, getAssetUrl } from '@clansty/maibot-utils/src/getAssetUrl';
66

7+
// TODO https://chunithm.anontokyo.com/alias
78
export default class ChuniSong {
89
name: string;
910
ver: string;
@@ -68,7 +69,7 @@ export default class ChuniSong {
6869
return new this(id, dataFromAllMusic);
6970
}
7071

71-
public static search(kw: string, ver: MaiVersion = 150) {
72+
public static search(kw: string) {
7273
const results = [] as ChuniSong[];
7374
if (Number(kw)) {
7475
const song = this.fromId(Number(kw));

packages/utils/src/computeRa.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,22 @@ export const computeRa = (ds: number, achievement: number): number => {
2929
}
3030
return Math.floor(ds * (Math.min(100.5, achievement / 1e4) / 100) * baseRa);
3131
};
32+
33+
function _chusanRating(lv: number, score: number) {
34+
lv = lv * 100;
35+
if (score >= 1009000) return lv + 215; // SSS+
36+
if (score >= 1007500) return lv + 200 + (score - 1007500) / 100; // SSS
37+
if (score >= 1005000) return lv + 150 + (score - 1005000) / 50; // SS+
38+
if (score >= 1000000) return lv + 100 + (score - 1000000) / 100; // SS
39+
if (score >= 975000) return lv + (score - 975000) / 250; // S+, S
40+
if (score >= 925000) return lv - 300 + (score - 925000) * 3 / 500; // AA
41+
if (score >= 900000) return lv - 500 + (score - 900000) * 4 / 500; // A
42+
if (score >= 800000) return ((lv - 500) / 2 + (score - 800000) * ((lv - 500) / 2) / (100000)); // BBB
43+
return 0; // C
44+
}
45+
46+
export function chusanRating(lv: number, score: number) {
47+
let original = _chusanRating(lv, score);
48+
original = Math.floor(original);
49+
return original / 100;
50+
}

qofbot.Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ FROM node:22-slim AS build
22

33
ENV PNPM_HOME="/pnpm"
44
ENV PATH="$PNPM_HOME:$PATH"
5-
RUN corepack enable
65

76
WORKDIR /app
87

@@ -19,6 +18,7 @@ COPY packages/adapters/package.json /app/packages/adapters/
1918

2019
RUN --mount=type=cache,id=pnpm,target=/pnpm/store,sharing=locked \
2120
--mount=type=secret,id=npmrc,target=/root/.npmrc \
21+
npm i -g [email protected] && \
2222
pnpm install --frozen-lockfile
2323

2424
COPY packages /app/packages

qqbot.Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ FROM node:22-slim AS build
22

33
ENV PNPM_HOME="/pnpm"
44
ENV PATH="$PNPM_HOME:$PATH"
5-
RUN corepack enable
65

76
WORKDIR /app
87

@@ -19,6 +18,7 @@ COPY packages/adapters/package.json /app/packages/adapters/
1918

2019
RUN --mount=type=cache,id=pnpm,target=/pnpm/store,sharing=locked \
2120
--mount=type=secret,id=npmrc,target=/root/.npmrc \
21+
npm i -g [email protected] && \
2222
pnpm install --frozen-lockfile
2323

2424
COPY packages /app/packages

0 commit comments

Comments
 (0)