diff --git a/README.md b/README.md
index f0c091b..77f3802 100644
--- a/README.md
+++ b/README.md
@@ -94,3 +94,30 @@ docker run --detach \
---
如需将监听接口改为仅内网或增加并发工作数(例如 `--workers 4`),可在 `setup.sh` 或 `systemd` 服务中调整。
+## 歌曲类型(Type)
+
+- 可选枚举:
+ - 01 Pop
+ - 02 Anime
+ - 03 Vocaloid
+ - 04 Children and Folk
+ - 05 Variety
+ - 06 Classical
+ - 07 Game Music
+ - 08 Live Festival Mode
+ - 09 Namco Original
+ - 10 Taiko Towers
+ - 11 Dan Dojo
+
+### 上传要求
+- 上传表单新增必填字段 `song_type`,取值为上述枚举之一
+- 成功后将写入 MongoDB `songs.song_type`
+
+### API 扩展
+- `GET /api/songs?type=<歌曲类型>` 按类型过滤返回启用歌曲
+- 示例:`/api/songs?type=02%20Anime`
+- 返回项包含 `song_type` 字段
+
+### 前端切换
+- 在歌曲选择页顶部显示当前歌曲类型标签
+- 使用左右跳转(Shift+左右或肩键)自动切换类型并刷新列表
diff --git a/app.py b/app.py
index 0b6203a..4b12cde 100644
--- a/app.py
+++ b/app.py
@@ -45,6 +45,19 @@ def take_config(name, required=False):
return None
app = Flask(__name__)
+SONG_TYPES = [
+ "01 Pop",
+ "02 Anime",
+ "03 Vocaloid",
+ "04 Children and Folk",
+ "05 Variety",
+ "06 Classical",
+ "07 Game Music",
+ "08 Live Festival Mode",
+ "09 Namco Original",
+ "10 Taiko Towers",
+ "11 Dan Dojo",
+]
def get_remote_address() -> str:
return flask.request.headers.get("CF-Connecting-IP") or flask.request.headers.get("X-Forwarded-For") or flask.request.remote_addr or "127.0.0.1"
@@ -90,6 +103,7 @@ sess.init_app(app)
db = client[take_config('MONGO', required=True)['database']]
db.users.create_index('username', unique=True)
db.songs.create_index('id', unique=True)
+db.songs.create_index('song_type')
db.scores.create_index('username')
@@ -480,7 +494,13 @@ def route_api_preview():
@app.route(basedir + 'api/songs')
@app.cache.cached(timeout=15)
def route_api_songs():
- songs = list(db.songs.find({'enabled': True}, {'_id': False, 'enabled': False}))
+ type_q = flask.request.args.get('type')
+ query = {'enabled': True}
+ if type_q:
+ if type_q not in SONG_TYPES:
+ return abort(400)
+ query['song_type'] = type_q
+ songs = list(db.songs.find(query, {'_id': False, 'enabled': False}))
for song in songs:
if song['maker_id']:
if song['maker_id'] == 0:
@@ -853,6 +873,12 @@ def upload_file():
db_entry['enabled'] = True
pprint.pprint(db_entry)
+ # 必要な歌曲类型
+ song_type = flask.request.form.get('song_type')
+ if not song_type or song_type not in SONG_TYPES:
+ return flask.jsonify({'error': 'invalid_song_type'})
+ db_entry['song_type'] = song_type
+
# mongoDBにデータをぶち込む(重複IDは部分更新で上書きし、_id を不変に保つ)
coll = client['taiko']["songs"]
try:
diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js
index 58ba125..343645b 100644
--- a/public/src/js/songselect.js
+++ b/public/src/js/songselect.js
@@ -386,6 +386,32 @@ class SongSelect{
}
this.songSelect = document.getElementById("song-select")
+ this.songTypes = [
+ "01 Pop",
+ "02 Anime",
+ "03 Vocaloid",
+ "04 Children and Folk",
+ "05 Variety",
+ "06 Classical",
+ "07 Game Music",
+ "08 Live Festival Mode",
+ "09 Namco Original",
+ "10 Taiko Towers",
+ "11 Dan Dojo",
+ ]
+ this.songTypeIndex = Math.max(0, Math.min(this.songTypes.length - 1, +(localStorage.getItem("songTypeIndex") || 0)))
+ this.typeLabel = document.createElement("div")
+ this.typeLabel.style.position = "absolute"
+ this.typeLabel.style.top = "8px"
+ this.typeLabel.style.left = "12px"
+ this.typeLabel.style.padding = "4px 8px"
+ this.typeLabel.style.background = "rgba(0,0,0,0.5)"
+ this.typeLabel.style.color = "#fff"
+ this.typeLabel.style.borderRadius = "6px"
+ this.typeLabel.style.fontSize = "14px"
+ this.typeLabel.style.zIndex = "10"
+ this.songSelect.appendChild(this.typeLabel)
+ this.updateTypeLabel()
var cat = this.songs[this.selectedSong].originalCategory
this.drawBackground(cat)
@@ -536,24 +562,20 @@ class SongSelect{
this.toSession()
}else if(name === "left"){
if(shift){
- if(!repeat){
- this.categoryJump(-1)
- }
+ if(!repeat){ this.changeType(-1) }
}else{
this.moveToSong(-1)
}
}else if(name === "right"){
if(shift){
- if(!repeat){
- this.categoryJump(1)
- }
+ if(!repeat){ this.changeType(1) }
}else{
this.moveToSong(1)
}
}else if(name === "jump_left" && !repeat){
- this.categoryJump(-1)
+ this.changeType(-1)
}else if(name === "jump_right" && !repeat){
- this.categoryJump(1)
+ this.changeType(1)
}else if(name === "mute" || name === "ctrlGamepad"){
this.endPreview(true)
this.playBgm(false)
@@ -597,6 +619,23 @@ class SongSelect{
}
}
}
+
+ updateTypeLabel(){
+ this.setAltText(this.typeLabel, this.songTypes[this.songTypeIndex])
+ }
+
+ changeType(delta){
+ this.songTypeIndex = (this.songTypeIndex + delta + this.songTypes.length) % this.songTypes.length
+ localStorage.setItem("songTypeIndex", this.songTypeIndex)
+ this.updateTypeLabel()
+ var type = encodeURIComponent(this.songTypes[this.songTypeIndex])
+ loader.ajax("api/songs?type=" + type).then(resp => {
+ var songs = JSON.parse(resp)
+ assets.songsDefault = songs
+ assets.songs = assets.songsDefault
+ new SongSelect(false, false, this.touchEnabled)
+ }).catch(() => {})
+ }
mouseDown(event){
if(event.target === this.selectable || event.target.parentNode === this.selectable){
diff --git a/public/upload/index.html b/public/upload/index.html
index 6c43997..b940417 100644
--- a/public/upload/index.html
+++ b/public/upload/index.html
@@ -16,6 +16,21 @@
+
+
+