diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7d3446b..9361b44 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,7 +2,8 @@ name: ci
on:
push:
- branches: [ main ]
+ branches:
+ - "*"
workflow_dispatch:
jobs:
diff --git a/config-example.json b/config-example.json
index dc7be86..1444065 100644
--- a/config-example.json
+++ b/config-example.json
@@ -1,81 +1,81 @@
{
- "hardware": "L07A",
- "account": "",
- "password": "",
- "mi_did": "",
- "cookie": "",
- "verbose": false,
- "music_path": "music",
- "conf_path": null,
- "hostname": "192.168.2.5",
- "port": 8090,
- "public_port": 0,
- "proxy": null,
- "search_prefix": "bilisearch:",
- "ffmpeg_location": "./ffmpeg/bin",
- "active_cmd": "play,random_play,playlocal,play_music_list,stop",
- "exclude_dirs": "@eaDir",
- "music_path_depth": 10,
- "disable_httpauth": true,
- "httpauth_username": "admin",
- "httpauth_password": "admin",
- "music_list_url": "",
- "music_list_json": "",
- "disable_download": false,
- "key_word_dict": {
- "播放歌曲": "play",
- "播放本地歌曲": "playlocal",
- "关机": "stop",
- "下一首": "play_next",
- "单曲循环": "set_play_type_one",
- "全部循环": "set_play_type_all",
- "随机播放": "random_play",
- "分钟后关机": "stop_after_minute",
- "播放列表": "play_music_list",
- "刷新列表": "gen_music_list",
- "set_volume#": "set_volume",
- "get_volume#": "get_volume",
- "本地播放歌曲": "playlocal",
- "放歌曲": "play",
- "暂停": "stop",
- "停止": "stop",
- "停止播放": "stop",
- "测试自定义口令": "exec#code1(\"hello\")",
- "测试链接": "exec#httpget(\"https://github.com/hanxi/xiaomusic\")"
- },
- "key_match_order": [
- "set_volume#",
- "get_volume#",
- "分钟后关机",
- "播放歌曲",
- "下一首",
- "单曲循环",
- "全部循环",
- "随机播放",
- "关机",
- "刷新列表",
- "播放列表",
- "播放本地歌曲",
- "本地播放歌曲",
- "放歌曲",
- "暂停",
- "停止",
- "停止播放",
- "测试自定义口令",
- "测试链接"
- ],
- "use_music_api": false,
- "use_music_audio_id": "1582971365183456177",
- "use_music_id": "355454500",
- "log_file": "/tmp/xiaomusic.txt",
- "fuzzy_match_cutoff": 0.6,
- "enable_fuzzy_match": true,
- "stop_tts_msg": "收到,再见",
- "keywords_playlocal": "播放本地歌曲,本地播放歌曲",
- "keywords_play": "播放歌曲,放歌曲",
- "keywords_stop": "关机,暂停,停止,停止播放",
- "user_key_word_dict": {
- "测试自定义口令": "exec#code1(\"hello\")",
- "测试链接": "exec#httpget(\"https://github.com/hanxi/xiaomusic\")"
- }
+ "account": "",
+ "password": "",
+ "mi_did": "",
+ "cookie": "",
+ "verbose": false,
+ "music_path": "music",
+ "download_path": "",
+ "conf_path": null,
+ "hostname": "192.168.2.5",
+ "port": 8090,
+ "public_port": 0,
+ "proxy": null,
+ "search_prefix": "bilisearch:",
+ "ffmpeg_location": "./ffmpeg/bin",
+ "active_cmd": "play,set_random_play,playlocal,play_music_list,stop",
+ "exclude_dirs": "@eaDir",
+ "music_path_depth": 10,
+ "disable_httpauth": true,
+ "httpauth_username": "",
+ "httpauth_password": "",
+ "music_list_url": "",
+ "music_list_json": "",
+ "disable_download": false,
+ "key_word_dict": {
+ "播放歌曲": "play",
+ "播放本地歌曲": "playlocal",
+ "关机": "stop",
+ "下一首": "play_next",
+ "单曲循环": "set_play_type_one",
+ "全部循环": "set_play_type_all",
+ "随机播放": "set_random_play",
+ "分钟后关机": "stop_after_minute",
+ "播放列表": "play_music_list",
+ "刷新列表": "gen_music_list",
+ "本地播放歌曲": "playlocal",
+ "放歌曲": "play",
+ "暂停": "stop",
+ "停止": "stop",
+ "停止播放": "stop",
+ "测试自定义口令": "exec#code1(\"hello\")",
+ "测试链接": "exec#httpget(\"https://github.com/hanxi/xiaomusic\")"
+ },
+ "key_match_order": [
+ "分钟后关机",
+ "播放歌曲",
+ "下一首",
+ "单曲循环",
+ "全部循环",
+ "随机播放",
+ "关机",
+ "刷新列表",
+ "播放列表",
+ "播放本地歌曲",
+ "本地播放歌曲",
+ "放歌曲",
+ "暂停",
+ "停止",
+ "停止播放",
+ "测试自定义口令",
+ "测试链接"
+ ],
+ "use_music_api": false,
+ "use_music_audio_id": "1582971365183456177",
+ "use_music_id": "355454500",
+ "log_file": "/tmp/xiaomusic.txt",
+ "fuzzy_match_cutoff": 0.6,
+ "enable_fuzzy_match": true,
+ "stop_tts_msg": "收到,再见",
+ "enable_config_example": true,
+ "keywords_playlocal": "播放本地歌曲,本地播放歌曲",
+ "keywords_play": "播放歌曲,放歌曲",
+ "keywords_stop": "关机,暂停,停止,停止播放",
+ "user_key_word_dict": {
+ "测试自定义口令": "exec#code1(\"hello\")",
+ "测试链接": "exec#httpget(\"https://github.com/hanxi/xiaomusic\")"
+ },
+ "enable_force_stop": false,
+ "devices": {},
+ "group_list": ""
}
\ No newline at end of file
diff --git a/xiaomusic/config.py b/xiaomusic/config.py
index 6e847f3..f894f0c 100644
--- a/xiaomusic/config.py
+++ b/xiaomusic/config.py
@@ -18,12 +18,10 @@ def default_key_word_dict():
"下一首": "play_next",
"单曲循环": "set_play_type_one",
"全部循环": "set_play_type_all",
- "随机播放": "random_play",
+ "随机播放": "set_random_play",
"分钟后关机": "stop_after_minute",
"播放列表": "play_music_list",
"刷新列表": "gen_music_list",
- "set_volume#": "set_volume",
- "get_volume#": "get_volume",
}
@@ -43,8 +41,6 @@ KEY_WORD_ARG_BEFORE_DICT = {
# 口令匹配优先级
def default_key_match_order():
return [
- "set_volume#",
- "get_volume#",
"分钟后关机",
"播放歌曲",
"下一首",
@@ -57,12 +53,22 @@ def default_key_match_order():
]
+@dataclass
+class Device:
+ did: str = ""
+ device_id: str = ""
+ hardware: str = ""
+ name: str = ""
+ play_type: int = ""
+ cur_music: str = ""
+ cur_playlist: str = ""
+
+
@dataclass
class Config:
account: str = os.getenv("MI_USER", "")
password: str = os.getenv("MI_PASS", "")
mi_did: str = os.getenv("MI_DID", "") # 逗号分割支持多设备
- hardware: str = os.getenv("MI_HARDWARE", "L07A") # 逗号分割支持多设备
cookie: str = ""
verbose: bool = os.getenv("XIAOMUSIC_VERBOSE", "").lower() == "true"
music_path: str = os.getenv(
@@ -79,7 +85,7 @@ class Config:
) # "bilisearch:" or "ytsearch:"
ffmpeg_location: str = os.getenv("XIAOMUSIC_FFMPEG_LOCATION", "./ffmpeg/bin")
active_cmd: str = os.getenv(
- "XIAOMUSIC_ACTIVE_CMD", "play,random_play,playlocal,play_music_list,stop"
+ "XIAOMUSIC_ACTIVE_CMD", "play,set_random_play,playlocal,play_music_list,stop"
)
exclude_dirs: str = os.getenv("XIAOMUSIC_EXCLUDE_DIRS", "@eaDir")
music_path_depth: int = int(os.getenv("XIAOMUSIC_MUSIC_PATH_DEPTH", "10"))
@@ -123,7 +129,10 @@ class Config:
enable_force_stop: bool = (
os.getenv("XIAOMUSIC_ENABLE_FORCE_STOP", "false").lower() == "true"
)
- play_type: int = int(os.getenv("XIAOMUSIC_PLAY_TYPE", "2"))
+ devices: dict[str, Device] = field(default_factory=dict)
+ group_list: str = os.getenv(
+ "XIAOMUSIC_GROUP_LIST", ""
+ ) # did2:group_name,did2:group_name
def append_keyword(self, keys, action):
for key in keys.split(","):
@@ -150,7 +159,7 @@ class Config:
if self.enable_config_example:
with open("config-example.json", "w") as f:
data = asdict(self)
- json.dump(data, f, ensure_ascii=False, indent=4)
+ json.dump(data, f, ensure_ascii=False, indent=2)
@classmethod
def from_options(cls, options: argparse.Namespace) -> Config:
@@ -171,6 +180,10 @@ class Config:
converted_value = False
if str(v).lower() == "true":
converted_value = True
+ elif expected_type == dict[str, Device]:
+ converted_value = {}
+ for kk, vv in v.items():
+ converted_value[kk] = Device(**vv)
else:
converted_value = expected_type(v)
return converted_value
@@ -192,7 +205,7 @@ class Config:
return result
def update_config(self, data):
- type_hints = get_type_hints(self)
+ type_hints = get_type_hints(self, globals(), locals())
for k, v in data.items():
converted_value = self.convert_value(k, v, type_hints)
diff --git a/xiaomusic/const.py b/xiaomusic/const.py
index e8d115c..570952d 100644
--- a/xiaomusic/const.py
+++ b/xiaomusic/const.py
@@ -9,3 +9,13 @@ SUPPORT_MUSIC_TYPE = [
LATEST_ASK_API = "https://userprofile.mina.mi.com/device_profile/v2/conversation?source=dialogu&hardware={hardware}×tamp={timestamp}&limit=2"
COOKIE_TEMPLATE = "deviceId={device_id}; serviceToken={service_token}; userId={user_id}"
+
+PLAY_TYPE_ONE = 0 # 单曲循环
+PLAY_TYPE_ALL = 1 # 全部循环
+PLAY_TYPE_RND = 2 # 随机播放
+
+PLAY_TYPE_TTS = {
+ PLAY_TYPE_ONE: "已经设置为单曲循环",
+ PLAY_TYPE_ALL: "已经设置为全部循环",
+ PLAY_TYPE_RND: "已经设置为随机播放",
+}
diff --git a/xiaomusic/httpserver.py b/xiaomusic/httpserver.py
index 3503f9f..21e7dcc 100644
--- a/xiaomusic/httpserver.py
+++ b/xiaomusic/httpserver.py
@@ -53,11 +53,29 @@ def getversion():
@app.route("/getvolume", methods=["GET"])
@auth.login_required
-def getvolume():
- volume = xiaomusic.get_volume_ret()
- return {
- "volume": volume,
- }
+async def getvolume():
+ did = request.args.get("did")
+ if not xiaomusic.did_exist(did):
+ return {"volume": 0}
+
+ volume = await xiaomusic.call_main_thread_function(xiaomusic.get_volume, did=did)
+ return {"volume": volume}
+
+
+@app.route("/setvolume", methods=["POST"])
+@auth.login_required
+async def setvolume():
+ data = request.get_json()
+ did = data.get("did")
+ volume = data.get("volume")
+ if not xiaomusic.did_exist(did):
+ return {"ret": "Did not exist"}
+
+ log.info(f"set_volume {did} {volume}")
+ await xiaomusic.call_main_thread_function(
+ xiaomusic.set_volume, did=did, arg1=volume
+ )
+ return {"ret": "OK", "volume": volume}
@app.route("/searchmusic", methods=["GET"])
@@ -70,13 +88,19 @@ def searchmusic():
@app.route("/playingmusic", methods=["GET"])
@auth.login_required
def playingmusic():
- return xiaomusic.playingmusic()
+ did = request.args.get("did")
+ if not xiaomusic.did_exist(did):
+ return ""
+ return xiaomusic.playingmusic(did)
@app.route("/isplaying", methods=["GET"])
@auth.login_required
def isplaying():
- return xiaomusic.isplaying()
+ did = request.args.get("did")
+ if not xiaomusic.did_exist(did):
+ return False
+ return xiaomusic.isplaying(did)
@app.route("/", methods=["GET"])
@@ -88,10 +112,14 @@ def index():
@auth.login_required
async def do_cmd():
data = request.get_json()
+ did = data.get("did")
cmd = data.get("cmd")
+ if not xiaomusic.did_exist(did):
+ return {"ret": "Did not exist"}
+
if len(cmd) > 0:
- log.debug("docmd. cmd:%s", cmd)
- xiaomusic.set_last_record(cmd)
+ log.info(f"docmd. did:{did} cmd:{cmd}")
+ xiaomusic.set_last_record(did, cmd)
return {"ret": "OK"}
return {"ret": "Unknow cmd"}
@@ -101,10 +129,9 @@ async def do_cmd():
async def getsetting():
config = xiaomusic.getconfig()
data = asdict(config)
- alldevices = await xiaomusic.call_main_thread_function(xiaomusic.getalldevices)
- log.info(f"getsetting alldevices: {alldevices}")
- data["mi_did_list"] = alldevices["did_list"]
- data["mi_hardware_list"] = alldevices["hardware_list"]
+ device_list = await xiaomusic.call_main_thread_function(xiaomusic.getalldevices)
+ log.info(f"getsetting device_list: {device_list}")
+ data["device_list"] = device_list
return data
@@ -127,7 +154,10 @@ async def musiclist():
@app.route("/curplaylist", methods=["GET"])
@auth.login_required
async def curplaylist():
- return xiaomusic.get_cur_play_list()
+ did = request.args.get("did")
+ if not xiaomusic.did_exist(did):
+ return ""
+ return xiaomusic.get_cur_play_list(did)
@app.route("/delmusic", methods=["POST"])
@@ -149,7 +179,7 @@ def downloadjson():
ret = "OK"
content = downloadfile(url)
except Exception as e:
- log.warning(f"downloadjson failed. url:{url} e:{e}")
+ log.exception(f"Execption {e}")
ret = "Download JSON file failed."
return {
"ret": ret,
@@ -166,9 +196,15 @@ def downloadlog():
@app.route("/playurl", methods=["GET"])
@auth.login_required
async def playurl():
+ did = request.args.get("did")
url = request.args.get("url")
- log.info(f"play_url:{url}")
- return await xiaomusic.call_main_thread_function(xiaomusic.play_url, arg1=url)
+ if not xiaomusic.did_exist(did):
+ return {"ret": "Did not exist"}
+
+ log.info(f"playurl did: {did} url: {url}")
+ return await xiaomusic.call_main_thread_function(
+ xiaomusic.play_url, did=did, arg1=url
+ )
@app.route("/debug_play_by_music_url", methods=["POST"])
diff --git a/xiaomusic/static/app.js b/xiaomusic/static/app.js
index e3d9fc7..70556d8 100644
--- a/xiaomusic/static/app.js
+++ b/xiaomusic/static/app.js
@@ -13,11 +13,40 @@ $(function(){
append_op_button_name("30分钟后关机");
append_op_button_name("60分钟后关机");
- // 拉取声音
- sendcmd("get_volume#");
- $.get("/getvolume", function(data, status) {
- console.log(data, status, data["volume"]);
- $("#volume").val(data.volume);
+ // 拉取现有配置
+ $.get("/getsetting", function(data, status) {
+ console.log(data, status);
+ localStorage.setItem('mi_did', data.mi_did);
+
+ var did = localStorage.getItem('cur_did');
+ if ((did == null || did == "") && data.mi_did != null) {
+ var dids = data.mi_did.split(',');
+ did = dids[0];
+ localStorage.setItem('cur_did', did);
+ }
+
+ window.did = did;
+ $.get(`/getvolume?did=${did}`, function(data, status) {
+ console.log(data, status, data["volume"]);
+ $("#volume").val(data.volume);
+ });
+ refresh_music_list();
+
+ $("#did").empty();
+ var dids = data.mi_did.split(',');
+ $.each(dids, function(index, value) {
+ var device = data.device_list.find(function(device) {
+ return device.miotDID == value;
+ });
+
+ if (device) {
+ var option = $('')
+ .val(value)
+ .text(device.name)
+ .prop('selected', value === did);
+ $("#did").append(option);
+ }
+ });
});
// 拉取版本
@@ -47,13 +76,20 @@ $(function(){
$('#music_list').trigger('change');
// 获取当前播放列表
- $.get("curplaylist", function(data, status) {
- $('#music_list').val(data);
- $('#music_list').trigger('change');
+ $.get(`curplaylist?did=${did}`, function(data, status) {
+ if (data != "") {
+ $('#music_list').val(data);
+ $('#music_list').trigger('change');
+ }
})
})
+
+ // 每3秒获取下正在播放的音乐
+ get_playing_music();
+ setInterval(() => {
+ get_playing_music();
+ }, 3000);
}
- refresh_music_list();
$("#play_music_list").on("click", () => {
var music_list = $("#music_list").val();
@@ -84,7 +120,7 @@ $(function(){
$("#playurl").on("click", () => {
var url = $("#music-url").val();
- $.get(`/playurl?url=${url}`, function(data, status) {
+ $.get(`/playurl?url=${url}&did=${did}`, function(data, status) {
console.log(data);
});
});
@@ -115,9 +151,18 @@ $(function(){
sendcmd(cmd);
});
- $("#volume").on('input', function () {
+ $("#volume").on('change', function () {
var value = $(this).val();
- sendcmd("set_volume#"+value);
+ $.ajax({
+ type: "POST",
+ url: "/setvolume",
+ contentType: "application/json; charset=utf-8",
+ data: JSON.stringify({did: did, volume: value}),
+ success: () => {
+ },
+ error: () => {
+ }
+ });
});
function sendcmd(cmd) {
@@ -125,7 +170,7 @@ $(function(){
type: "POST",
url: "/cmd",
contentType: "application/json; charset=utf-8",
- data: JSON.stringify({cmd: cmd}),
+ data: JSON.stringify({did: did, cmd: cmd}),
success: () => {
if (cmd == "刷新列表") {
setTimeout(refresh_music_list, 3000);
@@ -160,18 +205,12 @@ $(function(){
});
function get_playing_music() {
- $.get("/playingmusic", function(data, status) {
+ $.get(`/playingmusic?did=${did}`, function(data, status) {
console.log(data);
$("#playering-music").text(data);
});
}
- // 每3秒获取下正在播放的音乐
- get_playing_music();
- setInterval(() => {
- get_playing_music();
- }, 3000);
-
function custom_sort_key(a, b) {
// 使用正则表达式提取数字前缀
const numericPrefixA = a.match(/^(\d+)/) ? parseInt(a.match(/^(\d+)/)[1], 10) : null;
diff --git a/xiaomusic/static/index.html b/xiaomusic/static/index.html
index aaf17f6..d5cd80e 100644
--- a/xiaomusic/static/index.html
+++ b/xiaomusic/static/index.html
@@ -6,6 +6,12 @@
+
+
+
+
小爱音箱操控面板
@@ -14,6 +20,8 @@
)
+
diff --git a/xiaomusic/static/setting.html b/xiaomusic/static/setting.html
index 767084b..010e8a8 100644
--- a/xiaomusic/static/setting.html
+++ b/xiaomusic/static/setting.html
@@ -24,8 +24,8 @@ var vConsole = new window.VConsole();
-
-
diff --git a/xiaomusic/static/setting.js b/xiaomusic/static/setting.js
index bd7d022..2982d48 100644
--- a/xiaomusic/static/setting.js
+++ b/xiaomusic/static/setting.js
@@ -16,23 +16,22 @@ $(function(){
});
};
- function updateCheckbox(selector, mi_did_list, mi_did, mi_hardware_list) {
+ function updateCheckbox(selector, mi_did, device_list) {
// 清除现有的内容
$(selector).empty();
// 将 mi_did 字符串通过逗号分割转换为数组,以便于判断默认选中项
var selected_dids = mi_did.split(',');
- // 遍历传入的 mi_did_list 和 mi_hardware_list
- $.each(mi_did_list, function(index, did) {
- // 获取硬件标识,假定列表是一一对应的
- var hardware = mi_hardware_list[index];
-
+ $.each(device_list, function(index, device) {
+ var did = device.miotDID;
+ var hardware = device.hardware;
+ var name = device.name;
// 创建复选框元素
var checkbox = $('
', {
type: 'checkbox',
id: did,
- value: `${did}|${hardware}`,
+ value: `${did}`,
class: 'custom-checkbox', // 添加样式类
// 如果mi_did中包含了该did,则默认选中
checked: selected_dids.indexOf(did) !== -1
@@ -42,7 +41,7 @@ $(function(){
var label = $('