🚀 高性能 WebAssembly · 📦 开箱即用 · 🎯 浏览器原生支持
功能完整的 osu! API 客户端库 WASM 绑定,为 Web 应用赋能
使用 npm、yarn 或 pnpm 安装:
# npm
npm install @osynicite/osynic-osuapi
# yarn
yarn add @osynicite/osynic-osuapi
# pnpm
pnpm add @osynicite/osynic-osuapi
确保您的项目配置支持 WebAssembly:
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import wasm from 'vite-plugin-wasm'
import topLevelAwait from 'vite-plugin-top-level-await'
export default defineConfig({
plugins: [
wasm(),
topLevelAwait(),
vue(),
],
})
访问您的 osu! 设置页面,在以下位置申请相应的 API 授权:
import { OsynicOsuApiV2GlooClient } from '@osynicite/osynic-osuapi'
async function getUserInfo() {
const client = new OsynicOsuApiV2GlooClient()
client.set_proxy_url('YOUR_CORS_PROXY_URL') // You can see https://github.com/Islatri/deno_osynic_cors
const token = await client.oauth.getTokenWithoutCode(
'YOUR_CLIENT_ID_HERE',
'YOUR_CLIENT_SECRET_HERE'
)
// 获取用户信息
const user = await client.users.getUserByUsername('peppy')
console.log(user)
}
getUserInfo().catch(console.error)
这一部分应当在后端完成,以保护 client_secret 不被暴露在前端。服务端具体实现可以参考 osynic-oauth 项目,部署例程可见 OsynicOauth。
前端部分可以参照examples中的实现。
import { OsynicOsuApiV1GlooClient } from '@osynicite/osynic-osuapi'
async function getBeatmapInfo() {
const client = new OsynicOsuApiV1GlooClient('YOUR_API_KEY_HERE')
client.set_proxy_url('YOUR_CORS_PROXY_URL') // You can see https://github.com/Islatri/deno_osynic_cors
// 通过哈希值查询谱面
const beatmaps = await client.beatmap.getBeatmaps({
hash: '69f77b0dfe67d288c1bf748f91ceb133'
})
console.log(beatmaps)
}
getBeatmapInfo().catch(console.error)
<template>
<div class="p-6 bg-gray-900 text-white min-h-screen">
<div class="max-w-2xl mx-auto space-y-4">
<div class="space-y-2">
<input v-model="query.bid" type="text" placeholder="谱面ID"
class="w-full px-3 py-2 bg-gray-800 rounded border border-gray-700" />
<input v-model="query.sid" type="text" placeholder="谱面集ID"
class="w-full px-3 py-2 bg-gray-800 rounded border border-gray-700" />
<select v-model="query.mode" class="w-full px-3 py-2 bg-gray-800 rounded border border-gray-700">
<option value="">所有模式</option>
<option value="0">标准</option>
<option value="1">太鼓</option>
<option value="2">接水果</option>
<option value="3">mania</option>
</select>
<button @click="search" :disabled="loading"
class="w-full px-3 py-2 bg-blue-600 hover:bg-blue-700 rounded disabled:opacity-50">
{{ loading ? '加载中...' : '搜索' }}
</button>
</div>
<div v-if="error" class="text-red-400">{{ error }}</div>
<div v-if="beatmaps.length" class="space-y-2">
<div v-for="m in beatmaps" :key="m.beatmap_id" class="bg-gray-800 p-3 rounded text-sm">
<div class="font-bold text-blue-300">{{ m.title }} [{{ m.version }}]</div>
<div class="text-gray-400">★{{ parseFloat(m.difficultyrating).toFixed(2) }} | {{ m.artist }}</div>
<div class="text-gray-500 text-xs mt-1">
{{ formatTime(m.total_length) }} | BPM {{ parseInt(m.bpm) }} | {{ calcPassRate(m.playcount,
m.passcount) }}% 通过率
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { OsynicOsuApiV1GlooClient } from '@osynicite/osynic-osuapi';
const client = new OsynicOsuApiV1GlooClient("YOUR_API_KEY_HERE_AND_PLS_NOT_SHARE_IT_IN_YOUR_CONCRETE_PROJECT");
client.setProxyUrl("YOUR_PROXY_URL_HERE_BECAUSE_CORS"); // You can see https://github.com/Islatri/deno_osynic_cors
const query = reactive({ bid: '', sid: '', mode: '' });
const beatmaps = ref([]);
const loading = ref(false);
const error = ref('');
const search = async () => {
loading.value = true;
error.value = '';
try {
const params = Object.fromEntries(Object.entries(query).filter(([, v]) => v));
beatmaps.value = await client.getBeatmaps(params).then(r => Array.isArray(r) ? r : [r]);
} catch (err: any) {
error.value = err?.message || '查询失败';
} finally {
loading.value = false;
}
};
const formatTime = (s: string) => {
const t = parseInt(s);
return `${Math.floor(t / 60)}:${(t % 60).toString().padStart(2, '0')}`;
};
const calcPassRate = (p: string, pa: string) => ((parseInt(pa) / parseInt(p)) * 100).toFixed(1);
</script>
| API | 支持 | 说明 |
|---|---|---|
| /get_beatmaps | ✅ | 获取谱面 |
| /get_user | ✅ | 获取用户 |
| /get_user_best | ✅ | 获取用户最佳成绩 |
| /get_user_recent | ✅ | 获取用户最近成绩 |
| /get_match | ✅ | 获取比赛 |
| /get_scores | ✅ | 获取谱面成绩 |
| /get_replay | ✅ | 获取回放 |
| 大类 | 支持情况 | 说明 |
|---|---|---|
| Authentication | ✅ 4/4 | OAuth 认证 |
| Beatmaps | ✅ 10/10 | 谱面 API |
| Beatmapsets | ⚠️ 2/7(部分需授权) | 谱面集 API |
| Changelog | ✅ 3/3 | 变更日志 |
| Comments | ⚠️ 2/7(部分需授权) | 评论 API |
| Events | ✅ 1/1 | 事件 API |
| Forum | ⚠️ 4/8(部分需授权) | 论坛 API |
| Matches | ✅ 2/2 | 比赛 API |
| News | ✅ 2/2 | 新闻 API |
| Rankings | ✅ 3/3 | 排行榜 API |
| Scores | ✅ 1/1 | 成绩 API |
| Users | ✅ 7/7 | 用户 API |
| Wiki | ✅ 1/1 | Wiki API |
| Friends | ✅ 2/2 | 好友 API |
更多详细信息请查看主项目的 API 检查表
本库提供完整的项目示例,位于 wasm/examples/ 目录下:
cd wasm/examples
npm install
npm run dev
npm install @osynicite/osynic-osuapi
然后在您的项目中:
import { OsynicOsuApiV2Client } from '@osynicite/osynic-osuapi'
const client = new OsynicOsuApiV2Client()
client.set_proxy_url('YOUR_CORS_PROXY_URL') // You can see https://github.com/Islatri/deno_osynic_cors
由于浏览器的跨域政策限制,JavaScript 客户端无法直接访问 osu! API。本库暴露了set_proxy_url 方法,允许您设置一个 CORS 代理 URL 来绕过此限制。关于 CORS 代理服务器的搭建和使用,可以参考 deno_osynic_cors。
使用本库时最常见的问题来源于 osu! API 官方实体结构的变动:
如果您在使用过程中遇到问题,请提交 Issue 并附上:
A: 强烈建议在生产环境中使用后端代理或者V2的 Authorization Code Grant 方案,而不是直接在浏览器中暴露 client_secret。
A: 本库主要支持 ES 模块。如需使用 CommonJS,请确保您的构建工具支持 ESM to CJS 转换。
A: 不建议在 Node.js 中使用 WASM 版本,因为 WASM 对 Node.js 的支持暂不完善,但可以通过 vite-plugin-wasm 、 vite-plugin-top-level-await 等插件来在各大前端框架中使用。
我们欢迎贡献!如果您发现任何问题或有改进建议,请遵循以下规则:
git checkout -b feature/your-featuregit commit -am 'Add your feature'git push origin feature/your-feature# 从项目根目录
cd wasm
# 构建 WASM 库
wasm-pack build --release --target bundler --out-dir pkg --scope osynicite
# 安装依赖
npm install
# 发布(维护者)
npm publish
cargo fmt 和 cargo clippy本项目基于 MIT License 开源。