diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 4e5d6a5..97e0311 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -31,13 +31,28 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: '20' + - name: Install dependencies + working-directory: ./docs # 指定工作目录为 docs + run: | + npm install + + - name: Build VitePress + working-directory: ./docs # 指定工作目录为 docs + run: | + npm run docs:build + - name: Setup Pages uses: actions/configure-pages@v5 - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: # Upload entire repository - path: 'docs/.vitepress/dist' + path: './docs/.vitepress/dist' - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index b5c37be..a311932 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -1,59 +1,69 @@ -import { defineConfig } from 'vitepress' +import { loadEnv, defineConfig } from 'vitepress' import AutoSidebar from 'vite-plugin-vitepress-auto-sidebar'; import GitHubIssuesPlugin from './vitepress-plugin-github-issues.mts'; -// https://vitepress.dev/reference/site-config -export default defineConfig({ - title: "XiaoMusic", - description: "XiaoMusic doc", - themeConfig: { - // https://vitepress.dev/reference/default-theme-config - nav: [ - { text: 'Home', link: '/' }, - { text: 'Examples', link: '/markdown-examples' } - ], +export default async ({ mode }) => { + const env = loadEnv(mode || '', process.cwd()) + return defineConfig({ + title: "XiaoMusic", + description: "XiaoMusic doc", + themeConfig: { + // https://vitepress.dev/reference/default-theme-config + nav: [ + { text: 'Guide', link: '/issues' }, + { text: 'Admin', link: 'https://x.hanxi.cc' }, + ], - socialLinks: [ - { icon: 'github', link: 'https://github.com/hanxi/xiaomusic' } - ] - }, - sitemap: { - hostname: 'https://docs.x.hanxi.cc' - }, - lastUpdated: true, - markdown: { - lineNumbers: false, // 关闭代码块行号显示 - // 自定义 markdown-it 插件 - config: (md) => { - md.renderer.rules.link_open = (tokens, idx, options, env, self) => { - const aIndex = tokens[idx].attrIndex('target'); - if (aIndex < 0) { - tokens[idx].attrPush(['target', '_self']); // 将默认行为改为不使用 _blank - } else { - tokens[idx].attrs![aIndex][1] = '_self'; // 替换 _blank 为 _self - } - return self.renderToken(tokens, idx, options); - }; + socialLinks: [ + { icon: 'github', link: 'https://github.com/hanxi/xiaomusic' } + ], + + footer: { + message: '基于 MIT 许可发布', + copyright: `版权所有 © 2023-${new Date().getFullYear()} 涵曦` + }, }, - }, - logLevel: 'warn', - vite:{ - plugins: [ - AutoSidebar({ - path:'.', - collapsed: true, - titleFromFile: true, - }), - GitHubIssuesPlugin({ - repo: 'hanxi/xiaomusic', - token: '', - replaceRules:[ - { - baseUrl: 'https://github.com/hanxi/xiaomusic/issues', - targetUrl: '/issues', - }, - ], - }), + sitemap: { + hostname: 'https://xdocs.hanxi.cc' + }, + head: [ + ['script', { defer: true, src: 'https://umami.hanxi.cc/script.js', 'data-website-id': '29cca3f5-e420-432b-adc7-8a1325d31c68' }] ], - } -}) + lastUpdated: true, + markdown: { + lineNumbers: false, // 关闭代码块行号显示 + // 自定义 markdown-it 插件 + config: (md) => { + md.renderer.rules.link_open = (tokens, idx, options, env, self) => { + const aIndex = tokens[idx].attrIndex('target'); + if (aIndex < 0) { + tokens[idx].attrPush(['target', '_self']); // 将默认行为改为不使用 _blank + } else { + tokens[idx].attrs![aIndex][1] = '_self'; // 替换 _blank 为 _self + } + return self.renderToken(tokens, idx, options); + }; + }, + }, + logLevel: 'warn', + vite:{ + plugins: [ + AutoSidebar({ + path:'.', + collapsed: true, + titleFromFile: true, + }), + GitHubIssuesPlugin({ + repo: 'hanxi/xiaomusic', + token: env.VITE_GITHUB_ISSUES_TOKEN, + replaceRules:[ + { + baseUrl: 'https://github.com/hanxi/xiaomusic/issues', + targetUrl: '/issues', + }, + ], + }), + ], + } + }) +} diff --git a/docs/.vitepress/dist/404.html b/docs/.vitepress/dist/404.html deleted file mode 100644 index a6365b8..0000000 --- a/docs/.vitepress/dist/404.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - 404 | XiaoMusic - - - - - - - - - - - -
- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/assets/app.BW3PQTBv.js b/docs/.vitepress/dist/assets/app.BW3PQTBv.js deleted file mode 100644 index bffb983..0000000 --- a/docs/.vitepress/dist/assets/app.BW3PQTBv.js +++ /dev/null @@ -1 +0,0 @@ -import{t as i}from"./chunks/theme.CHcreDy7.js";import{R as o,a1 as u,a2 as c,a3 as l,a4 as f,a5 as d,a6 as m,a7 as h,a8 as g,a9 as A,aa as v,d as P,u as y,v as C,s as b,ab as w,ac as R,ad as E,ae as S}from"./chunks/framework.p2VkXzrt.js";function p(e){if(e.extends){const a=p(e.extends);return{...a,...e,async enhanceApp(t){a.enhanceApp&&await a.enhanceApp(t),e.enhanceApp&&await e.enhanceApp(t)}}}return e}const s=p(i),T=P({name:"VitePressApp",setup(){const{site:e,lang:a,dir:t}=y();return C(()=>{b(()=>{document.documentElement.lang=a.value,document.documentElement.dir=t.value})}),e.value.router.prefetchLinks&&w(),R(),E(),s.setup&&s.setup(),()=>S(s.Layout)}});async function D(){globalThis.__VITEPRESS__=!0;const e=j(),a=_();a.provide(c,e);const t=l(e.route);return a.provide(f,t),a.component("Content",d),a.component("ClientOnly",m),Object.defineProperties(a.config.globalProperties,{$frontmatter:{get(){return t.frontmatter.value}},$params:{get(){return t.page.value.params}}}),s.enhanceApp&&await s.enhanceApp({app:a,router:e,siteData:h}),{app:a,router:e,data:t}}function _(){return g(T)}function j(){let e=o,a;return A(t=>{let n=v(t),r=null;return n&&(e&&(a=n),(e||a===n)&&(n=n.replace(/\.js$/,".lean.js")),r=import(n)),o&&(e=!1),r},s.NotFound)}o&&D().then(({app:e,router:a,data:t})=>{a.go().then(()=>{u(a.route,t.site),e.mount("#app")})});export{D as createApp}; diff --git a/docs/.vitepress/dist/assets/chunks/framework.p2VkXzrt.js b/docs/.vitepress/dist/assets/chunks/framework.p2VkXzrt.js deleted file mode 100644 index 352c876..0000000 --- a/docs/.vitepress/dist/assets/chunks/framework.p2VkXzrt.js +++ /dev/null @@ -1,18 +0,0 @@ -/** -* @vue/shared v3.5.13 -* (c) 2018-present Yuxi (Evan) You and Vue contributors -* @license MIT -**//*! #__NO_SIDE_EFFECTS__ */function Es(e){const t=Object.create(null);for(const n of e.split(","))t[n]=1;return n=>n in t}const te={},xt=[],Ue=()=>{},xo=()=>!1,Xt=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),Cs=e=>e.startsWith("onUpdate:"),le=Object.assign,Ts=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},So=Object.prototype.hasOwnProperty,z=(e,t)=>So.call(e,t),B=Array.isArray,St=e=>En(e)==="[object Map]",Dr=e=>En(e)==="[object Set]",q=e=>typeof e=="function",re=e=>typeof e=="string",Ge=e=>typeof e=="symbol",ne=e=>e!==null&&typeof e=="object",jr=e=>(ne(e)||q(e))&&q(e.then)&&q(e.catch),Vr=Object.prototype.toString,En=e=>Vr.call(e),Eo=e=>En(e).slice(8,-1),Ur=e=>En(e)==="[object Object]",As=e=>re(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,Et=Es(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),Cn=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},Co=/-(\w)/g,Le=Cn(e=>e.replace(Co,(t,n)=>n?n.toUpperCase():"")),To=/\B([A-Z])/g,st=Cn(e=>e.replace(To,"-$1").toLowerCase()),Tn=Cn(e=>e.charAt(0).toUpperCase()+e.slice(1)),dn=Cn(e=>e?`on${Tn(e)}`:""),et=(e,t)=>!Object.is(e,t),kn=(e,...t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:s,value:n})},Ao=e=>{const t=parseFloat(e);return isNaN(t)?e:t},Ro=e=>{const t=re(e)?Number(e):NaN;return isNaN(t)?e:t};let Ys;const An=()=>Ys||(Ys=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Rs(e){if(B(e)){const t={};for(let n=0;n{if(n){const s=n.split(Mo);s.length>1&&(t[s[0].trim()]=s[1].trim())}}),t}function Os(e){let t="";if(re(e))t=e;else if(B(e))for(let n=0;n!!(e&&e.__v_isRef===!0),Fo=e=>re(e)?e:e==null?"":B(e)||ne(e)&&(e.toString===Vr||!q(e.toString))?Wr(e)?Fo(e.value):JSON.stringify(e,Kr,2):String(e),Kr=(e,t)=>Wr(t)?Kr(e,t.value):St(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[s,r],i)=>(n[Wn(s,i)+" =>"]=r,n),{})}:Dr(t)?{[`Set(${t.size})`]:[...t.values()].map(n=>Wn(n))}:Ge(t)?Wn(t):ne(t)&&!B(t)&&!Ur(t)?String(t):t,Wn=(e,t="")=>{var n;return Ge(e)?`Symbol(${(n=e.description)!=null?n:t})`:e};/** -* @vue/reactivity v3.5.13 -* (c) 2018-present Yuxi (Evan) You and Vue contributors -* @license MIT -**/let be;class Ho{constructor(t=!1){this.detached=t,this._active=!0,this.effects=[],this.cleanups=[],this._isPaused=!1,this.parent=be,!t&&be&&(this.index=(be.scopes||(be.scopes=[])).push(this)-1)}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,n;if(this.scopes)for(t=0,n=this.scopes.length;t0)return;if(Ht){let t=Ht;for(Ht=void 0;t;){const n=t.next;t.next=void 0,t.flags&=-9,t=n}}let e;for(;Ft;){let t=Ft;for(Ft=void 0;t;){const n=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(s){e||(e=s)}t=n}}if(e)throw e}function Jr(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function zr(e){let t,n=e.depsTail,s=n;for(;s;){const r=s.prevDep;s.version===-1?(s===n&&(n=r),Ls(s),Do(s)):t=s,s.dep.activeLink=s.prevActiveLink,s.prevActiveLink=void 0,s=r}e.deps=t,e.depsTail=n}function fs(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(Qr(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function Qr(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===Vt))return;e.globalVersion=Vt;const t=e.dep;if(e.flags|=2,t.version>0&&!e.isSSR&&e.deps&&!fs(e)){e.flags&=-3;return}const n=ee,s=Ne;ee=e,Ne=!0;try{Jr(e);const r=e.fn(e._value);(t.version===0||et(r,e._value))&&(e._value=r,t.version++)}catch(r){throw t.version++,r}finally{ee=n,Ne=s,zr(e),e.flags&=-3}}function Ls(e,t=!1){const{dep:n,prevSub:s,nextSub:r}=e;if(s&&(s.nextSub=r,e.prevSub=void 0),r&&(r.prevSub=s,e.nextSub=void 0),n.subs===e&&(n.subs=s,!s&&n.computed)){n.computed.flags&=-5;for(let i=n.computed.deps;i;i=i.nextDep)Ls(i,!0)}!t&&!--n.sc&&n.map&&n.map.delete(n.key)}function Do(e){const{prevDep:t,nextDep:n}=e;t&&(t.nextDep=n,e.prevDep=void 0),n&&(n.prevDep=t,e.nextDep=void 0)}let Ne=!0;const Zr=[];function rt(){Zr.push(Ne),Ne=!1}function it(){const e=Zr.pop();Ne=e===void 0?!0:e}function Xs(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const n=ee;ee=void 0;try{t()}finally{ee=n}}}let Vt=0;class jo{constructor(t,n){this.sub=t,this.dep=n,this.version=n.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class Rn{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0}track(t){if(!ee||!Ne||ee===this.computed)return;let n=this.activeLink;if(n===void 0||n.sub!==ee)n=this.activeLink=new jo(ee,this),ee.deps?(n.prevDep=ee.depsTail,ee.depsTail.nextDep=n,ee.depsTail=n):ee.deps=ee.depsTail=n,ei(n);else if(n.version===-1&&(n.version=this.version,n.nextDep)){const s=n.nextDep;s.prevDep=n.prevDep,n.prevDep&&(n.prevDep.nextDep=s),n.prevDep=ee.depsTail,n.nextDep=void 0,ee.depsTail.nextDep=n,ee.depsTail=n,ee.deps===n&&(ee.deps=s)}return n}trigger(t){this.version++,Vt++,this.notify(t)}notify(t){Ms();try{for(let n=this.subs;n;n=n.prevSub)n.sub.notify()&&n.sub.dep.notify()}finally{Is()}}}function ei(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let s=t.deps;s;s=s.nextDep)ei(s)}const n=e.dep.subs;n!==e&&(e.prevSub=n,n&&(n.nextSub=e)),e.dep.subs=e}}const yn=new WeakMap,dt=Symbol(""),us=Symbol(""),Ut=Symbol("");function ge(e,t,n){if(Ne&&ee){let s=yn.get(e);s||yn.set(e,s=new Map);let r=s.get(n);r||(s.set(n,r=new Rn),r.map=s,r.key=n),r.track()}}function Ke(e,t,n,s,r,i){const o=yn.get(e);if(!o){Vt++;return}const l=c=>{c&&c.trigger()};if(Ms(),t==="clear")o.forEach(l);else{const c=B(e),u=c&&As(n);if(c&&n==="length"){const a=Number(s);o.forEach((h,v)=>{(v==="length"||v===Ut||!Ge(v)&&v>=a)&&l(h)})}else switch((n!==void 0||o.has(void 0))&&l(o.get(n)),u&&l(o.get(Ut)),t){case"add":c?u&&l(o.get("length")):(l(o.get(dt)),St(e)&&l(o.get(us)));break;case"delete":c||(l(o.get(dt)),St(e)&&l(o.get(us)));break;case"set":St(e)&&l(o.get(dt));break}}Is()}function Vo(e,t){const n=yn.get(e);return n&&n.get(t)}function _t(e){const t=J(e);return t===e?t:(ge(t,"iterate",Ut),Ie(e)?t:t.map(me))}function On(e){return ge(e=J(e),"iterate",Ut),e}const Uo={__proto__:null,[Symbol.iterator](){return qn(this,Symbol.iterator,me)},concat(...e){return _t(this).concat(...e.map(t=>B(t)?_t(t):t))},entries(){return qn(this,"entries",e=>(e[1]=me(e[1]),e))},every(e,t){return Be(this,"every",e,t,void 0,arguments)},filter(e,t){return Be(this,"filter",e,t,n=>n.map(me),arguments)},find(e,t){return Be(this,"find",e,t,me,arguments)},findIndex(e,t){return Be(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){return Be(this,"findLast",e,t,me,arguments)},findLastIndex(e,t){return Be(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){return Be(this,"forEach",e,t,void 0,arguments)},includes(...e){return Gn(this,"includes",e)},indexOf(...e){return Gn(this,"indexOf",e)},join(e){return _t(this).join(e)},lastIndexOf(...e){return Gn(this,"lastIndexOf",e)},map(e,t){return Be(this,"map",e,t,void 0,arguments)},pop(){return Lt(this,"pop")},push(...e){return Lt(this,"push",e)},reduce(e,...t){return Js(this,"reduce",e,t)},reduceRight(e,...t){return Js(this,"reduceRight",e,t)},shift(){return Lt(this,"shift")},some(e,t){return Be(this,"some",e,t,void 0,arguments)},splice(...e){return Lt(this,"splice",e)},toReversed(){return _t(this).toReversed()},toSorted(e){return _t(this).toSorted(e)},toSpliced(...e){return _t(this).toSpliced(...e)},unshift(...e){return Lt(this,"unshift",e)},values(){return qn(this,"values",me)}};function qn(e,t,n){const s=On(e),r=s[t]();return s!==e&&!Ie(e)&&(r._next=r.next,r.next=()=>{const i=r._next();return i.value&&(i.value=n(i.value)),i}),r}const Bo=Array.prototype;function Be(e,t,n,s,r,i){const o=On(e),l=o!==e&&!Ie(e),c=o[t];if(c!==Bo[t]){const h=c.apply(e,i);return l?me(h):h}let u=n;o!==e&&(l?u=function(h,v){return n.call(this,me(h),v,e)}:n.length>2&&(u=function(h,v){return n.call(this,h,v,e)}));const a=c.call(o,u,s);return l&&r?r(a):a}function Js(e,t,n,s){const r=On(e);let i=n;return r!==e&&(Ie(e)?n.length>3&&(i=function(o,l,c){return n.call(this,o,l,c,e)}):i=function(o,l,c){return n.call(this,o,me(l),c,e)}),r[t](i,...s)}function Gn(e,t,n){const s=J(e);ge(s,"iterate",Ut);const r=s[t](...n);return(r===-1||r===!1)&&Fs(n[0])?(n[0]=J(n[0]),s[t](...n)):r}function Lt(e,t,n=[]){rt(),Ms();const s=J(e)[t].apply(e,n);return Is(),it(),s}const ko=Es("__proto__,__v_isRef,__isVue"),ti=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Ge));function Wo(e){Ge(e)||(e=String(e));const t=J(this);return ge(t,"has",e),t.hasOwnProperty(e)}class ni{constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}get(t,n,s){if(n==="__v_skip")return t.__v_skip;const r=this._isReadonly,i=this._isShallow;if(n==="__v_isReactive")return!r;if(n==="__v_isReadonly")return r;if(n==="__v_isShallow")return i;if(n==="__v_raw")return s===(r?i?el:oi:i?ii:ri).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(s)?t:void 0;const o=B(t);if(!r){let c;if(o&&(c=Uo[n]))return c;if(n==="hasOwnProperty")return Wo}const l=Reflect.get(t,n,ce(t)?t:s);return(Ge(n)?ti.has(n):ko(n))||(r||ge(t,"get",n),i)?l:ce(l)?o&&As(n)?l:l.value:ne(l)?r?In(l):Mn(l):l}}class si extends ni{constructor(t=!1){super(!1,t)}set(t,n,s,r){let i=t[n];if(!this._isShallow){const c=yt(i);if(!Ie(s)&&!yt(s)&&(i=J(i),s=J(s)),!B(t)&&ce(i)&&!ce(s))return c?!1:(i.value=s,!0)}const o=B(t)&&As(n)?Number(n)e,en=e=>Reflect.getPrototypeOf(e);function Xo(e,t,n){return function(...s){const r=this.__v_raw,i=J(r),o=St(i),l=e==="entries"||e===Symbol.iterator&&o,c=e==="keys"&&o,u=r[e](...s),a=n?ds:t?hs:me;return!t&&ge(i,"iterate",c?us:dt),{next(){const{value:h,done:v}=u.next();return v?{value:h,done:v}:{value:l?[a(h[0]),a(h[1])]:a(h),done:v}},[Symbol.iterator](){return this}}}}function tn(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function Jo(e,t){const n={get(r){const i=this.__v_raw,o=J(i),l=J(r);e||(et(r,l)&&ge(o,"get",r),ge(o,"get",l));const{has:c}=en(o),u=t?ds:e?hs:me;if(c.call(o,r))return u(i.get(r));if(c.call(o,l))return u(i.get(l));i!==o&&i.get(r)},get size(){const r=this.__v_raw;return!e&&ge(J(r),"iterate",dt),Reflect.get(r,"size",r)},has(r){const i=this.__v_raw,o=J(i),l=J(r);return e||(et(r,l)&&ge(o,"has",r),ge(o,"has",l)),r===l?i.has(r):i.has(r)||i.has(l)},forEach(r,i){const o=this,l=o.__v_raw,c=J(l),u=t?ds:e?hs:me;return!e&&ge(c,"iterate",dt),l.forEach((a,h)=>r.call(i,u(a),u(h),o))}};return le(n,e?{add:tn("add"),set:tn("set"),delete:tn("delete"),clear:tn("clear")}:{add(r){!t&&!Ie(r)&&!yt(r)&&(r=J(r));const i=J(this);return en(i).has.call(i,r)||(i.add(r),Ke(i,"add",r,r)),this},set(r,i){!t&&!Ie(i)&&!yt(i)&&(i=J(i));const o=J(this),{has:l,get:c}=en(o);let u=l.call(o,r);u||(r=J(r),u=l.call(o,r));const a=c.call(o,r);return o.set(r,i),u?et(i,a)&&Ke(o,"set",r,i):Ke(o,"add",r,i),this},delete(r){const i=J(this),{has:o,get:l}=en(i);let c=o.call(i,r);c||(r=J(r),c=o.call(i,r)),l&&l.call(i,r);const u=i.delete(r);return c&&Ke(i,"delete",r,void 0),u},clear(){const r=J(this),i=r.size!==0,o=r.clear();return i&&Ke(r,"clear",void 0,void 0),o}}),["keys","values","entries",Symbol.iterator].forEach(r=>{n[r]=Xo(r,e,t)}),n}function Ps(e,t){const n=Jo(e,t);return(s,r,i)=>r==="__v_isReactive"?!e:r==="__v_isReadonly"?e:r==="__v_raw"?s:Reflect.get(z(n,r)&&r in s?n:s,r,i)}const zo={get:Ps(!1,!1)},Qo={get:Ps(!1,!0)},Zo={get:Ps(!0,!1)};const ri=new WeakMap,ii=new WeakMap,oi=new WeakMap,el=new WeakMap;function tl(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function nl(e){return e.__v_skip||!Object.isExtensible(e)?0:tl(Eo(e))}function Mn(e){return yt(e)?e:Ns(e,!1,qo,zo,ri)}function sl(e){return Ns(e,!1,Yo,Qo,ii)}function In(e){return Ns(e,!0,Go,Zo,oi)}function Ns(e,t,n,s,r){if(!ne(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const i=r.get(e);if(i)return i;const o=nl(e);if(o===0)return e;const l=new Proxy(e,o===2?s:n);return r.set(e,l),l}function ht(e){return yt(e)?ht(e.__v_raw):!!(e&&e.__v_isReactive)}function yt(e){return!!(e&&e.__v_isReadonly)}function Ie(e){return!!(e&&e.__v_isShallow)}function Fs(e){return e?!!e.__v_raw:!1}function J(e){const t=e&&e.__v_raw;return t?J(t):e}function hn(e){return!z(e,"__v_skip")&&Object.isExtensible(e)&&Br(e,"__v_skip",!0),e}const me=e=>ne(e)?Mn(e):e,hs=e=>ne(e)?In(e):e;function ce(e){return e?e.__v_isRef===!0:!1}function ue(e){return ci(e,!1)}function li(e){return ci(e,!0)}function ci(e,t){return ce(e)?e:new rl(e,t)}class rl{constructor(t,n){this.dep=new Rn,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=n?t:J(t),this._value=n?t:me(t),this.__v_isShallow=n}get value(){return this.dep.track(),this._value}set value(t){const n=this._rawValue,s=this.__v_isShallow||Ie(t)||yt(t);t=s?t:J(t),et(t,n)&&(this._rawValue=t,this._value=s?t:me(t),this.dep.trigger())}}function ai(e){return ce(e)?e.value:e}const il={get:(e,t,n)=>t==="__v_raw"?e:ai(Reflect.get(e,t,n)),set:(e,t,n,s)=>{const r=e[t];return ce(r)&&!ce(n)?(r.value=n,!0):Reflect.set(e,t,n,s)}};function fi(e){return ht(e)?e:new Proxy(e,il)}class ol{constructor(t){this.__v_isRef=!0,this._value=void 0;const n=this.dep=new Rn,{get:s,set:r}=t(n.track.bind(n),n.trigger.bind(n));this._get=s,this._set=r}get value(){return this._value=this._get()}set value(t){this._set(t)}}function ll(e){return new ol(e)}class cl{constructor(t,n,s){this._object=t,this._key=n,this._defaultValue=s,this.__v_isRef=!0,this._value=void 0}get value(){const t=this._object[this._key];return this._value=t===void 0?this._defaultValue:t}set value(t){this._object[this._key]=t}get dep(){return Vo(J(this._object),this._key)}}class al{constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isReadonly=!0,this._value=void 0}get value(){return this._value=this._getter()}}function fl(e,t,n){return ce(e)?e:q(e)?new al(e):ne(e)&&arguments.length>1?ul(e,t,n):ue(e)}function ul(e,t,n){const s=e[t];return ce(s)?s:new cl(e,t,n)}class dl{constructor(t,n,s){this.fn=t,this.setter=n,this._value=void 0,this.dep=new Rn(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=Vt-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!n,this.isSSR=s}notify(){if(this.flags|=16,!(this.flags&8)&&ee!==this)return Xr(this,!0),!0}get value(){const t=this.dep.track();return Qr(this),t&&(t.version=this.dep.version),this._value}set value(t){this.setter&&this.setter(t)}}function hl(e,t,n=!1){let s,r;return q(e)?s=e:(s=e.get,r=e.set),new dl(s,r,n)}const nn={},vn=new WeakMap;let ft;function pl(e,t=!1,n=ft){if(n){let s=vn.get(n);s||vn.set(n,s=[]),s.push(e)}}function gl(e,t,n=te){const{immediate:s,deep:r,once:i,scheduler:o,augmentJob:l,call:c}=n,u=p=>r?p:Ie(p)||r===!1||r===0?Ze(p,1):Ze(p);let a,h,v,x,L=!1,O=!1;if(ce(e)?(h=()=>e.value,L=Ie(e)):ht(e)?(h=()=>u(e),L=!0):B(e)?(O=!0,L=e.some(p=>ht(p)||Ie(p)),h=()=>e.map(p=>{if(ce(p))return p.value;if(ht(p))return u(p);if(q(p))return c?c(p,2):p()})):q(e)?t?h=c?()=>c(e,2):e:h=()=>{if(v){rt();try{v()}finally{it()}}const p=ft;ft=a;try{return c?c(e,3,[x]):e(x)}finally{ft=p}}:h=Ue,t&&r){const p=h,A=r===!0?1/0:r;h=()=>Ze(p(),A)}const G=qr(),U=()=>{a.stop(),G&&G.active&&Ts(G.effects,a)};if(i&&t){const p=t;t=(...A)=>{p(...A),U()}}let W=O?new Array(e.length).fill(nn):nn;const g=p=>{if(!(!(a.flags&1)||!a.dirty&&!p))if(t){const A=a.run();if(r||L||(O?A.some((D,j)=>et(D,W[j])):et(A,W))){v&&v();const D=ft;ft=a;try{const j=[A,W===nn?void 0:O&&W[0]===nn?[]:W,x];c?c(t,3,j):t(...j),W=A}finally{ft=D}}}else a.run()};return l&&l(g),a=new Gr(h),a.scheduler=o?()=>o(g,!1):g,x=p=>pl(p,!1,a),v=a.onStop=()=>{const p=vn.get(a);if(p){if(c)c(p,4);else for(const A of p)A();vn.delete(a)}},t?s?g(!0):W=a.run():o?o(g.bind(null,!0),!0):a.run(),U.pause=a.pause.bind(a),U.resume=a.resume.bind(a),U.stop=U,U}function Ze(e,t=1/0,n){if(t<=0||!ne(e)||e.__v_skip||(n=n||new Set,n.has(e)))return e;if(n.add(e),t--,ce(e))Ze(e.value,t,n);else if(B(e))for(let s=0;s{Ze(s,t,n)});else if(Ur(e)){for(const s in e)Ze(e[s],t,n);for(const s of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,s)&&Ze(e[s],t,n)}return e}/** -* @vue/runtime-core v3.5.13 -* (c) 2018-present Yuxi (Evan) You and Vue contributors -* @license MIT -**/function Jt(e,t,n,s){try{return s?e(...s):e()}catch(r){Ln(r,t,n)}}function He(e,t,n,s){if(q(e)){const r=Jt(e,t,n,s);return r&&jr(r)&&r.catch(i=>{Ln(i,t,n)}),r}if(B(e)){const r=[];for(let i=0;i>>1,r=we[s],i=Bt(r);i=Bt(n)?we.push(e):we.splice(yl(t),0,e),e.flags|=1,di()}}function di(){_n||(_n=ui.then(hi))}function vl(e){B(e)?Ct.push(...e):Je&&e.id===-1?Je.splice(wt+1,0,e):e.flags&1||(Ct.push(e),e.flags|=1),di()}function zs(e,t,n=je+1){for(;nBt(n)-Bt(s));if(Ct.length=0,Je){Je.push(...t);return}for(Je=t,wt=0;wte.id==null?e.flags&2?-1:1/0:e.id;function hi(e){try{for(je=0;je{s._d&&ar(-1);const i=wn(t);let o;try{o=e(...r)}finally{wn(i),s._d&&ar(1)}return o};return s._n=!0,s._c=!0,s._d=!0,s}function Ve(e,t,n,s){const r=e.dirs,i=t&&t.dirs;for(let o=0;oe.__isTeleport,ze=Symbol("_leaveCb"),sn=Symbol("_enterCb");function wl(){const e={isMounted:!1,isLeaving:!1,isUnmounting:!1,leavingVNodes:new Map};return Ot(()=>{e.isMounted=!0}),Ei(()=>{e.isUnmounting=!0}),e}const Re=[Function,Array],mi={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:Re,onEnter:Re,onAfterEnter:Re,onEnterCancelled:Re,onBeforeLeave:Re,onLeave:Re,onAfterLeave:Re,onLeaveCancelled:Re,onBeforeAppear:Re,onAppear:Re,onAfterAppear:Re,onAppearCancelled:Re},yi=e=>{const t=e.subTree;return t.component?yi(t.component):t},xl={name:"BaseTransition",props:mi,setup(e,{slots:t}){const n=jn(),s=wl();return()=>{const r=t.default&&bi(t.default(),!0);if(!r||!r.length)return;const i=vi(r),o=J(e),{mode:l}=o;if(s.isLeaving)return Yn(i);const c=Qs(i);if(!c)return Yn(i);let u=ps(c,o,s,n,h=>u=h);c.type!==ye&&kt(c,u);let a=n.subTree&&Qs(n.subTree);if(a&&a.type!==ye&&!ut(c,a)&&yi(n).type!==ye){let h=ps(a,o,s,n);if(kt(a,h),l==="out-in"&&c.type!==ye)return s.isLeaving=!0,h.afterLeave=()=>{s.isLeaving=!1,n.job.flags&8||n.update(),delete h.afterLeave,a=void 0},Yn(i);l==="in-out"&&c.type!==ye?h.delayLeave=(v,x,L)=>{const O=_i(s,a);O[String(a.key)]=a,v[ze]=()=>{x(),v[ze]=void 0,delete u.delayedLeave,a=void 0},u.delayedLeave=()=>{L(),delete u.delayedLeave,a=void 0}}:a=void 0}else a&&(a=void 0);return i}}};function vi(e){let t=e[0];if(e.length>1){for(const n of e)if(n.type!==ye){t=n;break}}return t}const Sl=xl;function _i(e,t){const{leavingVNodes:n}=e;let s=n.get(t.type);return s||(s=Object.create(null),n.set(t.type,s)),s}function ps(e,t,n,s,r){const{appear:i,mode:o,persisted:l=!1,onBeforeEnter:c,onEnter:u,onAfterEnter:a,onEnterCancelled:h,onBeforeLeave:v,onLeave:x,onAfterLeave:L,onLeaveCancelled:O,onBeforeAppear:G,onAppear:U,onAfterAppear:W,onAppearCancelled:g}=t,p=String(e.key),A=_i(n,e),D=(I,_)=>{I&&He(I,s,9,_)},j=(I,_)=>{const P=_[1];D(I,_),B(I)?I.every(b=>b.length<=1)&&P():I.length<=1&&P()},K={mode:o,persisted:l,beforeEnter(I){let _=c;if(!n.isMounted)if(i)_=G||c;else return;I[ze]&&I[ze](!0);const P=A[p];P&&ut(e,P)&&P.el[ze]&&P.el[ze](),D(_,[I])},enter(I){let _=u,P=a,b=h;if(!n.isMounted)if(i)_=U||u,P=W||a,b=g||h;else return;let V=!1;const se=I[sn]=ie=>{V||(V=!0,ie?D(b,[I]):D(P,[I]),K.delayedLeave&&K.delayedLeave(),I[sn]=void 0)};_?j(_,[I,se]):se()},leave(I,_){const P=String(e.key);if(I[sn]&&I[sn](!0),n.isUnmounting)return _();D(v,[I]);let b=!1;const V=I[ze]=se=>{b||(b=!0,_(),se?D(O,[I]):D(L,[I]),I[ze]=void 0,A[P]===e&&delete A[P])};A[P]=e,x?j(x,[I,V]):V()},clone(I){const _=ps(I,t,n,s,r);return r&&r(_),_}};return K}function Yn(e){if(Nn(e))return e=nt(e),e.children=null,e}function Qs(e){if(!Nn(e))return gi(e.type)&&e.children?vi(e.children):e;const{shapeFlag:t,children:n}=e;if(n){if(t&16)return n[0];if(t&32&&q(n.default))return n.default()}}function kt(e,t){e.shapeFlag&6&&e.component?(e.transition=t,kt(e.component.subTree,t)):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function bi(e,t=!1,n){let s=[],r=0;for(let i=0;i1)for(let i=0;iWt(L,t&&(B(t)?t[O]:t),n,s,r));return}if(pt(s)&&!r){s.shapeFlag&512&&s.type.__asyncResolved&&s.component.subTree.component&&Wt(e,t,n,s.component.subTree);return}const i=s.shapeFlag&4?Vs(s.component):s.el,o=r?null:i,{i:l,r:c}=e,u=t&&t.r,a=l.refs===te?l.refs={}:l.refs,h=l.setupState,v=J(h),x=h===te?()=>!1:L=>z(v,L);if(u!=null&&u!==c&&(re(u)?(a[u]=null,x(u)&&(h[u]=null)):ce(u)&&(u.value=null)),q(c))Jt(c,l,12,[o,a]);else{const L=re(c),O=ce(c);if(L||O){const G=()=>{if(e.f){const U=L?x(c)?h[c]:a[c]:c.value;r?B(U)&&Ts(U,i):B(U)?U.includes(i)||U.push(i):L?(a[c]=[i],x(c)&&(h[c]=a[c])):(c.value=[i],e.k&&(a[e.k]=c.value))}else L?(a[c]=o,x(c)&&(h[c]=o)):O&&(c.value=o,e.k&&(a[e.k]=o))};o?(G.id=-1,Te(G,n)):G()}}}let Zs=!1;const bt=()=>{Zs||(console.error("Hydration completed but contains mismatches."),Zs=!0)},El=e=>e.namespaceURI.includes("svg")&&e.tagName!=="foreignObject",Cl=e=>e.namespaceURI.includes("MathML"),rn=e=>{if(e.nodeType===1){if(El(e))return"svg";if(Cl(e))return"mathml"}},on=e=>e.nodeType===8;function Tl(e){const{mt:t,p:n,o:{patchProp:s,createText:r,nextSibling:i,parentNode:o,remove:l,insert:c,createComment:u}}=e,a=(g,p)=>{if(!p.hasChildNodes()){n(null,g,p),bn(),p._vnode=g;return}h(p.firstChild,g,null,null,null),bn(),p._vnode=g},h=(g,p,A,D,j,K=!1)=>{K=K||!!p.dynamicChildren;const I=on(g)&&g.data==="[",_=()=>O(g,p,A,D,j,I),{type:P,ref:b,shapeFlag:V,patchFlag:se}=p;let ie=g.nodeType;p.el=g,se===-2&&(K=!1,p.dynamicChildren=null);let H=null;switch(P){case gt:ie!==3?p.children===""?(c(p.el=r(""),o(g),g),H=g):H=_():(g.data!==p.children&&(bt(),g.data=p.children),H=i(g));break;case ye:W(g)?(H=i(g),U(p.el=g.content.firstChild,g,A)):ie!==8||I?H=_():H=i(g);break;case Dt:if(I&&(g=i(g),ie=g.nodeType),ie===1||ie===3){H=g;const Y=!p.children.length;for(let F=0;F{K=K||!!p.dynamicChildren;const{type:I,props:_,patchFlag:P,shapeFlag:b,dirs:V,transition:se}=p,ie=I==="input"||I==="option";if(ie||P!==-1){V&&Ve(p,null,A,"created");let H=!1;if(W(g)){H=Vi(null,se)&&A&&A.vnode.props&&A.vnode.props.appear;const F=g.content.firstChild;H&&se.beforeEnter(F),U(F,g,A),p.el=g=F}if(b&16&&!(_&&(_.innerHTML||_.textContent))){let F=x(g.firstChild,p,g,A,D,j,K);for(;F;){ln(g,1)||bt();const ae=F;F=F.nextSibling,l(ae)}}else if(b&8){let F=p.children;F[0]===` -`&&(g.tagName==="PRE"||g.tagName==="TEXTAREA")&&(F=F.slice(1)),g.textContent!==F&&(ln(g,0)||bt(),g.textContent=p.children)}if(_){if(ie||!K||P&48){const F=g.tagName.includes("-");for(const ae in _)(ie&&(ae.endsWith("value")||ae==="indeterminate")||Xt(ae)&&!Et(ae)||ae[0]==="."||F)&&s(g,ae,null,_[ae],void 0,A)}else if(_.onClick)s(g,"onClick",null,_.onClick,void 0,A);else if(P&4&&ht(_.style))for(const F in _.style)_.style[F]}let Y;(Y=_&&_.onVnodeBeforeMount)&&Oe(Y,A,p),V&&Ve(p,null,A,"beforeMount"),((Y=_&&_.onVnodeMounted)||V||H)&&Yi(()=>{Y&&Oe(Y,A,p),H&&se.enter(g),V&&Ve(p,null,A,"mounted")},D)}return g.nextSibling},x=(g,p,A,D,j,K,I)=>{I=I||!!p.dynamicChildren;const _=p.children,P=_.length;for(let b=0;b{const{slotScopeIds:I}=p;I&&(j=j?j.concat(I):I);const _=o(g),P=x(i(g),p,_,A,D,j,K);return P&&on(P)&&P.data==="]"?i(p.anchor=P):(bt(),c(p.anchor=u("]"),_,P),P)},O=(g,p,A,D,j,K)=>{if(ln(g.parentElement,1)||bt(),p.el=null,K){const P=G(g);for(;;){const b=i(g);if(b&&b!==P)l(b);else break}}const I=i(g),_=o(g);return l(g),n(null,p,_,I,A,D,rn(_),j),A&&(A.vnode.el=p.el,qi(A,p.el)),I},G=(g,p="[",A="]")=>{let D=0;for(;g;)if(g=i(g),g&&on(g)&&(g.data===p&&D++,g.data===A)){if(D===0)return i(g);D--}return g},U=(g,p,A)=>{const D=p.parentNode;D&&D.replaceChild(g,p);let j=A;for(;j;)j.vnode.el===p&&(j.vnode.el=j.subTree.el=g),j=j.parent},W=g=>g.nodeType===1&&g.tagName==="TEMPLATE";return[a,h]}const er="data-allow-mismatch",Al={0:"text",1:"children",2:"class",3:"style",4:"attribute"};function ln(e,t){if(t===0||t===1)for(;e&&!e.hasAttribute(er);)e=e.parentElement;const n=e&&e.getAttribute(er);if(n==null)return!1;if(n==="")return!0;{const s=n.split(",");return t===0&&s.includes("children")?!0:n.split(",").includes(Al[t])}}An().requestIdleCallback;An().cancelIdleCallback;const pt=e=>!!e.type.__asyncLoader,Nn=e=>e.type.__isKeepAlive;function Rl(e,t){Si(e,"a",t)}function Ol(e,t){Si(e,"da",t)}function Si(e,t,n=fe){const s=e.__wdc||(e.__wdc=()=>{let r=n;for(;r;){if(r.isDeactivated)return;r=r.parent}return e()});if(Fn(t,s,n),n){let r=n.parent;for(;r&&r.parent;)Nn(r.parent.vnode)&&Ml(s,t,n,r),r=r.parent}}function Ml(e,t,n,s){const r=Fn(t,e,s,!0);Hn(()=>{Ts(s[t],r)},n)}function Fn(e,t,n=fe,s=!1){if(n){const r=n[e]||(n[e]=[]),i=t.__weh||(t.__weh=(...o)=>{rt();const l=zt(n),c=He(t,n,e,o);return l(),it(),c});return s?r.unshift(i):r.push(i),i}}const Ye=e=>(t,n=fe)=>{(!Gt||e==="sp")&&Fn(e,(...s)=>t(...s),n)},Il=Ye("bm"),Ot=Ye("m"),Ll=Ye("bu"),Pl=Ye("u"),Ei=Ye("bum"),Hn=Ye("um"),Nl=Ye("sp"),Fl=Ye("rtg"),Hl=Ye("rtc");function $l(e,t=fe){Fn("ec",e,t)}const Ci="components";function Ja(e,t){return Ai(Ci,e,!0,t)||e}const Ti=Symbol.for("v-ndc");function za(e){return re(e)?Ai(Ci,e,!1)||e:e||Ti}function Ai(e,t,n=!0,s=!1){const r=ve||fe;if(r){const i=r.type;{const l=wc(i,!1);if(l&&(l===t||l===Le(t)||l===Tn(Le(t))))return i}const o=tr(r[e]||i[e],t)||tr(r.appContext[e],t);return!o&&s?i:o}}function tr(e,t){return e&&(e[t]||e[Le(t)]||e[Tn(Le(t))])}function Qa(e,t,n,s){let r;const i=n,o=B(e);if(o||re(e)){const l=o&&ht(e);let c=!1;l&&(c=!Ie(e),e=On(e)),r=new Array(e.length);for(let u=0,a=e.length;ut(l,c,void 0,i));else{const l=Object.keys(e);r=new Array(l.length);for(let c=0,u=l.length;cqt(t)?!(t.type===ye||t.type===xe&&!Ri(t.children)):!0)?e:null}function ef(e,t){const n={};for(const s in e)n[/[A-Z]/.test(s)?`on:${s}`:dn(s)]=e[s];return n}const gs=e=>e?Zi(e)?Vs(e):gs(e.parent):null,$t=le(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>gs(e.parent),$root:e=>gs(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>$s(e),$forceUpdate:e=>e.f||(e.f=()=>{Hs(e.update)}),$nextTick:e=>e.n||(e.n=Pn.bind(e.proxy)),$watch:e=>ic.bind(e)}),Xn=(e,t)=>e!==te&&!e.__isScriptSetup&&z(e,t),Dl={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:n,setupState:s,data:r,props:i,accessCache:o,type:l,appContext:c}=e;let u;if(t[0]!=="$"){const x=o[t];if(x!==void 0)switch(x){case 1:return s[t];case 2:return r[t];case 4:return n[t];case 3:return i[t]}else{if(Xn(s,t))return o[t]=1,s[t];if(r!==te&&z(r,t))return o[t]=2,r[t];if((u=e.propsOptions[0])&&z(u,t))return o[t]=3,i[t];if(n!==te&&z(n,t))return o[t]=4,n[t];ms&&(o[t]=0)}}const a=$t[t];let h,v;if(a)return t==="$attrs"&&ge(e.attrs,"get",""),a(e);if((h=l.__cssModules)&&(h=h[t]))return h;if(n!==te&&z(n,t))return o[t]=4,n[t];if(v=c.config.globalProperties,z(v,t))return v[t]},set({_:e},t,n){const{data:s,setupState:r,ctx:i}=e;return Xn(r,t)?(r[t]=n,!0):s!==te&&z(s,t)?(s[t]=n,!0):z(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(i[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:s,appContext:r,propsOptions:i}},o){let l;return!!n[o]||e!==te&&z(e,o)||Xn(t,o)||(l=i[0])&&z(l,o)||z(s,o)||z($t,o)||z(r.config.globalProperties,o)},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:z(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function tf(){return jl().slots}function jl(){const e=jn();return e.setupContext||(e.setupContext=to(e))}function nr(e){return B(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let ms=!0;function Vl(e){const t=$s(e),n=e.proxy,s=e.ctx;ms=!1,t.beforeCreate&&sr(t.beforeCreate,e,"bc");const{data:r,computed:i,methods:o,watch:l,provide:c,inject:u,created:a,beforeMount:h,mounted:v,beforeUpdate:x,updated:L,activated:O,deactivated:G,beforeDestroy:U,beforeUnmount:W,destroyed:g,unmounted:p,render:A,renderTracked:D,renderTriggered:j,errorCaptured:K,serverPrefetch:I,expose:_,inheritAttrs:P,components:b,directives:V,filters:se}=t;if(u&&Ul(u,s,null),o)for(const Y in o){const F=o[Y];q(F)&&(s[Y]=F.bind(n))}if(r){const Y=r.call(n,n);ne(Y)&&(e.data=Mn(Y))}if(ms=!0,i)for(const Y in i){const F=i[Y],ae=q(F)?F.bind(n,n):q(F.get)?F.get.bind(n,n):Ue,Qt=!q(F)&&q(F.set)?F.set.bind(n):Ue,ot=oe({get:ae,set:Qt});Object.defineProperty(s,Y,{enumerable:!0,configurable:!0,get:()=>ot.value,set:$e=>ot.value=$e})}if(l)for(const Y in l)Oi(l[Y],s,n,Y);if(c){const Y=q(c)?c.call(n):c;Reflect.ownKeys(Y).forEach(F=>{Gl(F,Y[F])})}a&&sr(a,e,"c");function H(Y,F){B(F)?F.forEach(ae=>Y(ae.bind(n))):F&&Y(F.bind(n))}if(H(Il,h),H(Ot,v),H(Ll,x),H(Pl,L),H(Rl,O),H(Ol,G),H($l,K),H(Hl,D),H(Fl,j),H(Ei,W),H(Hn,p),H(Nl,I),B(_))if(_.length){const Y=e.exposed||(e.exposed={});_.forEach(F=>{Object.defineProperty(Y,F,{get:()=>n[F],set:ae=>n[F]=ae})})}else e.exposed||(e.exposed={});A&&e.render===Ue&&(e.render=A),P!=null&&(e.inheritAttrs=P),b&&(e.components=b),V&&(e.directives=V),I&&xi(e)}function Ul(e,t,n=Ue){B(e)&&(e=ys(e));for(const s in e){const r=e[s];let i;ne(r)?"default"in r?i=At(r.from||s,r.default,!0):i=At(r.from||s):i=At(r),ce(i)?Object.defineProperty(t,s,{enumerable:!0,configurable:!0,get:()=>i.value,set:o=>i.value=o}):t[s]=i}}function sr(e,t,n){He(B(e)?e.map(s=>s.bind(t.proxy)):e.bind(t.proxy),t,n)}function Oi(e,t,n,s){let r=s.includes(".")?Wi(n,s):()=>n[s];if(re(e)){const i=t[e];q(i)&&Fe(r,i)}else if(q(e))Fe(r,e.bind(n));else if(ne(e))if(B(e))e.forEach(i=>Oi(i,t,n,s));else{const i=q(e.handler)?e.handler.bind(n):t[e.handler];q(i)&&Fe(r,i,e)}}function $s(e){const t=e.type,{mixins:n,extends:s}=t,{mixins:r,optionsCache:i,config:{optionMergeStrategies:o}}=e.appContext,l=i.get(t);let c;return l?c=l:!r.length&&!n&&!s?c=t:(c={},r.length&&r.forEach(u=>xn(c,u,o,!0)),xn(c,t,o)),ne(t)&&i.set(t,c),c}function xn(e,t,n,s=!1){const{mixins:r,extends:i}=t;i&&xn(e,i,n,!0),r&&r.forEach(o=>xn(e,o,n,!0));for(const o in t)if(!(s&&o==="expose")){const l=Bl[o]||n&&n[o];e[o]=l?l(e[o],t[o]):t[o]}return e}const Bl={data:rr,props:ir,emits:ir,methods:Nt,computed:Nt,beforeCreate:_e,created:_e,beforeMount:_e,mounted:_e,beforeUpdate:_e,updated:_e,beforeDestroy:_e,beforeUnmount:_e,destroyed:_e,unmounted:_e,activated:_e,deactivated:_e,errorCaptured:_e,serverPrefetch:_e,components:Nt,directives:Nt,watch:Wl,provide:rr,inject:kl};function rr(e,t){return t?e?function(){return le(q(e)?e.call(this,this):e,q(t)?t.call(this,this):t)}:t:e}function kl(e,t){return Nt(ys(e),ys(t))}function ys(e){if(B(e)){const t={};for(let n=0;n1)return n&&q(t)?t.call(s&&s.proxy):t}}const Ii={},Li=()=>Object.create(Ii),Pi=e=>Object.getPrototypeOf(e)===Ii;function Yl(e,t,n,s=!1){const r={},i=Li();e.propsDefaults=Object.create(null),Ni(e,t,r,i);for(const o in e.propsOptions[0])o in r||(r[o]=void 0);n?e.props=s?r:sl(r):e.type.props?e.props=r:e.props=i,e.attrs=i}function Xl(e,t,n,s){const{props:r,attrs:i,vnode:{patchFlag:o}}=e,l=J(r),[c]=e.propsOptions;let u=!1;if((s||o>0)&&!(o&16)){if(o&8){const a=e.vnode.dynamicProps;for(let h=0;h{c=!0;const[v,x]=Fi(h,t,!0);le(o,v),x&&l.push(...x)};!n&&t.mixins.length&&t.mixins.forEach(a),e.extends&&a(e.extends),e.mixins&&e.mixins.forEach(a)}if(!i&&!c)return ne(e)&&s.set(e,xt),xt;if(B(i))for(let a=0;ae[0]==="_"||e==="$stable",Ds=e=>B(e)?e.map(Me):[Me(e)],zl=(e,t,n)=>{if(t._n)return t;const s=_l((...r)=>Ds(t(...r)),n);return s._c=!1,s},$i=(e,t,n)=>{const s=e._ctx;for(const r in e){if(Hi(r))continue;const i=e[r];if(q(i))t[r]=zl(r,i,s);else if(i!=null){const o=Ds(i);t[r]=()=>o}}},Di=(e,t)=>{const n=Ds(t);e.slots.default=()=>n},ji=(e,t,n)=>{for(const s in t)(n||s!=="_")&&(e[s]=t[s])},Ql=(e,t,n)=>{const s=e.slots=Li();if(e.vnode.shapeFlag&32){const r=t._;r?(ji(s,t,n),n&&Br(s,"_",r,!0)):$i(t,s)}else t&&Di(e,t)},Zl=(e,t,n)=>{const{vnode:s,slots:r}=e;let i=!0,o=te;if(s.shapeFlag&32){const l=t._;l?n&&l===1?i=!1:ji(r,t,n):(i=!t.$stable,$i(t,r)),o=t}else t&&(Di(e,t),o={default:1});if(i)for(const l in r)!Hi(l)&&o[l]==null&&delete r[l]},Te=Yi;function ec(e){return tc(e,Tl)}function tc(e,t){const n=An();n.__VUE__=!0;const{insert:s,remove:r,patchProp:i,createElement:o,createText:l,createComment:c,setText:u,setElementText:a,parentNode:h,nextSibling:v,setScopeId:x=Ue,insertStaticContent:L}=e,O=(f,d,m,S=null,y=null,w=null,R=void 0,T=null,C=!!d.dynamicChildren)=>{if(f===d)return;f&&!ut(f,d)&&(S=Zt(f),$e(f,y,w,!0),f=null),d.patchFlag===-2&&(C=!1,d.dynamicChildren=null);const{type:E,ref:$,shapeFlag:M}=d;switch(E){case gt:G(f,d,m,S);break;case ye:U(f,d,m,S);break;case Dt:f==null&&W(d,m,S,R);break;case xe:b(f,d,m,S,y,w,R,T,C);break;default:M&1?A(f,d,m,S,y,w,R,T,C):M&6?V(f,d,m,S,y,w,R,T,C):(M&64||M&128)&&E.process(f,d,m,S,y,w,R,T,C,vt)}$!=null&&y&&Wt($,f&&f.ref,w,d||f,!d)},G=(f,d,m,S)=>{if(f==null)s(d.el=l(d.children),m,S);else{const y=d.el=f.el;d.children!==f.children&&u(y,d.children)}},U=(f,d,m,S)=>{f==null?s(d.el=c(d.children||""),m,S):d.el=f.el},W=(f,d,m,S)=>{[f.el,f.anchor]=L(f.children,d,m,S,f.el,f.anchor)},g=({el:f,anchor:d},m,S)=>{let y;for(;f&&f!==d;)y=v(f),s(f,m,S),f=y;s(d,m,S)},p=({el:f,anchor:d})=>{let m;for(;f&&f!==d;)m=v(f),r(f),f=m;r(d)},A=(f,d,m,S,y,w,R,T,C)=>{d.type==="svg"?R="svg":d.type==="math"&&(R="mathml"),f==null?D(d,m,S,y,w,R,T,C):I(f,d,y,w,R,T,C)},D=(f,d,m,S,y,w,R,T)=>{let C,E;const{props:$,shapeFlag:M,transition:N,dirs:k}=f;if(C=f.el=o(f.type,w,$&&$.is,$),M&8?a(C,f.children):M&16&&K(f.children,C,null,S,y,Jn(f,w),R,T),k&&Ve(f,null,S,"created"),j(C,f,f.scopeId,R,S),$){for(const Z in $)Z!=="value"&&!Et(Z)&&i(C,Z,null,$[Z],w,S);"value"in $&&i(C,"value",null,$.value,w),(E=$.onVnodeBeforeMount)&&Oe(E,S,f)}k&&Ve(f,null,S,"beforeMount");const X=Vi(y,N);X&&N.beforeEnter(C),s(C,d,m),((E=$&&$.onVnodeMounted)||X||k)&&Te(()=>{E&&Oe(E,S,f),X&&N.enter(C),k&&Ve(f,null,S,"mounted")},y)},j=(f,d,m,S,y)=>{if(m&&x(f,m),S)for(let w=0;w{for(let E=C;E{const T=d.el=f.el;let{patchFlag:C,dynamicChildren:E,dirs:$}=d;C|=f.patchFlag&16;const M=f.props||te,N=d.props||te;let k;if(m&<(m,!1),(k=N.onVnodeBeforeUpdate)&&Oe(k,m,d,f),$&&Ve(d,f,m,"beforeUpdate"),m&<(m,!0),(M.innerHTML&&N.innerHTML==null||M.textContent&&N.textContent==null)&&a(T,""),E?_(f.dynamicChildren,E,T,m,S,Jn(d,y),w):R||F(f,d,T,null,m,S,Jn(d,y),w,!1),C>0){if(C&16)P(T,M,N,m,y);else if(C&2&&M.class!==N.class&&i(T,"class",null,N.class,y),C&4&&i(T,"style",M.style,N.style,y),C&8){const X=d.dynamicProps;for(let Z=0;Z{k&&Oe(k,m,d,f),$&&Ve(d,f,m,"updated")},S)},_=(f,d,m,S,y,w,R)=>{for(let T=0;T{if(d!==m){if(d!==te)for(const w in d)!Et(w)&&!(w in m)&&i(f,w,d[w],null,y,S);for(const w in m){if(Et(w))continue;const R=m[w],T=d[w];R!==T&&w!=="value"&&i(f,w,T,R,y,S)}"value"in m&&i(f,"value",d.value,m.value,y)}},b=(f,d,m,S,y,w,R,T,C)=>{const E=d.el=f?f.el:l(""),$=d.anchor=f?f.anchor:l("");let{patchFlag:M,dynamicChildren:N,slotScopeIds:k}=d;k&&(T=T?T.concat(k):k),f==null?(s(E,m,S),s($,m,S),K(d.children||[],m,$,y,w,R,T,C)):M>0&&M&64&&N&&f.dynamicChildren?(_(f.dynamicChildren,N,m,y,w,R,T),(d.key!=null||y&&d===y.subTree)&&Ui(f,d,!0)):F(f,d,m,$,y,w,R,T,C)},V=(f,d,m,S,y,w,R,T,C)=>{d.slotScopeIds=T,f==null?d.shapeFlag&512?y.ctx.activate(d,m,S,R,C):se(d,m,S,y,w,R,C):ie(f,d,C)},se=(f,d,m,S,y,w,R)=>{const T=f.component=yc(f,S,y);if(Nn(f)&&(T.ctx.renderer=vt),vc(T,!1,R),T.asyncDep){if(y&&y.registerDep(T,H,R),!f.el){const C=T.subTree=de(ye);U(null,C,d,m)}}else H(T,f,d,m,y,w,R)},ie=(f,d,m)=>{const S=d.component=f.component;if(fc(f,d,m))if(S.asyncDep&&!S.asyncResolved){Y(S,d,m);return}else S.next=d,S.update();else d.el=f.el,S.vnode=d},H=(f,d,m,S,y,w,R)=>{const T=()=>{if(f.isMounted){let{next:M,bu:N,u:k,parent:X,vnode:Z}=f;{const Ee=Bi(f);if(Ee){M&&(M.el=Z.el,Y(f,M,R)),Ee.asyncDep.then(()=>{f.isUnmounted||T()});return}}let Q=M,Se;lt(f,!1),M?(M.el=Z.el,Y(f,M,R)):M=Z,N&&kn(N),(Se=M.props&&M.props.onVnodeBeforeUpdate)&&Oe(Se,X,M,Z),lt(f,!0);const he=zn(f),Pe=f.subTree;f.subTree=he,O(Pe,he,h(Pe.el),Zt(Pe),f,y,w),M.el=he.el,Q===null&&qi(f,he.el),k&&Te(k,y),(Se=M.props&&M.props.onVnodeUpdated)&&Te(()=>Oe(Se,X,M,Z),y)}else{let M;const{el:N,props:k}=d,{bm:X,m:Z,parent:Q,root:Se,type:he}=f,Pe=pt(d);if(lt(f,!1),X&&kn(X),!Pe&&(M=k&&k.onVnodeBeforeMount)&&Oe(M,Q,d),lt(f,!0),N&&Bn){const Ee=()=>{f.subTree=zn(f),Bn(N,f.subTree,f,y,null)};Pe&&he.__asyncHydrate?he.__asyncHydrate(N,f,Ee):Ee()}else{Se.ce&&Se.ce._injectChildStyle(he);const Ee=f.subTree=zn(f);O(null,Ee,m,S,f,y,w),d.el=Ee.el}if(Z&&Te(Z,y),!Pe&&(M=k&&k.onVnodeMounted)){const Ee=d;Te(()=>Oe(M,Q,Ee),y)}(d.shapeFlag&256||Q&&pt(Q.vnode)&&Q.vnode.shapeFlag&256)&&f.a&&Te(f.a,y),f.isMounted=!0,d=m=S=null}};f.scope.on();const C=f.effect=new Gr(T);f.scope.off();const E=f.update=C.run.bind(C),$=f.job=C.runIfDirty.bind(C);$.i=f,$.id=f.uid,C.scheduler=()=>Hs($),lt(f,!0),E()},Y=(f,d,m)=>{d.component=f;const S=f.vnode.props;f.vnode=d,f.next=null,Xl(f,d.props,S,m),Zl(f,d.children,m),rt(),zs(f),it()},F=(f,d,m,S,y,w,R,T,C=!1)=>{const E=f&&f.children,$=f?f.shapeFlag:0,M=d.children,{patchFlag:N,shapeFlag:k}=d;if(N>0){if(N&128){Qt(E,M,m,S,y,w,R,T,C);return}else if(N&256){ae(E,M,m,S,y,w,R,T,C);return}}k&8?($&16&&Mt(E,y,w),M!==E&&a(m,M)):$&16?k&16?Qt(E,M,m,S,y,w,R,T,C):Mt(E,y,w,!0):($&8&&a(m,""),k&16&&K(M,m,S,y,w,R,T,C))},ae=(f,d,m,S,y,w,R,T,C)=>{f=f||xt,d=d||xt;const E=f.length,$=d.length,M=Math.min(E,$);let N;for(N=0;N$?Mt(f,y,w,!0,!1,M):K(d,m,S,y,w,R,T,C,M)},Qt=(f,d,m,S,y,w,R,T,C)=>{let E=0;const $=d.length;let M=f.length-1,N=$-1;for(;E<=M&&E<=N;){const k=f[E],X=d[E]=C?Qe(d[E]):Me(d[E]);if(ut(k,X))O(k,X,m,null,y,w,R,T,C);else break;E++}for(;E<=M&&E<=N;){const k=f[M],X=d[N]=C?Qe(d[N]):Me(d[N]);if(ut(k,X))O(k,X,m,null,y,w,R,T,C);else break;M--,N--}if(E>M){if(E<=N){const k=N+1,X=k<$?d[k].el:S;for(;E<=N;)O(null,d[E]=C?Qe(d[E]):Me(d[E]),m,X,y,w,R,T,C),E++}}else if(E>N)for(;E<=M;)$e(f[E],y,w,!0),E++;else{const k=E,X=E,Z=new Map;for(E=X;E<=N;E++){const Ce=d[E]=C?Qe(d[E]):Me(d[E]);Ce.key!=null&&Z.set(Ce.key,E)}let Q,Se=0;const he=N-X+1;let Pe=!1,Ee=0;const It=new Array(he);for(E=0;E=he){$e(Ce,y,w,!0);continue}let De;if(Ce.key!=null)De=Z.get(Ce.key);else for(Q=X;Q<=N;Q++)if(It[Q-X]===0&&ut(Ce,d[Q])){De=Q;break}De===void 0?$e(Ce,y,w,!0):(It[De-X]=E+1,De>=Ee?Ee=De:Pe=!0,O(Ce,d[De],m,null,y,w,R,T,C),Se++)}const qs=Pe?nc(It):xt;for(Q=qs.length-1,E=he-1;E>=0;E--){const Ce=X+E,De=d[Ce],Gs=Ce+1<$?d[Ce+1].el:S;It[E]===0?O(null,De,m,Gs,y,w,R,T,C):Pe&&(Q<0||E!==qs[Q]?ot(De,m,Gs,2):Q--)}}},ot=(f,d,m,S,y=null)=>{const{el:w,type:R,transition:T,children:C,shapeFlag:E}=f;if(E&6){ot(f.component.subTree,d,m,S);return}if(E&128){f.suspense.move(d,m,S);return}if(E&64){R.move(f,d,m,vt);return}if(R===xe){s(w,d,m);for(let M=0;MT.enter(w),y);else{const{leave:M,delayLeave:N,afterLeave:k}=T,X=()=>s(w,d,m),Z=()=>{M(w,()=>{X(),k&&k()})};N?N(w,X,Z):Z()}else s(w,d,m)},$e=(f,d,m,S=!1,y=!1)=>{const{type:w,props:R,ref:T,children:C,dynamicChildren:E,shapeFlag:$,patchFlag:M,dirs:N,cacheIndex:k}=f;if(M===-2&&(y=!1),T!=null&&Wt(T,null,m,f,!0),k!=null&&(d.renderCache[k]=void 0),$&256){d.ctx.deactivate(f);return}const X=$&1&&N,Z=!pt(f);let Q;if(Z&&(Q=R&&R.onVnodeBeforeUnmount)&&Oe(Q,d,f),$&6)wo(f.component,m,S);else{if($&128){f.suspense.unmount(m,S);return}X&&Ve(f,null,d,"beforeUnmount"),$&64?f.type.remove(f,d,m,vt,S):E&&!E.hasOnce&&(w!==xe||M>0&&M&64)?Mt(E,d,m,!1,!0):(w===xe&&M&384||!y&&$&16)&&Mt(C,d,m),S&&Ws(f)}(Z&&(Q=R&&R.onVnodeUnmounted)||X)&&Te(()=>{Q&&Oe(Q,d,f),X&&Ve(f,null,d,"unmounted")},m)},Ws=f=>{const{type:d,el:m,anchor:S,transition:y}=f;if(d===xe){bo(m,S);return}if(d===Dt){p(f);return}const w=()=>{r(m),y&&!y.persisted&&y.afterLeave&&y.afterLeave()};if(f.shapeFlag&1&&y&&!y.persisted){const{leave:R,delayLeave:T}=y,C=()=>R(m,w);T?T(f.el,w,C):C()}else w()},bo=(f,d)=>{let m;for(;f!==d;)m=v(f),r(f),f=m;r(d)},wo=(f,d,m)=>{const{bum:S,scope:y,job:w,subTree:R,um:T,m:C,a:E}=f;lr(C),lr(E),S&&kn(S),y.stop(),w&&(w.flags|=8,$e(R,f,d,m)),T&&Te(T,d),Te(()=>{f.isUnmounted=!0},d),d&&d.pendingBranch&&!d.isUnmounted&&f.asyncDep&&!f.asyncResolved&&f.suspenseId===d.pendingId&&(d.deps--,d.deps===0&&d.resolve())},Mt=(f,d,m,S=!1,y=!1,w=0)=>{for(let R=w;R{if(f.shapeFlag&6)return Zt(f.component.subTree);if(f.shapeFlag&128)return f.suspense.next();const d=v(f.anchor||f.el),m=d&&d[bl];return m?v(m):d};let Vn=!1;const Ks=(f,d,m)=>{f==null?d._vnode&&$e(d._vnode,null,null,!0):O(d._vnode||null,f,d,null,null,null,m),d._vnode=f,Vn||(Vn=!0,zs(),bn(),Vn=!1)},vt={p:O,um:$e,m:ot,r:Ws,mt:se,mc:K,pc:F,pbc:_,n:Zt,o:e};let Un,Bn;return t&&([Un,Bn]=t(vt)),{render:Ks,hydrate:Un,createApp:ql(Ks,Un)}}function Jn({type:e,props:t},n){return n==="svg"&&e==="foreignObject"||n==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:n}function lt({effect:e,job:t},n){n?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function Vi(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function Ui(e,t,n=!1){const s=e.children,r=t.children;if(B(s)&&B(r))for(let i=0;i>1,e[n[l]]0&&(t[s]=n[i-1]),n[i]=s)}}for(i=n.length,o=n[i-1];i-- >0;)n[i]=o,o=t[o];return n}function Bi(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:Bi(t)}function lr(e){if(e)for(let t=0;tAt(sc);function ki(e,t){return $n(e,null,t)}function nf(e,t){return $n(e,null,{flush:"post"})}function Fe(e,t,n){return $n(e,t,n)}function $n(e,t,n=te){const{immediate:s,deep:r,flush:i,once:o}=n,l=le({},n),c=t&&s||!t&&i!=="post";let u;if(Gt){if(i==="sync"){const x=rc();u=x.__watcherHandles||(x.__watcherHandles=[])}else if(!c){const x=()=>{};return x.stop=Ue,x.resume=Ue,x.pause=Ue,x}}const a=fe;l.call=(x,L,O)=>He(x,a,L,O);let h=!1;i==="post"?l.scheduler=x=>{Te(x,a&&a.suspense)}:i!=="sync"&&(h=!0,l.scheduler=(x,L)=>{L?x():Hs(x)}),l.augmentJob=x=>{t&&(x.flags|=4),h&&(x.flags|=2,a&&(x.id=a.uid,x.i=a))};const v=gl(e,t,l);return Gt&&(u?u.push(v):c&&v()),v}function ic(e,t,n){const s=this.proxy,r=re(e)?e.includes(".")?Wi(s,e):()=>s[e]:e.bind(s,s);let i;q(t)?i=t:(i=t.handler,n=t);const o=zt(this),l=$n(r,i.bind(s),n);return o(),l}function Wi(e,t){const n=t.split(".");return()=>{let s=e;for(let r=0;rt==="modelValue"||t==="model-value"?e.modelModifiers:e[`${t}Modifiers`]||e[`${Le(t)}Modifiers`]||e[`${st(t)}Modifiers`];function lc(e,t,...n){if(e.isUnmounted)return;const s=e.vnode.props||te;let r=n;const i=t.startsWith("update:"),o=i&&oc(s,t.slice(7));o&&(o.trim&&(r=n.map(a=>re(a)?a.trim():a)),o.number&&(r=n.map(Ao)));let l,c=s[l=dn(t)]||s[l=dn(Le(t))];!c&&i&&(c=s[l=dn(st(t))]),c&&He(c,e,6,r);const u=s[l+"Once"];if(u){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,He(u,e,6,r)}}function Ki(e,t,n=!1){const s=t.emitsCache,r=s.get(e);if(r!==void 0)return r;const i=e.emits;let o={},l=!1;if(!q(e)){const c=u=>{const a=Ki(u,t,!0);a&&(l=!0,le(o,a))};!n&&t.mixins.length&&t.mixins.forEach(c),e.extends&&c(e.extends),e.mixins&&e.mixins.forEach(c)}return!i&&!l?(ne(e)&&s.set(e,null),null):(B(i)?i.forEach(c=>o[c]=null):le(o,i),ne(e)&&s.set(e,o),o)}function Dn(e,t){return!e||!Xt(t)?!1:(t=t.slice(2).replace(/Once$/,""),z(e,t[0].toLowerCase()+t.slice(1))||z(e,st(t))||z(e,t))}function zn(e){const{type:t,vnode:n,proxy:s,withProxy:r,propsOptions:[i],slots:o,attrs:l,emit:c,render:u,renderCache:a,props:h,data:v,setupState:x,ctx:L,inheritAttrs:O}=e,G=wn(e);let U,W;try{if(n.shapeFlag&4){const p=r||s,A=p;U=Me(u.call(A,p,a,h,x,v,L)),W=l}else{const p=t;U=Me(p.length>1?p(h,{attrs:l,slots:o,emit:c}):p(h,null)),W=t.props?l:cc(l)}}catch(p){jt.length=0,Ln(p,e,1),U=de(ye)}let g=U;if(W&&O!==!1){const p=Object.keys(W),{shapeFlag:A}=g;p.length&&A&7&&(i&&p.some(Cs)&&(W=ac(W,i)),g=nt(g,W,!1,!0))}return n.dirs&&(g=nt(g,null,!1,!0),g.dirs=g.dirs?g.dirs.concat(n.dirs):n.dirs),n.transition&&kt(g,n.transition),U=g,wn(G),U}const cc=e=>{let t;for(const n in e)(n==="class"||n==="style"||Xt(n))&&((t||(t={}))[n]=e[n]);return t},ac=(e,t)=>{const n={};for(const s in e)(!Cs(s)||!(s.slice(9)in t))&&(n[s]=e[s]);return n};function fc(e,t,n){const{props:s,children:r,component:i}=e,{props:o,children:l,patchFlag:c}=t,u=i.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&c>=0){if(c&1024)return!0;if(c&16)return s?cr(s,o,u):!!o;if(c&8){const a=t.dynamicProps;for(let h=0;he.__isSuspense;function Yi(e,t){t&&t.pendingBranch?B(e)?t.effects.push(...e):t.effects.push(e):vl(e)}const xe=Symbol.for("v-fgt"),gt=Symbol.for("v-txt"),ye=Symbol.for("v-cmt"),Dt=Symbol.for("v-stc"),jt=[];let Ae=null;function _s(e=!1){jt.push(Ae=e?null:[])}function uc(){jt.pop(),Ae=jt[jt.length-1]||null}let Kt=1;function ar(e,t=!1){Kt+=e,e<0&&Ae&&t&&(Ae.hasOnce=!0)}function Xi(e){return e.dynamicChildren=Kt>0?Ae||xt:null,uc(),Kt>0&&Ae&&Ae.push(e),e}function sf(e,t,n,s,r,i){return Xi(zi(e,t,n,s,r,i,!0))}function bs(e,t,n,s,r){return Xi(de(e,t,n,s,r,!0))}function qt(e){return e?e.__v_isVNode===!0:!1}function ut(e,t){return e.type===t.type&&e.key===t.key}const Ji=({key:e})=>e??null,pn=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?re(e)||ce(e)||q(e)?{i:ve,r:e,k:t,f:!!n}:e:null);function zi(e,t=null,n=null,s=0,r=null,i=e===xe?0:1,o=!1,l=!1){const c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Ji(t),ref:t&&pn(t),scopeId:pi,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:i,patchFlag:s,dynamicProps:r,dynamicChildren:null,appContext:null,ctx:ve};return l?(js(c,n),i&128&&e.normalize(c)):n&&(c.shapeFlag|=re(n)?8:16),Kt>0&&!o&&Ae&&(c.patchFlag>0||i&6)&&c.patchFlag!==32&&Ae.push(c),c}const de=dc;function dc(e,t=null,n=null,s=0,r=null,i=!1){if((!e||e===Ti)&&(e=ye),qt(e)){const l=nt(e,t,!0);return n&&js(l,n),Kt>0&&!i&&Ae&&(l.shapeFlag&6?Ae[Ae.indexOf(e)]=l:Ae.push(l)),l.patchFlag=-2,l}if(xc(e)&&(e=e.__vccOpts),t){t=hc(t);let{class:l,style:c}=t;l&&!re(l)&&(t.class=Os(l)),ne(c)&&(Fs(c)&&!B(c)&&(c=le({},c)),t.style=Rs(c))}const o=re(e)?1:Gi(e)?128:gi(e)?64:ne(e)?4:q(e)?2:0;return zi(e,t,n,s,r,o,i,!0)}function hc(e){return e?Fs(e)||Pi(e)?le({},e):e:null}function nt(e,t,n=!1,s=!1){const{props:r,ref:i,patchFlag:o,children:l,transition:c}=e,u=t?pc(r||{},t):r,a={__v_isVNode:!0,__v_skip:!0,type:e.type,props:u,key:u&&Ji(u),ref:t&&t.ref?n&&i?B(i)?i.concat(pn(t)):[i,pn(t)]:pn(t):i,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:l,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==xe?o===-1?16:o|16:o,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:c,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&nt(e.ssContent),ssFallback:e.ssFallback&&nt(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return c&&s&&kt(a,c.clone(a)),a}function Qi(e=" ",t=0){return de(gt,null,e,t)}function rf(e,t){const n=de(Dt,null,e);return n.staticCount=t,n}function of(e="",t=!1){return t?(_s(),bs(ye,null,e)):de(ye,null,e)}function Me(e){return e==null||typeof e=="boolean"?de(ye):B(e)?de(xe,null,e.slice()):qt(e)?Qe(e):de(gt,null,String(e))}function Qe(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:nt(e)}function js(e,t){let n=0;const{shapeFlag:s}=e;if(t==null)t=null;else if(B(t))n=16;else if(typeof t=="object")if(s&65){const r=t.default;r&&(r._c&&(r._d=!1),js(e,r()),r._c&&(r._d=!0));return}else{n=32;const r=t._;!r&&!Pi(t)?t._ctx=ve:r===3&&ve&&(ve.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else q(t)?(t={default:t,_ctx:ve},n=32):(t=String(t),s&64?(n=16,t=[Qi(t)]):n=8);e.children=t,e.shapeFlag|=n}function pc(...e){const t={};for(let n=0;nfe||ve;let Sn,ws;{const e=An(),t=(n,s)=>{let r;return(r=e[n])||(r=e[n]=[]),r.push(s),i=>{r.length>1?r.forEach(o=>o(i)):r[0](i)}};Sn=t("__VUE_INSTANCE_SETTERS__",n=>fe=n),ws=t("__VUE_SSR_SETTERS__",n=>Gt=n)}const zt=e=>{const t=fe;return Sn(e),e.scope.on(),()=>{e.scope.off(),Sn(t)}},fr=()=>{fe&&fe.scope.off(),Sn(null)};function Zi(e){return e.vnode.shapeFlag&4}let Gt=!1;function vc(e,t=!1,n=!1){t&&ws(t);const{props:s,children:r}=e.vnode,i=Zi(e);Yl(e,s,i,t),Ql(e,r,n);const o=i?_c(e,t):void 0;return t&&ws(!1),o}function _c(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,Dl);const{setup:s}=n;if(s){rt();const r=e.setupContext=s.length>1?to(e):null,i=zt(e),o=Jt(s,e,0,[e.props,r]),l=jr(o);if(it(),i(),(l||e.sp)&&!pt(e)&&xi(e),l){if(o.then(fr,fr),t)return o.then(c=>{ur(e,c,t)}).catch(c=>{Ln(c,e,0)});e.asyncDep=o}else ur(e,o,t)}else eo(e,t)}function ur(e,t,n){q(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:ne(t)&&(e.setupState=fi(t)),eo(e,n)}let dr;function eo(e,t,n){const s=e.type;if(!e.render){if(!t&&dr&&!s.render){const r=s.template||$s(e).template;if(r){const{isCustomElement:i,compilerOptions:o}=e.appContext.config,{delimiters:l,compilerOptions:c}=s,u=le(le({isCustomElement:i,delimiters:l},o),c);s.render=dr(r,u)}}e.render=s.render||Ue}{const r=zt(e);rt();try{Vl(e)}finally{it(),r()}}}const bc={get(e,t){return ge(e,"get",""),e[t]}};function to(e){const t=n=>{e.exposed=n||{}};return{attrs:new Proxy(e.attrs,bc),slots:e.slots,emit:e.emit,expose:t}}function Vs(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(fi(hn(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in $t)return $t[n](e)},has(t,n){return n in t||n in $t}})):e.proxy}function wc(e,t=!0){return q(e)?e.displayName||e.name:e.name||t&&e.__name}function xc(e){return q(e)&&"__vccOpts"in e}const oe=(e,t)=>hl(e,t,Gt);function xs(e,t,n){const s=arguments.length;return s===2?ne(t)&&!B(t)?qt(t)?de(e,null,[t]):de(e,t):de(e,null,t):(s>3?n=Array.prototype.slice.call(arguments,2):s===3&&qt(n)&&(n=[n]),de(e,t,n))}const Sc="3.5.13";/** -* @vue/runtime-dom v3.5.13 -* (c) 2018-present Yuxi (Evan) You and Vue contributors -* @license MIT -**/let Ss;const hr=typeof window<"u"&&window.trustedTypes;if(hr)try{Ss=hr.createPolicy("vue",{createHTML:e=>e})}catch{}const no=Ss?e=>Ss.createHTML(e):e=>e,Ec="http://www.w3.org/2000/svg",Cc="http://www.w3.org/1998/Math/MathML",We=typeof document<"u"?document:null,pr=We&&We.createElement("template"),Tc={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,s)=>{const r=t==="svg"?We.createElementNS(Ec,e):t==="mathml"?We.createElementNS(Cc,e):n?We.createElement(e,{is:n}):We.createElement(e);return e==="select"&&s&&s.multiple!=null&&r.setAttribute("multiple",s.multiple),r},createText:e=>We.createTextNode(e),createComment:e=>We.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>We.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,s,r,i){const o=n?n.previousSibling:t.lastChild;if(r&&(r===i||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),n),!(r===i||!(r=r.nextSibling)););else{pr.innerHTML=no(s==="svg"?`${e}`:s==="mathml"?`${e}`:e);const l=pr.content;if(s==="svg"||s==="mathml"){const c=l.firstChild;for(;c.firstChild;)l.appendChild(c.firstChild);l.removeChild(c)}t.insertBefore(l,n)}return[o?o.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},Xe="transition",Pt="animation",Yt=Symbol("_vtc"),so={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},Ac=le({},mi,so),Rc=e=>(e.displayName="Transition",e.props=Ac,e),lf=Rc((e,{slots:t})=>xs(Sl,Oc(e),t)),ct=(e,t=[])=>{B(e)?e.forEach(n=>n(...t)):e&&e(...t)},gr=e=>e?B(e)?e.some(t=>t.length>1):e.length>1:!1;function Oc(e){const t={};for(const b in e)b in so||(t[b]=e[b]);if(e.css===!1)return t;const{name:n="v",type:s,duration:r,enterFromClass:i=`${n}-enter-from`,enterActiveClass:o=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:c=i,appearActiveClass:u=o,appearToClass:a=l,leaveFromClass:h=`${n}-leave-from`,leaveActiveClass:v=`${n}-leave-active`,leaveToClass:x=`${n}-leave-to`}=e,L=Mc(r),O=L&&L[0],G=L&&L[1],{onBeforeEnter:U,onEnter:W,onEnterCancelled:g,onLeave:p,onLeaveCancelled:A,onBeforeAppear:D=U,onAppear:j=W,onAppearCancelled:K=g}=t,I=(b,V,se,ie)=>{b._enterCancelled=ie,at(b,V?a:l),at(b,V?u:o),se&&se()},_=(b,V)=>{b._isLeaving=!1,at(b,h),at(b,x),at(b,v),V&&V()},P=b=>(V,se)=>{const ie=b?j:W,H=()=>I(V,b,se);ct(ie,[V,H]),mr(()=>{at(V,b?c:i),ke(V,b?a:l),gr(ie)||yr(V,s,O,H)})};return le(t,{onBeforeEnter(b){ct(U,[b]),ke(b,i),ke(b,o)},onBeforeAppear(b){ct(D,[b]),ke(b,c),ke(b,u)},onEnter:P(!1),onAppear:P(!0),onLeave(b,V){b._isLeaving=!0;const se=()=>_(b,V);ke(b,h),b._enterCancelled?(ke(b,v),br()):(br(),ke(b,v)),mr(()=>{b._isLeaving&&(at(b,h),ke(b,x),gr(p)||yr(b,s,G,se))}),ct(p,[b,se])},onEnterCancelled(b){I(b,!1,void 0,!0),ct(g,[b])},onAppearCancelled(b){I(b,!0,void 0,!0),ct(K,[b])},onLeaveCancelled(b){_(b),ct(A,[b])}})}function Mc(e){if(e==null)return null;if(ne(e))return[Qn(e.enter),Qn(e.leave)];{const t=Qn(e);return[t,t]}}function Qn(e){return Ro(e)}function ke(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[Yt]||(e[Yt]=new Set)).add(t)}function at(e,t){t.split(/\s+/).forEach(s=>s&&e.classList.remove(s));const n=e[Yt];n&&(n.delete(t),n.size||(e[Yt]=void 0))}function mr(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let Ic=0;function yr(e,t,n,s){const r=e._endId=++Ic,i=()=>{r===e._endId&&s()};if(n!=null)return setTimeout(i,n);const{type:o,timeout:l,propCount:c}=Lc(e,t);if(!o)return s();const u=o+"end";let a=0;const h=()=>{e.removeEventListener(u,v),i()},v=x=>{x.target===e&&++a>=c&&h()};setTimeout(()=>{a(n[L]||"").split(", "),r=s(`${Xe}Delay`),i=s(`${Xe}Duration`),o=vr(r,i),l=s(`${Pt}Delay`),c=s(`${Pt}Duration`),u=vr(l,c);let a=null,h=0,v=0;t===Xe?o>0&&(a=Xe,h=o,v=i.length):t===Pt?u>0&&(a=Pt,h=u,v=c.length):(h=Math.max(o,u),a=h>0?o>u?Xe:Pt:null,v=a?a===Xe?i.length:c.length:0);const x=a===Xe&&/\b(transform|all)(,|$)/.test(s(`${Xe}Property`).toString());return{type:a,timeout:h,propCount:v,hasTransform:x}}function vr(e,t){for(;e.length_r(n)+_r(e[s])))}function _r(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function br(){return document.body.offsetHeight}function Pc(e,t,n){const s=e[Yt];s&&(t=(t?[t,...s]:[...s]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const wr=Symbol("_vod"),Nc=Symbol("_vsh"),Fc=Symbol(""),Hc=/(^|;)\s*display\s*:/;function $c(e,t,n){const s=e.style,r=re(n);let i=!1;if(n&&!r){if(t)if(re(t))for(const o of t.split(";")){const l=o.slice(0,o.indexOf(":")).trim();n[l]==null&&gn(s,l,"")}else for(const o in t)n[o]==null&&gn(s,o,"");for(const o in n)o==="display"&&(i=!0),gn(s,o,n[o])}else if(r){if(t!==n){const o=s[Fc];o&&(n+=";"+o),s.cssText=n,i=Hc.test(n)}}else t&&e.removeAttribute("style");wr in e&&(e[wr]=i?s.display:"",e[Nc]&&(s.display="none"))}const xr=/\s*!important$/;function gn(e,t,n){if(B(n))n.forEach(s=>gn(e,t,s));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const s=Dc(e,t);xr.test(n)?e.setProperty(st(s),n.replace(xr,""),"important"):e[s]=n}}const Sr=["Webkit","Moz","ms"],Zn={};function Dc(e,t){const n=Zn[t];if(n)return n;let s=Le(t);if(s!=="filter"&&s in e)return Zn[t]=s;s=Tn(s);for(let r=0;res||(kc.then(()=>es=0),es=Date.now());function Kc(e,t){const n=s=>{if(!s._vts)s._vts=Date.now();else if(s._vts<=n.attached)return;He(qc(s,n.value),t,5,[s])};return n.value=e,n.attached=Wc(),n}function qc(e,t){if(B(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(s=>r=>!r._stopped&&s&&s(r))}else return t}const Or=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,Gc=(e,t,n,s,r,i)=>{const o=r==="svg";t==="class"?Pc(e,s,o):t==="style"?$c(e,n,s):Xt(t)?Cs(t)||Uc(e,t,n,s,i):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):Yc(e,t,s,o))?(Tr(e,t,s),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&Cr(e,t,s,o,i,t!=="value")):e._isVueCE&&(/[A-Z]/.test(t)||!re(s))?Tr(e,Le(t),s,i,t):(t==="true-value"?e._trueValue=s:t==="false-value"&&(e._falseValue=s),Cr(e,t,s,o))};function Yc(e,t,n,s){if(s)return!!(t==="innerHTML"||t==="textContent"||t in e&&Or(t)&&q(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const r=e.tagName;if(r==="IMG"||r==="VIDEO"||r==="CANVAS"||r==="SOURCE")return!1}return Or(t)&&re(n)?!1:t in e}const Xc=["ctrl","shift","alt","meta"],Jc={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>Xc.some(n=>e[`${n}Key`]&&!t.includes(n))},cf=(e,t)=>{const n=e._withMods||(e._withMods={}),s=t.join(".");return n[s]||(n[s]=(r,...i)=>{for(let o=0;o{const n=e._withKeys||(e._withKeys={}),s=t.join(".");return n[s]||(n[s]=r=>{if(!("key"in r))return;const i=st(r.key);if(t.some(o=>o===i||zc[o]===i))return e(r)})},Qc=le({patchProp:Gc},Tc);let ts,Mr=!1;function Zc(){return ts=Mr?ts:ec(Qc),Mr=!0,ts}const ff=(...e)=>{const t=Zc().createApp(...e),{mount:n}=t;return t.mount=s=>{const r=ta(s);if(r)return n(r,!0,ea(r))},t};function ea(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function ta(e){return re(e)?document.querySelector(e):e}const uf=(e,t)=>{const n=e.__vccOpts||e;for(const[s,r]of t)n[s]=r;return n},na=window.__VP_SITE_DATA__;function Us(e){return qr()?($o(e),!0):!1}function tt(e){return typeof e=="function"?e():ai(e)}const ro=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const sa=Object.prototype.toString,ra=e=>sa.call(e)==="[object Object]",io=()=>{},Ir=ia();function ia(){var e,t;return ro&&((e=window==null?void 0:window.navigator)==null?void 0:e.userAgent)&&(/iP(?:ad|hone|od)/.test(window.navigator.userAgent)||((t=window==null?void 0:window.navigator)==null?void 0:t.maxTouchPoints)>2&&/iPad|Macintosh/.test(window==null?void 0:window.navigator.userAgent))}function oa(e,t){function n(...s){return new Promise((r,i)=>{Promise.resolve(e(()=>t.apply(this,s),{fn:t,thisArg:this,args:s})).then(r).catch(i)})}return n}const oo=e=>e();function la(e=oo){const t=ue(!0);function n(){t.value=!1}function s(){t.value=!0}const r=(...i)=>{t.value&&e(...i)};return{isActive:In(t),pause:n,resume:s,eventFilter:r}}function ca(e){return jn()}function lo(...e){if(e.length!==1)return fl(...e);const t=e[0];return typeof t=="function"?In(ll(()=>({get:t,set:io}))):ue(t)}function aa(e,t,n={}){const{eventFilter:s=oo,...r}=n;return Fe(e,oa(s,t),r)}function fa(e,t,n={}){const{eventFilter:s,...r}=n,{eventFilter:i,pause:o,resume:l,isActive:c}=la(s);return{stop:aa(e,t,{...r,eventFilter:i}),pause:o,resume:l,isActive:c}}function Bs(e,t=!0,n){ca()?Ot(e,n):t?e():Pn(e)}const qe=ro?window:void 0;function co(e){var t;const n=tt(e);return(t=n==null?void 0:n.$el)!=null?t:n}function Rt(...e){let t,n,s,r;if(typeof e[0]=="string"||Array.isArray(e[0])?([n,s,r]=e,t=qe):[t,n,s,r]=e,!t)return io;Array.isArray(n)||(n=[n]),Array.isArray(s)||(s=[s]);const i=[],o=()=>{i.forEach(a=>a()),i.length=0},l=(a,h,v,x)=>(a.addEventListener(h,v,x),()=>a.removeEventListener(h,v,x)),c=Fe(()=>[co(t),tt(r)],([a,h])=>{if(o(),!a)return;const v=ra(h)?{...h}:h;i.push(...n.flatMap(x=>s.map(L=>l(a,x,L,v))))},{immediate:!0,flush:"post"}),u=()=>{c(),o()};return Us(u),u}function ua(e){return typeof e=="function"?e:typeof e=="string"?t=>t.key===e:Array.isArray(e)?t=>e.includes(t.key):()=>!0}function df(...e){let t,n,s={};e.length===3?(t=e[0],n=e[1],s=e[2]):e.length===2?typeof e[1]=="object"?(t=!0,n=e[0],s=e[1]):(t=e[0],n=e[1]):(t=!0,n=e[0]);const{target:r=qe,eventName:i="keydown",passive:o=!1,dedupe:l=!1}=s,c=ua(t);return Rt(r,i,a=>{a.repeat&&tt(l)||c(a)&&n(a)},o)}function da(){const e=ue(!1),t=jn();return t&&Ot(()=>{e.value=!0},t),e}function ha(e){const t=da();return oe(()=>(t.value,!!e()))}function ao(e,t={}){const{window:n=qe}=t,s=ha(()=>n&&"matchMedia"in n&&typeof n.matchMedia=="function");let r;const i=ue(!1),o=u=>{i.value=u.matches},l=()=>{r&&("removeEventListener"in r?r.removeEventListener("change",o):r.removeListener(o))},c=ki(()=>{s.value&&(l(),r=n.matchMedia(tt(e)),"addEventListener"in r?r.addEventListener("change",o):r.addListener(o),i.value=r.matches)});return Us(()=>{c(),l(),r=void 0}),i}const cn=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},an="__vueuse_ssr_handlers__",pa=ga();function ga(){return an in cn||(cn[an]=cn[an]||{}),cn[an]}function fo(e,t){return pa[e]||t}function ks(e){return ao("(prefers-color-scheme: dark)",e)}function ma(e){return e==null?"any":e instanceof Set?"set":e instanceof Map?"map":e instanceof Date?"date":typeof e=="boolean"?"boolean":typeof e=="string"?"string":typeof e=="object"?"object":Number.isNaN(e)?"any":"number"}const ya={boolean:{read:e=>e==="true",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}},Lr="vueuse-storage";function va(e,t,n,s={}){var r;const{flush:i="pre",deep:o=!0,listenToStorageChanges:l=!0,writeDefaults:c=!0,mergeDefaults:u=!1,shallow:a,window:h=qe,eventFilter:v,onError:x=_=>{console.error(_)},initOnMounted:L}=s,O=(a?li:ue)(typeof t=="function"?t():t);if(!n)try{n=fo("getDefaultStorage",()=>{var _;return(_=qe)==null?void 0:_.localStorage})()}catch(_){x(_)}if(!n)return O;const G=tt(t),U=ma(G),W=(r=s.serializer)!=null?r:ya[U],{pause:g,resume:p}=fa(O,()=>D(O.value),{flush:i,deep:o,eventFilter:v});h&&l&&Bs(()=>{n instanceof Storage?Rt(h,"storage",K):Rt(h,Lr,I),L&&K()}),L||K();function A(_,P){if(h){const b={key:e,oldValue:_,newValue:P,storageArea:n};h.dispatchEvent(n instanceof Storage?new StorageEvent("storage",b):new CustomEvent(Lr,{detail:b}))}}function D(_){try{const P=n.getItem(e);if(_==null)A(P,null),n.removeItem(e);else{const b=W.write(_);P!==b&&(n.setItem(e,b),A(P,b))}}catch(P){x(P)}}function j(_){const P=_?_.newValue:n.getItem(e);if(P==null)return c&&G!=null&&n.setItem(e,W.write(G)),G;if(!_&&u){const b=W.read(P);return typeof u=="function"?u(b,G):U==="object"&&!Array.isArray(b)?{...G,...b}:b}else return typeof P!="string"?P:W.read(P)}function K(_){if(!(_&&_.storageArea!==n)){if(_&&_.key==null){O.value=G;return}if(!(_&&_.key!==e)){g();try{(_==null?void 0:_.newValue)!==W.write(O.value)&&(O.value=j(_))}catch(P){x(P)}finally{_?Pn(p):p()}}}}function I(_){K(_.detail)}return O}const _a="*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}";function ba(e={}){const{selector:t="html",attribute:n="class",initialValue:s="auto",window:r=qe,storage:i,storageKey:o="vueuse-color-scheme",listenToStorageChanges:l=!0,storageRef:c,emitAuto:u,disableTransition:a=!0}=e,h={auto:"",light:"light",dark:"dark",...e.modes||{}},v=ks({window:r}),x=oe(()=>v.value?"dark":"light"),L=c||(o==null?lo(s):va(o,s,i,{window:r,listenToStorageChanges:l})),O=oe(()=>L.value==="auto"?x.value:L.value),G=fo("updateHTMLAttrs",(p,A,D)=>{const j=typeof p=="string"?r==null?void 0:r.document.querySelector(p):co(p);if(!j)return;const K=new Set,I=new Set;let _=null;if(A==="class"){const b=D.split(/\s/g);Object.values(h).flatMap(V=>(V||"").split(/\s/g)).filter(Boolean).forEach(V=>{b.includes(V)?K.add(V):I.add(V)})}else _={key:A,value:D};if(K.size===0&&I.size===0&&_===null)return;let P;a&&(P=r.document.createElement("style"),P.appendChild(document.createTextNode(_a)),r.document.head.appendChild(P));for(const b of K)j.classList.add(b);for(const b of I)j.classList.remove(b);_&&j.setAttribute(_.key,_.value),a&&(r.getComputedStyle(P).opacity,document.head.removeChild(P))});function U(p){var A;G(t,n,(A=h[p])!=null?A:p)}function W(p){e.onChanged?e.onChanged(p,U):U(p)}Fe(O,W,{flush:"post",immediate:!0}),Bs(()=>W(O.value));const g=oe({get(){return u?L.value:O.value},set(p){L.value=p}});try{return Object.assign(g,{store:L,system:x,state:O})}catch{return g}}function wa(e={}){const{valueDark:t="dark",valueLight:n="",window:s=qe}=e,r=ba({...e,onChanged:(l,c)=>{var u;e.onChanged?(u=e.onChanged)==null||u.call(e,l==="dark",c,l):c(l)},modes:{dark:t,light:n}}),i=oe(()=>r.system?r.system.value:ks({window:s}).value?"dark":"light");return oe({get(){return r.value==="dark"},set(l){const c=l?"dark":"light";i.value===c?r.value="auto":r.value=c}})}function ns(e){return typeof Window<"u"&&e instanceof Window?e.document.documentElement:typeof Document<"u"&&e instanceof Document?e.documentElement:e}function uo(e){const t=window.getComputedStyle(e);if(t.overflowX==="scroll"||t.overflowY==="scroll"||t.overflowX==="auto"&&e.clientWidth1?!0:(t.preventDefault&&t.preventDefault(),!1)}const ss=new WeakMap;function hf(e,t=!1){const n=ue(t);let s=null,r="";Fe(lo(e),l=>{const c=ns(tt(l));if(c){const u=c;if(ss.get(u)||ss.set(u,u.style.overflow),u.style.overflow!=="hidden"&&(r=u.style.overflow),u.style.overflow==="hidden")return n.value=!0;if(n.value)return u.style.overflow="hidden"}},{immediate:!0});const i=()=>{const l=ns(tt(e));!l||n.value||(Ir&&(s=Rt(l,"touchmove",c=>{xa(c)},{passive:!1})),l.style.overflow="hidden",n.value=!0)},o=()=>{const l=ns(tt(e));!l||!n.value||(Ir&&(s==null||s()),l.style.overflow=r,ss.delete(l),n.value=!1)};return Us(o),oe({get(){return n.value},set(l){l?i():o()}})}function pf(e={}){const{window:t=qe,behavior:n="auto"}=e;if(!t)return{x:ue(0),y:ue(0)};const s=ue(t.scrollX),r=ue(t.scrollY),i=oe({get(){return s.value},set(l){scrollTo({left:l,behavior:n})}}),o=oe({get(){return r.value},set(l){scrollTo({top:l,behavior:n})}});return Rt(t,"scroll",()=>{s.value=t.scrollX,r.value=t.scrollY},{capture:!1,passive:!0}),{x:i,y:o}}function gf(e={}){const{window:t=qe,initialWidth:n=Number.POSITIVE_INFINITY,initialHeight:s=Number.POSITIVE_INFINITY,listenOrientation:r=!0,includeScrollbar:i=!0,type:o="inner"}=e,l=ue(n),c=ue(s),u=()=>{t&&(o==="outer"?(l.value=t.outerWidth,c.value=t.outerHeight):i?(l.value=t.innerWidth,c.value=t.innerHeight):(l.value=t.document.documentElement.clientWidth,c.value=t.document.documentElement.clientHeight))};if(u(),Bs(u),Rt("resize",u,{passive:!0}),r){const a=ao("(orientation: portrait)");Fe(a,()=>u())}return{width:l,height:c}}const rs={BASE_URL:"/",DEV:!1,MODE:"production",PROD:!0,SSR:!1};var is={};const ho=/^(?:[a-z]+:|\/\/)/i,Sa="vitepress-theme-appearance",Ea=/#.*$/,Ca=/[?#].*$/,Ta=/(?:(^|\/)index)?\.(?:md|html)$/,pe=typeof document<"u",po={relativePath:"404.md",filePath:"",title:"404",description:"Not Found",headers:[],frontmatter:{sidebar:!1,layout:"page"},lastUpdated:0,isNotFound:!0};function Aa(e,t,n=!1){if(t===void 0)return!1;if(e=Pr(`/${e}`),n)return new RegExp(t).test(e);if(Pr(t)!==e)return!1;const s=t.match(Ea);return s?(pe?location.hash:"")===s[0]:!0}function Pr(e){return decodeURI(e).replace(Ca,"").replace(Ta,"$1")}function Ra(e){return ho.test(e)}function Oa(e,t){return Object.keys((e==null?void 0:e.locales)||{}).find(n=>n!=="root"&&!Ra(n)&&Aa(t,`/${n}/`,!0))||"root"}function Ma(e,t){var s,r,i,o,l,c,u;const n=Oa(e,t);return Object.assign({},e,{localeIndex:n,lang:((s=e.locales[n])==null?void 0:s.lang)??e.lang,dir:((r=e.locales[n])==null?void 0:r.dir)??e.dir,title:((i=e.locales[n])==null?void 0:i.title)??e.title,titleTemplate:((o=e.locales[n])==null?void 0:o.titleTemplate)??e.titleTemplate,description:((l=e.locales[n])==null?void 0:l.description)??e.description,head:mo(e.head,((c=e.locales[n])==null?void 0:c.head)??[]),themeConfig:{...e.themeConfig,...(u=e.locales[n])==null?void 0:u.themeConfig}})}function go(e,t){const n=t.title||e.title,s=t.titleTemplate??e.titleTemplate;if(typeof s=="string"&&s.includes(":title"))return s.replace(/:title/g,n);const r=Ia(e.title,s);return n===r.slice(3)?n:`${n}${r}`}function Ia(e,t){return t===!1?"":t===!0||t===void 0?` | ${e}`:e===t?"":` | ${t}`}function La(e,t){const[n,s]=t;if(n!=="meta")return!1;const r=Object.entries(s)[0];return r==null?!1:e.some(([i,o])=>i===n&&o[r[0]]===r[1])}function mo(e,t){return[...e.filter(n=>!La(t,n)),...t]}const Pa=/[\u0000-\u001F"#$&*+,:;<=>?[\]^`{|}\u007F]/g,Na=/^[a-z]:/i;function Nr(e){const t=Na.exec(e),n=t?t[0]:"";return n+e.slice(n.length).replace(Pa,"_").replace(/(^|\/)_+(?=[^/]*$)/,"$1")}const os=new Set;function Fa(e){if(os.size===0){const n=typeof process=="object"&&(is==null?void 0:is.VITE_EXTRA_EXTENSIONS)||(rs==null?void 0:rs.VITE_EXTRA_EXTENSIONS)||"";("3g2,3gp,aac,ai,apng,au,avif,bin,bmp,cer,class,conf,crl,css,csv,dll,doc,eps,epub,exe,gif,gz,ics,ief,jar,jpe,jpeg,jpg,js,json,jsonld,m4a,man,mid,midi,mjs,mov,mp2,mp3,mp4,mpe,mpeg,mpg,mpp,oga,ogg,ogv,ogx,opus,otf,p10,p7c,p7m,p7s,pdf,png,ps,qt,roff,rtf,rtx,ser,svg,t,tif,tiff,tr,ts,tsv,ttf,txt,vtt,wav,weba,webm,webp,woff,woff2,xhtml,xml,yaml,yml,zip"+(n&&typeof n=="string"?","+n:"")).split(",").forEach(s=>os.add(s))}const t=e.split(".").pop();return t==null||!os.has(t.toLowerCase())}const Ha=Symbol(),mt=li(na);function mf(e){const t=oe(()=>Ma(mt.value,e.data.relativePath)),n=t.value.appearance,s=n==="force-dark"?ue(!0):n==="force-auto"?ks():n?wa({storageKey:Sa,initialValue:()=>n==="dark"?"dark":"auto",...typeof n=="object"?n:{}}):ue(!1),r=ue(pe?location.hash:"");return pe&&window.addEventListener("hashchange",()=>{r.value=location.hash}),Fe(()=>e.data,()=>{r.value=pe?location.hash:""}),{site:t,theme:oe(()=>t.value.themeConfig),page:oe(()=>e.data),frontmatter:oe(()=>e.data.frontmatter),params:oe(()=>e.data.params),lang:oe(()=>t.value.lang),dir:oe(()=>e.data.frontmatter.dir||t.value.dir),localeIndex:oe(()=>t.value.localeIndex||"root"),title:oe(()=>go(t.value,e.data)),description:oe(()=>e.data.description||t.value.description),isDark:s,hash:oe(()=>r.value)}}function $a(){const e=At(Ha);if(!e)throw new Error("vitepress data not properly injected in app");return e}function Da(e,t){return`${e}${t}`.replace(/\/+/g,"/")}function Fr(e){return ho.test(e)||!e.startsWith("/")?e:Da(mt.value.base,e)}function ja(e){let t=e.replace(/\.html$/,"");if(t=decodeURIComponent(t),t=t.replace(/\/$/,"/index"),pe){const n="/";t=Nr(t.slice(n.length).replace(/\//g,"_")||"index")+".md";let s=__VP_HASH_MAP__[t.toLowerCase()];if(s||(t=t.endsWith("_index.md")?t.slice(0,-9)+".md":t.slice(0,-3)+"_index.md",s=__VP_HASH_MAP__[t.toLowerCase()]),!s)return null;t=`${n}assets/${t}.${s}.js`}else t=`./${Nr(t.slice(1).replace(/\//g,"_"))}.md.js`;return t}let mn=[];function yf(e){mn.push(e),Hn(()=>{mn=mn.filter(t=>t!==e)})}function Va(){let e=mt.value.scrollOffset,t=0,n=24;if(typeof e=="object"&&"padding"in e&&(n=e.padding,e=e.selector),typeof e=="number")t=e;else if(typeof e=="string")t=Hr(e,n);else if(Array.isArray(e))for(const s of e){const r=Hr(s,n);if(r){t=r;break}}return t}function Hr(e,t){const n=document.querySelector(e);if(!n)return 0;const s=n.getBoundingClientRect().bottom;return s<0?0:s+t}const Ua=Symbol(),yo="http://a.com",Ba=()=>({path:"/",component:null,data:po});function vf(e,t){const n=Mn(Ba()),s={route:n,go:r};async function r(l=pe?location.href:"/"){var c,u;l=ls(l),await((c=s.onBeforeRouteChange)==null?void 0:c.call(s,l))!==!1&&(pe&&l!==ls(location.href)&&(history.replaceState({scrollPosition:window.scrollY},""),history.pushState({},"",l)),await o(l),await((u=s.onAfterRouteChanged)==null?void 0:u.call(s,l)))}let i=null;async function o(l,c=0,u=!1){var v,x;if(await((v=s.onBeforePageLoad)==null?void 0:v.call(s,l))===!1)return;const a=new URL(l,yo),h=i=a.pathname;try{let L=await e(h);if(!L)throw new Error(`Page not found: ${h}`);if(i===h){i=null;const{default:O,__pageData:G}=L;if(!O)throw new Error(`Invalid route component: ${O}`);await((x=s.onAfterPageLoad)==null?void 0:x.call(s,l)),n.path=pe?h:Fr(h),n.component=hn(O),n.data=hn(G),pe&&Pn(()=>{let U=mt.value.base+G.relativePath.replace(/(?:(^|\/)index)?\.md$/,"$1");if(!mt.value.cleanUrls&&!U.endsWith("/")&&(U+=".html"),U!==a.pathname&&(a.pathname=U,l=U+a.search+a.hash,history.replaceState({},"",l)),a.hash&&!c){let W=null;try{W=document.getElementById(decodeURIComponent(a.hash).slice(1))}catch(g){console.warn(g)}if(W){$r(W,a.hash);return}}window.scrollTo(0,c)})}}catch(L){if(!/fetch|Page not found/.test(L.message)&&!/^\/404(\.html|\/)?$/.test(l)&&console.error(L),!u)try{const O=await fetch(mt.value.base+"hashmap.json");window.__VP_HASH_MAP__=await O.json(),await o(l,c,!0);return}catch{}if(i===h){i=null,n.path=pe?h:Fr(h),n.component=t?hn(t):null;const O=pe?h.replace(/(^|\/)$/,"$1index").replace(/(\.html)?$/,".md").replace(/^\//,""):"404.md";n.data={...po,relativePath:O}}}}return pe&&(history.state===null&&history.replaceState({},""),window.addEventListener("click",l=>{if(l.defaultPrevented||!(l.target instanceof Element)||l.target.closest("button")||l.button!==0||l.ctrlKey||l.shiftKey||l.altKey||l.metaKey)return;const c=l.target.closest("a");if(!c||c.closest(".vp-raw")||c.hasAttribute("download")||c.hasAttribute("target"))return;const u=c.getAttribute("href")??(c instanceof SVGAElement?c.getAttribute("xlink:href"):null);if(u==null)return;const{href:a,origin:h,pathname:v,hash:x,search:L}=new URL(u,c.baseURI),O=new URL(location.href);h===O.origin&&Fa(v)&&(l.preventDefault(),v===O.pathname&&L===O.search?(x!==O.hash&&(history.pushState({},"",a),window.dispatchEvent(new HashChangeEvent("hashchange",{oldURL:O.href,newURL:a}))),x?$r(c,x,c.classList.contains("header-anchor")):window.scrollTo(0,0)):r(a))},{capture:!0}),window.addEventListener("popstate",async l=>{var c;l.state!==null&&(await o(ls(location.href),l.state&&l.state.scrollPosition||0),(c=s.onAfterRouteChanged)==null||c.call(s,location.href))}),window.addEventListener("hashchange",l=>{l.preventDefault()})),s}function ka(){const e=At(Ua);if(!e)throw new Error("useRouter() is called without provider.");return e}function vo(){return ka().route}function $r(e,t,n=!1){let s=null;try{s=e.classList.contains("header-anchor")?e:document.getElementById(decodeURIComponent(t).slice(1))}catch(r){console.warn(r)}if(s){let r=function(){!n||Math.abs(o-window.scrollY)>window.innerHeight?window.scrollTo(0,o):window.scrollTo({left:0,top:o,behavior:"smooth"})};const i=parseInt(window.getComputedStyle(s).paddingTop,10),o=window.scrollY+s.getBoundingClientRect().top-Va()+i;requestAnimationFrame(r)}}function ls(e){const t=new URL(e,yo);return t.pathname=t.pathname.replace(/(^|\/)index(\.html)?$/,"$1"),mt.value.cleanUrls?t.pathname=t.pathname.replace(/\.html$/,""):!t.pathname.endsWith("/")&&!t.pathname.endsWith(".html")&&(t.pathname+=".html"),t.pathname+t.search+t.hash}const fn=()=>mn.forEach(e=>e()),_f=wi({name:"VitePressContent",props:{as:{type:[Object,String],default:"div"}},setup(e){const t=vo(),{frontmatter:n,site:s}=$a();return Fe(n,fn,{deep:!0,flush:"post"}),()=>xs(e.as,s.value.contentProps??{style:{position:"relative"}},[t.component?xs(t.component,{onVnodeMounted:fn,onVnodeUpdated:fn,onVnodeUnmounted:fn}):"404 Page Not Found"])}}),bf=wi({setup(e,{slots:t}){const n=ue(!1);return Ot(()=>{n.value=!0}),()=>n.value&&t.default?t.default():null}});function wf(){pe&&window.addEventListener("click",e=>{var n;const t=e.target;if(t.matches(".vp-code-group input")){const s=(n=t.parentElement)==null?void 0:n.parentElement;if(!s)return;const r=Array.from(s.querySelectorAll("input")).indexOf(t);if(r<0)return;const i=s.querySelector(".blocks");if(!i)return;const o=Array.from(i.children).find(u=>u.classList.contains("active"));if(!o)return;const l=i.children[r];if(!l||o===l)return;o.classList.remove("active"),l.classList.add("active");const c=s==null?void 0:s.querySelector(`label[for="${t.id}"]`);c==null||c.scrollIntoView({block:"nearest"})}})}function xf(){if(pe){const e=new WeakMap;window.addEventListener("click",t=>{var s;const n=t.target;if(n.matches('div[class*="language-"] > button.copy')){const r=n.parentElement,i=(s=n.nextElementSibling)==null?void 0:s.nextElementSibling;if(!r||!i)return;const o=/language-(shellscript|shell|bash|sh|zsh)/.test(r.className),l=[".vp-copy-ignore",".diff.remove"],c=i.cloneNode(!0);c.querySelectorAll(l.join(",")).forEach(a=>a.remove());let u=c.textContent||"";o&&(u=u.replace(/^ *(\$|>) /gm,"").trim()),Wa(u).then(()=>{n.classList.add("copied"),clearTimeout(e.get(n));const a=setTimeout(()=>{n.classList.remove("copied"),n.blur(),e.delete(n)},2e3);e.set(n,a)})}})}}async function Wa(e){try{return navigator.clipboard.writeText(e)}catch{const t=document.createElement("textarea"),n=document.activeElement;t.value=e,t.setAttribute("readonly",""),t.style.contain="strict",t.style.position="absolute",t.style.left="-9999px",t.style.fontSize="12pt";const s=document.getSelection(),r=s?s.rangeCount>0&&s.getRangeAt(0):null;document.body.appendChild(t),t.select(),t.selectionStart=0,t.selectionEnd=e.length,document.execCommand("copy"),document.body.removeChild(t),r&&(s.removeAllRanges(),s.addRange(r)),n&&n.focus()}}function Sf(e,t){let n=!0,s=[];const r=i=>{if(n){n=!1,i.forEach(l=>{const c=cs(l);for(const u of document.head.children)if(u.isEqualNode(c)){s.push(u);return}});return}const o=i.map(cs);s.forEach((l,c)=>{const u=o.findIndex(a=>a==null?void 0:a.isEqualNode(l??null));u!==-1?delete o[u]:(l==null||l.remove(),delete s[c])}),o.forEach(l=>l&&document.head.appendChild(l)),s=[...s,...o].filter(Boolean)};ki(()=>{const i=e.data,o=t.value,l=i&&i.description,c=i&&i.frontmatter.head||[],u=go(o,i);u!==document.title&&(document.title=u);const a=l||o.description;let h=document.querySelector("meta[name=description]");h?h.getAttribute("content")!==a&&h.setAttribute("content",a):cs(["meta",{name:"description",content:a}]),r(mo(o.head,qa(c)))})}function cs([e,t,n]){const s=document.createElement(e);for(const r in t)s.setAttribute(r,t[r]);return n&&(s.innerHTML=n),e==="script"&&t.async==null&&(s.async=!1),s}function Ka(e){return e[0]==="meta"&&e[1]&&e[1].name==="description"}function qa(e){return e.filter(t=>!Ka(t))}const as=new Set,_o=()=>document.createElement("link"),Ga=e=>{const t=_o();t.rel="prefetch",t.href=e,document.head.appendChild(t)},Ya=e=>{const t=new XMLHttpRequest;t.open("GET",e,t.withCredentials=!0),t.send()};let un;const Xa=pe&&(un=_o())&&un.relList&&un.relList.supports&&un.relList.supports("prefetch")?Ga:Ya;function Ef(){if(!pe||!window.IntersectionObserver)return;let e;if((e=navigator.connection)&&(e.saveData||/2g/.test(e.effectiveType)))return;const t=window.requestIdleCallback||setTimeout;let n=null;const s=()=>{n&&n.disconnect(),n=new IntersectionObserver(i=>{i.forEach(o=>{if(o.isIntersecting){const l=o.target;n.unobserve(l);const{pathname:c}=l;if(!as.has(c)){as.add(c);const u=ja(c);u&&Xa(u)}}})}),t(()=>{document.querySelectorAll("#app a").forEach(i=>{const{hostname:o,pathname:l}=new URL(i.href instanceof SVGAnimatedString?i.href.animVal:i.href,i.baseURI),c=l.match(/\.\w+$/);c&&c[0]!==".html"||i.target!=="_blank"&&o===location.hostname&&(l!==location.pathname?n.observe(i):as.add(l))})})};Ot(s);const r=vo();Fe(()=>r.path,s),Hn(()=>{n&&n.disconnect()})}export{tf as $,Va as A,Ja as B,Qa as C,li as D,yf as E,xe as F,de as G,za as H,ho as I,vo as J,pc as K,At as L,gf as M,Rs as N,df as O,Pn as P,pf as Q,pe as R,In as S,lf as T,hf as U,Gl as V,af as W,ef as X,Ei as Y,cf as Z,uf as _,Qi as a,rf as a0,Sf as a1,Ua as a2,mf as a3,Ha as a4,_f as a5,bf as a6,mt as a7,ff as a8,vf as a9,ja as aa,Ef as ab,xf as ac,wf as ad,xs as ae,bs as b,sf as c,wi as d,of as e,Fa as f,Fr as g,oe as h,Ra as i,zi as j,ai as k,Aa as l,ao as m,Os as n,_s as o,ue as p,Fe as q,Za as r,ki as s,Fo as t,$a as u,Ot as v,_l as w,Hn as x,nf as y,Pl as z}; diff --git a/docs/.vitepress/dist/assets/chunks/theme.CHcreDy7.js b/docs/.vitepress/dist/assets/chunks/theme.CHcreDy7.js deleted file mode 100644 index 5e93ad1..0000000 --- a/docs/.vitepress/dist/assets/chunks/theme.CHcreDy7.js +++ /dev/null @@ -1 +0,0 @@ -import{d as m,o as a,c as u,r as c,n as N,a as z,t as M,b as k,w as p,e as h,T as ce,_ as b,u as Ae,i as Be,f as Ce,g as ue,h as $,j as v,k as r,l as W,m as ae,p as T,q as D,s as Q,v as j,x as de,y as ve,z as Ee,A as Fe,B as q,F as w,C as B,D as ge,E as X,G as _,H as E,I as $e,J as Z,K as U,L as x,M as De,N as ye,O as Oe,P as Pe,Q as Le,R as ee,S as Ge,U as Ve,V as Se,W as Ue,X as je,Y as ze,Z as We,$ as qe}from"./framework.p2VkXzrt.js";const Ke=m({__name:"VPBadge",props:{text:{},type:{default:"tip"}},setup(s){return(e,t)=>(a(),u("span",{class:N(["VPBadge",e.type])},[c(e.$slots,"default",{},()=>[z(M(e.text),1)])],2))}}),Je={key:0,class:"VPBackdrop"},Re=m({__name:"VPBackdrop",props:{show:{type:Boolean}},setup(s){return(e,t)=>(a(),k(ce,{name:"fade"},{default:p(()=>[e.show?(a(),u("div",Je)):h("",!0)]),_:1}))}}),Ye=b(Re,[["__scopeId","data-v-54a304ca"]]),P=Ae;function Qe(s,e){let t,o=!1;return()=>{t&&clearTimeout(t),o?t=setTimeout(s,e):(s(),(o=!0)&&setTimeout(()=>o=!1,e))}}function re(s){return/^\//.test(s)?s:`/${s}`}function pe(s){const{pathname:e,search:t,hash:o,protocol:n}=new URL(s,"http://a.com");if(Be(s)||s.startsWith("#")||!n.startsWith("http")||!Ce(e))return s;const{site:i}=P(),l=e.endsWith("/")||e.endsWith(".html")?s:s.replace(/(?:(^\.+)\/)?.*$/,`$1${e.replace(/(\.md)?$/,i.value.cleanUrls?"":".html")}${t}${o}`);return ue(l)}function J({correspondingLink:s=!1}={}){const{site:e,localeIndex:t,page:o,theme:n,hash:i}=P(),l=$(()=>{var d,y;return{label:(d=e.value.locales[t.value])==null?void 0:d.label,link:((y=e.value.locales[t.value])==null?void 0:y.link)||(t.value==="root"?"/":`/${t.value}/`)}});return{localeLinks:$(()=>Object.entries(e.value.locales).flatMap(([d,y])=>l.value.label===y.label?[]:{text:y.label,link:Xe(y.link||(d==="root"?"/":`/${d}/`),n.value.i18nRouting!==!1&&s,o.value.relativePath.slice(l.value.link.length-1),!e.value.cleanUrls)+i.value})),currentLang:l}}function Xe(s,e,t,o){return e?s.replace(/\/$/,"")+re(t.replace(/(^|\/)index\.md$/,"$1").replace(/\.md$/,o?".html":"")):s}const Ze={class:"NotFound"},xe={class:"code"},et={class:"title"},tt={class:"quote"},nt={class:"action"},ot=["href","aria-label"],st=m({__name:"NotFound",setup(s){const{theme:e}=P(),{currentLang:t}=J();return(o,n)=>{var i,l,f,d,y;return a(),u("div",Ze,[v("p",xe,M(((i=r(e).notFound)==null?void 0:i.code)??"404"),1),v("h1",et,M(((l=r(e).notFound)==null?void 0:l.title)??"PAGE NOT FOUND"),1),n[0]||(n[0]=v("div",{class:"divider"},null,-1)),v("blockquote",tt,M(((f=r(e).notFound)==null?void 0:f.quote)??"But if you don't change your direction, and if you keep looking, you may end up where you are heading."),1),v("div",nt,[v("a",{class:"link",href:r(ue)(r(t).link),"aria-label":((d=r(e).notFound)==null?void 0:d.linkLabel)??"go to home"},M(((y=r(e).notFound)==null?void 0:y.linkText)??"Take me home"),9,ot)])])}}}),at=b(st,[["__scopeId","data-v-6ff51ddd"]]);function Te(s,e){if(Array.isArray(s))return R(s);if(s==null)return[];e=re(e);const t=Object.keys(s).sort((n,i)=>i.split("/").length-n.split("/").length).find(n=>e.startsWith(re(n))),o=t?s[t]:[];return Array.isArray(o)?R(o):R(o.items,o.base)}function rt(s){const e=[];let t=0;for(const o in s){const n=s[o];if(n.items){t=e.push(n);continue}e[t]||e.push({items:[]}),e[t].items.push(n)}return e}function it(s){const e=[];function t(o){for(const n of o)n.text&&n.link&&e.push({text:n.text,link:n.link,docFooterText:n.docFooterText}),n.items&&t(n.items)}return t(s),e}function ie(s,e){return Array.isArray(e)?e.some(t=>ie(s,t)):W(s,e.link)?!0:e.items?ie(s,e.items):!1}function R(s,e){return[...s].map(t=>{const o={...t},n=o.base||e;return n&&o.link&&(o.link=n+o.link),o.items&&(o.items=R(o.items,n)),o})}function O(){const{frontmatter:s,page:e,theme:t}=P(),o=ae("(min-width: 960px)"),n=T(!1),i=$(()=>{const A=t.value.sidebar,S=e.value.relativePath;return A?Te(A,S):[]}),l=T(i.value);D(i,(A,S)=>{JSON.stringify(A)!==JSON.stringify(S)&&(l.value=i.value)});const f=$(()=>s.value.sidebar!==!1&&l.value.length>0&&s.value.layout!=="home"),d=$(()=>y?s.value.aside==null?t.value.aside==="left":s.value.aside==="left":!1),y=$(()=>s.value.layout==="home"?!1:s.value.aside!=null?!!s.value.aside:t.value.aside!==!1),L=$(()=>f.value&&o.value),g=$(()=>f.value?rt(l.value):[]);function V(){n.value=!0}function I(){n.value=!1}function H(){n.value?I():V()}return{isOpen:n,sidebar:l,sidebarGroups:g,hasSidebar:f,hasAside:y,leftAside:d,isSidebarEnabled:L,open:V,close:I,toggle:H}}function lt(s,e){let t;Q(()=>{t=s.value?document.activeElement:void 0}),j(()=>{window.addEventListener("keyup",o)}),de(()=>{window.removeEventListener("keyup",o)});function o(n){n.key==="Escape"&&s.value&&(e(),t==null||t.focus())}}function ct(s){const{page:e,hash:t}=P(),o=T(!1),n=$(()=>s.value.collapsed!=null),i=$(()=>!!s.value.link),l=T(!1),f=()=>{l.value=W(e.value.relativePath,s.value.link)};D([e,s,t],f),j(f);const d=$(()=>l.value?!0:s.value.items?ie(e.value.relativePath,s.value.items):!1),y=$(()=>!!(s.value.items&&s.value.items.length));Q(()=>{o.value=!!(n.value&&s.value.collapsed)}),ve(()=>{(l.value||d.value)&&(o.value=!1)});function L(){n.value&&(o.value=!o.value)}return{collapsed:o,collapsible:n,isLink:i,isActiveLink:l,hasActiveLink:d,hasChildren:y,toggle:L}}function ut(){const{hasSidebar:s}=O(),e=ae("(min-width: 960px)"),t=ae("(min-width: 1280px)");return{isAsideEnabled:$(()=>!t.value&&!e.value?!1:s.value?t.value:e.value)}}const le=[];function Ne(s){return typeof s.outline=="object"&&!Array.isArray(s.outline)&&s.outline.label||s.outlineTitle||"On this page"}function fe(s){const e=[...document.querySelectorAll(".VPDoc :where(h1,h2,h3,h4,h5,h6)")].filter(t=>t.id&&t.hasChildNodes()).map(t=>{const o=Number(t.tagName[1]);return{element:t,title:dt(t),link:"#"+t.id,level:o}});return vt(e,s)}function dt(s){let e="";for(const t of s.childNodes)if(t.nodeType===1){if(t.classList.contains("VPBadge")||t.classList.contains("header-anchor")||t.classList.contains("ignore-header"))continue;e+=t.textContent}else t.nodeType===3&&(e+=t.textContent);return e.trim()}function vt(s,e){if(e===!1)return[];const t=(typeof e=="object"&&!Array.isArray(e)?e.level:e)||2,[o,n]=typeof t=="number"?[t,t]:t==="deep"?[2,6]:t;return ht(s,o,n)}function pt(s,e){const{isAsideEnabled:t}=ut(),o=Qe(i,100);let n=null;j(()=>{requestAnimationFrame(i),window.addEventListener("scroll",o)}),Ee(()=>{l(location.hash)}),de(()=>{window.removeEventListener("scroll",o)});function i(){if(!t.value)return;const f=window.scrollY,d=window.innerHeight,y=document.body.offsetHeight,L=Math.abs(f+d-y)<1,g=le.map(({element:I,link:H})=>({link:H,top:ft(I)})).filter(({top:I})=>!Number.isNaN(I)).sort((I,H)=>I.top-H.top);if(!g.length){l(null);return}if(f<1){l(null);return}if(L){l(g[g.length-1].link);return}let V=null;for(const{link:I,top:H}of g){if(H>f+Fe()+4)break;V=I}l(V)}function l(f){n&&n.classList.remove("active"),f==null?n=null:n=s.value.querySelector(`a[href="${decodeURIComponent(f)}"]`);const d=n;d?(d.classList.add("active"),e.value.style.top=d.offsetTop+39+"px",e.value.style.opacity="1"):(e.value.style.top="33px",e.value.style.opacity="0")}}function ft(s){let e=0;for(;s!==document.body;){if(s===null)return NaN;e+=s.offsetTop,s=s.offsetParent}return e}function ht(s,e,t){le.length=0;const o=[],n=[];return s.forEach(i=>{const l={...i,children:[]};let f=n[n.length-1];for(;f&&f.level>=l.level;)n.pop(),f=n[n.length-1];if(l.element.classList.contains("ignore-header")||f&&"shouldIgnore"in f){n.push({level:l.level,shouldIgnore:!0});return}l.level>t||l.level{const n=q("VPDocOutlineItem",!0);return a(),u("ul",{class:N(["VPDocOutlineItem",t.root?"root":"nested"])},[(a(!0),u(w,null,B(t.headers,({children:i,link:l,title:f})=>(a(),u("li",null,[v("a",{class:"outline-link",href:l,onClick:e,title:f},M(f),9,mt),i!=null&&i.length?(a(),k(n,{key:0,headers:i},null,8,["headers"])):h("",!0)]))),256))],2)}}}),Me=b(_t,[["__scopeId","data-v-53c99d69"]]),kt={class:"content"},bt={"aria-level":"2",class:"outline-title",id:"doc-outline-aria-label",role:"heading"},gt=m({__name:"VPDocAsideOutline",setup(s){const{frontmatter:e,theme:t}=P(),o=ge([]);X(()=>{o.value=fe(e.value.outline??t.value.outline)});const n=T(),i=T();return pt(n,i),(l,f)=>(a(),u("nav",{"aria-labelledby":"doc-outline-aria-label",class:N(["VPDocAsideOutline",{"has-outline":o.value.length>0}]),ref_key:"container",ref:n},[v("div",kt,[v("div",{class:"outline-marker",ref_key:"marker",ref:i},null,512),v("div",bt,M(r(Ne)(r(t))),1),_(Me,{headers:o.value,root:!0},null,8,["headers"])])],2))}}),$t=b(gt,[["__scopeId","data-v-f610f197"]]),yt={class:"VPDocAsideCarbonAds"},Pt=m({__name:"VPDocAsideCarbonAds",props:{carbonAds:{}},setup(s){const e=()=>null;return(t,o)=>(a(),u("div",yt,[_(r(e),{"carbon-ads":t.carbonAds},null,8,["carbon-ads"])]))}}),Lt={class:"VPDocAside"},Vt=m({__name:"VPDocAside",setup(s){const{theme:e}=P();return(t,o)=>(a(),u("div",Lt,[c(t.$slots,"aside-top",{},void 0,!0),c(t.$slots,"aside-outline-before",{},void 0,!0),_($t),c(t.$slots,"aside-outline-after",{},void 0,!0),o[0]||(o[0]=v("div",{class:"spacer"},null,-1)),c(t.$slots,"aside-ads-before",{},void 0,!0),r(e).carbonAds?(a(),k(Pt,{key:0,"carbon-ads":r(e).carbonAds},null,8,["carbon-ads"])):h("",!0),c(t.$slots,"aside-ads-after",{},void 0,!0),c(t.$slots,"aside-bottom",{},void 0,!0)]))}}),St=b(Vt,[["__scopeId","data-v-cb998dce"]]);function Tt(){const{theme:s,page:e}=P();return $(()=>{const{text:t="Edit this page",pattern:o=""}=s.value.editLink||{};let n;return typeof o=="function"?n=o(e.value):n=o.replace(/:path/g,e.value.filePath),{url:n,text:t}})}function Nt(){const{page:s,theme:e,frontmatter:t}=P();return $(()=>{var y,L,g,V,I,H,A,S;const o=Te(e.value.sidebar,s.value.relativePath),n=it(o),i=Mt(n,C=>C.link.replace(/[?#].*$/,"")),l=i.findIndex(C=>W(s.value.relativePath,C.link)),f=((y=e.value.docFooter)==null?void 0:y.prev)===!1&&!t.value.prev||t.value.prev===!1,d=((L=e.value.docFooter)==null?void 0:L.next)===!1&&!t.value.next||t.value.next===!1;return{prev:f?void 0:{text:(typeof t.value.prev=="string"?t.value.prev:typeof t.value.prev=="object"?t.value.prev.text:void 0)??((g=i[l-1])==null?void 0:g.docFooterText)??((V=i[l-1])==null?void 0:V.text),link:(typeof t.value.prev=="object"?t.value.prev.link:void 0)??((I=i[l-1])==null?void 0:I.link)},next:d?void 0:{text:(typeof t.value.next=="string"?t.value.next:typeof t.value.next=="object"?t.value.next.text:void 0)??((H=i[l+1])==null?void 0:H.docFooterText)??((A=i[l+1])==null?void 0:A.text),link:(typeof t.value.next=="object"?t.value.next.link:void 0)??((S=i[l+1])==null?void 0:S.link)}}})}function Mt(s,e){const t=new Set;return s.filter(o=>{const n=e(o);return t.has(n)?!1:t.add(n)})}const F=m({__name:"VPLink",props:{tag:{},href:{},noIcon:{type:Boolean},target:{},rel:{}},setup(s){const e=s,t=$(()=>e.tag??(e.href?"a":"span")),o=$(()=>e.href&&$e.test(e.href)||e.target==="_blank");return(n,i)=>(a(),k(E(t.value),{class:N(["VPLink",{link:n.href,"vp-external-link-icon":o.value,"no-icon":n.noIcon}]),href:n.href?r(pe)(n.href):void 0,target:n.target??(o.value?"_blank":void 0),rel:n.rel??(o.value?"noreferrer":void 0)},{default:p(()=>[c(n.$slots,"default")]),_:3},8,["class","href","target","rel"]))}}),It={class:"VPLastUpdated"},wt=["datetime"],Ht=m({__name:"VPDocFooterLastUpdated",setup(s){const{theme:e,page:t,lang:o}=P(),n=$(()=>new Date(t.value.lastUpdated)),i=$(()=>n.value.toISOString()),l=T("");return j(()=>{Q(()=>{var f,d,y;l.value=new Intl.DateTimeFormat((d=(f=e.value.lastUpdated)==null?void 0:f.formatOptions)!=null&&d.forceLocale?o.value:void 0,((y=e.value.lastUpdated)==null?void 0:y.formatOptions)??{dateStyle:"short",timeStyle:"short"}).format(n.value)})}),(f,d)=>{var y;return a(),u("p",It,[z(M(((y=r(e).lastUpdated)==null?void 0:y.text)||r(e).lastUpdatedText||"Last updated")+": ",1),v("time",{datetime:i.value},M(l.value),9,wt)])}}}),At=b(Ht,[["__scopeId","data-v-1bb0c8a8"]]),Bt={key:0,class:"VPDocFooter"},Ct={key:0,class:"edit-info"},Et={key:0,class:"edit-link"},Ft={key:1,class:"last-updated"},Dt={key:1,class:"prev-next","aria-labelledby":"doc-footer-aria-label"},Ot={class:"pager"},Gt=["innerHTML"],Ut=["innerHTML"],jt={class:"pager"},zt=["innerHTML"],Wt=["innerHTML"],qt=m({__name:"VPDocFooter",setup(s){const{theme:e,page:t,frontmatter:o}=P(),n=Tt(),i=Nt(),l=$(()=>e.value.editLink&&o.value.editLink!==!1),f=$(()=>t.value.lastUpdated),d=$(()=>l.value||f.value||i.value.prev||i.value.next);return(y,L)=>{var g,V,I,H;return d.value?(a(),u("footer",Bt,[c(y.$slots,"doc-footer-before",{},void 0,!0),l.value||f.value?(a(),u("div",Ct,[l.value?(a(),u("div",Et,[_(F,{class:"edit-link-button",href:r(n).url,"no-icon":!0},{default:p(()=>[L[0]||(L[0]=v("span",{class:"vpi-square-pen edit-link-icon"},null,-1)),z(" "+M(r(n).text),1)]),_:1},8,["href"])])):h("",!0),f.value?(a(),u("div",Ft,[_(At)])):h("",!0)])):h("",!0),(g=r(i).prev)!=null&&g.link||(V=r(i).next)!=null&&V.link?(a(),u("nav",Dt,[L[1]||(L[1]=v("span",{class:"visually-hidden",id:"doc-footer-aria-label"},"Pager",-1)),v("div",Ot,[(I=r(i).prev)!=null&&I.link?(a(),k(F,{key:0,class:"pager-link prev",href:r(i).prev.link},{default:p(()=>{var A;return[v("span",{class:"desc",innerHTML:((A=r(e).docFooter)==null?void 0:A.prev)||"Previous page"},null,8,Gt),v("span",{class:"title",innerHTML:r(i).prev.text},null,8,Ut)]}),_:1},8,["href"])):h("",!0)]),v("div",jt,[(H=r(i).next)!=null&&H.link?(a(),k(F,{key:0,class:"pager-link next",href:r(i).next.link},{default:p(()=>{var A;return[v("span",{class:"desc",innerHTML:((A=r(e).docFooter)==null?void 0:A.next)||"Next page"},null,8,zt),v("span",{class:"title",innerHTML:r(i).next.text},null,8,Wt)]}),_:1},8,["href"])):h("",!0)])])):h("",!0)])):h("",!0)}}}),Kt=b(qt,[["__scopeId","data-v-1bcd8184"]]),Jt={class:"container"},Rt={class:"aside-container"},Yt={class:"aside-content"},Qt={class:"content"},Xt={class:"content-container"},Zt={class:"main"},xt=m({__name:"VPDoc",setup(s){const{theme:e}=P(),t=Z(),{hasSidebar:o,hasAside:n,leftAside:i}=O(),l=$(()=>t.path.replace(/[./]+/g,"_").replace(/_html$/,""));return(f,d)=>{const y=q("Content");return a(),u("div",{class:N(["VPDoc",{"has-sidebar":r(o),"has-aside":r(n)}])},[c(f.$slots,"doc-top",{},void 0,!0),v("div",Jt,[r(n)?(a(),u("div",{key:0,class:N(["aside",{"left-aside":r(i)}])},[d[0]||(d[0]=v("div",{class:"aside-curtain"},null,-1)),v("div",Rt,[v("div",Yt,[_(St,null,{"aside-top":p(()=>[c(f.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":p(()=>[c(f.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":p(()=>[c(f.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":p(()=>[c(f.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":p(()=>[c(f.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":p(()=>[c(f.$slots,"aside-ads-after",{},void 0,!0)]),_:3})])])],2)):h("",!0),v("div",Qt,[v("div",Xt,[c(f.$slots,"doc-before",{},void 0,!0),v("main",Zt,[_(y,{class:N(["vp-doc",[l.value,r(e).externalLinkIcon&&"external-link-icon-enabled"]])},null,8,["class"])]),_(Kt,null,{"doc-footer-before":p(()=>[c(f.$slots,"doc-footer-before",{},void 0,!0)]),_:3}),c(f.$slots,"doc-after",{},void 0,!0)])])]),c(f.$slots,"doc-bottom",{},void 0,!0)],2)}}}),en=b(xt,[["__scopeId","data-v-e6f2a212"]]),tn=m({__name:"VPButton",props:{tag:{},size:{default:"medium"},theme:{default:"brand"},text:{},href:{},target:{},rel:{}},setup(s){const e=s,t=$(()=>e.href&&$e.test(e.href)),o=$(()=>e.tag||(e.href?"a":"button"));return(n,i)=>(a(),k(E(o.value),{class:N(["VPButton",[n.size,n.theme]]),href:n.href?r(pe)(n.href):void 0,target:e.target??(t.value?"_blank":void 0),rel:e.rel??(t.value?"noreferrer":void 0)},{default:p(()=>[z(M(n.text),1)]),_:1},8,["class","href","target","rel"]))}}),nn=b(tn,[["__scopeId","data-v-93dc4167"]]),on=["src","alt"],sn=m({inheritAttrs:!1,__name:"VPImage",props:{image:{},alt:{}},setup(s){return(e,t)=>{const o=q("VPImage",!0);return e.image?(a(),u(w,{key:0},[typeof e.image=="string"||"src"in e.image?(a(),u("img",U({key:0,class:"VPImage"},typeof e.image=="string"?e.$attrs:{...e.image,...e.$attrs},{src:r(ue)(typeof e.image=="string"?e.image:e.image.src),alt:e.alt??(typeof e.image=="string"?"":e.image.alt||"")}),null,16,on)):(a(),u(w,{key:1},[_(o,U({class:"dark",image:e.image.dark,alt:e.image.alt},e.$attrs),null,16,["image","alt"]),_(o,U({class:"light",image:e.image.light,alt:e.image.alt},e.$attrs),null,16,["image","alt"])],64))],64)):h("",!0)}}}),Y=b(sn,[["__scopeId","data-v-ab19afbb"]]),an={class:"container"},rn={class:"main"},ln={key:0,class:"name"},cn=["innerHTML"],un=["innerHTML"],dn=["innerHTML"],vn={key:0,class:"actions"},pn={key:0,class:"image"},fn={class:"image-container"},hn=m({__name:"VPHero",props:{name:{},text:{},tagline:{},image:{},actions:{}},setup(s){const e=x("hero-image-slot-exists");return(t,o)=>(a(),u("div",{class:N(["VPHero",{"has-image":t.image||r(e)}])},[v("div",an,[v("div",rn,[c(t.$slots,"home-hero-info-before",{},void 0,!0),c(t.$slots,"home-hero-info",{},()=>[t.name?(a(),u("h1",ln,[v("span",{innerHTML:t.name,class:"clip"},null,8,cn)])):h("",!0),t.text?(a(),u("p",{key:1,innerHTML:t.text,class:"text"},null,8,un)):h("",!0),t.tagline?(a(),u("p",{key:2,innerHTML:t.tagline,class:"tagline"},null,8,dn)):h("",!0)],!0),c(t.$slots,"home-hero-info-after",{},void 0,!0),t.actions?(a(),u("div",vn,[(a(!0),u(w,null,B(t.actions,n=>(a(),u("div",{key:n.link,class:"action"},[_(nn,{tag:"a",size:"medium",theme:n.theme,text:n.text,href:n.link,target:n.target,rel:n.rel},null,8,["theme","text","href","target","rel"])]))),128))])):h("",!0),c(t.$slots,"home-hero-actions-after",{},void 0,!0)]),t.image||r(e)?(a(),u("div",pn,[v("div",fn,[o[0]||(o[0]=v("div",{class:"image-bg"},null,-1)),c(t.$slots,"home-hero-image",{},()=>[t.image?(a(),k(Y,{key:0,class:"image-src",image:t.image},null,8,["image"])):h("",!0)],!0)])])):h("",!0)])],2))}}),mn=b(hn,[["__scopeId","data-v-b10c5094"]]),_n=m({__name:"VPHomeHero",setup(s){const{frontmatter:e}=P();return(t,o)=>r(e).hero?(a(),k(mn,{key:0,class:"VPHomeHero",name:r(e).hero.name,text:r(e).hero.text,tagline:r(e).hero.tagline,image:r(e).hero.image,actions:r(e).hero.actions},{"home-hero-info-before":p(()=>[c(t.$slots,"home-hero-info-before")]),"home-hero-info":p(()=>[c(t.$slots,"home-hero-info")]),"home-hero-info-after":p(()=>[c(t.$slots,"home-hero-info-after")]),"home-hero-actions-after":p(()=>[c(t.$slots,"home-hero-actions-after")]),"home-hero-image":p(()=>[c(t.$slots,"home-hero-image")]),_:3},8,["name","text","tagline","image","actions"])):h("",!0)}}),kn={class:"box"},bn={key:0,class:"icon"},gn=["innerHTML"],$n=["innerHTML"],yn=["innerHTML"],Pn={key:4,class:"link-text"},Ln={class:"link-text-value"},Vn=m({__name:"VPFeature",props:{icon:{},title:{},details:{},link:{},linkText:{},rel:{},target:{}},setup(s){return(e,t)=>(a(),k(F,{class:"VPFeature",href:e.link,rel:e.rel,target:e.target,"no-icon":!0,tag:e.link?"a":"div"},{default:p(()=>[v("article",kn,[typeof e.icon=="object"&&e.icon.wrap?(a(),u("div",bn,[_(Y,{image:e.icon,alt:e.icon.alt,height:e.icon.height||48,width:e.icon.width||48},null,8,["image","alt","height","width"])])):typeof e.icon=="object"?(a(),k(Y,{key:1,image:e.icon,alt:e.icon.alt,height:e.icon.height||48,width:e.icon.width||48},null,8,["image","alt","height","width"])):e.icon?(a(),u("div",{key:2,class:"icon",innerHTML:e.icon},null,8,gn)):h("",!0),v("h2",{class:"title",innerHTML:e.title},null,8,$n),e.details?(a(),u("p",{key:3,class:"details",innerHTML:e.details},null,8,yn)):h("",!0),e.linkText?(a(),u("div",Pn,[v("p",Ln,[z(M(e.linkText)+" ",1),t[0]||(t[0]=v("span",{class:"vpi-arrow-right link-text-icon"},null,-1))])])):h("",!0)])]),_:1},8,["href","rel","target","tag"]))}}),Sn=b(Vn,[["__scopeId","data-v-bd37d1a2"]]),Tn={key:0,class:"VPFeatures"},Nn={class:"container"},Mn={class:"items"},In=m({__name:"VPFeatures",props:{features:{}},setup(s){const e=s,t=$(()=>{const o=e.features.length;if(o){if(o===2)return"grid-2";if(o===3)return"grid-3";if(o%3===0)return"grid-6";if(o>3)return"grid-4"}else return});return(o,n)=>o.features?(a(),u("div",Tn,[v("div",Nn,[v("div",Mn,[(a(!0),u(w,null,B(o.features,i=>(a(),u("div",{key:i.title,class:N(["item",[t.value]])},[_(Sn,{icon:i.icon,title:i.title,details:i.details,link:i.link,"link-text":i.linkText,rel:i.rel,target:i.target},null,8,["icon","title","details","link","link-text","rel","target"])],2))),128))])])])):h("",!0)}}),wn=b(In,[["__scopeId","data-v-b1eea84a"]]),Hn=m({__name:"VPHomeFeatures",setup(s){const{frontmatter:e}=P();return(t,o)=>r(e).features?(a(),k(wn,{key:0,class:"VPHomeFeatures",features:r(e).features},null,8,["features"])):h("",!0)}}),An=m({__name:"VPHomeContent",setup(s){const{width:e}=De({initialWidth:0,includeScrollbar:!1});return(t,o)=>(a(),u("div",{class:"vp-doc container",style:ye(r(e)?{"--vp-offset":`calc(50% - ${r(e)/2}px)`}:{})},[c(t.$slots,"default",{},void 0,!0)],4))}}),Bn=b(An,[["__scopeId","data-v-c141a4bd"]]),Cn={class:"VPHome"},En=m({__name:"VPHome",setup(s){const{frontmatter:e}=P();return(t,o)=>{const n=q("Content");return a(),u("div",Cn,[c(t.$slots,"home-hero-before",{},void 0,!0),_(_n,null,{"home-hero-info-before":p(()=>[c(t.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":p(()=>[c(t.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":p(()=>[c(t.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":p(()=>[c(t.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-image":p(()=>[c(t.$slots,"home-hero-image",{},void 0,!0)]),_:3}),c(t.$slots,"home-hero-after",{},void 0,!0),c(t.$slots,"home-features-before",{},void 0,!0),_(Hn),c(t.$slots,"home-features-after",{},void 0,!0),r(e).markdownStyles!==!1?(a(),k(Bn,{key:0},{default:p(()=>[_(n)]),_:1})):(a(),k(n,{key:1}))])}}}),Fn=b(En,[["__scopeId","data-v-07b1ad08"]]),Dn={},On={class:"VPPage"};function Gn(s,e){const t=q("Content");return a(),u("div",On,[c(s.$slots,"page-top"),_(t),c(s.$slots,"page-bottom")])}const Un=b(Dn,[["render",Gn]]),jn=m({__name:"VPContent",setup(s){const{page:e,frontmatter:t}=P(),{hasSidebar:o}=O();return(n,i)=>(a(),u("div",{class:N(["VPContent",{"has-sidebar":r(o),"is-home":r(t).layout==="home"}]),id:"VPContent"},[r(e).isNotFound?c(n.$slots,"not-found",{key:0},()=>[_(at)],!0):r(t).layout==="page"?(a(),k(Un,{key:1},{"page-top":p(()=>[c(n.$slots,"page-top",{},void 0,!0)]),"page-bottom":p(()=>[c(n.$slots,"page-bottom",{},void 0,!0)]),_:3})):r(t).layout==="home"?(a(),k(Fn,{key:2},{"home-hero-before":p(()=>[c(n.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info-before":p(()=>[c(n.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":p(()=>[c(n.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":p(()=>[c(n.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":p(()=>[c(n.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-image":p(()=>[c(n.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":p(()=>[c(n.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":p(()=>[c(n.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":p(()=>[c(n.$slots,"home-features-after",{},void 0,!0)]),_:3})):r(t).layout&&r(t).layout!=="doc"?(a(),k(E(r(t).layout),{key:3})):(a(),k(en,{key:4},{"doc-top":p(()=>[c(n.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":p(()=>[c(n.$slots,"doc-bottom",{},void 0,!0)]),"doc-footer-before":p(()=>[c(n.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":p(()=>[c(n.$slots,"doc-before",{},void 0,!0)]),"doc-after":p(()=>[c(n.$slots,"doc-after",{},void 0,!0)]),"aside-top":p(()=>[c(n.$slots,"aside-top",{},void 0,!0)]),"aside-outline-before":p(()=>[c(n.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":p(()=>[c(n.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":p(()=>[c(n.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":p(()=>[c(n.$slots,"aside-ads-after",{},void 0,!0)]),"aside-bottom":p(()=>[c(n.$slots,"aside-bottom",{},void 0,!0)]),_:3}))],2))}}),zn=b(jn,[["__scopeId","data-v-9a6c75ad"]]),Wn={class:"container"},qn=["innerHTML"],Kn=["innerHTML"],Jn=m({__name:"VPFooter",setup(s){const{theme:e,frontmatter:t}=P(),{hasSidebar:o}=O();return(n,i)=>r(e).footer&&r(t).footer!==!1?(a(),u("footer",{key:0,class:N(["VPFooter",{"has-sidebar":r(o)}])},[v("div",Wn,[r(e).footer.message?(a(),u("p",{key:0,class:"message",innerHTML:r(e).footer.message},null,8,qn)):h("",!0),r(e).footer.copyright?(a(),u("p",{key:1,class:"copyright",innerHTML:r(e).footer.copyright},null,8,Kn)):h("",!0)])],2)):h("",!0)}}),Rn=b(Jn,[["__scopeId","data-v-566314d4"]]);function Yn(){const{theme:s,frontmatter:e}=P(),t=ge([]),o=$(()=>t.value.length>0);return X(()=>{t.value=fe(e.value.outline??s.value.outline)}),{headers:t,hasLocalNav:o}}const Qn={class:"menu-text"},Xn={class:"header"},Zn={class:"outline"},xn=m({__name:"VPLocalNavOutlineDropdown",props:{headers:{},navHeight:{}},setup(s){const e=s,{theme:t}=P(),o=T(!1),n=T(0),i=T(),l=T();function f(g){var V;(V=i.value)!=null&&V.contains(g.target)||(o.value=!1)}D(o,g=>{if(g){document.addEventListener("click",f);return}document.removeEventListener("click",f)}),Oe("Escape",()=>{o.value=!1}),X(()=>{o.value=!1});function d(){o.value=!o.value,n.value=window.innerHeight+Math.min(window.scrollY-e.navHeight,0)}function y(g){g.target.classList.contains("outline-link")&&(l.value&&(l.value.style.transition="none"),Pe(()=>{o.value=!1}))}function L(){o.value=!1,window.scrollTo({top:0,left:0,behavior:"smooth"})}return(g,V)=>(a(),u("div",{class:"VPLocalNavOutlineDropdown",style:ye({"--vp-vh":n.value+"px"}),ref_key:"main",ref:i},[g.headers.length>0?(a(),u("button",{key:0,onClick:d,class:N({open:o.value})},[v("span",Qn,M(r(Ne)(r(t))),1),V[0]||(V[0]=v("span",{class:"vpi-chevron-right icon"},null,-1))],2)):(a(),u("button",{key:1,onClick:L},M(r(t).returnToTopLabel||"Return to top"),1)),_(ce,{name:"flyout"},{default:p(()=>[o.value?(a(),u("div",{key:0,ref_key:"items",ref:l,class:"items",onClick:y},[v("div",Xn,[v("a",{class:"top-link",href:"#",onClick:L},M(r(t).returnToTopLabel||"Return to top"),1)]),v("div",Zn,[_(Me,{headers:g.headers},null,8,["headers"])])],512)):h("",!0)]),_:1})],4))}}),eo=b(xn,[["__scopeId","data-v-883964e0"]]),to={class:"container"},no=["aria-expanded"],oo={class:"menu-text"},so=m({__name:"VPLocalNav",props:{open:{type:Boolean}},emits:["open-menu"],setup(s){const{theme:e,frontmatter:t}=P(),{hasSidebar:o}=O(),{headers:n}=Yn(),{y:i}=Le(),l=T(0);j(()=>{l.value=parseInt(getComputedStyle(document.documentElement).getPropertyValue("--vp-nav-height"))}),X(()=>{n.value=fe(t.value.outline??e.value.outline)});const f=$(()=>n.value.length===0),d=$(()=>f.value&&!o.value),y=$(()=>({VPLocalNav:!0,"has-sidebar":o.value,empty:f.value,fixed:d.value}));return(L,g)=>r(t).layout!=="home"&&(!d.value||r(i)>=l.value)?(a(),u("div",{key:0,class:N(y.value)},[v("div",to,[r(o)?(a(),u("button",{key:0,class:"menu","aria-expanded":L.open,"aria-controls":"VPSidebarNav",onClick:g[0]||(g[0]=V=>L.$emit("open-menu"))},[g[1]||(g[1]=v("span",{class:"vpi-align-left menu-icon"},null,-1)),v("span",oo,M(r(e).sidebarMenuLabel||"Menu"),1)],8,no)):h("",!0),_(eo,{headers:r(n),navHeight:l.value},null,8,["headers","navHeight"])])],2)):h("",!0)}}),ao=b(so,[["__scopeId","data-v-2488c25a"]]);function ro(){const s=T(!1);function e(){s.value=!0,window.addEventListener("resize",n)}function t(){s.value=!1,window.removeEventListener("resize",n)}function o(){s.value?t():e()}function n(){window.outerWidth>=768&&t()}const i=Z();return D(()=>i.path,t),{isScreenOpen:s,openScreen:e,closeScreen:t,toggleScreen:o}}const io={},lo={class:"VPSwitch",type:"button",role:"switch"},co={class:"check"},uo={key:0,class:"icon"};function vo(s,e){return a(),u("button",lo,[v("span",co,[s.$slots.default?(a(),u("span",uo,[c(s.$slots,"default",{},void 0,!0)])):h("",!0)])])}const po=b(io,[["render",vo],["__scopeId","data-v-b4ccac88"]]),fo=m({__name:"VPSwitchAppearance",setup(s){const{isDark:e,theme:t}=P(),o=x("toggle-appearance",()=>{e.value=!e.value}),n=T("");return ve(()=>{n.value=e.value?t.value.lightModeSwitchTitle||"Switch to light theme":t.value.darkModeSwitchTitle||"Switch to dark theme"}),(i,l)=>(a(),k(po,{title:n.value,class:"VPSwitchAppearance","aria-checked":r(e),onClick:r(o)},{default:p(()=>l[0]||(l[0]=[v("span",{class:"vpi-sun sun"},null,-1),v("span",{class:"vpi-moon moon"},null,-1)])),_:1},8,["title","aria-checked","onClick"]))}}),he=b(fo,[["__scopeId","data-v-be9742d9"]]),ho={key:0,class:"VPNavBarAppearance"},mo=m({__name:"VPNavBarAppearance",setup(s){const{site:e}=P();return(t,o)=>r(e).appearance&&r(e).appearance!=="force-dark"&&r(e).appearance!=="force-auto"?(a(),u("div",ho,[_(he)])):h("",!0)}}),_o=b(mo,[["__scopeId","data-v-3f90c1a5"]]),me=T();let Ie=!1,se=0;function ko(s){const e=T(!1);if(ee){!Ie&&bo(),se++;const t=D(me,o=>{var n,i,l;o===s.el.value||(n=s.el.value)!=null&&n.contains(o)?(e.value=!0,(i=s.onFocus)==null||i.call(s)):(e.value=!1,(l=s.onBlur)==null||l.call(s))});de(()=>{t(),se--,se||go()})}return Ge(e)}function bo(){document.addEventListener("focusin",we),Ie=!0,me.value=document.activeElement}function go(){document.removeEventListener("focusin",we)}function we(){me.value=document.activeElement}const $o={class:"VPMenuLink"},yo=["innerHTML"],Po=m({__name:"VPMenuLink",props:{item:{}},setup(s){const{page:e}=P();return(t,o)=>(a(),u("div",$o,[_(F,{class:N({active:r(W)(r(e).relativePath,t.item.activeMatch||t.item.link,!!t.item.activeMatch)}),href:t.item.link,target:t.item.target,rel:t.item.rel,"no-icon":t.item.noIcon},{default:p(()=>[v("span",{innerHTML:t.item.text},null,8,yo)]),_:1},8,["class","href","target","rel","no-icon"])]))}}),te=b(Po,[["__scopeId","data-v-7eeeb2dc"]]),Lo={class:"VPMenuGroup"},Vo={key:0,class:"title"},So=m({__name:"VPMenuGroup",props:{text:{},items:{}},setup(s){return(e,t)=>(a(),u("div",Lo,[e.text?(a(),u("p",Vo,M(e.text),1)):h("",!0),(a(!0),u(w,null,B(e.items,o=>(a(),u(w,null,["link"in o?(a(),k(te,{key:0,item:o},null,8,["item"])):h("",!0)],64))),256))]))}}),To=b(So,[["__scopeId","data-v-a6b0397c"]]),No={class:"VPMenu"},Mo={key:0,class:"items"},Io=m({__name:"VPMenu",props:{items:{}},setup(s){return(e,t)=>(a(),u("div",No,[e.items?(a(),u("div",Mo,[(a(!0),u(w,null,B(e.items,o=>(a(),u(w,{key:JSON.stringify(o)},["link"in o?(a(),k(te,{key:0,item:o},null,8,["item"])):"component"in o?(a(),k(E(o.component),U({key:1,ref_for:!0},o.props),null,16)):(a(),k(To,{key:2,text:o.text,items:o.items},null,8,["text","items"]))],64))),128))])):h("",!0),c(e.$slots,"default",{},void 0,!0)]))}}),wo=b(Io,[["__scopeId","data-v-20ed86d6"]]),Ho=["aria-expanded","aria-label"],Ao={key:0,class:"text"},Bo=["innerHTML"],Co={key:1,class:"vpi-more-horizontal icon"},Eo={class:"menu"},Fo=m({__name:"VPFlyout",props:{icon:{},button:{},label:{},items:{}},setup(s){const e=T(!1),t=T();ko({el:t,onBlur:o});function o(){e.value=!1}return(n,i)=>(a(),u("div",{class:"VPFlyout",ref_key:"el",ref:t,onMouseenter:i[1]||(i[1]=l=>e.value=!0),onMouseleave:i[2]||(i[2]=l=>e.value=!1)},[v("button",{type:"button",class:"button","aria-haspopup":"true","aria-expanded":e.value,"aria-label":n.label,onClick:i[0]||(i[0]=l=>e.value=!e.value)},[n.button||n.icon?(a(),u("span",Ao,[n.icon?(a(),u("span",{key:0,class:N([n.icon,"option-icon"])},null,2)):h("",!0),n.button?(a(),u("span",{key:1,innerHTML:n.button},null,8,Bo)):h("",!0),i[3]||(i[3]=v("span",{class:"vpi-chevron-down text-icon"},null,-1))])):(a(),u("span",Co))],8,Ho),v("div",Eo,[_(wo,{items:n.items},{default:p(()=>[c(n.$slots,"default",{},void 0,!0)]),_:3},8,["items"])])],544))}}),_e=b(Fo,[["__scopeId","data-v-bfe7971f"]]),Do=["href","aria-label","innerHTML"],Oo=m({__name:"VPSocialLink",props:{icon:{},link:{},ariaLabel:{}},setup(s){const e=s,t=T();j(async()=>{var i;await Pe();const n=(i=t.value)==null?void 0:i.children[0];n instanceof HTMLElement&&n.className.startsWith("vpi-social-")&&(getComputedStyle(n).maskImage||getComputedStyle(n).webkitMaskImage)==="none"&&n.style.setProperty("--icon",`url('https://api.iconify.design/simple-icons/${e.icon}.svg')`)});const o=$(()=>typeof e.icon=="object"?e.icon.svg:``);return(n,i)=>(a(),u("a",{ref_key:"el",ref:t,class:"VPSocialLink no-icon",href:n.link,"aria-label":n.ariaLabel??(typeof n.icon=="string"?n.icon:""),target:"_blank",rel:"noopener",innerHTML:o.value},null,8,Do))}}),Go=b(Oo,[["__scopeId","data-v-60a9a2d3"]]),Uo={class:"VPSocialLinks"},jo=m({__name:"VPSocialLinks",props:{links:{}},setup(s){return(e,t)=>(a(),u("div",Uo,[(a(!0),u(w,null,B(e.links,({link:o,icon:n,ariaLabel:i})=>(a(),k(Go,{key:o,icon:n,link:o,ariaLabel:i},null,8,["icon","link","ariaLabel"]))),128))]))}}),ke=b(jo,[["__scopeId","data-v-e71e869c"]]),zo={key:0,class:"group translations"},Wo={class:"trans-title"},qo={key:1,class:"group"},Ko={class:"item appearance"},Jo={class:"label"},Ro={class:"appearance-action"},Yo={key:2,class:"group"},Qo={class:"item social-links"},Xo=m({__name:"VPNavBarExtra",setup(s){const{site:e,theme:t}=P(),{localeLinks:o,currentLang:n}=J({correspondingLink:!0}),i=$(()=>o.value.length&&n.value.label||e.value.appearance||t.value.socialLinks);return(l,f)=>i.value?(a(),k(_e,{key:0,class:"VPNavBarExtra",label:"extra navigation"},{default:p(()=>[r(o).length&&r(n).label?(a(),u("div",zo,[v("p",Wo,M(r(n).label),1),(a(!0),u(w,null,B(r(o),d=>(a(),k(te,{key:d.link,item:d},null,8,["item"]))),128))])):h("",!0),r(e).appearance&&r(e).appearance!=="force-dark"&&r(e).appearance!=="force-auto"?(a(),u("div",qo,[v("div",Ko,[v("p",Jo,M(r(t).darkModeSwitchLabel||"Appearance"),1),v("div",Ro,[_(he)])])])):h("",!0),r(t).socialLinks?(a(),u("div",Yo,[v("div",Qo,[_(ke,{class:"social-links-list",links:r(t).socialLinks},null,8,["links"])])])):h("",!0)]),_:1})):h("",!0)}}),Zo=b(Xo,[["__scopeId","data-v-f953d92f"]]),xo=["aria-expanded"],es=m({__name:"VPNavBarHamburger",props:{active:{type:Boolean}},emits:["click"],setup(s){return(e,t)=>(a(),u("button",{type:"button",class:N(["VPNavBarHamburger",{active:e.active}]),"aria-label":"mobile navigation","aria-expanded":e.active,"aria-controls":"VPNavScreen",onClick:t[0]||(t[0]=o=>e.$emit("click"))},t[1]||(t[1]=[v("span",{class:"container"},[v("span",{class:"top"}),v("span",{class:"middle"}),v("span",{class:"bottom"})],-1)]),10,xo))}}),ts=b(es,[["__scopeId","data-v-6bee1efd"]]),ns=["innerHTML"],os=m({__name:"VPNavBarMenuLink",props:{item:{}},setup(s){const{page:e}=P();return(t,o)=>(a(),k(F,{class:N({VPNavBarMenuLink:!0,active:r(W)(r(e).relativePath,t.item.activeMatch||t.item.link,!!t.item.activeMatch)}),href:t.item.link,target:t.item.target,rel:t.item.rel,"no-icon":t.item.noIcon,tabindex:"0"},{default:p(()=>[v("span",{innerHTML:t.item.text},null,8,ns)]),_:1},8,["class","href","target","rel","no-icon"]))}}),ss=b(os,[["__scopeId","data-v-815115f5"]]),as=m({__name:"VPNavBarMenuGroup",props:{item:{}},setup(s){const e=s,{page:t}=P(),o=i=>"component"in i?!1:"link"in i?W(t.value.relativePath,i.link,!!e.item.activeMatch):i.items.some(o),n=$(()=>o(e.item));return(i,l)=>(a(),k(_e,{class:N({VPNavBarMenuGroup:!0,active:r(W)(r(t).relativePath,i.item.activeMatch,!!i.item.activeMatch)||n.value}),button:i.item.text,items:i.item.items},null,8,["class","button","items"]))}}),rs={key:0,"aria-labelledby":"main-nav-aria-label",class:"VPNavBarMenu"},is=m({__name:"VPNavBarMenu",setup(s){const{theme:e}=P();return(t,o)=>r(e).nav?(a(),u("nav",rs,[o[0]||(o[0]=v("span",{id:"main-nav-aria-label",class:"visually-hidden"}," Main Navigation ",-1)),(a(!0),u(w,null,B(r(e).nav,n=>(a(),u(w,{key:JSON.stringify(n)},["link"in n?(a(),k(ss,{key:0,item:n},null,8,["item"])):"component"in n?(a(),k(E(n.component),U({key:1,ref_for:!0},n.props),null,16)):(a(),k(as,{key:2,item:n},null,8,["item"]))],64))),128))])):h("",!0)}}),ls=b(is,[["__scopeId","data-v-afb2845e"]]);function cs(s){const{localeIndex:e,theme:t}=P();function o(n){var H,A,S;const i=n.split("."),l=(H=t.value.search)==null?void 0:H.options,f=l&&typeof l=="object",d=f&&((S=(A=l.locales)==null?void 0:A[e.value])==null?void 0:S.translations)||null,y=f&&l.translations||null;let L=d,g=y,V=s;const I=i.pop();for(const C of i){let G=null;const K=V==null?void 0:V[C];K&&(G=V=K);const ne=g==null?void 0:g[C];ne&&(G=g=ne);const oe=L==null?void 0:L[C];oe&&(G=L=oe),K||(V=G),ne||(g=G),oe||(L=G)}return(L==null?void 0:L[I])??(g==null?void 0:g[I])??(V==null?void 0:V[I])??""}return o}const us=["aria-label"],ds={class:"DocSearch-Button-Container"},vs={class:"DocSearch-Button-Placeholder"},be=m({__name:"VPNavBarSearchButton",setup(s){const t=cs({button:{buttonText:"Search",buttonAriaLabel:"Search"}});return(o,n)=>(a(),u("button",{type:"button",class:"DocSearch DocSearch-Button","aria-label":r(t)("button.buttonAriaLabel")},[v("span",ds,[n[0]||(n[0]=v("span",{class:"vp-icon DocSearch-Search-Icon"},null,-1)),v("span",vs,M(r(t)("button.buttonText")),1)]),n[1]||(n[1]=v("span",{class:"DocSearch-Button-Keys"},[v("kbd",{class:"DocSearch-Button-Key"}),v("kbd",{class:"DocSearch-Button-Key"},"K")],-1))],8,us))}}),ps={class:"VPNavBarSearch"},fs={id:"local-search"},hs={key:1,id:"docsearch"},ms=m({__name:"VPNavBarSearch",setup(s){const e=()=>null,t=()=>null,{theme:o}=P(),n=T(!1),i=T(!1);j(()=>{});function l(){n.value||(n.value=!0,setTimeout(f,16))}function f(){const L=new Event("keydown");L.key="k",L.metaKey=!0,window.dispatchEvent(L),setTimeout(()=>{document.querySelector(".DocSearch-Modal")||f()},16)}const d=T(!1),y="";return(L,g)=>{var V;return a(),u("div",ps,[r(y)==="local"?(a(),u(w,{key:0},[d.value?(a(),k(r(e),{key:0,onClose:g[0]||(g[0]=I=>d.value=!1)})):h("",!0),v("div",fs,[_(be,{onClick:g[1]||(g[1]=I=>d.value=!0)})])],64)):r(y)==="algolia"?(a(),u(w,{key:1},[n.value?(a(),k(r(t),{key:0,algolia:((V=r(o).search)==null?void 0:V.options)??r(o).algolia,onVnodeBeforeMount:g[2]||(g[2]=I=>i.value=!0)},null,8,["algolia"])):h("",!0),i.value?h("",!0):(a(),u("div",hs,[_(be,{onClick:l})]))],64)):h("",!0)])}}}),_s=m({__name:"VPNavBarSocialLinks",setup(s){const{theme:e}=P();return(t,o)=>r(e).socialLinks?(a(),k(ke,{key:0,class:"VPNavBarSocialLinks",links:r(e).socialLinks},null,8,["links"])):h("",!0)}}),ks=b(_s,[["__scopeId","data-v-ef6192dc"]]),bs=["href","rel","target"],gs=["innerHTML"],$s={key:2},ys=m({__name:"VPNavBarTitle",setup(s){const{site:e,theme:t}=P(),{hasSidebar:o}=O(),{currentLang:n}=J(),i=$(()=>{var d;return typeof t.value.logoLink=="string"?t.value.logoLink:(d=t.value.logoLink)==null?void 0:d.link}),l=$(()=>{var d;return typeof t.value.logoLink=="string"||(d=t.value.logoLink)==null?void 0:d.rel}),f=$(()=>{var d;return typeof t.value.logoLink=="string"||(d=t.value.logoLink)==null?void 0:d.target});return(d,y)=>(a(),u("div",{class:N(["VPNavBarTitle",{"has-sidebar":r(o)}])},[v("a",{class:"title",href:i.value??r(pe)(r(n).link),rel:l.value,target:f.value},[c(d.$slots,"nav-bar-title-before",{},void 0,!0),r(t).logo?(a(),k(Y,{key:0,class:"logo",image:r(t).logo},null,8,["image"])):h("",!0),r(t).siteTitle?(a(),u("span",{key:1,innerHTML:r(t).siteTitle},null,8,gs)):r(t).siteTitle===void 0?(a(),u("span",$s,M(r(e).title),1)):h("",!0),c(d.$slots,"nav-bar-title-after",{},void 0,!0)],8,bs)],2))}}),Ps=b(ys,[["__scopeId","data-v-9f43907a"]]),Ls={class:"items"},Vs={class:"title"},Ss=m({__name:"VPNavBarTranslations",setup(s){const{theme:e}=P(),{localeLinks:t,currentLang:o}=J({correspondingLink:!0});return(n,i)=>r(t).length&&r(o).label?(a(),k(_e,{key:0,class:"VPNavBarTranslations",icon:"vpi-languages",label:r(e).langMenuLabel||"Change language"},{default:p(()=>[v("div",Ls,[v("p",Vs,M(r(o).label),1),(a(!0),u(w,null,B(r(t),l=>(a(),k(te,{key:l.link,item:l},null,8,["item"]))),128))])]),_:1},8,["label"])):h("",!0)}}),Ts=b(Ss,[["__scopeId","data-v-acee064b"]]),Ns={class:"wrapper"},Ms={class:"container"},Is={class:"title"},ws={class:"content"},Hs={class:"content-body"},As=m({__name:"VPNavBar",props:{isScreenOpen:{type:Boolean}},emits:["toggle-screen"],setup(s){const e=s,{y:t}=Le(),{hasSidebar:o}=O(),{frontmatter:n}=P(),i=T({});return ve(()=>{i.value={"has-sidebar":o.value,home:n.value.layout==="home",top:t.value===0,"screen-open":e.isScreenOpen}}),(l,f)=>(a(),u("div",{class:N(["VPNavBar",i.value])},[v("div",Ns,[v("div",Ms,[v("div",Is,[_(Ps,null,{"nav-bar-title-before":p(()=>[c(l.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":p(()=>[c(l.$slots,"nav-bar-title-after",{},void 0,!0)]),_:3})]),v("div",ws,[v("div",Hs,[c(l.$slots,"nav-bar-content-before",{},void 0,!0),_(ms,{class:"search"}),_(ls,{class:"menu"}),_(Ts,{class:"translations"}),_(_o,{class:"appearance"}),_(ks,{class:"social-links"}),_(Zo,{class:"extra"}),c(l.$slots,"nav-bar-content-after",{},void 0,!0),_(ts,{class:"hamburger",active:l.isScreenOpen,onClick:f[0]||(f[0]=d=>l.$emit("toggle-screen"))},null,8,["active"])])])])]),f[1]||(f[1]=v("div",{class:"divider"},[v("div",{class:"divider-line"})],-1))],2))}}),Bs=b(As,[["__scopeId","data-v-9fd4d1dd"]]),Cs={key:0,class:"VPNavScreenAppearance"},Es={class:"text"},Fs=m({__name:"VPNavScreenAppearance",setup(s){const{site:e,theme:t}=P();return(o,n)=>r(e).appearance&&r(e).appearance!=="force-dark"&&r(e).appearance!=="force-auto"?(a(),u("div",Cs,[v("p",Es,M(r(t).darkModeSwitchLabel||"Appearance"),1),_(he)])):h("",!0)}}),Ds=b(Fs,[["__scopeId","data-v-a3e2920d"]]),Os=["innerHTML"],Gs=m({__name:"VPNavScreenMenuLink",props:{item:{}},setup(s){const e=x("close-screen");return(t,o)=>(a(),k(F,{class:"VPNavScreenMenuLink",href:t.item.link,target:t.item.target,rel:t.item.rel,"no-icon":t.item.noIcon,onClick:r(e)},{default:p(()=>[v("span",{innerHTML:t.item.text},null,8,Os)]),_:1},8,["href","target","rel","no-icon","onClick"]))}}),Us=b(Gs,[["__scopeId","data-v-fa963d97"]]),js=["innerHTML"],zs=m({__name:"VPNavScreenMenuGroupLink",props:{item:{}},setup(s){const e=x("close-screen");return(t,o)=>(a(),k(F,{class:"VPNavScreenMenuGroupLink",href:t.item.link,target:t.item.target,rel:t.item.rel,"no-icon":t.item.noIcon,onClick:r(e)},{default:p(()=>[v("span",{innerHTML:t.item.text},null,8,js)]),_:1},8,["href","target","rel","no-icon","onClick"]))}}),He=b(zs,[["__scopeId","data-v-e04f3e85"]]),Ws={class:"VPNavScreenMenuGroupSection"},qs={key:0,class:"title"},Ks=m({__name:"VPNavScreenMenuGroupSection",props:{text:{},items:{}},setup(s){return(e,t)=>(a(),u("div",Ws,[e.text?(a(),u("p",qs,M(e.text),1)):h("",!0),(a(!0),u(w,null,B(e.items,o=>(a(),k(He,{key:o.text,item:o},null,8,["item"]))),128))]))}}),Js=b(Ks,[["__scopeId","data-v-f60dbfa7"]]),Rs=["aria-controls","aria-expanded"],Ys=["innerHTML"],Qs=["id"],Xs={key:0,class:"item"},Zs={key:1,class:"item"},xs={key:2,class:"group"},ea=m({__name:"VPNavScreenMenuGroup",props:{text:{},items:{}},setup(s){const e=s,t=T(!1),o=$(()=>`NavScreenGroup-${e.text.replace(" ","-").toLowerCase()}`);function n(){t.value=!t.value}return(i,l)=>(a(),u("div",{class:N(["VPNavScreenMenuGroup",{open:t.value}])},[v("button",{class:"button","aria-controls":o.value,"aria-expanded":t.value,onClick:n},[v("span",{class:"button-text",innerHTML:i.text},null,8,Ys),l[0]||(l[0]=v("span",{class:"vpi-plus button-icon"},null,-1))],8,Rs),v("div",{id:o.value,class:"items"},[(a(!0),u(w,null,B(i.items,f=>(a(),u(w,{key:JSON.stringify(f)},["link"in f?(a(),u("div",Xs,[_(He,{item:f},null,8,["item"])])):"component"in f?(a(),u("div",Zs,[(a(),k(E(f.component),U({ref_for:!0},f.props,{"screen-menu":""}),null,16))])):(a(),u("div",xs,[_(Js,{text:f.text,items:f.items},null,8,["text","items"])]))],64))),128))],8,Qs)],2))}}),ta=b(ea,[["__scopeId","data-v-d99bfeec"]]),na={key:0,class:"VPNavScreenMenu"},oa=m({__name:"VPNavScreenMenu",setup(s){const{theme:e}=P();return(t,o)=>r(e).nav?(a(),u("nav",na,[(a(!0),u(w,null,B(r(e).nav,n=>(a(),u(w,{key:JSON.stringify(n)},["link"in n?(a(),k(Us,{key:0,item:n},null,8,["item"])):"component"in n?(a(),k(E(n.component),U({key:1,ref_for:!0},n.props,{"screen-menu":""}),null,16)):(a(),k(ta,{key:2,text:n.text||"",items:n.items},null,8,["text","items"]))],64))),128))])):h("",!0)}}),sa=m({__name:"VPNavScreenSocialLinks",setup(s){const{theme:e}=P();return(t,o)=>r(e).socialLinks?(a(),k(ke,{key:0,class:"VPNavScreenSocialLinks",links:r(e).socialLinks},null,8,["links"])):h("",!0)}}),aa={class:"list"},ra=m({__name:"VPNavScreenTranslations",setup(s){const{localeLinks:e,currentLang:t}=J({correspondingLink:!0}),o=T(!1);function n(){o.value=!o.value}return(i,l)=>r(e).length&&r(t).label?(a(),u("div",{key:0,class:N(["VPNavScreenTranslations",{open:o.value}])},[v("button",{class:"title",onClick:n},[l[0]||(l[0]=v("span",{class:"vpi-languages icon lang"},null,-1)),z(" "+M(r(t).label)+" ",1),l[1]||(l[1]=v("span",{class:"vpi-chevron-down icon chevron"},null,-1))]),v("ul",aa,[(a(!0),u(w,null,B(r(e),f=>(a(),u("li",{key:f.link,class:"item"},[_(F,{class:"link",href:f.link},{default:p(()=>[z(M(f.text),1)]),_:2},1032,["href"])]))),128))])],2)):h("",!0)}}),ia=b(ra,[["__scopeId","data-v-516e4bc3"]]),la={class:"container"},ca=m({__name:"VPNavScreen",props:{open:{type:Boolean}},setup(s){const e=T(null),t=Ve(ee?document.body:null);return(o,n)=>(a(),k(ce,{name:"fade",onEnter:n[0]||(n[0]=i=>t.value=!0),onAfterLeave:n[1]||(n[1]=i=>t.value=!1)},{default:p(()=>[o.open?(a(),u("div",{key:0,class:"VPNavScreen",ref_key:"screen",ref:e,id:"VPNavScreen"},[v("div",la,[c(o.$slots,"nav-screen-content-before",{},void 0,!0),_(oa,{class:"menu"}),_(ia,{class:"translations"}),_(Ds,{class:"appearance"}),_(sa,{class:"social-links"}),c(o.$slots,"nav-screen-content-after",{},void 0,!0)])],512)):h("",!0)]),_:3}))}}),ua=b(ca,[["__scopeId","data-v-2dd6d0c7"]]),da={key:0,class:"VPNav"},va=m({__name:"VPNav",setup(s){const{isScreenOpen:e,closeScreen:t,toggleScreen:o}=ro(),{frontmatter:n}=P(),i=$(()=>n.value.navbar!==!1);return Se("close-screen",t),Q(()=>{ee&&document.documentElement.classList.toggle("hide-nav",!i.value)}),(l,f)=>i.value?(a(),u("header",da,[_(Bs,{"is-screen-open":r(e),onToggleScreen:r(o)},{"nav-bar-title-before":p(()=>[c(l.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":p(()=>[c(l.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":p(()=>[c(l.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":p(()=>[c(l.$slots,"nav-bar-content-after",{},void 0,!0)]),_:3},8,["is-screen-open","onToggleScreen"]),_(ua,{open:r(e)},{"nav-screen-content-before":p(()=>[c(l.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":p(()=>[c(l.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3},8,["open"])])):h("",!0)}}),pa=b(va,[["__scopeId","data-v-7ad780c2"]]),fa=["role","tabindex"],ha={key:1,class:"items"},ma=m({__name:"VPSidebarItem",props:{item:{},depth:{}},setup(s){const e=s,{collapsed:t,collapsible:o,isLink:n,isActiveLink:i,hasActiveLink:l,hasChildren:f,toggle:d}=ct($(()=>e.item)),y=$(()=>f.value?"section":"div"),L=$(()=>n.value?"a":"div"),g=$(()=>f.value?e.depth+2===7?"p":`h${e.depth+2}`:"p"),V=$(()=>n.value?void 0:"button"),I=$(()=>[[`level-${e.depth}`],{collapsible:o.value},{collapsed:t.value},{"is-link":n.value},{"is-active":i.value},{"has-active":l.value}]);function H(S){"key"in S&&S.key!=="Enter"||!e.item.link&&d()}function A(){e.item.link&&d()}return(S,C)=>{const G=q("VPSidebarItem",!0);return a(),k(E(y.value),{class:N(["VPSidebarItem",I.value])},{default:p(()=>[S.item.text?(a(),u("div",U({key:0,class:"item",role:V.value},je(S.item.items?{click:H,keydown:H}:{},!0),{tabindex:S.item.items&&0}),[C[1]||(C[1]=v("div",{class:"indicator"},null,-1)),S.item.link?(a(),k(F,{key:0,tag:L.value,class:"link",href:S.item.link,rel:S.item.rel,target:S.item.target},{default:p(()=>[(a(),k(E(g.value),{class:"text",innerHTML:S.item.text},null,8,["innerHTML"]))]),_:1},8,["tag","href","rel","target"])):(a(),k(E(g.value),{key:1,class:"text",innerHTML:S.item.text},null,8,["innerHTML"])),S.item.collapsed!=null&&S.item.items&&S.item.items.length?(a(),u("div",{key:2,class:"caret",role:"button","aria-label":"toggle section",onClick:A,onKeydown:Ue(A,["enter"]),tabindex:"0"},C[0]||(C[0]=[v("span",{class:"vpi-chevron-right caret-icon"},null,-1)]),32)):h("",!0)],16,fa)):h("",!0),S.item.items&&S.item.items.length?(a(),u("div",ha,[S.depth<5?(a(!0),u(w,{key:0},B(S.item.items,K=>(a(),k(G,{key:K.text,item:K,depth:S.depth+1},null,8,["item","depth"]))),128)):h("",!0)])):h("",!0)]),_:1},8,["class"])}}}),_a=b(ma,[["__scopeId","data-v-edd2eed8"]]),ka=m({__name:"VPSidebarGroup",props:{items:{}},setup(s){const e=T(!0);let t=null;return j(()=>{t=setTimeout(()=>{t=null,e.value=!1},300)}),ze(()=>{t!=null&&(clearTimeout(t),t=null)}),(o,n)=>(a(!0),u(w,null,B(o.items,i=>(a(),u("div",{key:i.text,class:N(["group",{"no-transition":e.value}])},[_(_a,{item:i,depth:0},null,8,["item"])],2))),128))}}),ba=b(ka,[["__scopeId","data-v-51288d80"]]),ga={class:"nav",id:"VPSidebarNav","aria-labelledby":"sidebar-aria-label",tabindex:"-1"},$a=m({__name:"VPSidebar",props:{open:{type:Boolean}},setup(s){const{sidebarGroups:e,hasSidebar:t}=O(),o=s,n=T(null),i=Ve(ee?document.body:null);D([o,n],()=>{var f;o.open?(i.value=!0,(f=n.value)==null||f.focus()):i.value=!1},{immediate:!0,flush:"post"});const l=T(0);return D(e,()=>{l.value+=1},{deep:!0}),(f,d)=>r(t)?(a(),u("aside",{key:0,class:N(["VPSidebar",{open:f.open}]),ref_key:"navEl",ref:n,onClick:d[0]||(d[0]=We(()=>{},["stop"]))},[d[2]||(d[2]=v("div",{class:"curtain"},null,-1)),v("nav",ga,[d[1]||(d[1]=v("span",{class:"visually-hidden",id:"sidebar-aria-label"}," Sidebar Navigation ",-1)),c(f.$slots,"sidebar-nav-before",{},void 0,!0),(a(),k(ba,{items:r(e),key:l.value},null,8,["items"])),c(f.$slots,"sidebar-nav-after",{},void 0,!0)])],2)):h("",!0)}}),ya=b($a,[["__scopeId","data-v-42c4c606"]]),Pa=m({__name:"VPSkipLink",setup(s){const e=Z(),t=T();D(()=>e.path,()=>t.value.focus());function o({target:n}){const i=document.getElementById(decodeURIComponent(n.hash).slice(1));if(i){const l=()=>{i.removeAttribute("tabindex"),i.removeEventListener("blur",l)};i.setAttribute("tabindex","-1"),i.addEventListener("blur",l),i.focus(),window.scrollTo(0,0)}}return(n,i)=>(a(),u(w,null,[v("span",{ref_key:"backToTop",ref:t,tabindex:"-1"},null,512),v("a",{href:"#VPContent",class:"VPSkipLink visually-hidden",onClick:o}," Skip to content ")],64))}}),La=b(Pa,[["__scopeId","data-v-c8291ffa"]]),Va=m({__name:"Layout",setup(s){const{isOpen:e,open:t,close:o}=O(),n=Z();D(()=>n.path,o),lt(e,o);const{frontmatter:i}=P(),l=qe(),f=$(()=>!!l["home-hero-image"]);return Se("hero-image-slot-exists",f),(d,y)=>{const L=q("Content");return r(i).layout!==!1?(a(),u("div",{key:0,class:N(["Layout",r(i).pageClass])},[c(d.$slots,"layout-top",{},void 0,!0),_(La),_(Ye,{class:"backdrop",show:r(e),onClick:r(o)},null,8,["show","onClick"]),_(pa,null,{"nav-bar-title-before":p(()=>[c(d.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":p(()=>[c(d.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":p(()=>[c(d.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":p(()=>[c(d.$slots,"nav-bar-content-after",{},void 0,!0)]),"nav-screen-content-before":p(()=>[c(d.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":p(()=>[c(d.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3}),_(ao,{open:r(e),onOpenMenu:r(t)},null,8,["open","onOpenMenu"]),_(ya,{open:r(e)},{"sidebar-nav-before":p(()=>[c(d.$slots,"sidebar-nav-before",{},void 0,!0)]),"sidebar-nav-after":p(()=>[c(d.$slots,"sidebar-nav-after",{},void 0,!0)]),_:3},8,["open"]),_(zn,null,{"page-top":p(()=>[c(d.$slots,"page-top",{},void 0,!0)]),"page-bottom":p(()=>[c(d.$slots,"page-bottom",{},void 0,!0)]),"not-found":p(()=>[c(d.$slots,"not-found",{},void 0,!0)]),"home-hero-before":p(()=>[c(d.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info-before":p(()=>[c(d.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":p(()=>[c(d.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":p(()=>[c(d.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":p(()=>[c(d.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-image":p(()=>[c(d.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":p(()=>[c(d.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":p(()=>[c(d.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":p(()=>[c(d.$slots,"home-features-after",{},void 0,!0)]),"doc-footer-before":p(()=>[c(d.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":p(()=>[c(d.$slots,"doc-before",{},void 0,!0)]),"doc-after":p(()=>[c(d.$slots,"doc-after",{},void 0,!0)]),"doc-top":p(()=>[c(d.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":p(()=>[c(d.$slots,"doc-bottom",{},void 0,!0)]),"aside-top":p(()=>[c(d.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":p(()=>[c(d.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":p(()=>[c(d.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":p(()=>[c(d.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":p(()=>[c(d.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":p(()=>[c(d.$slots,"aside-ads-after",{},void 0,!0)]),_:3}),_(Rn),c(d.$slots,"layout-bottom",{},void 0,!0)],2)):(a(),k(L,{key:1}))}}}),Sa=b(Va,[["__scopeId","data-v-d8b57b2d"]]),Na={Layout:Sa,enhanceApp:({app:s})=>{s.component("Badge",Ke)}};export{Na as t}; diff --git a/docs/.vitepress/dist/assets/index.md.ByDiiWcV.js b/docs/.vitepress/dist/assets/index.md.ByDiiWcV.js deleted file mode 100644 index 1c3847f..0000000 --- a/docs/.vitepress/dist/assets/index.md.ByDiiWcV.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as e,c as t,o as a}from"./chunks/framework.p2VkXzrt.js";const p=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"XiaoMusic","text":"无限听歌,解放小爱音箱","tagline":"使用小爱音箱播放音乐,音乐使用 yt-dlp 下载","actions":[{"theme":"brand","text":"快速开始","link":"/issues/index"},{"theme":"alt","text":"文档汇总","link":"/issues/211"}]},"features":[{"title":"MIT 开源","details":"完全开源,自主可控"},{"title":"一键部署","details":"支持 Docker 部署,兼容各大 NAS 平台"},{"title":"口令自定义","details":"可以完全自定义语音口令,可以写自己的插件"}]},"headers":[],"relativePath":"index.md","filePath":"index.md","lastUpdated":null}'),i={name:"index.md"};function s(n,o,d,l,r,c){return a(),t("div")}const x=e(i,[["render",s]]);export{p as __pageData,x as default}; diff --git a/docs/.vitepress/dist/assets/index.md.ByDiiWcV.lean.js b/docs/.vitepress/dist/assets/index.md.ByDiiWcV.lean.js deleted file mode 100644 index 1c3847f..0000000 --- a/docs/.vitepress/dist/assets/index.md.ByDiiWcV.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as e,c as t,o as a}from"./chunks/framework.p2VkXzrt.js";const p=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"XiaoMusic","text":"无限听歌,解放小爱音箱","tagline":"使用小爱音箱播放音乐,音乐使用 yt-dlp 下载","actions":[{"theme":"brand","text":"快速开始","link":"/issues/index"},{"theme":"alt","text":"文档汇总","link":"/issues/211"}]},"features":[{"title":"MIT 开源","details":"完全开源,自主可控"},{"title":"一键部署","details":"支持 Docker 部署,兼容各大 NAS 平台"},{"title":"口令自定义","details":"可以完全自定义语音口令,可以写自己的插件"}]},"headers":[],"relativePath":"index.md","filePath":"index.md","lastUpdated":null}'),i={name:"index.md"};function s(n,o,d,l,r,c){return a(),t("div")}const x=e(i,[["render",s]]);export{p as __pageData,x as default}; diff --git a/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 b/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 deleted file mode 100644 index b6b603d..0000000 Binary files a/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 and /dev/null differ diff --git a/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 b/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 deleted file mode 100644 index def40a4..0000000 Binary files a/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 and /dev/null differ diff --git a/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 b/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 deleted file mode 100644 index e070c3d..0000000 Binary files a/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 and /dev/null differ diff --git a/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 b/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 deleted file mode 100644 index a3c16ca..0000000 Binary files a/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 and /dev/null differ diff --git a/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 b/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 deleted file mode 100644 index 2210a89..0000000 Binary files a/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 and /dev/null differ diff --git a/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 b/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 deleted file mode 100644 index 790d62d..0000000 Binary files a/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 and /dev/null differ diff --git a/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 b/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 deleted file mode 100644 index 1eec077..0000000 Binary files a/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 and /dev/null differ diff --git a/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 b/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 deleted file mode 100644 index 2cfe615..0000000 Binary files a/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 and /dev/null differ diff --git a/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 b/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 deleted file mode 100644 index e3886dd..0000000 Binary files a/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 and /dev/null differ diff --git a/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 b/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 deleted file mode 100644 index 36d6748..0000000 Binary files a/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 and /dev/null differ diff --git a/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 b/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 deleted file mode 100644 index 2bed1e8..0000000 Binary files a/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 and /dev/null differ diff --git a/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 b/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 deleted file mode 100644 index 9a8d1e2..0000000 Binary files a/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 and /dev/null differ diff --git a/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 b/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 deleted file mode 100644 index 07d3c53..0000000 Binary files a/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 and /dev/null differ diff --git a/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 b/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 deleted file mode 100644 index 57bdc22..0000000 Binary files a/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 and /dev/null differ diff --git a/docs/.vitepress/dist/assets/issues_101.md.DBQG9jJP.js b/docs/.vitepress/dist/assets/issues_101.md.DBQG9jJP.js deleted file mode 100644 index baf20fb..0000000 --- a/docs/.vitepress/dist/assets/issues_101.md.DBQG9jJP.js +++ /dev/null @@ -1,2 +0,0 @@ -import{_ as i,c as e,a0 as t,o as s}from"./chunks/framework.p2VkXzrt.js";const p=JSON.parse('{"title":"群晖docker安装 xiaomusic","description":"","frontmatter":{"title":"群晖docker安装 xiaomusic"},"headers":[],"relativePath":"issues/101.md","filePath":"issues/101.md","lastUpdated":null}'),o={name:"issues/101.md"};function r(c,a,d,h,n,l){return s(),e("div",null,a[0]||(a[0]=[t(`

群晖docker安装 xiaomusic

由于现在群晖已经无法正常下载 docker 里的镜像了,绕了好多弯;现在用 ssh 服务命令拉取镜像来创建容器; 1、ssh 输入账号密码进入群晖 2、输入 sudo -i 再次输入密码进入 root 权限 3、输入 docker search xiaomusic来查找到该镜像名; 4、然后输入 docker pull xiaomusic 试试是否能安装,如果不能;就得在命令前加个代理地址;下面列了一些代理地址可以一个个的试 docker.fxxk.dedyn.iodocker.io registry-docker-hub-latest-9vgc.onrender.com docker.chenby.cn dockerproxy.com hub.uuuadc.top docker.jsdelivr.fyi docker.registry.cyou dockerhub.anzu.vip

我是用的最下面这个成功的 docker pull dockerhub.anzu.vip/xiaomusic:latest 5、安装完成后就进入群晖 DOCKER 配置 xiaomusic image MI_HARDWARE=型号 前面第4 步骤获取的 XIAOMUSIC_SEARCH=搜索方式,我填写的bilisearch: 意思是通过 bilibili 搜索 MI_DID=前面第4 步骤获取的 MI_USER=小米账号 MI_PASS=小米密码 XIAOMUSIC_FUZZY_MATCH_CUTOFF=模糊匹配,最小为 0.1 最大为 1,越小越模糊,越大越精准

6、配置端口 image (1) 7、映射路径 image (2)

评论

评论 1 - kiwi5656

MI_DID=前面第4 步骤获取的,第4步骤在哪?


评论 2 - hanxi

MI_DID=前面第4 步骤获取的,第4步骤在哪?

不用设置 MI_DID


评论 3 - hanxi

国内 docker 镜像

docker pull m.daocloud.io/docker.io/hanxi/xiaomusic:latest
-docker tag m.daocloud.io/docker.io/hanxi/xiaomusic:latest hanxi/xiaomusic:latest

评论 4 - ginitaimeiyty

如果手头上有能科学上网的机器,直接把群辉的代理服务器IP填写成可以科学上网的机器IP+端口,翻墙软件打开允许局域网连接就可以


链接到 GitHub Issue

`,20)]))}const m=i(o,[["render",r]]);export{p as __pageData,m as default}; diff --git a/docs/.vitepress/dist/assets/issues_101.md.DBQG9jJP.lean.js b/docs/.vitepress/dist/assets/issues_101.md.DBQG9jJP.lean.js deleted file mode 100644 index baf20fb..0000000 --- a/docs/.vitepress/dist/assets/issues_101.md.DBQG9jJP.lean.js +++ /dev/null @@ -1,2 +0,0 @@ -import{_ as i,c as e,a0 as t,o as s}from"./chunks/framework.p2VkXzrt.js";const p=JSON.parse('{"title":"群晖docker安装 xiaomusic","description":"","frontmatter":{"title":"群晖docker安装 xiaomusic"},"headers":[],"relativePath":"issues/101.md","filePath":"issues/101.md","lastUpdated":null}'),o={name:"issues/101.md"};function r(c,a,d,h,n,l){return s(),e("div",null,a[0]||(a[0]=[t(`

群晖docker安装 xiaomusic

由于现在群晖已经无法正常下载 docker 里的镜像了,绕了好多弯;现在用 ssh 服务命令拉取镜像来创建容器; 1、ssh 输入账号密码进入群晖 2、输入 sudo -i 再次输入密码进入 root 权限 3、输入 docker search xiaomusic来查找到该镜像名; 4、然后输入 docker pull xiaomusic 试试是否能安装,如果不能;就得在命令前加个代理地址;下面列了一些代理地址可以一个个的试 docker.fxxk.dedyn.iodocker.io registry-docker-hub-latest-9vgc.onrender.com docker.chenby.cn dockerproxy.com hub.uuuadc.top docker.jsdelivr.fyi docker.registry.cyou dockerhub.anzu.vip

我是用的最下面这个成功的 docker pull dockerhub.anzu.vip/xiaomusic:latest 5、安装完成后就进入群晖 DOCKER 配置 xiaomusic image MI_HARDWARE=型号 前面第4 步骤获取的 XIAOMUSIC_SEARCH=搜索方式,我填写的bilisearch: 意思是通过 bilibili 搜索 MI_DID=前面第4 步骤获取的 MI_USER=小米账号 MI_PASS=小米密码 XIAOMUSIC_FUZZY_MATCH_CUTOFF=模糊匹配,最小为 0.1 最大为 1,越小越模糊,越大越精准

6、配置端口 image (1) 7、映射路径 image (2)

评论

评论 1 - kiwi5656

MI_DID=前面第4 步骤获取的,第4步骤在哪?


评论 2 - hanxi

MI_DID=前面第4 步骤获取的,第4步骤在哪?

不用设置 MI_DID


评论 3 - hanxi

国内 docker 镜像

docker pull m.daocloud.io/docker.io/hanxi/xiaomusic:latest
-docker tag m.daocloud.io/docker.io/hanxi/xiaomusic:latest hanxi/xiaomusic:latest

评论 4 - ginitaimeiyty

如果手头上有能科学上网的机器,直接把群辉的代理服务器IP填写成可以科学上网的机器IP+端口,翻墙软件打开允许局域网连接就可以


链接到 GitHub Issue

`,20)]))}const m=i(o,[["render",r]]);export{p as __pageData,m as default}; diff --git a/docs/.vitepress/dist/assets/issues_105.md.8_yuCS-D.js b/docs/.vitepress/dist/assets/issues_105.md.8_yuCS-D.js deleted file mode 100644 index b205293..0000000 --- a/docs/.vitepress/dist/assets/issues_105.md.8_yuCS-D.js +++ /dev/null @@ -1,189 +0,0 @@ -import{_ as a,c as i,a0 as n,o as t}from"./chunks/framework.p2VkXzrt.js";const d=JSON.parse('{"title":"【插件】自定义口令功能","description":"","frontmatter":{"title":"【插件】自定义口令功能"},"headers":[],"relativePath":"issues/105.md","filePath":"issues/105.md","lastUpdated":null}'),p={name:"issues/105.md"};function l(e,s,o,h,k,u){return t(),i("div",null,s[0]||(s[0]=[n(`

【插件】自定义口令功能

自定义口令配置需要配置到 config.json 文件里,使用 config.json 方式启动。参考 </issues/94.html> 。

口令的配置方式见 config-example.json 文件。口令对应的代码需要写到 plugins/ 目录下面,如果是容器启动,则需要把这个目录挂载出来。

config.json 格式是下面这样的。

json
{
-    "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\\")"
-    }
-}

配置自定义口令时,只需要配置 user_key_word_dict 即可,会自动插入到 key_word_dict 里的。配置格式是:

 "测试自定义口令": "exec#code1(\\"hello\\")",

其中 "测试自定义口令" 就是对小爱音箱说的,"exec#code1(\\"hello\\")" 就是要执行的插件代码,代码以 exec# 开头,后面紧跟着执行代码。这里 code1 是一个插件函数,插件函数需要在 plugin 目录里实现,一个文件只会导出一个与文件名相同的插件函数。所以 code1 函数是在 plugin/code1.py 里实现的。

async def code1(arg1):
-    global log, xiaomusic
-    log.info(f"code1:{arg1}")
-    await xiaomusic.do_tts("你好,我是自定义的测试口令")

这里只是演示了打印日志和让小爱音箱说话。还有一个示例插件是 httpget ,可以用来访问 url 。

比如下面这样配置的话,当对小爱音箱说测试链接时,会去访问 url ,可以用来很多其他的事情。

"测试链接": "exec#httpget(\\"https://github.com/hanxi/xiaomusic\\")

最后还需要在 active_cmd 中配上口令用于唤醒:

  "active_cmd": "play,set_random_play,playlocal,play_music_list,play_music_list_index,stop_after_minute,stop,测试自定义口令",

感兴趣的可以体验一下,写了有什么好玩的插件也可以在这里分享,或者提 pr 合并进官方库里作为自带插件。

评论

评论 1 - carson512

如果开启服务的状态下 如何唤醒才能调用原有的播放QQ 网易云?而特定唤醒词调用xiaoai播放?


评论 2 - hanxi

如果开启服务的状态下 如何唤醒才能调用原有的播放QQ 网易云?而特定唤醒词调用xiaoai播放?

不使用 xiaomusic 的唤醒词就会调用音箱自带的,比如说播放音乐


评论 3 - shellingford37

[23:26:12] [0.3.30] [INFO] xiaomusic.py:531: 收到消息:测试自定义口令 控制面板:False did:290874427
-[23:26:12] [0.3.30] [INFO] xiaomusic.py:577: 完全匹配指令. query:测试自定义口令 opvalue:exec#code1("hello")
-[23:26:12] [0.3.30] [INFO] code1.py:3: code1:hello
-[23:26:12] [0.3.30] [ERROR] xiaomusic.py:542: Execption XiaoMusic.do_tts() missing 1 required positional argument: 'value'
-Traceback (most recent call last):
-  File "/app/xiaomusic/xiaomusic.py", line 540, in do_check_cmd
-    await func(did=did, arg1=oparg)
-  File "/app/xiaomusic/xiaomusic.py", line 890, in exec
-    await self.plugin_manager.execute_plugin(code)
-  File "/app/xiaomusic/plugin.py", line 66, in execute_plugin
-    await coroutine
-  File "/app/plugins/code1.py", line 4, in code1
-    await xiaomusic.do_tts("你好,我是自定义的测试口令")
-TypeError: XiaoMusic.do_tts() missing 1 required positional argument: 'value'

我用code1的代码执行报错,有大佬知道为什么吗?


评论 4 - hanxi

@shellingford37 重构后漏改了,修复了。


评论 5 - guoxiangke

先说播放歌曲,再说 测试自定义口令 就行


评论 6 - CZJCC

想请教下插件那个功能,如何把用户的语音输入作为参数内容传到自定义函数里


评论 7 - hanxi

想请教下插件那个功能,如何把用户的语音输入作为参数内容传到自定义函数里

现在获取不到,等我加个接口获取吧。


评论 8 - CZJCC

666,支持以后我可以贡献一个接入通义模型的插件


评论 9 - hanxi

@CZJCC 你可以更新看看 plugins/code1.py 的测试代码,我测试了是可以拿到语音输入的原始内容的。


评论 10 - hanxi

文档更新了下,active_cmd 也需要配置一下才能正常唤醒。


评论 11 - CZJCC

@CZJCC 你可以更新看看 plugins/code1.py 的测试代码,我测试了是可以拿到语音输入的原始内容的。

我原先设想的事用户的话术是”通义提问为什么地球是圆的“,指令匹配的时候通义提问前缀匹配到类似于code1方法,为什么地球是圆的作为参数传入这个函数,但我看现在是拿历史记录实现的


评论 12 - hanxi

是的,插件函数里面再切割一下前缀就行。last_record就是当前的那条语音数据。


评论 13 - hanxi

@CZJCC 你可以更新看看 plugins/code1.py 的测试代码,我测试了是可以拿到语音输入的原始内容的。

我原先设想的事用户的话术是”通义提问为什么地球是圆的“,指令匹配的时候通义提问前缀匹配到类似于code1方法,为什么地球是圆的作为参数传入这个函数,但我看现在是拿历史记录实现的

是的,这样比较简单,交给插件里面处理也比较自由。


评论 14 - mogeqian

key_word_dict中的“播放歌曲”口令是不能修改的是吧?因为以前用小爱同学播放歌曲说习惯了,总是触发xiaomusic自动下载歌曲,我想把口令改成“查找歌曲”,这样我当说播放歌曲的时候就调用网易云音乐或者QQ音乐,当我说查找歌曲的时候就先看本地有没有歌曲,没有话就自动下载到本地。 当我先按照config-example.json的模板写好如下配置并重命名为config.json

  "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": [
-    "分钟后关机",
-    "查找歌曲",
-    "下一首",
-    "单曲循环",
-    "全部循环",
-    "随机播放",
-    "关机",
-    "刷新列表",
-    "播放列表",
-    "播放本地歌曲",
-    "本地播放歌曲",
-    "下载歌曲",
-    "暂停",
-    "停止",
-    "停止播放",
-    "测试自定义口令",
-    "测试链接"
-  ],

用以下命令安装docker docker run --name xiaomusic -p 5488:5488 -v /mnt/sharedata/audiodata/musci/xiaomusic:/app/music -v /mnt/data_sdb1/docker/xiaomusic/config.json:/app/config.json -e XIAOMUSIC_PORT=5488 hanxi/xiaomusic --config /app/config.json 日志里提示的依然是:

key_word_dict={'播放歌曲': 'play', '播放本地歌曲': 'playlocal', '关机': 'stop', '下一首': 'play_next', '上一首': 'play_prev', '单曲循环': 'set_play_type_one', '全部循环': 'set_play_type_all', '随机播放': 'set_random_play', '分钟后关机': 'stop_after_minute', '播放列表': 'play_music_list', '刷新列表': 'gen_music_list', '加入收藏': 'add_to_favorites', '收藏歌曲': 'add_to_favorites', '取消收藏': 'del_from_favorites', '播放列表第': 'play_music_list_index', '本地播放歌曲': 'playlocal', '查找歌曲': 'play', '下载歌曲': 'play', '暂停': 'stop', '停止': 'stop', '停止播放': 'stop', '播放歌单': 'play_music_list', '测试自定义口令': 'exec#code1("hello")', '测试链接': 'exec#httpget("https://github.com/hanxi/xiaomusic")'}, key_match_order=['分钟后关机', '播放歌曲', '下一首', '上一首', '单曲循环', '全部循环', '随机播放', '关机', '刷新列表', '播放列表第', '播放列表', '加入收藏', '收藏歌曲', '取消收藏', '播放本地歌曲', '本地播放歌曲', '查找歌曲', '下载歌曲', '暂停', '停止', '停止播放', '播放歌单', '测试自定义口令', '测试链接']

似乎自定义的口令只能以插入的方式添加上去,并不能替换掉原来的口令


评论 15 - hanxi

key_word_dict中的“播放歌曲”口令是不能修改的是吧?因为以前用小爱同学播放歌曲说习惯了,总是触发xiaomusic自动下载歌曲,我想把口令改成“查找歌曲”,这样我当说播放歌曲的时候就调用网易云音乐或者QQ音乐,当我说查找歌曲的时候就先看本地有没有歌曲,没有话就自动下载到本地。 当我先按照config-example.json的模板写好如下配置并重命名为config.json

  "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": [
-    "分钟后关机",
-    "查找歌曲",
-    "下一首",
-    "单曲循环",
-    "全部循环",
-    "随机播放",
-    "关机",
-    "刷新列表",
-    "播放列表",
-    "播放本地歌曲",
-    "本地播放歌曲",
-    "下载歌曲",
-    "暂停",
-    "停止",
-    "停止播放",
-    "测试自定义口令",
-    "测试链接"
-  ],

用以下命令安装docker docker run --name xiaomusic -p 5488:5488 -v /mnt/sharedata/audiodata/musci/xiaomusic:/app/music -v /mnt/data_sdb1/docker/xiaomusic/config.json:/app/config.json -e XIAOMUSIC_PORT=5488 hanxi/xiaomusic --config /app/config.json 日志里提示的依然是:

key_word_dict={'播放歌曲': 'play', '播放本地歌曲': 'playlocal', '关机': 'stop', '下一首': 'play_next', '上一首': 'play_prev', '单曲循环': 'set_play_type_one', '全部循环': 'set_play_type_all', '随机播放': 'set_random_play', '分钟后关机': 'stop_after_minute', '播放列表': 'play_music_list', '刷新列表': 'gen_music_list', '加入收藏': 'add_to_favorites', '收藏歌曲': 'add_to_favorites', '取消收藏': 'del_from_favorites', '播放列表第': 'play_music_list_index', '本地播放歌曲': 'playlocal', '查找歌曲': 'play', '下载歌曲': 'play', '暂停': 'stop', '停止': 'stop', '停止播放': 'stop', '播放歌单': 'play_music_list', '测试自定义口令': 'exec#code1("hello")', '测试链接': 'exec#httpget("https://github.com/hanxi/xiaomusic")'}, key_match_order=['分钟后关机', '播放歌曲', '下一首', '上一首', '单曲循环', '全部循环', '随机播放', '关机', '刷新列表', '播放列表第', '播放列表', '加入收藏', '收藏歌曲', '取消收藏', '播放本地歌曲', '本地播放歌曲', '查找歌曲', '下载歌曲', '暂停', '停止', '停止播放', '播放歌单', '测试自定义口令', '测试链接']

似乎自定义的口令只能以插入的方式添加上去,并不能替换掉原来的口令

可以在网页后台设置页面改。


评论 16 - mogeqian

不行,后台设置如图 QQ截图20241111181411 日志如下:

[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:1130: update_config_from_setting ok. data:Config(account='**', password='**', mi_did='726577518,570867755', miio_tts_command='', cookie='', verbose=False, music_path='music', download_path='music/download', conf_path='conf', cache_dir='cache', hostname='192.168.22.4', port=8090, public_port=0, proxy='', 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='', custom_play_list_json='', disable_download=False, key_word_dict={'播放歌曲': 'play', '播放本地歌曲': 'playlocal', '关机': 'stop', '下一首': 'play_next', '上一首': 'play_prev', '单曲循环': 'set_play_type_one', '全部循环': 'set_play_type_all', '随机播放': 'set_random_play', '分钟后关机': 'stop_after_minute', '播放列表': 'play_music_list', '刷新列表': 'gen_music_list', '加入收藏': 'add_to_favorites', '收藏歌曲': 'add_to_favorites', '取消收藏': 'del_from_favorites', '播放列表第': 'play_music_list_index', '本地播放歌曲': 'playlocal', '查找歌曲': 'play', '下载歌曲': 'play', '暂停': 'stop', '停止': 'stop', '停止播放': 'stop', '播放歌单': 'play_music_list', '测试自定义口令': '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=False, keywords_playlocal='播放本地歌曲,本地播放歌曲', keywords_play='查找歌曲,下载歌曲', keywords_stop='关机,暂停,停止,停止播放', keywords_playlist='播放列表,播放歌单', user_key_word_dict={'测试自定义口令': 'exec#code1("hello")', '测试链接': 'exec#httpget("https://github.com/hanxi/xiaomusic")'}, enable_force_stop=False, devices={'726577518': Device(did='726577518', device_id='******', hardware='LX06', name='小爱音箱Pro', play_type='', cur_music='', cur_playlist='全部'), '570867755': Device(did='570867755', device_id='*********', hardware='L15A', name='小米AI音箱(第二代)', play_type='', cur_music='', cur_playlist='全部')}, group_list='', remove_id3tag=False, convert_to_mp3=False, delay_sec=3, continue_play=False, pull_ask_sec=1, crontab_json='', enable_yt_dlp_cookies=False, get_ask_by_mina=False)
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:1133: 语音控制已启动, 用【分钟后关机/播放歌曲/下一首/上一首/单曲循环/全部循环/随机播放/关机/刷新列表/播放列表第/播放列表/加入收藏/收藏歌曲/取消收藏/播放本地歌曲/本地播放歌曲/查找歌曲/下载歌曲/暂停/停止/停止播放/播放歌单/测试自定义口令/测试链接】开头来控制
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:543: 协程时间循环未启动
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:1250: 没打乱 全部 ['乌兰托娅 火红的萨日朗', '凤凰传奇麝香夫人'] ... ['罗大佑童年', '阿嬷'] with len: 7
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:1250: 没打乱 全部 ['乌兰托娅 火红的萨日朗', '凤凰传奇麝香夫人'] ... ['罗大佑童年', '阿嬷'] with len: 7
-[2024-11-11 18:08:04] [0.3.46] [INFO] analytics.py:28: analytics init ok
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:104: Startup OK. Config(account='***', password='***', mi_did='726577518,570867755', miio_tts_command='', cookie='', verbose=False, music_path='music', download_path='music/download', conf_path='conf', cache_dir='cache', hostname='192.168.22.4', port=8090, public_port=0, proxy='', 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='', custom_play_list_json='', disable_download=False, key_word_dict={'播放歌曲': 'play', '播放本地歌曲': 'playlocal', '关机': 'stop', '下一首': 'play_next', '上一首': 'play_prev', '单曲循环': 'set_play_type_one', '全部循环': 'set_play_type_all', '随机播放': 'set_random_play', '分钟后关机': 'stop_after_minute', '播放列表': 'play_music_list', '刷新列表': 'gen_music_list', '加入收藏': 'add_to_favorites', '收藏歌曲': 'add_to_favorites', '取消收藏': 'del_from_favorites', '播放列表第': 'play_music_list_index', '本地播放歌曲': 'playlocal', '查找歌曲': 'play', '下载歌曲': 'play', '暂停': 'stop', '停止': 'stop', '停止播放': 'stop', '播放歌单': 'play_music_list', '测试自定义口令': '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=False, keywords_playlocal='播放本地歌曲,本地播放歌曲', keywords_play='查找歌曲,下载歌曲', keywords_stop='关机,暂停,停止,停止播放', keywords_playlist='播放列表,播放歌单', user_key_word_dict={'测试自定义口令': 'exec#code1("hello")', '测试链接': 'exec#httpget("https://github.com/hanxi/xiaomusic")'}, enable_force_stop=False, devices={'726577518': Device(did='726577518', device_id='*****', hardware='LX06', name='小爱音箱Pro', play_type='', cur_music='', cur_playlist='全部'), '570867755': Device(did='570867755', device_id='*********', hardware='L15A', name='小米AI音箱(第二代)', play_type='', cur_music='', cur_playlist='全部')}, group_list='', remove_id3tag=False, convert_to_mp3=False, delay_sec=3, continue_play=False, pull_ask_sec=1, crontab_json='', enable_yt_dlp_cookies=False, get_ask_by_mina=False)
-[2024-11-11 18:08:04] [0.3.46] [INFO] httpserver.py:111: disable_httpauth:True
-[18:08:04] [0.3.46] [INFO] Started server process [1]
-[18:08:04] [0.3.46] [INFO] Waiting for application startup.
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:541: 启动后台构建 tag cache
-[18:08:04] [0.3.46] [INFO] Application startup complete.
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:513: 已从【cache/tag_cache.json】加载 tag cache
-[18:08:04] [0.3.46] [INFO] Uvicorn running on http://['0.0.0.0', '::']:8090 (Press CTRL+C to quit)
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:527: 保存:tag cache 已保存到【cache/tag_cache.json】
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:577: tag 更新完成
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:248: 选中的设备: {'726577518': Device(did='726577518', device_id='******', hardware='LX06', name='小爱音箱Pro', play_type='', cur_music='', cur_playlist='全部'), '570867755': Device(did='570867755', device_id='*********', hardware='L15A', name='小米AI音箱(第二代)', play_type='', cur_music='', cur_playlist='全部')}
-[18:08:34] [0.3.46] [INFO] 172.20.0.1:35058 - "GET /static/default/setting.html HTTP/1.1" 304
-[18:08:34] [0.3.46] [INFO] 172.20.0.1:35058 - "GET /getversion HTTP/1.1" 200

使用的docker-compose命令安装 services: xiaomusic: image: hanxi/xiaomusic container_name: xiaomusic restart: unless-stopped ports: - 8090:8090 volumes: - /mnt/sharedata/audiodata/musci/xiaomusic:/app/music - /mnt/data_sdb1/docker/xiaomusic/config.json:/app/config.json command: ['--config', '/app/config.json']

根据日志的提示,'播放歌曲': 'play'依然存在,只是增加了 '查找歌曲': 'play', '下载歌曲': 'play', 这两个关于play的自定义口令,所以实际上play有三条口令 “播放歌曲、查找歌曲、下载歌曲”,能否删除掉'播放歌曲': 'play'这个系统默认的口令?只使用 '查找歌曲': 'play', '下载歌曲': 'play', 这两个关于play的自定义口令


评论 17 - hanxi

@mogeqian 另外提个 issue 吧,现在应该是不支持删除默认的口令。


评论 18 - mogeqian

好的,已经重开了一个issue #259


链接到 GitHub Issue

`,84)]))}const r=a(p,[["render",l]]);export{d as __pageData,r as default}; diff --git a/docs/.vitepress/dist/assets/issues_105.md.8_yuCS-D.lean.js b/docs/.vitepress/dist/assets/issues_105.md.8_yuCS-D.lean.js deleted file mode 100644 index b205293..0000000 --- a/docs/.vitepress/dist/assets/issues_105.md.8_yuCS-D.lean.js +++ /dev/null @@ -1,189 +0,0 @@ -import{_ as a,c as i,a0 as n,o as t}from"./chunks/framework.p2VkXzrt.js";const d=JSON.parse('{"title":"【插件】自定义口令功能","description":"","frontmatter":{"title":"【插件】自定义口令功能"},"headers":[],"relativePath":"issues/105.md","filePath":"issues/105.md","lastUpdated":null}'),p={name:"issues/105.md"};function l(e,s,o,h,k,u){return t(),i("div",null,s[0]||(s[0]=[n(`

【插件】自定义口令功能

自定义口令配置需要配置到 config.json 文件里,使用 config.json 方式启动。参考 </issues/94.html> 。

口令的配置方式见 config-example.json 文件。口令对应的代码需要写到 plugins/ 目录下面,如果是容器启动,则需要把这个目录挂载出来。

config.json 格式是下面这样的。

json
{
-    "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\\")"
-    }
-}

配置自定义口令时,只需要配置 user_key_word_dict 即可,会自动插入到 key_word_dict 里的。配置格式是:

 "测试自定义口令": "exec#code1(\\"hello\\")",

其中 "测试自定义口令" 就是对小爱音箱说的,"exec#code1(\\"hello\\")" 就是要执行的插件代码,代码以 exec# 开头,后面紧跟着执行代码。这里 code1 是一个插件函数,插件函数需要在 plugin 目录里实现,一个文件只会导出一个与文件名相同的插件函数。所以 code1 函数是在 plugin/code1.py 里实现的。

async def code1(arg1):
-    global log, xiaomusic
-    log.info(f"code1:{arg1}")
-    await xiaomusic.do_tts("你好,我是自定义的测试口令")

这里只是演示了打印日志和让小爱音箱说话。还有一个示例插件是 httpget ,可以用来访问 url 。

比如下面这样配置的话,当对小爱音箱说测试链接时,会去访问 url ,可以用来很多其他的事情。

"测试链接": "exec#httpget(\\"https://github.com/hanxi/xiaomusic\\")

最后还需要在 active_cmd 中配上口令用于唤醒:

  "active_cmd": "play,set_random_play,playlocal,play_music_list,play_music_list_index,stop_after_minute,stop,测试自定义口令",

感兴趣的可以体验一下,写了有什么好玩的插件也可以在这里分享,或者提 pr 合并进官方库里作为自带插件。

评论

评论 1 - carson512

如果开启服务的状态下 如何唤醒才能调用原有的播放QQ 网易云?而特定唤醒词调用xiaoai播放?


评论 2 - hanxi

如果开启服务的状态下 如何唤醒才能调用原有的播放QQ 网易云?而特定唤醒词调用xiaoai播放?

不使用 xiaomusic 的唤醒词就会调用音箱自带的,比如说播放音乐


评论 3 - shellingford37

[23:26:12] [0.3.30] [INFO] xiaomusic.py:531: 收到消息:测试自定义口令 控制面板:False did:290874427
-[23:26:12] [0.3.30] [INFO] xiaomusic.py:577: 完全匹配指令. query:测试自定义口令 opvalue:exec#code1("hello")
-[23:26:12] [0.3.30] [INFO] code1.py:3: code1:hello
-[23:26:12] [0.3.30] [ERROR] xiaomusic.py:542: Execption XiaoMusic.do_tts() missing 1 required positional argument: 'value'
-Traceback (most recent call last):
-  File "/app/xiaomusic/xiaomusic.py", line 540, in do_check_cmd
-    await func(did=did, arg1=oparg)
-  File "/app/xiaomusic/xiaomusic.py", line 890, in exec
-    await self.plugin_manager.execute_plugin(code)
-  File "/app/xiaomusic/plugin.py", line 66, in execute_plugin
-    await coroutine
-  File "/app/plugins/code1.py", line 4, in code1
-    await xiaomusic.do_tts("你好,我是自定义的测试口令")
-TypeError: XiaoMusic.do_tts() missing 1 required positional argument: 'value'

我用code1的代码执行报错,有大佬知道为什么吗?


评论 4 - hanxi

@shellingford37 重构后漏改了,修复了。


评论 5 - guoxiangke

先说播放歌曲,再说 测试自定义口令 就行


评论 6 - CZJCC

想请教下插件那个功能,如何把用户的语音输入作为参数内容传到自定义函数里


评论 7 - hanxi

想请教下插件那个功能,如何把用户的语音输入作为参数内容传到自定义函数里

现在获取不到,等我加个接口获取吧。


评论 8 - CZJCC

666,支持以后我可以贡献一个接入通义模型的插件


评论 9 - hanxi

@CZJCC 你可以更新看看 plugins/code1.py 的测试代码,我测试了是可以拿到语音输入的原始内容的。


评论 10 - hanxi

文档更新了下,active_cmd 也需要配置一下才能正常唤醒。


评论 11 - CZJCC

@CZJCC 你可以更新看看 plugins/code1.py 的测试代码,我测试了是可以拿到语音输入的原始内容的。

我原先设想的事用户的话术是”通义提问为什么地球是圆的“,指令匹配的时候通义提问前缀匹配到类似于code1方法,为什么地球是圆的作为参数传入这个函数,但我看现在是拿历史记录实现的


评论 12 - hanxi

是的,插件函数里面再切割一下前缀就行。last_record就是当前的那条语音数据。


评论 13 - hanxi

@CZJCC 你可以更新看看 plugins/code1.py 的测试代码,我测试了是可以拿到语音输入的原始内容的。

我原先设想的事用户的话术是”通义提问为什么地球是圆的“,指令匹配的时候通义提问前缀匹配到类似于code1方法,为什么地球是圆的作为参数传入这个函数,但我看现在是拿历史记录实现的

是的,这样比较简单,交给插件里面处理也比较自由。


评论 14 - mogeqian

key_word_dict中的“播放歌曲”口令是不能修改的是吧?因为以前用小爱同学播放歌曲说习惯了,总是触发xiaomusic自动下载歌曲,我想把口令改成“查找歌曲”,这样我当说播放歌曲的时候就调用网易云音乐或者QQ音乐,当我说查找歌曲的时候就先看本地有没有歌曲,没有话就自动下载到本地。 当我先按照config-example.json的模板写好如下配置并重命名为config.json

  "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": [
-    "分钟后关机",
-    "查找歌曲",
-    "下一首",
-    "单曲循环",
-    "全部循环",
-    "随机播放",
-    "关机",
-    "刷新列表",
-    "播放列表",
-    "播放本地歌曲",
-    "本地播放歌曲",
-    "下载歌曲",
-    "暂停",
-    "停止",
-    "停止播放",
-    "测试自定义口令",
-    "测试链接"
-  ],

用以下命令安装docker docker run --name xiaomusic -p 5488:5488 -v /mnt/sharedata/audiodata/musci/xiaomusic:/app/music -v /mnt/data_sdb1/docker/xiaomusic/config.json:/app/config.json -e XIAOMUSIC_PORT=5488 hanxi/xiaomusic --config /app/config.json 日志里提示的依然是:

key_word_dict={'播放歌曲': 'play', '播放本地歌曲': 'playlocal', '关机': 'stop', '下一首': 'play_next', '上一首': 'play_prev', '单曲循环': 'set_play_type_one', '全部循环': 'set_play_type_all', '随机播放': 'set_random_play', '分钟后关机': 'stop_after_minute', '播放列表': 'play_music_list', '刷新列表': 'gen_music_list', '加入收藏': 'add_to_favorites', '收藏歌曲': 'add_to_favorites', '取消收藏': 'del_from_favorites', '播放列表第': 'play_music_list_index', '本地播放歌曲': 'playlocal', '查找歌曲': 'play', '下载歌曲': 'play', '暂停': 'stop', '停止': 'stop', '停止播放': 'stop', '播放歌单': 'play_music_list', '测试自定义口令': 'exec#code1("hello")', '测试链接': 'exec#httpget("https://github.com/hanxi/xiaomusic")'}, key_match_order=['分钟后关机', '播放歌曲', '下一首', '上一首', '单曲循环', '全部循环', '随机播放', '关机', '刷新列表', '播放列表第', '播放列表', '加入收藏', '收藏歌曲', '取消收藏', '播放本地歌曲', '本地播放歌曲', '查找歌曲', '下载歌曲', '暂停', '停止', '停止播放', '播放歌单', '测试自定义口令', '测试链接']

似乎自定义的口令只能以插入的方式添加上去,并不能替换掉原来的口令


评论 15 - hanxi

key_word_dict中的“播放歌曲”口令是不能修改的是吧?因为以前用小爱同学播放歌曲说习惯了,总是触发xiaomusic自动下载歌曲,我想把口令改成“查找歌曲”,这样我当说播放歌曲的时候就调用网易云音乐或者QQ音乐,当我说查找歌曲的时候就先看本地有没有歌曲,没有话就自动下载到本地。 当我先按照config-example.json的模板写好如下配置并重命名为config.json

  "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": [
-    "分钟后关机",
-    "查找歌曲",
-    "下一首",
-    "单曲循环",
-    "全部循环",
-    "随机播放",
-    "关机",
-    "刷新列表",
-    "播放列表",
-    "播放本地歌曲",
-    "本地播放歌曲",
-    "下载歌曲",
-    "暂停",
-    "停止",
-    "停止播放",
-    "测试自定义口令",
-    "测试链接"
-  ],

用以下命令安装docker docker run --name xiaomusic -p 5488:5488 -v /mnt/sharedata/audiodata/musci/xiaomusic:/app/music -v /mnt/data_sdb1/docker/xiaomusic/config.json:/app/config.json -e XIAOMUSIC_PORT=5488 hanxi/xiaomusic --config /app/config.json 日志里提示的依然是:

key_word_dict={'播放歌曲': 'play', '播放本地歌曲': 'playlocal', '关机': 'stop', '下一首': 'play_next', '上一首': 'play_prev', '单曲循环': 'set_play_type_one', '全部循环': 'set_play_type_all', '随机播放': 'set_random_play', '分钟后关机': 'stop_after_minute', '播放列表': 'play_music_list', '刷新列表': 'gen_music_list', '加入收藏': 'add_to_favorites', '收藏歌曲': 'add_to_favorites', '取消收藏': 'del_from_favorites', '播放列表第': 'play_music_list_index', '本地播放歌曲': 'playlocal', '查找歌曲': 'play', '下载歌曲': 'play', '暂停': 'stop', '停止': 'stop', '停止播放': 'stop', '播放歌单': 'play_music_list', '测试自定义口令': 'exec#code1("hello")', '测试链接': 'exec#httpget("https://github.com/hanxi/xiaomusic")'}, key_match_order=['分钟后关机', '播放歌曲', '下一首', '上一首', '单曲循环', '全部循环', '随机播放', '关机', '刷新列表', '播放列表第', '播放列表', '加入收藏', '收藏歌曲', '取消收藏', '播放本地歌曲', '本地播放歌曲', '查找歌曲', '下载歌曲', '暂停', '停止', '停止播放', '播放歌单', '测试自定义口令', '测试链接']

似乎自定义的口令只能以插入的方式添加上去,并不能替换掉原来的口令

可以在网页后台设置页面改。


评论 16 - mogeqian

不行,后台设置如图 QQ截图20241111181411 日志如下:

[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:1130: update_config_from_setting ok. data:Config(account='**', password='**', mi_did='726577518,570867755', miio_tts_command='', cookie='', verbose=False, music_path='music', download_path='music/download', conf_path='conf', cache_dir='cache', hostname='192.168.22.4', port=8090, public_port=0, proxy='', 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='', custom_play_list_json='', disable_download=False, key_word_dict={'播放歌曲': 'play', '播放本地歌曲': 'playlocal', '关机': 'stop', '下一首': 'play_next', '上一首': 'play_prev', '单曲循环': 'set_play_type_one', '全部循环': 'set_play_type_all', '随机播放': 'set_random_play', '分钟后关机': 'stop_after_minute', '播放列表': 'play_music_list', '刷新列表': 'gen_music_list', '加入收藏': 'add_to_favorites', '收藏歌曲': 'add_to_favorites', '取消收藏': 'del_from_favorites', '播放列表第': 'play_music_list_index', '本地播放歌曲': 'playlocal', '查找歌曲': 'play', '下载歌曲': 'play', '暂停': 'stop', '停止': 'stop', '停止播放': 'stop', '播放歌单': 'play_music_list', '测试自定义口令': '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=False, keywords_playlocal='播放本地歌曲,本地播放歌曲', keywords_play='查找歌曲,下载歌曲', keywords_stop='关机,暂停,停止,停止播放', keywords_playlist='播放列表,播放歌单', user_key_word_dict={'测试自定义口令': 'exec#code1("hello")', '测试链接': 'exec#httpget("https://github.com/hanxi/xiaomusic")'}, enable_force_stop=False, devices={'726577518': Device(did='726577518', device_id='******', hardware='LX06', name='小爱音箱Pro', play_type='', cur_music='', cur_playlist='全部'), '570867755': Device(did='570867755', device_id='*********', hardware='L15A', name='小米AI音箱(第二代)', play_type='', cur_music='', cur_playlist='全部')}, group_list='', remove_id3tag=False, convert_to_mp3=False, delay_sec=3, continue_play=False, pull_ask_sec=1, crontab_json='', enable_yt_dlp_cookies=False, get_ask_by_mina=False)
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:1133: 语音控制已启动, 用【分钟后关机/播放歌曲/下一首/上一首/单曲循环/全部循环/随机播放/关机/刷新列表/播放列表第/播放列表/加入收藏/收藏歌曲/取消收藏/播放本地歌曲/本地播放歌曲/查找歌曲/下载歌曲/暂停/停止/停止播放/播放歌单/测试自定义口令/测试链接】开头来控制
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:543: 协程时间循环未启动
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:1250: 没打乱 全部 ['乌兰托娅 火红的萨日朗', '凤凰传奇麝香夫人'] ... ['罗大佑童年', '阿嬷'] with len: 7
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:1250: 没打乱 全部 ['乌兰托娅 火红的萨日朗', '凤凰传奇麝香夫人'] ... ['罗大佑童年', '阿嬷'] with len: 7
-[2024-11-11 18:08:04] [0.3.46] [INFO] analytics.py:28: analytics init ok
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:104: Startup OK. Config(account='***', password='***', mi_did='726577518,570867755', miio_tts_command='', cookie='', verbose=False, music_path='music', download_path='music/download', conf_path='conf', cache_dir='cache', hostname='192.168.22.4', port=8090, public_port=0, proxy='', 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='', custom_play_list_json='', disable_download=False, key_word_dict={'播放歌曲': 'play', '播放本地歌曲': 'playlocal', '关机': 'stop', '下一首': 'play_next', '上一首': 'play_prev', '单曲循环': 'set_play_type_one', '全部循环': 'set_play_type_all', '随机播放': 'set_random_play', '分钟后关机': 'stop_after_minute', '播放列表': 'play_music_list', '刷新列表': 'gen_music_list', '加入收藏': 'add_to_favorites', '收藏歌曲': 'add_to_favorites', '取消收藏': 'del_from_favorites', '播放列表第': 'play_music_list_index', '本地播放歌曲': 'playlocal', '查找歌曲': 'play', '下载歌曲': 'play', '暂停': 'stop', '停止': 'stop', '停止播放': 'stop', '播放歌单': 'play_music_list', '测试自定义口令': '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=False, keywords_playlocal='播放本地歌曲,本地播放歌曲', keywords_play='查找歌曲,下载歌曲', keywords_stop='关机,暂停,停止,停止播放', keywords_playlist='播放列表,播放歌单', user_key_word_dict={'测试自定义口令': 'exec#code1("hello")', '测试链接': 'exec#httpget("https://github.com/hanxi/xiaomusic")'}, enable_force_stop=False, devices={'726577518': Device(did='726577518', device_id='*****', hardware='LX06', name='小爱音箱Pro', play_type='', cur_music='', cur_playlist='全部'), '570867755': Device(did='570867755', device_id='*********', hardware='L15A', name='小米AI音箱(第二代)', play_type='', cur_music='', cur_playlist='全部')}, group_list='', remove_id3tag=False, convert_to_mp3=False, delay_sec=3, continue_play=False, pull_ask_sec=1, crontab_json='', enable_yt_dlp_cookies=False, get_ask_by_mina=False)
-[2024-11-11 18:08:04] [0.3.46] [INFO] httpserver.py:111: disable_httpauth:True
-[18:08:04] [0.3.46] [INFO] Started server process [1]
-[18:08:04] [0.3.46] [INFO] Waiting for application startup.
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:541: 启动后台构建 tag cache
-[18:08:04] [0.3.46] [INFO] Application startup complete.
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:513: 已从【cache/tag_cache.json】加载 tag cache
-[18:08:04] [0.3.46] [INFO] Uvicorn running on http://['0.0.0.0', '::']:8090 (Press CTRL+C to quit)
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:527: 保存:tag cache 已保存到【cache/tag_cache.json】
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:577: tag 更新完成
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:248: 选中的设备: {'726577518': Device(did='726577518', device_id='******', hardware='LX06', name='小爱音箱Pro', play_type='', cur_music='', cur_playlist='全部'), '570867755': Device(did='570867755', device_id='*********', hardware='L15A', name='小米AI音箱(第二代)', play_type='', cur_music='', cur_playlist='全部')}
-[18:08:34] [0.3.46] [INFO] 172.20.0.1:35058 - "GET /static/default/setting.html HTTP/1.1" 304
-[18:08:34] [0.3.46] [INFO] 172.20.0.1:35058 - "GET /getversion HTTP/1.1" 200

使用的docker-compose命令安装 services: xiaomusic: image: hanxi/xiaomusic container_name: xiaomusic restart: unless-stopped ports: - 8090:8090 volumes: - /mnt/sharedata/audiodata/musci/xiaomusic:/app/music - /mnt/data_sdb1/docker/xiaomusic/config.json:/app/config.json command: ['--config', '/app/config.json']

根据日志的提示,'播放歌曲': 'play'依然存在,只是增加了 '查找歌曲': 'play', '下载歌曲': 'play', 这两个关于play的自定义口令,所以实际上play有三条口令 “播放歌曲、查找歌曲、下载歌曲”,能否删除掉'播放歌曲': 'play'这个系统默认的口令?只使用 '查找歌曲': 'play', '下载歌曲': 'play', 这两个关于play的自定义口令


评论 17 - hanxi

@mogeqian 另外提个 issue 吧,现在应该是不支持删除默认的口令。


评论 18 - mogeqian

好的,已经重开了一个issue #259


链接到 GitHub Issue

`,84)]))}const r=a(p,[["render",l]]);export{d as __pageData,r as default}; diff --git a/docs/.vitepress/dist/assets/issues_182.md.CpqPtztd.js b/docs/.vitepress/dist/assets/issues_182.md.CpqPtztd.js deleted file mode 100644 index 7de6584..0000000 --- a/docs/.vitepress/dist/assets/issues_182.md.CpqPtztd.js +++ /dev/null @@ -1,53 +0,0 @@ -import{_ as i,c as a,a0 as n,o as t}from"./chunks/framework.p2VkXzrt.js";const d=JSON.parse('{"title":"定时任务配置格式","description":"","frontmatter":{"title":"定时任务配置格式"},"headers":[],"relativePath":"issues/182.md","filePath":"issues/182.md","lastUpdated":null}'),l={name:"issues/182.md"};function h(p,s,k,e,E,r){return t(),a("div",null,s[0]||(s[0]=[n(`

定时任务配置格式

支持采用 crontab 的格式配置定时任务,已经支持下面的任务类型:

  • stop 关机
  • play 播放歌曲
  • play_music_list 播放列表
  • tts 文字转语音
  • refresh_music_list 刷新播放列表
  • set_volume 设置音量
  • set_play_type 设置播放类型,单曲循环 0 , 全部循环 1 , 随机播放 2 , 单曲播放 3 , 顺序播放 4

示例

json
[
-    {
-        "expression": "0 8 * * 0-4",
-        "name": "play",
-        "did": "123456789",
-        "arg1": "周杰伦晴天"
-    },
-    {
-        "expression": "10 8 * * 0-4",
-        "name": "stop",
-        "did": "123456789"
-    },
-    {
-        "expression": "0 9 * * *",
-        "name": "play",
-        "did": "123456789",
-        "arg1": "周杰伦晴天"
-    },
-    {
-        "expression": "0 10 * * *",
-        "name": "play_music_list",
-        "did": "123456789",
-        "arg1": "周杰伦"
-    },
-    {
-        "expression": "30 10 * * *",
-        "name": "play_music_list",
-        "did": "123456789",
-        "arg1": "周杰伦|晴天"
-    },
-    {
-        "expression": "0 7 * * *",
-        "name": "tts",
-        "did": "123456789",
-        "arg1": "早上好!该起床了!"
-    },
-    {
-        "expression": "0 3 * * *",
-        "name": "refresh_music_list"
-    },
-    {
-        "expression": "* * * * *",
-        "name": "set_volume",
-        "did": "123456789",
-        "arg1": "25"
-    },
-    {
-        "expression": "* * * * *",
-        "name": "set_play_type",
-        "did": "123456789",
-        "arg1": "2"
-    }
-]

示例中的意思是:

  • 周一到周五每天 8 点播放歌曲 "周杰伦晴天"
  • 周一到周五每天 8 点 10 分执行关机指令
  • 每天 9 点播放歌曲 "周杰伦晴天"
  • 每天 10 点播放列表 "周杰伦"
  • 每天 10 点 30 分播放列表 "周杰伦" 里的 "晴天"
  • 每天 7 点发出语音 "早上好!该起床了!"
  • 每天 3 点刷新播放列表,用于自动更新目录下的歌曲到播放列表里。
  • 每分钟设置音量为 25
  • 每分钟设置为随机播放

注意星期一是0,星期二是1,星期日是6。 (0-6 or mon,tue,wed,thu,fri,sat,sun) The first weekday is always monday.

参数意思

  • expression 的格式是标准的 crontab 的格式,用于配置任务的执行时机,如何配置可以直接用下面的 crontab ai 工具生成
  • name 是任务名,目前只支持上面那几种。
  • did 是小爱音箱的设备ID,就是设置页面的音箱型号后面的那串数字。
  • arg1 根据任务不同而不同。
    • play 的 arg1 表示要播放的歌曲名。
    • play_music_list 的 arg1 表示要播放的播放目录名,可以加 | 符合加上目录下面的歌曲名,也可不加。
    • tts 的 arg1 表示要说的语音文字。

评论

评论 1 - hanxi

0.3.38版本功能。


链接到 GitHub Issue

`,15)]))}const u=i(l,[["render",h]]);export{d as __pageData,u as default}; diff --git a/docs/.vitepress/dist/assets/issues_182.md.CpqPtztd.lean.js b/docs/.vitepress/dist/assets/issues_182.md.CpqPtztd.lean.js deleted file mode 100644 index 7de6584..0000000 --- a/docs/.vitepress/dist/assets/issues_182.md.CpqPtztd.lean.js +++ /dev/null @@ -1,53 +0,0 @@ -import{_ as i,c as a,a0 as n,o as t}from"./chunks/framework.p2VkXzrt.js";const d=JSON.parse('{"title":"定时任务配置格式","description":"","frontmatter":{"title":"定时任务配置格式"},"headers":[],"relativePath":"issues/182.md","filePath":"issues/182.md","lastUpdated":null}'),l={name:"issues/182.md"};function h(p,s,k,e,E,r){return t(),a("div",null,s[0]||(s[0]=[n(`

定时任务配置格式

支持采用 crontab 的格式配置定时任务,已经支持下面的任务类型:

  • stop 关机
  • play 播放歌曲
  • play_music_list 播放列表
  • tts 文字转语音
  • refresh_music_list 刷新播放列表
  • set_volume 设置音量
  • set_play_type 设置播放类型,单曲循环 0 , 全部循环 1 , 随机播放 2 , 单曲播放 3 , 顺序播放 4

示例

json
[
-    {
-        "expression": "0 8 * * 0-4",
-        "name": "play",
-        "did": "123456789",
-        "arg1": "周杰伦晴天"
-    },
-    {
-        "expression": "10 8 * * 0-4",
-        "name": "stop",
-        "did": "123456789"
-    },
-    {
-        "expression": "0 9 * * *",
-        "name": "play",
-        "did": "123456789",
-        "arg1": "周杰伦晴天"
-    },
-    {
-        "expression": "0 10 * * *",
-        "name": "play_music_list",
-        "did": "123456789",
-        "arg1": "周杰伦"
-    },
-    {
-        "expression": "30 10 * * *",
-        "name": "play_music_list",
-        "did": "123456789",
-        "arg1": "周杰伦|晴天"
-    },
-    {
-        "expression": "0 7 * * *",
-        "name": "tts",
-        "did": "123456789",
-        "arg1": "早上好!该起床了!"
-    },
-    {
-        "expression": "0 3 * * *",
-        "name": "refresh_music_list"
-    },
-    {
-        "expression": "* * * * *",
-        "name": "set_volume",
-        "did": "123456789",
-        "arg1": "25"
-    },
-    {
-        "expression": "* * * * *",
-        "name": "set_play_type",
-        "did": "123456789",
-        "arg1": "2"
-    }
-]

示例中的意思是:

  • 周一到周五每天 8 点播放歌曲 "周杰伦晴天"
  • 周一到周五每天 8 点 10 分执行关机指令
  • 每天 9 点播放歌曲 "周杰伦晴天"
  • 每天 10 点播放列表 "周杰伦"
  • 每天 10 点 30 分播放列表 "周杰伦" 里的 "晴天"
  • 每天 7 点发出语音 "早上好!该起床了!"
  • 每天 3 点刷新播放列表,用于自动更新目录下的歌曲到播放列表里。
  • 每分钟设置音量为 25
  • 每分钟设置为随机播放

注意星期一是0,星期二是1,星期日是6。 (0-6 or mon,tue,wed,thu,fri,sat,sun) The first weekday is always monday.

参数意思

  • expression 的格式是标准的 crontab 的格式,用于配置任务的执行时机,如何配置可以直接用下面的 crontab ai 工具生成
  • name 是任务名,目前只支持上面那几种。
  • did 是小爱音箱的设备ID,就是设置页面的音箱型号后面的那串数字。
  • arg1 根据任务不同而不同。
    • play 的 arg1 表示要播放的歌曲名。
    • play_music_list 的 arg1 表示要播放的播放目录名,可以加 | 符合加上目录下面的歌曲名,也可不加。
    • tts 的 arg1 表示要说的语音文字。

评论

评论 1 - hanxi

0.3.38版本功能。


链接到 GitHub Issue

`,15)]))}const u=i(l,[["render",h]]);export{d as __pageData,u as default}; diff --git a/docs/.vitepress/dist/assets/issues_19.md.DB81ml38.js b/docs/.vitepress/dist/assets/issues_19.md.DB81ml38.js deleted file mode 100644 index 59438b5..0000000 --- a/docs/.vitepress/dist/assets/issues_19.md.DB81ml38.js +++ /dev/null @@ -1,50 +0,0 @@ -import{_ as a,c as n,a0 as i,o as p}from"./chunks/framework.p2VkXzrt.js";const u=JSON.parse('{"title":"如何修改默认的8090端口","description":"","frontmatter":{"title":"如何修改默认的8090端口"},"headers":[],"relativePath":"issues/19.md","filePath":"issues/19.md","lastUpdated":null}'),e={name:"issues/19.md"};function t(l,s,o,c,h,d){return p(),n("div",null,s[0]||(s[0]=[i(`

如何修改默认的8090端口

docker-compose 修改映射端口会播放失败

ports:
-      - 80:8090

从日志看继续调用了 http://10.0.0.4:8090 而不是修改映射的80,还原成

ports:
-      - 8090:8090

则一切正常

xiaomusic    | [BiliBiliSearch] Playlist 安河桥北: Downloading 1 items of 1
-xiaomusic    | [download] Downloading item 1 of 1
-xiaomusic    | [BiliBili] Extracting URL: http://www.bilibili.com/video/av319943893
-xiaomusic    | [BiliBili] 319943893: Downloading webpage
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | [BiliBili] BV1tw411X7Rr: Extracting videos in anthology
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | [BiliBili] 319943893: Extracting chapters
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | [BiliBili] Format(s) 1080P 高码率, 1080P 高清, 720P 高清, 4K 超清 are missing; you have to login or become premium member to download them. Use --cookies-from-browser or --cookies for the authentication. See  https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp  for how to manually pass cookies
-xiaomusic    | [info] BV1tw411X7Rr: Downloading 1 format(s): 30280
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | [download] Destination: music/安河桥北.m4a
-[download]   0.3% of    5.61MiB at    6.24MiB/s ETA 00:0010.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-[download]   0.5% of    5.61MiB at    2.42MiB/s ETA 00:0210.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-[download]   1.1% of    5.61MiB at    1.50MiB/s ETA 00:0310.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-[download]   4.4% of    5.61MiB at    2.35MiB/s ETA 00:0210.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-[download]   8.9% of    5.61MiB at    3.59MiB/s ETA 00:0110.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-[download] 100% of    5.61MiB in 00:00:00 at 10.83MiB/s  
-xiaomusic    | [ExtractAudio] Destination: music/安河桥北.mp3
-xiaomusic    | Deleting original file music/安河桥北.m4a (pass -k to keep)
-xiaomusic    | [download] Finished downloading playlist: 安河桥北
-xiaomusic    | [02/21/24 15:29:29] INFO     播放                               xiaomusic.py:461
-xiaomusic    |                              http://10.0.0.4:8090/music/%E5%AE%                 
-xiaomusic    |                              89%E6%B2%B3%E6%A1%A5%E5%8C%97.mp3                  
-xiaomusic    |                     INFO     已经开始播放了                     xiaomusic.py:464
-xiaomusic    |                     INFO     歌曲music/安河桥北.mp3的时长251秒  xiaomusic.py:371
-xiaomusic    |                     INFO     251秒后将会播放下一首              xiaomusic.py:385
-xiaomusic    |                     INFO     匹配到指令. opkey:set_volume#      xiaomusic.py:441
-xiaomusic    |                              opvalue:set_volume oparg:24

评论

评论 1 - hanxi

需要添加环境变量

environment:
-    XIAOMUSIC_PORT:80
-ports:
-      - 80:80

评论 2 - newrookie001

需要添加环境变量

environment:
-    XIAOMUSIC_PORT:80
-ports:
-      - 80:80

自己走了点弯路,半天才搞明白。补充说明:

environment: XIAOMUSIC_PORT: 5678 #就是“5678”可以根据自己要求设置,但要求上下的5678都设置成一个 ports: - 5678:5678


评论 3 - hanxi

如果换端口,需要3个数字一致,比如

environment:
-    XIAOMUSIC_PORT:6874
-ports:
-      - 6874:6874

评论 4 - hanxi

文档类型的我都打开下,方便其他人看到。


评论 5 - flymin

docker-compose 中对应关系应该是

yaml
ports:
-    - aaaa:bbbb
-environment:
-    XIAOMUSIC_PORT: bbbb  # 配置文件中的 port,后台:监听端口(修改后需要重启)
-    XIAOMUSIC_PUBLIC_PORT: aaaa # 配置文件中的 public_port,后台:外网访问端口(0表示跟监听端口一致)

以上,docker 环境中基本不存在需要修改 bbbb 的情况,也就是不用设置 XIAOMUSIC_PORT。如果需要修改端口,只需要修改两处 aaaa 如果使用反向代理,则转发 localhost:aaaa,XIAOMUSIC_PUBLIC_PORT 设置成代理的监听端口 cccc

另外,setting 文件存在会覆盖环境变量。启动过之后需要直接修改 settings.json 或者在后台修改


链接到 GitHub Issue

`,31)]))}const m=a(e,[["render",t]]);export{u as __pageData,m as default}; diff --git a/docs/.vitepress/dist/assets/issues_19.md.DB81ml38.lean.js b/docs/.vitepress/dist/assets/issues_19.md.DB81ml38.lean.js deleted file mode 100644 index 59438b5..0000000 --- a/docs/.vitepress/dist/assets/issues_19.md.DB81ml38.lean.js +++ /dev/null @@ -1,50 +0,0 @@ -import{_ as a,c as n,a0 as i,o as p}from"./chunks/framework.p2VkXzrt.js";const u=JSON.parse('{"title":"如何修改默认的8090端口","description":"","frontmatter":{"title":"如何修改默认的8090端口"},"headers":[],"relativePath":"issues/19.md","filePath":"issues/19.md","lastUpdated":null}'),e={name:"issues/19.md"};function t(l,s,o,c,h,d){return p(),n("div",null,s[0]||(s[0]=[i(`

如何修改默认的8090端口

docker-compose 修改映射端口会播放失败

ports:
-      - 80:8090

从日志看继续调用了 http://10.0.0.4:8090 而不是修改映射的80,还原成

ports:
-      - 8090:8090

则一切正常

xiaomusic    | [BiliBiliSearch] Playlist 安河桥北: Downloading 1 items of 1
-xiaomusic    | [download] Downloading item 1 of 1
-xiaomusic    | [BiliBili] Extracting URL: http://www.bilibili.com/video/av319943893
-xiaomusic    | [BiliBili] 319943893: Downloading webpage
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | [BiliBili] BV1tw411X7Rr: Extracting videos in anthology
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | [BiliBili] 319943893: Extracting chapters
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | [BiliBili] Format(s) 1080P 高码率, 1080P 高清, 720P 高清, 4K 超清 are missing; you have to login or become premium member to download them. Use --cookies-from-browser or --cookies for the authentication. See  https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp  for how to manually pass cookies
-xiaomusic    | [info] BV1tw411X7Rr: Downloading 1 format(s): 30280
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | [download] Destination: music/安河桥北.m4a
-[download]   0.3% of    5.61MiB at    6.24MiB/s ETA 00:0010.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-[download]   0.5% of    5.61MiB at    2.42MiB/s ETA 00:0210.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-[download]   1.1% of    5.61MiB at    1.50MiB/s ETA 00:0310.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-[download]   4.4% of    5.61MiB at    2.35MiB/s ETA 00:0210.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-[download]   8.9% of    5.61MiB at    3.59MiB/s ETA 00:0110.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-[download] 100% of    5.61MiB in 00:00:00 at 10.83MiB/s  
-xiaomusic    | [ExtractAudio] Destination: music/安河桥北.mp3
-xiaomusic    | Deleting original file music/安河桥北.m4a (pass -k to keep)
-xiaomusic    | [download] Finished downloading playlist: 安河桥北
-xiaomusic    | [02/21/24 15:29:29] INFO     播放                               xiaomusic.py:461
-xiaomusic    |                              http://10.0.0.4:8090/music/%E5%AE%                 
-xiaomusic    |                              89%E6%B2%B3%E6%A1%A5%E5%8C%97.mp3                  
-xiaomusic    |                     INFO     已经开始播放了                     xiaomusic.py:464
-xiaomusic    |                     INFO     歌曲music/安河桥北.mp3的时长251秒  xiaomusic.py:371
-xiaomusic    |                     INFO     251秒后将会播放下一首              xiaomusic.py:385
-xiaomusic    |                     INFO     匹配到指令. opkey:set_volume#      xiaomusic.py:441
-xiaomusic    |                              opvalue:set_volume oparg:24

评论

评论 1 - hanxi

需要添加环境变量

environment:
-    XIAOMUSIC_PORT:80
-ports:
-      - 80:80

评论 2 - newrookie001

需要添加环境变量

environment:
-    XIAOMUSIC_PORT:80
-ports:
-      - 80:80

自己走了点弯路,半天才搞明白。补充说明:

environment: XIAOMUSIC_PORT: 5678 #就是“5678”可以根据自己要求设置,但要求上下的5678都设置成一个 ports: - 5678:5678


评论 3 - hanxi

如果换端口,需要3个数字一致,比如

environment:
-    XIAOMUSIC_PORT:6874
-ports:
-      - 6874:6874

评论 4 - hanxi

文档类型的我都打开下,方便其他人看到。


评论 5 - flymin

docker-compose 中对应关系应该是

yaml
ports:
-    - aaaa:bbbb
-environment:
-    XIAOMUSIC_PORT: bbbb  # 配置文件中的 port,后台:监听端口(修改后需要重启)
-    XIAOMUSIC_PUBLIC_PORT: aaaa # 配置文件中的 public_port,后台:外网访问端口(0表示跟监听端口一致)

以上,docker 环境中基本不存在需要修改 bbbb 的情况,也就是不用设置 XIAOMUSIC_PORT。如果需要修改端口,只需要修改两处 aaaa 如果使用反向代理,则转发 localhost:aaaa,XIAOMUSIC_PUBLIC_PORT 设置成代理的监听端口 cccc

另外,setting 文件存在会覆盖环境变量。启动过之后需要直接修改 settings.json 或者在后台修改


链接到 GitHub Issue

`,31)]))}const m=a(e,[["render",t]]);export{u as __pageData,m as default}; diff --git a/docs/.vitepress/dist/assets/issues_210.md.C4LG69AC.js b/docs/.vitepress/dist/assets/issues_210.md.C4LG69AC.js deleted file mode 100644 index 0cb1542..0000000 --- a/docs/.vitepress/dist/assets/issues_210.md.C4LG69AC.js +++ /dev/null @@ -1,2 +0,0 @@ -import{_ as e,c as a,a0 as o,o as i}from"./chunks/framework.p2VkXzrt.js";const u=JSON.parse('{"title":"yt-dlp cookies 文件上传功能","description":"","frontmatter":{"title":"yt-dlp cookies 文件上传功能"},"headers":[],"relativePath":"issues/210.md","filePath":"issues/210.md","lastUpdated":null}'),s={name:"issues/210.md"};function l(c,t,r,n,h,d){return i(),a("div",null,t[0]||(t[0]=[o(`

yt-dlp cookies 文件上传功能

此功能用于解决 yt-dlp 下载资源失败时使用,比如 ip 被 B站或者 youtube 加入黑名单后才需要使用。

上传的文件用于 yt-dlp 的 --cookies 参数。

--cookies FILE   Netscape formatted file to read cookies from
-                      and dump cookie jar in

获取 cookies.txt 文件

  1. 下载插件 Get cookies.txt LOCALLY
  2. 给予插件访问权限和无痕模式允许使用 image
  3. 打开无痕窗口
  4. 打开 youtube.com
  5. 登陆 youtube.com
  6. 打开新标签页
  7. 关闭 youtube.com 的标签页
  8. 保存 cookies.txt image

原因见 https://github.com/yt-dlp/yt-dlp/wiki/Extractors#exporting-youtube-cookies

上传 cookies.txt

  1. 打开设置页面
  2. 设置启用yt-dlp-cookies 选项为 true Screenshot_2024-09-29-22-31-40-134_com android chrome-edit
  3. 点击保存
  4. 再点击选择文件,选择前面保存好的 cookies.txt 文件,点击上传。 Screenshot_2024-09-29-22-33-21-361_com android chrome-edit

后续用途

  1. 语音下载歌曲都会带上前面上传的 cookies.txt 文件去搜索下载歌曲。
  2. 歌曲批量下载工具下载歌曲或者歌单时也会带上 cookies.txt 文件。

评论

评论 1 - kingfly2016

0.3.37的版本并没有发现可以开启yt-dlp-cookies 并上传cookies文件的地方,尝试把导出的cookies.txt手工上传到conf目录下,没有生效. 屏幕截图_11-10-2024_183725_192 168 6 202


评论 2 - hanxi

需要等38版本,或者用测试版本,镜像名后面加 :main


评论 3 - kingfly2016

需要等38版本,或者用测试版本,镜像名后面加 :main

谢谢


链接到 GitHub Issue

`,23)]))}const b=e(s,[["render",l]]);export{u as __pageData,b as default}; diff --git a/docs/.vitepress/dist/assets/issues_210.md.C4LG69AC.lean.js b/docs/.vitepress/dist/assets/issues_210.md.C4LG69AC.lean.js deleted file mode 100644 index 0cb1542..0000000 --- a/docs/.vitepress/dist/assets/issues_210.md.C4LG69AC.lean.js +++ /dev/null @@ -1,2 +0,0 @@ -import{_ as e,c as a,a0 as o,o as i}from"./chunks/framework.p2VkXzrt.js";const u=JSON.parse('{"title":"yt-dlp cookies 文件上传功能","description":"","frontmatter":{"title":"yt-dlp cookies 文件上传功能"},"headers":[],"relativePath":"issues/210.md","filePath":"issues/210.md","lastUpdated":null}'),s={name:"issues/210.md"};function l(c,t,r,n,h,d){return i(),a("div",null,t[0]||(t[0]=[o(`

yt-dlp cookies 文件上传功能

此功能用于解决 yt-dlp 下载资源失败时使用,比如 ip 被 B站或者 youtube 加入黑名单后才需要使用。

上传的文件用于 yt-dlp 的 --cookies 参数。

--cookies FILE   Netscape formatted file to read cookies from
-                      and dump cookie jar in

获取 cookies.txt 文件

  1. 下载插件 Get cookies.txt LOCALLY
  2. 给予插件访问权限和无痕模式允许使用 image
  3. 打开无痕窗口
  4. 打开 youtube.com
  5. 登陆 youtube.com
  6. 打开新标签页
  7. 关闭 youtube.com 的标签页
  8. 保存 cookies.txt image

原因见 https://github.com/yt-dlp/yt-dlp/wiki/Extractors#exporting-youtube-cookies

上传 cookies.txt

  1. 打开设置页面
  2. 设置启用yt-dlp-cookies 选项为 true Screenshot_2024-09-29-22-31-40-134_com android chrome-edit
  3. 点击保存
  4. 再点击选择文件,选择前面保存好的 cookies.txt 文件,点击上传。 Screenshot_2024-09-29-22-33-21-361_com android chrome-edit

后续用途

  1. 语音下载歌曲都会带上前面上传的 cookies.txt 文件去搜索下载歌曲。
  2. 歌曲批量下载工具下载歌曲或者歌单时也会带上 cookies.txt 文件。

评论

评论 1 - kingfly2016

0.3.37的版本并没有发现可以开启yt-dlp-cookies 并上传cookies文件的地方,尝试把导出的cookies.txt手工上传到conf目录下,没有生效. 屏幕截图_11-10-2024_183725_192 168 6 202


评论 2 - hanxi

需要等38版本,或者用测试版本,镜像名后面加 :main


评论 3 - kingfly2016

需要等38版本,或者用测试版本,镜像名后面加 :main

谢谢


链接到 GitHub Issue

`,23)]))}const b=e(s,[["render",l]]);export{u as __pageData,b as default}; diff --git a/docs/.vitepress/dist/assets/issues_211.md.DUZAr3Vg.js b/docs/.vitepress/dist/assets/issues_211.md.DUZAr3Vg.js deleted file mode 100644 index 63d05d3..0000000 --- a/docs/.vitepress/dist/assets/issues_211.md.DUZAr3Vg.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as e,c as t,a0 as s,o as l}from"./chunks/framework.p2VkXzrt.js";const p=JSON.parse('{"title":"📝 文档汇总","description":"","frontmatter":{"title":"📝 文档汇总"},"headers":[],"relativePath":"issues/211.md","filePath":"issues/211.md","lastUpdated":null}'),i={name:"issues/211.md"};function r(h,a,o,f,m,u){return l(),t("div",null,a[0]||(a[0]=[s('

📝 文档汇总

1️⃣ 基础文档

2️⃣ 进阶文档

3️⃣ 其他安装文档

NOTE

下面教程可能比较旧,只供参考

评论

评论 1 - sghuenn

redmi小爱触屏音箱8,仍然需要打开“型号兼容模式”才能播放 打开兼容模式后的问题有:每首歌曲播放完毕后都要再从头播放4、5秒才播放下一曲;语音命令小爱同学播放下一曲,它会从头开始播放当前歌曲。


评论 2 - hanxi

redmi小爱触屏音箱8,仍然需要打开“型号兼容模式”才能播放 打开兼容模式后的问题有:每首歌曲播放完毕后都要再从头播放4、5秒才播放下一曲;语音命令小爱同学播放下一曲,它会从头开始播放当前歌曲。

播放歌曲的接口应该是有点问题,等有设备有开发能力的人来搞吧。


评论 3 - zhoukk37

想问下如何利用反向代理,完成使得小爱外网访问nas呢,您能提供一下关键词,我自己去检索学下一下吗


评论 4 - hanxi

想问下如何利用反向代理,完成使得小爱外网访问nas呢,您能提供一下关键词,我自己去检索学下一下吗

内网穿透,frp能实现,就是把局域网的端口映射成公网的端口。


链接到 GitHub Issue

',24)]))}const c=e(i,[["render",r]]);export{p as __pageData,c as default}; diff --git a/docs/.vitepress/dist/assets/issues_211.md.DUZAr3Vg.lean.js b/docs/.vitepress/dist/assets/issues_211.md.DUZAr3Vg.lean.js deleted file mode 100644 index 63d05d3..0000000 --- a/docs/.vitepress/dist/assets/issues_211.md.DUZAr3Vg.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as e,c as t,a0 as s,o as l}from"./chunks/framework.p2VkXzrt.js";const p=JSON.parse('{"title":"📝 文档汇总","description":"","frontmatter":{"title":"📝 文档汇总"},"headers":[],"relativePath":"issues/211.md","filePath":"issues/211.md","lastUpdated":null}'),i={name:"issues/211.md"};function r(h,a,o,f,m,u){return l(),t("div",null,a[0]||(a[0]=[s('

📝 文档汇总

1️⃣ 基础文档

2️⃣ 进阶文档

3️⃣ 其他安装文档

NOTE

下面教程可能比较旧,只供参考

评论

评论 1 - sghuenn

redmi小爱触屏音箱8,仍然需要打开“型号兼容模式”才能播放 打开兼容模式后的问题有:每首歌曲播放完毕后都要再从头播放4、5秒才播放下一曲;语音命令小爱同学播放下一曲,它会从头开始播放当前歌曲。


评论 2 - hanxi

redmi小爱触屏音箱8,仍然需要打开“型号兼容模式”才能播放 打开兼容模式后的问题有:每首歌曲播放完毕后都要再从头播放4、5秒才播放下一曲;语音命令小爱同学播放下一曲,它会从头开始播放当前歌曲。

播放歌曲的接口应该是有点问题,等有设备有开发能力的人来搞吧。


评论 3 - zhoukk37

想问下如何利用反向代理,完成使得小爱外网访问nas呢,您能提供一下关键词,我自己去检索学下一下吗


评论 4 - hanxi

想问下如何利用反向代理,完成使得小爱外网访问nas呢,您能提供一下关键词,我自己去检索学下一下吗

内网穿透,frp能实现,就是把局域网的端口映射成公网的端口。


链接到 GitHub Issue

',24)]))}const c=e(i,[["render",r]]);export{p as __pageData,c as default}; diff --git a/docs/.vitepress/dist/assets/issues_212.md.j95HTJwO.js b/docs/.vitepress/dist/assets/issues_212.md.j95HTJwO.js deleted file mode 100644 index e05ad18..0000000 --- a/docs/.vitepress/dist/assets/issues_212.md.j95HTJwO.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as a,c as e,a0 as s,o as i}from"./chunks/framework.p2VkXzrt.js";const u=JSON.parse('{"title":"如何批量下载歌曲","description":"","frontmatter":{"title":"如何批量下载歌曲"},"headers":[],"relativePath":"issues/212.md","filePath":"issues/212.md","lastUpdated":null}'),o={name:"issues/212.md"};function r(p,t,l,h,c,n){return i(),e("div",null,t[0]||(t[0]=[s('

如何批量下载歌曲

批量下载歌曲依赖的是 yt-dlp 批量下载播放列表里的视频并转为 mp3 实现的。

先进入到歌曲下载工具页面:

默认主题 => 设置 => 歌曲下载工具

Screenshot_2024-09-29-22-36-12-178_com android chrome-edit

已经测试过 B 站和 youtube 两种播放列表,播放列表的链接是有要求,不能有其他多余参数。

比如 B 站的是这样的

https://m.bilibili.com/video/BV1WUsDezE88

youtube 的是这样的

https://m.youtube.com/playlist?list=PLUD2d-pqyvT6_ztf31hx-5SsUUvY5UsQn

输入歌单名字是用于保存的文件夹名字,最好不是已经存在的名字,每次下载歌单都取个新名字比较合适。

已知 youtube 需要上传无痕模式下的 cookies.txt 文件才能正常下载。具体步骤见 /issues/210.html 。

也支持单独下载一个链接只有一首歌曲的。

评论

评论 1 - lazybabyz

默认主题 => 设置 =>没有显示找到 歌曲下载工具, 有一个 歌单地址 歌单内容: 输入B站测试地址显示返回无效

aaa


评论 2 - hanxi

等0.3.38版本。


链接到 GitHub Issue

',22)]))}const b=a(o,[["render",r]]);export{u as __pageData,b as default}; diff --git a/docs/.vitepress/dist/assets/issues_212.md.j95HTJwO.lean.js b/docs/.vitepress/dist/assets/issues_212.md.j95HTJwO.lean.js deleted file mode 100644 index e05ad18..0000000 --- a/docs/.vitepress/dist/assets/issues_212.md.j95HTJwO.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as a,c as e,a0 as s,o as i}from"./chunks/framework.p2VkXzrt.js";const u=JSON.parse('{"title":"如何批量下载歌曲","description":"","frontmatter":{"title":"如何批量下载歌曲"},"headers":[],"relativePath":"issues/212.md","filePath":"issues/212.md","lastUpdated":null}'),o={name:"issues/212.md"};function r(p,t,l,h,c,n){return i(),e("div",null,t[0]||(t[0]=[s('

如何批量下载歌曲

批量下载歌曲依赖的是 yt-dlp 批量下载播放列表里的视频并转为 mp3 实现的。

先进入到歌曲下载工具页面:

默认主题 => 设置 => 歌曲下载工具

Screenshot_2024-09-29-22-36-12-178_com android chrome-edit

已经测试过 B 站和 youtube 两种播放列表,播放列表的链接是有要求,不能有其他多余参数。

比如 B 站的是这样的

https://m.bilibili.com/video/BV1WUsDezE88

youtube 的是这样的

https://m.youtube.com/playlist?list=PLUD2d-pqyvT6_ztf31hx-5SsUUvY5UsQn

输入歌单名字是用于保存的文件夹名字,最好不是已经存在的名字,每次下载歌单都取个新名字比较合适。

已知 youtube 需要上传无痕模式下的 cookies.txt 文件才能正常下载。具体步骤见 /issues/210.html 。

也支持单独下载一个链接只有一首歌曲的。

评论

评论 1 - lazybabyz

默认主题 => 设置 =>没有显示找到 歌曲下载工具, 有一个 歌单地址 歌单内容: 输入B站测试地址显示返回无效

aaa


评论 2 - hanxi

等0.3.38版本。


链接到 GitHub Issue

',22)]))}const b=a(o,[["render",r]]);export{u as __pageData,b as default}; diff --git a/docs/.vitepress/dist/assets/issues_269.md.CwdSqsHN.js b/docs/.vitepress/dist/assets/issues_269.md.CwdSqsHN.js deleted file mode 100644 index 0006635..0000000 --- a/docs/.vitepress/dist/assets/issues_269.md.CwdSqsHN.js +++ /dev/null @@ -1,347 +0,0 @@ -import{_ as i,c as a,a0 as n,o as t}from"./chunks/framework.p2VkXzrt.js";const o=JSON.parse('{"title":"如何添加 网易云音乐playlist","description":"","frontmatter":{"title":"如何添加 网易云音乐playlist"},"headers":[],"relativePath":"issues/269.md","filePath":"issues/269.md","lastUpdated":null}'),p={name:"issues/269.md"};function l(h,s,k,e,E,r){return t(),a("div",null,s[0]||(s[0]=[n(`

如何添加 网易云音乐playlist

利用 NeteaseCloudMusicApi 获取歌单和播放地址 我在你基础上改了一下,但是我的逻辑不合理 https://github.com/dissipator/xiaomusic

评论

评论 1 - hanxi

建议通过插件实现或者新增一个页面工具把歌单导出 json 。 歌单的 json 格式见 /issues/78.html


评论 2 - qiujie8092916

利用 NeteaseCloudMusicApi 获取歌单和播放地址 我在你基础上改了一下,但是我的逻辑不合理 dissipator/xiaomusic

老哥在实现插件了吗?我也急需播放歌单 我诉求是:我用网易云音乐单独新建了一个歌单,我往里面扔歌曲,以更新歌单。希望创建一个自定义的语音命令,让小爱同学随机播放这个歌单里的音乐。然后我通过在米家里执行,比如触发了「我回来了」的智能场景时,就让小爱音箱执行这个自定义的语音命令,就会自动播放我新建的这个歌单里的音乐了。 现在的小爱音箱虽然能勉强实现,但是很垃圾。随机播放的随机性有问题,并且只能选择歌单里的 30 首歌。 (老哥如果没实现的话,我可以尝试搞搞)


评论 3 - dissipator

我都能直接利用这个直接播放NeteaseCloudMusicApi这上的歌了。在配合unblk,无敌。我就是没设备,还在路上 通过插件实现,非常好,就是不知道怎么开发,有文档我可以尝试一下。


评论 4 - dissipator

建议通过插件实现或者新增一个页面工具把歌单导出 json 。 歌单的 json 格式见 #78

配置处直接填了api的接口 http://127.0.0.1:3000/playlist/detail?id=12758992226 我是直接再你歌单保存上强改的。

python
async def downloadjson(data: UrlInfo, Verifcation=Depends(verification)):
-    log.info(data)
-    url = data.url
-    content = "[{"
-    host = f"{url.split('/')[0]}//{url.split('/')[2]}"
-    try:
-        ret = "OK"
-        jsons = await downloadfile(url,"json")
-        # print(jsons)
-        list_name = jsons['playlist']['name']
-        content += '"name":"'+list_name+'","musics":['
-        for song in jsons['playlist']['tracks']:
-            content += f"{{\\"name\\":\\"{song['name']}\\",\\"url\\": \\"{host}/song/url?br=999000&proxy=http:%2F%2F127.0.0.1:8080&realIP=211.161.244.70&id={song['id']}\\"}}"
-    except Exception as e:
-        log.exception(f"Execption {e}")
-        ret = "Download JSON file failed."
-    content = content[:-1] + "]}]"

照着你的说明。而且能成功播发

python
@app.get("/musicinfo")
-async def musicinfo(
-    name: str, musictag: bool = False, Verifcation=Depends(verification)
-):
-    url = xiaomusic.get_music_url(name)
-    if("song/url" in url):
-        jsons = await downloadfile(url,"json")
-        url = jsons['data'][0]['url']

播放处加了一个判断


评论 5 - hanxi

建议通过插件实现或者新增一个页面工具把歌单导出 json 。 歌单的 json 格式见 #78

你的修改我看了,不太通用。生成json,再用现有的接口提交json更通用。


评论 6 - dissipator

是的,不通用。最好是用插件实现。

  1. 就是不知道你插件的逻辑。
  2. 如果用插件就考虑直接读取网易账号下所有歌单。然后选择一个导入,或者全部导入。
  3. 等你完善文档后我可以尝试写一个。同理,qq等其他平台的歌单也就都可以弄了

评论 7 - hanxi

等有空我写个修改歌单内容的插件示例吧。


评论 8 - dissipator

import requests
-
-def getmy_playlist(type="netease",api_host="http://127.0.0.1/api", playlist_id=None,uid=None):
-    """
-    Purpose: 
-    """
-    global log, xiaomusic
-    if type == "netease":
-        if uid:
-            api_url = f"{api_host}/user/playlist?uid={uid}"
-            # 发起请求
-            response = requests.get(api_url, timeout=5)  # 增加超时以避免长时间挂起
-            response.raise_for_status()  # 如果响应不是200,引发HTTPError异常
-            # log.info(f"getmy_playlist url:{api_url} response:{response.text}")
-
-            music_list = response.json()
-            for item in music_list['playlist']:
-                list_name = item.get("name")
-
-                log.info(f"getmy_playlist name:{list_name}")
-                # if item.get("id") in [12709941656,]:
-                songs_url = f"{api_host}/playlist/detail?id={item['id']}"
-                # 发起请求
-                response = requests.get(songs_url, timeout=5)  # 增加超时以避免长时间挂起
-                response.raise_for_status()  # 如果响应不是200,引发HTTPError异常
-                # log.info(f"getmy_playlist url:{api_url} response:{response.text}")
-                musics = response.json()
-                one_music_list = []
-                for music in musics['playlist']['tracks']:
-                    if (not music):
-                        continue
-                    # try:
-                    name = music['name']
-                    picUrl = music['al']['picUrl']
-                    artist = music['ar'][0]['name']
-                    album = music['al']['name']
-
-                    name = music.get("name")
-                    url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"
-                    if (not name) or (not url):
-                        continue
-                    xiaomusic.all_music[name] = url
-                    xiaomusic.all_music_tags[name] =  {
-                            "title": name,
-                            "artist": artist,
-                            "album": album,
-                            "year": "",
-                            "genre": "",
-                            "picture": picUrl,
-                            "lyrics": ""
-                        }
-                    
-                    one_music_list.append(name)
-                log.debug(f"getmy_playlist name:{list_name}")
-                log.debug(one_music_list)
-                # 歌曲名字相同会覆盖
-                xiaomusic.music_list[list_name] = one_music_list
-            xiaomusic.try_save_tag_cache()
-            log.debug(xiaomusic.all_music)
-            log.debug(xiaomusic.music_list)
-
-            return
-        if playlist_id:
-            songs_url = f"{api_host}/playlist/detail?id={playlist_id}"
-            # 发起请求
-            response = requests.get(songs_url, timeout=5)  # 增加超时以避免长时间挂起
-            response.raise_for_status()  # 如果响应不是200,引发HTTPError异常
-            # log.info(f"getmy_playlist url:{api_url} response:{response.text}")
-            musics = response.json()
-            list_name = musics['playlist']['name']
-            one_music_list = []
-            for music in musics['playlist']['tracks']:
-                if (not music):
-                    continue
-                # try:
-                name = music['name']
-                picUrl = music['al']['picUrl']
-                artist = music['ar'][0]['name']
-                album = music['al']['name']
-
-                name = music.get("name")
-                url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"
-                if (not name) or (not url):
-                    continue
-                xiaomusic.all_music[name] = url
-                xiaomusic.all_music_tags[name] =  {
-                        "title": name,
-                        "artist": artist,
-                        "album": album,
-                        "year": "",
-                        "genre": "",
-                        "picture": picUrl,
-                        "lyrics": ""
-                    }
-                one_music_list.append(name)
-            log.debug(f"getmy_playlist name:{list_name}")
-            log.debug(one_music_list)
-            # 歌曲名字相同会覆盖
-            xiaomusic.music_list[list_name] = one_music_list
-            xiaomusic.try_save_tag_cache()
-            log.debug(xiaomusic.all_music)
-            log.debug(xiaomusic.music_list)
-            return
-    else:
-        log.error(f"getmy_playlist type:{type} not support")

评论 9 - dissipator

等有空我写个修改歌单内容的插件示例吧。

不用拉,插件我已经写出来了 image


评论 10 - hanxi

发下你的 setting.json 配置吧,方便他人知道怎么配。


评论 11 - dissipator

我还在测试,设备到了能用了再发吧


评论 12 - guitarbug

也需要网易歌单功能, 坐等教程


评论 13 - dissipator

成功了

image

最好是在setting.json里去配置,我测试老是不匹配,在setting.json里去配置终于成功。可以直接调用 NeteaseCloudMusicApi 接口,甚至使用UnblockNeteaseMusic 解锁灰色,但是代码需要小调整。

setting.json

json
{
-  "account": "",
-  "password": "",
-  "mi_did": "",
-  "miio_tts_command": "",
-  "cookie": "",
-  "verbose": false,
-  "music_path": "music",
-  "download_path": "music/download",
-  "conf_path": "conf",
-  "cache_dir": "cache",
-  "hostname": "192.168.2.5",
-  "port": 8090,
-  "public_port": 0,
-  "proxy": "",
-  "search_prefix": "bilisearch:",
-  "ffmpeg_location": "./ffmpeg/bin",
-  "active_cmd": "play,set_random_play,playlocal,play_music_list,play_music_list_index,stop_after_minute,stop,获取歌单",
-  "exclude_dirs": "@eaDir,tmp",
-  "music_path_depth": 10,
-  "disable_httpauth": true,
-  "httpauth_username": "",
-  "httpauth_password": "",
-  "music_list_url": "",
-  "music_list_json": "",
-  "custom_play_list_json": "",
-  "disable_download": false,
-  "key_word_dict": {
-    "播放歌曲": "play",
-    "播放本地歌曲": "playlocal",
-    "关机": "stop",
-    "下一首": "play_next",
-    "上一首": "play_prev",
-    "单曲循环": "set_play_type_one",
-    "全部循环": "set_play_type_all",
-    "随机播放": "set_random_play",
-    "分钟后关机": "stop_after_minute",
-    "播放列表": "play_music_list",
-    "刷新列表": "gen_music_list",
-    "加入收藏": "add_to_favorites",
-    "收藏歌曲": "add_to_favorites",
-    "取消收藏": "del_from_favorites",
-    "播放列表第": "play_music_list_index",
-    "本地播放歌曲": "playlocal",
-    "放歌曲": "play",
-    "暂停": "stop",
-    "停止": "stop",
-    "停止播放": "stop",
-    "播放歌单": "play_music_list",
-    "测试自定义口令": "exec#code1(\\"hello\\")",
-    "测试链接": "exec#httpget(\\"https://github.com/hanxi/xiaomusic\\")",
-    "获取歌单": "exec#getmy_playlist(playlist_id=12758992225)"
-  },
-  "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": false,
-  "keywords_playlocal": "播放本地歌曲,本地播放歌曲",
-  "keywords_play": "播放歌曲,放歌曲",
-  "keywords_stop": "关机,暂停,停止,停止播放",
-  "keywords_playlist": "播放列表,播放歌单",
-  "user_key_word_dict": {
-    "测试自定义口令": "exec#code1(\\"hello\\")",
-    "测试链接": "exec#httpget(\\"https://github.com/hanxi/xiaomusic\\")",
-    "获取歌单": "exec#getmy_playlist(playlist_id=12758992225)"
-  },
-  "enable_force_stop": false,
-  "devices": {
-    " ": {
-      "did": " ",
-      "device_id": " -17c6-4204- - ",
-      "hardware": "L05C",
-      "name": "小黑你好",
-      "play_type": "",
-      "cur_music": "",
-      "cur_playlist": ""
-    }
-  },
-  "group_list": "",
-  "remove_id3tag": false,
-  "convert_to_mp3": false,
-  "delay_sec": 3,
-  "continue_play": false,
-  "pull_ask_sec": 1,
-  "crontab_json": "",
-  "enable_yt_dlp_cookies": false,
-  "get_ask_by_mina": false
-}

getmy_playlist.py

python
import requests
-
-async def getmy_playlist(type="netease",api_host="http://127.0.0.1/api", playlist_id=None,uid=None):
-    """
-    Purpose: 
-    """
-    global log, xiaomusic
-    if type == "netease":
-        if uid:
-            api_url = f"{api_host}/user/playlist?uid={uid}"
-            # 发起请求
-            response = requests.get(api_url, timeout=5) # 增加超时以避免长时间挂起
-            response.raise_for_status() # 如果响应不是200,引发HTTPError异常
-            # log.info(f"getmy_playlist url:{api_url} response:{response.text}")
-
-            music_list = response.json()
-            for item in music_list['playlist']:
-                list_name = item.get("name")
-
-                log.info(f"getmy_playlist name:{list_name}")
-                # if item.get("id") in [12709941656,]:
-                songs_url = f"{api_host}/playlist/detail?id={item['id']}"
-                # 发起请求
-                response = requests.get(songs_url, timeout=5) # 增加超时以避免长时间挂起
-                response.raise_for_status() # 如果响应不是200,引发HTTPError异常
-                # log.info(f"getmy_playlist url:{api_url} response:{response.text}")
-                musics = response.json()
-                one_music_list = []
-                for music in musics['playlist']['tracks']:
-                    if (not music):
-                        continue
-                    # try:
-                    name = music['name']
-                    picUrl = music['al']['picUrl']
-                    artist = music['ar'][0]['name']
-                    album = music['al']['name']
-
-                    name = music.get("name")
-                    url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"
-                    if (not name) or (not url):
-                        continue
-                    xiaomusic.all_music[name] = url
-                    xiaomusic.all_music_tags[name] = {
-                        "title": name,
-                        "artist": artist,
-                        "album": album,
-                        "year": "",
-                        "genre": "",
-                        "picture": picUrl,
-                        "lyrics": ""
-                        }
-
-                    one_music_list.append(name)
-                    log.debug(f"getmy_playlist name:{list_name}")
-                log.debug(one_music_list)
-                # 歌曲名字相同会覆盖
-                xiaomusic.music_list[list_name] = one_music_list
-                xiaomusic.try_save_tag_cache()
-                log.debug(xiaomusic.all_music)
-                log.debug(xiaomusic.music_list)
-            return
-        if playlist_id:
-            songs_url = f"{api_host}/playlist/detail?id={playlist_id}"
-            # 发起请求
-            response = requests.get(songs_url, timeout=5) # 增加超时以避免长时间挂起
-            response.raise_for_status() # 如果响应不是200,引发HTTPError异常
-            
-            musics = response.json()
-            list_name = musics['playlist']['name']
-            log.info(f"getmy_playlist list_name:{list_name} ")
-            one_music_list = []
-            for music in musics['playlist']['tracks']:
-                if (not music):
-                    continue
-                # try:
-                name = music['name']
-                picUrl = music['al']['picUrl']
-                artist = music['ar'][0]['name']
-                album = music['al']['name']
-
-                name = music.get("name")
-                url = f"{api_host}/song/url?id={music['id']}&br=350000&proxy=HTTP:%2F%2F127.0.0.1:8080"
-                if (not name) or (not url):
-                  continue
-                xiaomusic.all_music[name] = url
-                xiaomusic.all_music_tags[name] = {
-                    "title": name,
-                    "artist": artist,
-                    "album": album,
-                    "year": "",
-                    "genre": "",
-                    "picture": picUrl,
-                    "lyrics": ""
-                }
-                one_music_list.append(name)
-            log.debug(f"getmy_playlist name:{list_name}")
-            log.debug(one_music_list)
-            # 歌曲名字相同会覆盖
-            xiaomusic.music_list[list_name] = one_music_list
-            xiaomusic.try_save_tag_cache()
-            return
-    else:
-        log.error(f"getmy_playlist type:{type} not support")

评论 14 - dissipator

imageimage


评论 15 - dludream

NeteaseCloudMusicApi似乎获取不到播放地址?其他信息倒是有。

2024-11-28_151952


评论 16 - hanxi

看代码是另一个接口获取url的: url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"


评论 17 - dludream

看代码是另一个接口获取url的: url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"

是的,我用的是这个接口,https://registry.hub.docker.com/r/gnehs/neteasecloudmusicapi-docker/

可能网易修改了api,这些获取不了播放地址了。

我倒不是要这个功能,只是感兴趣看看。我不获取,我直接用yt把歌全下载下来播放。


评论 18 - dissipator

这个不是网易的接口,是NeteaseCloudMusicApi 接口。proxy=HTTP:%2F%2F127.0.0.1:8080"是UnblockNeteaseMusic 解锁灰色; 完整的使用方式和docker 可以到 https://github.com/dissipator/xiaomusic/tree/dev 看README.md;目前没有教程。本人就在群2,有问题可以找我。


链接到 GitHub Issue

`,77)]))}const g=i(p,[["render",l]]);export{o as __pageData,g as default}; diff --git a/docs/.vitepress/dist/assets/issues_269.md.CwdSqsHN.lean.js b/docs/.vitepress/dist/assets/issues_269.md.CwdSqsHN.lean.js deleted file mode 100644 index 0006635..0000000 --- a/docs/.vitepress/dist/assets/issues_269.md.CwdSqsHN.lean.js +++ /dev/null @@ -1,347 +0,0 @@ -import{_ as i,c as a,a0 as n,o as t}from"./chunks/framework.p2VkXzrt.js";const o=JSON.parse('{"title":"如何添加 网易云音乐playlist","description":"","frontmatter":{"title":"如何添加 网易云音乐playlist"},"headers":[],"relativePath":"issues/269.md","filePath":"issues/269.md","lastUpdated":null}'),p={name:"issues/269.md"};function l(h,s,k,e,E,r){return t(),a("div",null,s[0]||(s[0]=[n(`

如何添加 网易云音乐playlist

利用 NeteaseCloudMusicApi 获取歌单和播放地址 我在你基础上改了一下,但是我的逻辑不合理 https://github.com/dissipator/xiaomusic

评论

评论 1 - hanxi

建议通过插件实现或者新增一个页面工具把歌单导出 json 。 歌单的 json 格式见 /issues/78.html


评论 2 - qiujie8092916

利用 NeteaseCloudMusicApi 获取歌单和播放地址 我在你基础上改了一下,但是我的逻辑不合理 dissipator/xiaomusic

老哥在实现插件了吗?我也急需播放歌单 我诉求是:我用网易云音乐单独新建了一个歌单,我往里面扔歌曲,以更新歌单。希望创建一个自定义的语音命令,让小爱同学随机播放这个歌单里的音乐。然后我通过在米家里执行,比如触发了「我回来了」的智能场景时,就让小爱音箱执行这个自定义的语音命令,就会自动播放我新建的这个歌单里的音乐了。 现在的小爱音箱虽然能勉强实现,但是很垃圾。随机播放的随机性有问题,并且只能选择歌单里的 30 首歌。 (老哥如果没实现的话,我可以尝试搞搞)


评论 3 - dissipator

我都能直接利用这个直接播放NeteaseCloudMusicApi这上的歌了。在配合unblk,无敌。我就是没设备,还在路上 通过插件实现,非常好,就是不知道怎么开发,有文档我可以尝试一下。


评论 4 - dissipator

建议通过插件实现或者新增一个页面工具把歌单导出 json 。 歌单的 json 格式见 #78

配置处直接填了api的接口 http://127.0.0.1:3000/playlist/detail?id=12758992226 我是直接再你歌单保存上强改的。

python
async def downloadjson(data: UrlInfo, Verifcation=Depends(verification)):
-    log.info(data)
-    url = data.url
-    content = "[{"
-    host = f"{url.split('/')[0]}//{url.split('/')[2]}"
-    try:
-        ret = "OK"
-        jsons = await downloadfile(url,"json")
-        # print(jsons)
-        list_name = jsons['playlist']['name']
-        content += '"name":"'+list_name+'","musics":['
-        for song in jsons['playlist']['tracks']:
-            content += f"{{\\"name\\":\\"{song['name']}\\",\\"url\\": \\"{host}/song/url?br=999000&proxy=http:%2F%2F127.0.0.1:8080&realIP=211.161.244.70&id={song['id']}\\"}}"
-    except Exception as e:
-        log.exception(f"Execption {e}")
-        ret = "Download JSON file failed."
-    content = content[:-1] + "]}]"

照着你的说明。而且能成功播发

python
@app.get("/musicinfo")
-async def musicinfo(
-    name: str, musictag: bool = False, Verifcation=Depends(verification)
-):
-    url = xiaomusic.get_music_url(name)
-    if("song/url" in url):
-        jsons = await downloadfile(url,"json")
-        url = jsons['data'][0]['url']

播放处加了一个判断


评论 5 - hanxi

建议通过插件实现或者新增一个页面工具把歌单导出 json 。 歌单的 json 格式见 #78

你的修改我看了,不太通用。生成json,再用现有的接口提交json更通用。


评论 6 - dissipator

是的,不通用。最好是用插件实现。

  1. 就是不知道你插件的逻辑。
  2. 如果用插件就考虑直接读取网易账号下所有歌单。然后选择一个导入,或者全部导入。
  3. 等你完善文档后我可以尝试写一个。同理,qq等其他平台的歌单也就都可以弄了

评论 7 - hanxi

等有空我写个修改歌单内容的插件示例吧。


评论 8 - dissipator

import requests
-
-def getmy_playlist(type="netease",api_host="http://127.0.0.1/api", playlist_id=None,uid=None):
-    """
-    Purpose: 
-    """
-    global log, xiaomusic
-    if type == "netease":
-        if uid:
-            api_url = f"{api_host}/user/playlist?uid={uid}"
-            # 发起请求
-            response = requests.get(api_url, timeout=5)  # 增加超时以避免长时间挂起
-            response.raise_for_status()  # 如果响应不是200,引发HTTPError异常
-            # log.info(f"getmy_playlist url:{api_url} response:{response.text}")
-
-            music_list = response.json()
-            for item in music_list['playlist']:
-                list_name = item.get("name")
-
-                log.info(f"getmy_playlist name:{list_name}")
-                # if item.get("id") in [12709941656,]:
-                songs_url = f"{api_host}/playlist/detail?id={item['id']}"
-                # 发起请求
-                response = requests.get(songs_url, timeout=5)  # 增加超时以避免长时间挂起
-                response.raise_for_status()  # 如果响应不是200,引发HTTPError异常
-                # log.info(f"getmy_playlist url:{api_url} response:{response.text}")
-                musics = response.json()
-                one_music_list = []
-                for music in musics['playlist']['tracks']:
-                    if (not music):
-                        continue
-                    # try:
-                    name = music['name']
-                    picUrl = music['al']['picUrl']
-                    artist = music['ar'][0]['name']
-                    album = music['al']['name']
-
-                    name = music.get("name")
-                    url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"
-                    if (not name) or (not url):
-                        continue
-                    xiaomusic.all_music[name] = url
-                    xiaomusic.all_music_tags[name] =  {
-                            "title": name,
-                            "artist": artist,
-                            "album": album,
-                            "year": "",
-                            "genre": "",
-                            "picture": picUrl,
-                            "lyrics": ""
-                        }
-                    
-                    one_music_list.append(name)
-                log.debug(f"getmy_playlist name:{list_name}")
-                log.debug(one_music_list)
-                # 歌曲名字相同会覆盖
-                xiaomusic.music_list[list_name] = one_music_list
-            xiaomusic.try_save_tag_cache()
-            log.debug(xiaomusic.all_music)
-            log.debug(xiaomusic.music_list)
-
-            return
-        if playlist_id:
-            songs_url = f"{api_host}/playlist/detail?id={playlist_id}"
-            # 发起请求
-            response = requests.get(songs_url, timeout=5)  # 增加超时以避免长时间挂起
-            response.raise_for_status()  # 如果响应不是200,引发HTTPError异常
-            # log.info(f"getmy_playlist url:{api_url} response:{response.text}")
-            musics = response.json()
-            list_name = musics['playlist']['name']
-            one_music_list = []
-            for music in musics['playlist']['tracks']:
-                if (not music):
-                    continue
-                # try:
-                name = music['name']
-                picUrl = music['al']['picUrl']
-                artist = music['ar'][0]['name']
-                album = music['al']['name']
-
-                name = music.get("name")
-                url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"
-                if (not name) or (not url):
-                    continue
-                xiaomusic.all_music[name] = url
-                xiaomusic.all_music_tags[name] =  {
-                        "title": name,
-                        "artist": artist,
-                        "album": album,
-                        "year": "",
-                        "genre": "",
-                        "picture": picUrl,
-                        "lyrics": ""
-                    }
-                one_music_list.append(name)
-            log.debug(f"getmy_playlist name:{list_name}")
-            log.debug(one_music_list)
-            # 歌曲名字相同会覆盖
-            xiaomusic.music_list[list_name] = one_music_list
-            xiaomusic.try_save_tag_cache()
-            log.debug(xiaomusic.all_music)
-            log.debug(xiaomusic.music_list)
-            return
-    else:
-        log.error(f"getmy_playlist type:{type} not support")

评论 9 - dissipator

等有空我写个修改歌单内容的插件示例吧。

不用拉,插件我已经写出来了 image


评论 10 - hanxi

发下你的 setting.json 配置吧,方便他人知道怎么配。


评论 11 - dissipator

我还在测试,设备到了能用了再发吧


评论 12 - guitarbug

也需要网易歌单功能, 坐等教程


评论 13 - dissipator

成功了

image

最好是在setting.json里去配置,我测试老是不匹配,在setting.json里去配置终于成功。可以直接调用 NeteaseCloudMusicApi 接口,甚至使用UnblockNeteaseMusic 解锁灰色,但是代码需要小调整。

setting.json

json
{
-  "account": "",
-  "password": "",
-  "mi_did": "",
-  "miio_tts_command": "",
-  "cookie": "",
-  "verbose": false,
-  "music_path": "music",
-  "download_path": "music/download",
-  "conf_path": "conf",
-  "cache_dir": "cache",
-  "hostname": "192.168.2.5",
-  "port": 8090,
-  "public_port": 0,
-  "proxy": "",
-  "search_prefix": "bilisearch:",
-  "ffmpeg_location": "./ffmpeg/bin",
-  "active_cmd": "play,set_random_play,playlocal,play_music_list,play_music_list_index,stop_after_minute,stop,获取歌单",
-  "exclude_dirs": "@eaDir,tmp",
-  "music_path_depth": 10,
-  "disable_httpauth": true,
-  "httpauth_username": "",
-  "httpauth_password": "",
-  "music_list_url": "",
-  "music_list_json": "",
-  "custom_play_list_json": "",
-  "disable_download": false,
-  "key_word_dict": {
-    "播放歌曲": "play",
-    "播放本地歌曲": "playlocal",
-    "关机": "stop",
-    "下一首": "play_next",
-    "上一首": "play_prev",
-    "单曲循环": "set_play_type_one",
-    "全部循环": "set_play_type_all",
-    "随机播放": "set_random_play",
-    "分钟后关机": "stop_after_minute",
-    "播放列表": "play_music_list",
-    "刷新列表": "gen_music_list",
-    "加入收藏": "add_to_favorites",
-    "收藏歌曲": "add_to_favorites",
-    "取消收藏": "del_from_favorites",
-    "播放列表第": "play_music_list_index",
-    "本地播放歌曲": "playlocal",
-    "放歌曲": "play",
-    "暂停": "stop",
-    "停止": "stop",
-    "停止播放": "stop",
-    "播放歌单": "play_music_list",
-    "测试自定义口令": "exec#code1(\\"hello\\")",
-    "测试链接": "exec#httpget(\\"https://github.com/hanxi/xiaomusic\\")",
-    "获取歌单": "exec#getmy_playlist(playlist_id=12758992225)"
-  },
-  "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": false,
-  "keywords_playlocal": "播放本地歌曲,本地播放歌曲",
-  "keywords_play": "播放歌曲,放歌曲",
-  "keywords_stop": "关机,暂停,停止,停止播放",
-  "keywords_playlist": "播放列表,播放歌单",
-  "user_key_word_dict": {
-    "测试自定义口令": "exec#code1(\\"hello\\")",
-    "测试链接": "exec#httpget(\\"https://github.com/hanxi/xiaomusic\\")",
-    "获取歌单": "exec#getmy_playlist(playlist_id=12758992225)"
-  },
-  "enable_force_stop": false,
-  "devices": {
-    " ": {
-      "did": " ",
-      "device_id": " -17c6-4204- - ",
-      "hardware": "L05C",
-      "name": "小黑你好",
-      "play_type": "",
-      "cur_music": "",
-      "cur_playlist": ""
-    }
-  },
-  "group_list": "",
-  "remove_id3tag": false,
-  "convert_to_mp3": false,
-  "delay_sec": 3,
-  "continue_play": false,
-  "pull_ask_sec": 1,
-  "crontab_json": "",
-  "enable_yt_dlp_cookies": false,
-  "get_ask_by_mina": false
-}

getmy_playlist.py

python
import requests
-
-async def getmy_playlist(type="netease",api_host="http://127.0.0.1/api", playlist_id=None,uid=None):
-    """
-    Purpose: 
-    """
-    global log, xiaomusic
-    if type == "netease":
-        if uid:
-            api_url = f"{api_host}/user/playlist?uid={uid}"
-            # 发起请求
-            response = requests.get(api_url, timeout=5) # 增加超时以避免长时间挂起
-            response.raise_for_status() # 如果响应不是200,引发HTTPError异常
-            # log.info(f"getmy_playlist url:{api_url} response:{response.text}")
-
-            music_list = response.json()
-            for item in music_list['playlist']:
-                list_name = item.get("name")
-
-                log.info(f"getmy_playlist name:{list_name}")
-                # if item.get("id") in [12709941656,]:
-                songs_url = f"{api_host}/playlist/detail?id={item['id']}"
-                # 发起请求
-                response = requests.get(songs_url, timeout=5) # 增加超时以避免长时间挂起
-                response.raise_for_status() # 如果响应不是200,引发HTTPError异常
-                # log.info(f"getmy_playlist url:{api_url} response:{response.text}")
-                musics = response.json()
-                one_music_list = []
-                for music in musics['playlist']['tracks']:
-                    if (not music):
-                        continue
-                    # try:
-                    name = music['name']
-                    picUrl = music['al']['picUrl']
-                    artist = music['ar'][0]['name']
-                    album = music['al']['name']
-
-                    name = music.get("name")
-                    url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"
-                    if (not name) or (not url):
-                        continue
-                    xiaomusic.all_music[name] = url
-                    xiaomusic.all_music_tags[name] = {
-                        "title": name,
-                        "artist": artist,
-                        "album": album,
-                        "year": "",
-                        "genre": "",
-                        "picture": picUrl,
-                        "lyrics": ""
-                        }
-
-                    one_music_list.append(name)
-                    log.debug(f"getmy_playlist name:{list_name}")
-                log.debug(one_music_list)
-                # 歌曲名字相同会覆盖
-                xiaomusic.music_list[list_name] = one_music_list
-                xiaomusic.try_save_tag_cache()
-                log.debug(xiaomusic.all_music)
-                log.debug(xiaomusic.music_list)
-            return
-        if playlist_id:
-            songs_url = f"{api_host}/playlist/detail?id={playlist_id}"
-            # 发起请求
-            response = requests.get(songs_url, timeout=5) # 增加超时以避免长时间挂起
-            response.raise_for_status() # 如果响应不是200,引发HTTPError异常
-            
-            musics = response.json()
-            list_name = musics['playlist']['name']
-            log.info(f"getmy_playlist list_name:{list_name} ")
-            one_music_list = []
-            for music in musics['playlist']['tracks']:
-                if (not music):
-                    continue
-                # try:
-                name = music['name']
-                picUrl = music['al']['picUrl']
-                artist = music['ar'][0]['name']
-                album = music['al']['name']
-
-                name = music.get("name")
-                url = f"{api_host}/song/url?id={music['id']}&br=350000&proxy=HTTP:%2F%2F127.0.0.1:8080"
-                if (not name) or (not url):
-                  continue
-                xiaomusic.all_music[name] = url
-                xiaomusic.all_music_tags[name] = {
-                    "title": name,
-                    "artist": artist,
-                    "album": album,
-                    "year": "",
-                    "genre": "",
-                    "picture": picUrl,
-                    "lyrics": ""
-                }
-                one_music_list.append(name)
-            log.debug(f"getmy_playlist name:{list_name}")
-            log.debug(one_music_list)
-            # 歌曲名字相同会覆盖
-            xiaomusic.music_list[list_name] = one_music_list
-            xiaomusic.try_save_tag_cache()
-            return
-    else:
-        log.error(f"getmy_playlist type:{type} not support")

评论 14 - dissipator

imageimage


评论 15 - dludream

NeteaseCloudMusicApi似乎获取不到播放地址?其他信息倒是有。

2024-11-28_151952


评论 16 - hanxi

看代码是另一个接口获取url的: url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"


评论 17 - dludream

看代码是另一个接口获取url的: url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"

是的,我用的是这个接口,https://registry.hub.docker.com/r/gnehs/neteasecloudmusicapi-docker/

可能网易修改了api,这些获取不了播放地址了。

我倒不是要这个功能,只是感兴趣看看。我不获取,我直接用yt把歌全下载下来播放。


评论 18 - dissipator

这个不是网易的接口,是NeteaseCloudMusicApi 接口。proxy=HTTP:%2F%2F127.0.0.1:8080"是UnblockNeteaseMusic 解锁灰色; 完整的使用方式和docker 可以到 https://github.com/dissipator/xiaomusic/tree/dev 看README.md;目前没有教程。本人就在群2,有问题可以找我。


链接到 GitHub Issue

`,77)]))}const g=i(p,[["render",l]]);export{o as __pageData,g as default}; diff --git a/docs/.vitepress/dist/assets/issues_285.md.0b7MF6MA.js b/docs/.vitepress/dist/assets/issues_285.md.0b7MF6MA.js deleted file mode 100644 index 9677341..0000000 --- a/docs/.vitepress/dist/assets/issues_285.md.0b7MF6MA.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as t,c as e,a0 as s,o}from"./chunks/framework.p2VkXzrt.js";const u=JSON.parse('{"title":"相关工具推荐","description":"","frontmatter":{"title":"相关工具推荐"},"headers":[],"relativePath":"issues/285.md","filePath":"issues/285.md","lastUpdated":null}'),i={name:"issues/285.md"};function r(l,a,h,c,p,n){return o(),e("div",null,a[0]||(a[0]=[s('

相关工具推荐

评论

评论 1 - F-loat

借楼加个小程序的图 🥳 https://github.com/F-loat/xiaoplayer

小程序码

weapp

截图


评论 2 - hanxi

@F-loat 可以在欢迎页加个链接显示小程序码。


评论 3 - F-loat

@hanxi 可以的,这样还能自动把 ip 用参数带过来,我有空搞一下


链接到 GitHub Issue

',17)]))}const m=t(i,[["render",r]]);export{u as __pageData,m as default}; diff --git a/docs/.vitepress/dist/assets/issues_285.md.0b7MF6MA.lean.js b/docs/.vitepress/dist/assets/issues_285.md.0b7MF6MA.lean.js deleted file mode 100644 index 9677341..0000000 --- a/docs/.vitepress/dist/assets/issues_285.md.0b7MF6MA.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as t,c as e,a0 as s,o}from"./chunks/framework.p2VkXzrt.js";const u=JSON.parse('{"title":"相关工具推荐","description":"","frontmatter":{"title":"相关工具推荐"},"headers":[],"relativePath":"issues/285.md","filePath":"issues/285.md","lastUpdated":null}'),i={name:"issues/285.md"};function r(l,a,h,c,p,n){return o(),e("div",null,a[0]||(a[0]=[s('

相关工具推荐

评论

评论 1 - F-loat

借楼加个小程序的图 🥳 https://github.com/F-loat/xiaoplayer

小程序码

weapp

截图


评论 2 - hanxi

@F-loat 可以在欢迎页加个链接显示小程序码。


评论 3 - F-loat

@hanxi 可以的,这样还能自动把 ip 用参数带过来,我有空搞一下


链接到 GitHub Issue

',17)]))}const m=t(i,[["render",r]]);export{u as __pageData,m as default}; diff --git a/docs/.vitepress/dist/assets/issues_294.md.CrOAwh-t.js b/docs/.vitepress/dist/assets/issues_294.md.CrOAwh-t.js deleted file mode 100644 index 41817af..0000000 --- a/docs/.vitepress/dist/assets/issues_294.md.CrOAwh-t.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as e,c as t,a0 as r,o}from"./chunks/framework.p2VkXzrt.js";const c=JSON.parse('{"title":"关于M01型号的注意事项","description":"","frontmatter":{"title":"关于M01型号的注意事项"},"headers":[],"relativePath":"issues/294.md","filePath":"issues/294.md","lastUpdated":null}'),s={name:"issues/294.md"};function i(h,a,l,n,u,p){return o(),t("div",null,a[0]||(a[0]=[r('

关于M01型号的注意事项

M01:在0.3.55版本【型号兼容模式】与【特殊型号获取对话记录】都设为false或true,都可以语音了。 如果【型号兼容模式】为 true,默认UI显示播放中,但音箱没声音。

型号:S12A、LX04、S12 在米家APP可以联动,比如客厅有人自定义指令:播放歌曲、关机...等 而M01无论【型号兼容模式】与【特殊型号获取对话记录】设为false或true,都无法执行任何自定义指令…

IMG_6460

评论

评论 1 - hanxi

M01 保持默认设置应该是能语音和播放的吧。自定义指令的功能应该是 M01 本身就不支持,可能属于放弃维护的产品吧。


评论 2 - bj803

M01 保持默认设置应该是能语音和播放的吧。自定义指令的功能应该是 M01 本身就不支持,可能属于放弃维护的产品吧。

刚试了用:pause是可以暂停(不过只暂2分钟左右又自己播放了)


评论 3 - hanxi

只能网页里点关机按钮,或者语音关机。所有型号都一样。


评论 4 - bj803

型号:S12A、LX04、S12

只能网页里点关机按钮,或者语音关机。所有型号都一样。

在型号:S12A、LX04、S12 除了能网页里点关机按钮或者语音开关机外,能在米家APP自定义指令进行播放与关机(例如当客厅客有人自定义指令"播放歌曲",无人自定义指令"关机"。M01自定义指令"pause"可以暂停,其他口令不行。


评论 5 - bj803

刚又试了一下,用自定义play stop、power off可以停止播放关机,用自定义play music可以播放音乐,可能M01不需要下划线


评论 6 - hanxi

感谢你的反馈。


链接到 GitHub Issue

',27)]))}const d=e(s,[["render",i]]);export{c as __pageData,d as default}; diff --git a/docs/.vitepress/dist/assets/issues_294.md.CrOAwh-t.lean.js b/docs/.vitepress/dist/assets/issues_294.md.CrOAwh-t.lean.js deleted file mode 100644 index 41817af..0000000 --- a/docs/.vitepress/dist/assets/issues_294.md.CrOAwh-t.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as e,c as t,a0 as r,o}from"./chunks/framework.p2VkXzrt.js";const c=JSON.parse('{"title":"关于M01型号的注意事项","description":"","frontmatter":{"title":"关于M01型号的注意事项"},"headers":[],"relativePath":"issues/294.md","filePath":"issues/294.md","lastUpdated":null}'),s={name:"issues/294.md"};function i(h,a,l,n,u,p){return o(),t("div",null,a[0]||(a[0]=[r('

关于M01型号的注意事项

M01:在0.3.55版本【型号兼容模式】与【特殊型号获取对话记录】都设为false或true,都可以语音了。 如果【型号兼容模式】为 true,默认UI显示播放中,但音箱没声音。

型号:S12A、LX04、S12 在米家APP可以联动,比如客厅有人自定义指令:播放歌曲、关机...等 而M01无论【型号兼容模式】与【特殊型号获取对话记录】设为false或true,都无法执行任何自定义指令…

IMG_6460

评论

评论 1 - hanxi

M01 保持默认设置应该是能语音和播放的吧。自定义指令的功能应该是 M01 本身就不支持,可能属于放弃维护的产品吧。


评论 2 - bj803

M01 保持默认设置应该是能语音和播放的吧。自定义指令的功能应该是 M01 本身就不支持,可能属于放弃维护的产品吧。

刚试了用:pause是可以暂停(不过只暂2分钟左右又自己播放了)


评论 3 - hanxi

只能网页里点关机按钮,或者语音关机。所有型号都一样。


评论 4 - bj803

型号:S12A、LX04、S12

只能网页里点关机按钮,或者语音关机。所有型号都一样。

在型号:S12A、LX04、S12 除了能网页里点关机按钮或者语音开关机外,能在米家APP自定义指令进行播放与关机(例如当客厅客有人自定义指令"播放歌曲",无人自定义指令"关机"。M01自定义指令"pause"可以暂停,其他口令不行。


评论 5 - bj803

刚又试了一下,用自定义play stop、power off可以停止播放关机,用自定义play music可以播放音乐,可能M01不需要下划线


评论 6 - hanxi

感谢你的反馈。


链接到 GitHub Issue

',27)]))}const d=e(s,[["render",i]]);export{c as __pageData,d as default}; diff --git a/docs/.vitepress/dist/assets/issues_297.md.CtkkBK4R.js b/docs/.vitepress/dist/assets/issues_297.md.CtkkBK4R.js deleted file mode 100644 index 83c51c8..0000000 --- a/docs/.vitepress/dist/assets/issues_297.md.CtkkBK4R.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as a,c as e,a0 as i,o as s}from"./chunks/framework.p2VkXzrt.js";const M=JSON.parse('{"title":"xiaomusic极空间安装教程(2024/12/4更新)","description":"","frontmatter":{"title":"xiaomusic极空间安装教程(2024/12/4更新)"},"headers":[],"relativePath":"issues/297.md","filePath":"issues/297.md","lastUpdated":null}'),o={name:"issues/297.md"};function c(h,t,l,r,m,n){return s(),e("div",null,t[0]||(t[0]=[i('

xiaomusic极空间安装教程(2024-12-4更新)

本教程同步更新于最新版的xiaomusic

看不懂/嫌麻烦/懒 但有点小钱,找 hanxi 预约微信或者 QQ 远程安装,他便宜,收费50一次,作法不成功不要钱

ARM架构自己想办法获取镜像 点名Z2PRO

获取镜像

科学环境:

  1. 搜索框 中输入 hanxi/xiaomusic,在搜索的结果中直接选择第一个,点击下载

图片

  1. 在新弹出的版本选择窗口中,根据你的情况选择。 图片

版本说明

  • 获取 最新版 直接点击 下载 即可,建议使用默认的 latest
  • 获取 特定版本 点击此处可查看 用于回退出现功能不兼容、恶性bug等情况,一般建议反馈开发者,修复很及时,尽量不要回退版本 请输入如 v0.3.55
  • 获取 实验版本(已修复部分bug但未推送)请输入 main
  1. 接着弹出如图所示的页面,耐心等待下载完成。 图片

  2. 下载完成后切换到 本地镜像 选项卡

剩余步骤与国内环境相同,见 部署镜像

国内环境:

  1. 打开docker,在左侧的菜单中选择 镜像 切换到 仓库 选项卡,点击 自定义拉取 按钮 图片
  2. 在弹出的对话框中输入 m.daocloud.io/docker.io/hanxi/xiaomusic ,点击 拉取 按钮 图片
  3. 下载完成后切换到 本地镜像 选项卡

部署镜像

  1. 找到刚才已经拉取好的镜像,单击选中,点击 添加到容器图片
  2. 在弹出的 创建容器 菜单中,切换到 文件夹路径 选项卡中,按图中的提示进行配置。 图片

注意:

  • 装载路径中的 配置文件目录音乐目录 必须进行配置。
  • 如有多个音乐目录,请按照下面的格式进行配置
文件/文件夹装载路径
/data/music1/app/music/music1
/data/music2/app/music/music2
  1. 切换到 端口 选项卡,修改成与你的极空间 不冲突 的本地端口号,如 5678 (示例按照本地端口号5678来进行配置,下同)

友情提醒: 尽量不要修改容器端口号,否则要到配置文件目录修改对应的setting.json文件中的配置,会增加很多麻烦

图片

  1. 切换到 环境 选项卡,将XIAOMUSIC_HOSTNAME 修改为你的 极空间的IP地址

友情提醒:

  1. 此处不可忽略,否则后续播放音乐会出现问题
  2. 不要尝试修改XIAOMUSIC_PORT!除非你没有看上一条的友情提醒
  3. 不要在此处配置ACCOUNTPASSWORD,没有过风控仍然无法使用!上古时代的教程不要再看了,容易走火入魔!

图片

  1. 点击 应用按钮,此时容器已经配置完成了,切换到左侧的 容器概况 菜单,可查看容器详情 图片

进入xiaomusic网页端进行配置

1.请关闭代理,打开浏览器,地址栏输入 极空间IP:本地端口号192.168.2.5:5678,打开网页后点击 默认主题

图片

注意:

  • 不要复制此处的地址,必须输入极空间的IP地址。不知道的建议上咸鱼50块换个不锈钢盆
  • 不要输入容器的端口号8090,极空间不能使用这个端口号。
  1. 点击 设置 按钮进入设置页面 图片

  2. 输入小米账号小米密码XIAOMUSIC_HOSTNAME(IP或域名):外网访问端口,滑到页面最下方点击 保存图片图片

注意:

  • 小米账号非手机号,请在手机设置-个人中心中查看小米ID
  • 密码不要输错,账号密码错误在上面会弹出提醒,不要假装看不见上面的提醒文字
  • XIAOMUSIC_HOSTNAME(IP或域名): 可以输入当前页面的IP地址(在地址栏),不要在此处输入端口号!!!,如果域名需要使用https协议,请加上https://

4.如果以上步骤没错,你将在设置中心看见设备列表 图片

  1. 回到首页,出现设备列表,切换对应设备即可畅享 图片

评论

评论 1 - xiaohuobanhahaha

xiaomusic.txt

截屏2024-12-05 00 43 24 an'zh 无法使用语音播放歌曲,小爱s12a。极空间z4pro。 1. 按照教程,点击播放本地歌曲,提示hostname和设置的端口映射不匹配。映射5678,容器端口8090. 2.网络host模式,能够本地播放,无法使用语音控制,提示“下载app”。日志已上传

评论 2 - 52fisher

xiaomusic.txt截屏2024-12-05 00 43 24 an'zh 无法使用语音播放歌曲,小爱s12a。极空间z4pro。 1. 按照教程,点击播放本地歌曲,提示hostname和设置的端口映射不匹配。映射5678,容器端口8090. 2.网络host模式,能够本地播放,无法使用语音控制,提示“下载app”。日志已上传

既然你映射的5678,为什么你又在那把监听端口改成11999? 我的教程里面全程没有写要修改监听端口


评论 3 - xiaohuobanhahaha

我没讲清楚。我试了两种极空间的桥接和host模式。桥接模式。我按照教程走的。报错如图 截屏2024-12-05 01 46 52截屏2024-12-05 01 49 11截屏2024-12-05 01 47 02截屏2024-12-05 01 47 15

提到的第二个问题和日志,是我将网络模式改为host的情况,能连上音箱,但是没法使用语音控制。


评论 4 - 52fisher

我没讲清楚。我试了两种极空间的桥接和host模式。桥接模式。我按照教程走的。报错如图 截屏2024-12-05 01 46 52 截屏2024-12-05 01 49 11 截屏2024-12-05 01 47 02 截屏2024-12-05 01 47 15

提到的第二个问题和日志,是我将网络模式改为host的情况,能连上音箱,但是没法使用语音控制。

把外网访问端口改成5678


评论 5 - xiaohuobanhahaha

可以连接上网页控制了,但是语音控制仍然不行。已经按照FAQ问题集合 #99 重启docker。 这是日志, xiaomusic.txt


评论 6 - 52fisher

可以连接上网页控制了,但是语音控制仍然不行。已经按照FAQ问题集合 #99 重启docker。 这是日志, xiaomusic.txt

你的设置 hostname='192.168.31.165', port=8090, public_port=5678, 后台的ip 192.168.31.143 检查一下你的地址 有可能是你的ip地址改变了


评论 7 - xiaohuobanhahaha

可以连接上网页控制了,但是语音控制仍然不行。已经按照FAQ问题集合 #99 重启docker。 这是日志, xiaomusic.txt

你的设置 hostname='192.168.31.165', port=8090, public_port=5678, 后台的ip 192.168.31.143 检查一下你的地址 有可能是你的ip地址改变了

确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。


评论 8 - 52fisher

确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。

容器的网络模式改成bridge试试呢 没解决的话你加群明天再详聊吧


评论 9 - xiaohuobanhahaha

截屏2024-12-05 02 23 53截屏2024-12-05 02 24 49

确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。

容器的网络模式改成bridge试试呢 没解决的话你加群明天再详聊吧

辛苦了,这么晚还在回复。我一直用的bridge。大佬,群号多少,不行我明天群里问吧。


评论 10 - 52fisher

截屏2024-12-05 02 23 53 截屏2024-12-05 02 24 49

确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。

容器的网络模式改成bridge试试呢 没解决的话你加群明天再详聊吧

辛苦了,这么晚还在回复。我一直用的bridge。大佬,群号多少,不行我明天群里问吧。

readme


评论 11 - xiaohuobanhahaha

截屏2024-12-05 02 23 53 截屏2024-12-05 02 24 49

确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。

容器的网络模式改成bridge试试呢 没解决的话你加群明天再详聊吧

辛苦了,这么晚还在回复。我一直用的bridge。大佬,群号多少,不行我明天群里问吧。

readme

已自查解决。问题是账号问题。绑定设备的一定是创建者,不能是管理员。


链接到 GitHub Issue

',85)]))}const d=a(o,[["render",c]]);export{M as __pageData,d as default}; diff --git a/docs/.vitepress/dist/assets/issues_297.md.CtkkBK4R.lean.js b/docs/.vitepress/dist/assets/issues_297.md.CtkkBK4R.lean.js deleted file mode 100644 index 83c51c8..0000000 --- a/docs/.vitepress/dist/assets/issues_297.md.CtkkBK4R.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as a,c as e,a0 as i,o as s}from"./chunks/framework.p2VkXzrt.js";const M=JSON.parse('{"title":"xiaomusic极空间安装教程(2024/12/4更新)","description":"","frontmatter":{"title":"xiaomusic极空间安装教程(2024/12/4更新)"},"headers":[],"relativePath":"issues/297.md","filePath":"issues/297.md","lastUpdated":null}'),o={name:"issues/297.md"};function c(h,t,l,r,m,n){return s(),e("div",null,t[0]||(t[0]=[i('

xiaomusic极空间安装教程(2024-12-4更新)

本教程同步更新于最新版的xiaomusic

看不懂/嫌麻烦/懒 但有点小钱,找 hanxi 预约微信或者 QQ 远程安装,他便宜,收费50一次,作法不成功不要钱

ARM架构自己想办法获取镜像 点名Z2PRO

获取镜像

科学环境:

  1. 搜索框 中输入 hanxi/xiaomusic,在搜索的结果中直接选择第一个,点击下载

图片

  1. 在新弹出的版本选择窗口中,根据你的情况选择。 图片

版本说明

  • 获取 最新版 直接点击 下载 即可,建议使用默认的 latest
  • 获取 特定版本 点击此处可查看 用于回退出现功能不兼容、恶性bug等情况,一般建议反馈开发者,修复很及时,尽量不要回退版本 请输入如 v0.3.55
  • 获取 实验版本(已修复部分bug但未推送)请输入 main
  1. 接着弹出如图所示的页面,耐心等待下载完成。 图片

  2. 下载完成后切换到 本地镜像 选项卡

剩余步骤与国内环境相同,见 部署镜像

国内环境:

  1. 打开docker,在左侧的菜单中选择 镜像 切换到 仓库 选项卡,点击 自定义拉取 按钮 图片
  2. 在弹出的对话框中输入 m.daocloud.io/docker.io/hanxi/xiaomusic ,点击 拉取 按钮 图片
  3. 下载完成后切换到 本地镜像 选项卡

部署镜像

  1. 找到刚才已经拉取好的镜像,单击选中,点击 添加到容器图片
  2. 在弹出的 创建容器 菜单中,切换到 文件夹路径 选项卡中,按图中的提示进行配置。 图片

注意:

  • 装载路径中的 配置文件目录音乐目录 必须进行配置。
  • 如有多个音乐目录,请按照下面的格式进行配置
文件/文件夹装载路径
/data/music1/app/music/music1
/data/music2/app/music/music2
  1. 切换到 端口 选项卡,修改成与你的极空间 不冲突 的本地端口号,如 5678 (示例按照本地端口号5678来进行配置,下同)

友情提醒: 尽量不要修改容器端口号,否则要到配置文件目录修改对应的setting.json文件中的配置,会增加很多麻烦

图片

  1. 切换到 环境 选项卡,将XIAOMUSIC_HOSTNAME 修改为你的 极空间的IP地址

友情提醒:

  1. 此处不可忽略,否则后续播放音乐会出现问题
  2. 不要尝试修改XIAOMUSIC_PORT!除非你没有看上一条的友情提醒
  3. 不要在此处配置ACCOUNTPASSWORD,没有过风控仍然无法使用!上古时代的教程不要再看了,容易走火入魔!

图片

  1. 点击 应用按钮,此时容器已经配置完成了,切换到左侧的 容器概况 菜单,可查看容器详情 图片

进入xiaomusic网页端进行配置

1.请关闭代理,打开浏览器,地址栏输入 极空间IP:本地端口号192.168.2.5:5678,打开网页后点击 默认主题

图片

注意:

  • 不要复制此处的地址,必须输入极空间的IP地址。不知道的建议上咸鱼50块换个不锈钢盆
  • 不要输入容器的端口号8090,极空间不能使用这个端口号。
  1. 点击 设置 按钮进入设置页面 图片

  2. 输入小米账号小米密码XIAOMUSIC_HOSTNAME(IP或域名):外网访问端口,滑到页面最下方点击 保存图片图片

注意:

  • 小米账号非手机号,请在手机设置-个人中心中查看小米ID
  • 密码不要输错,账号密码错误在上面会弹出提醒,不要假装看不见上面的提醒文字
  • XIAOMUSIC_HOSTNAME(IP或域名): 可以输入当前页面的IP地址(在地址栏),不要在此处输入端口号!!!,如果域名需要使用https协议,请加上https://

4.如果以上步骤没错,你将在设置中心看见设备列表 图片

  1. 回到首页,出现设备列表,切换对应设备即可畅享 图片

评论

评论 1 - xiaohuobanhahaha

xiaomusic.txt

截屏2024-12-05 00 43 24 an'zh 无法使用语音播放歌曲,小爱s12a。极空间z4pro。 1. 按照教程,点击播放本地歌曲,提示hostname和设置的端口映射不匹配。映射5678,容器端口8090. 2.网络host模式,能够本地播放,无法使用语音控制,提示“下载app”。日志已上传

评论 2 - 52fisher

xiaomusic.txt截屏2024-12-05 00 43 24 an'zh 无法使用语音播放歌曲,小爱s12a。极空间z4pro。 1. 按照教程,点击播放本地歌曲,提示hostname和设置的端口映射不匹配。映射5678,容器端口8090. 2.网络host模式,能够本地播放,无法使用语音控制,提示“下载app”。日志已上传

既然你映射的5678,为什么你又在那把监听端口改成11999? 我的教程里面全程没有写要修改监听端口


评论 3 - xiaohuobanhahaha

我没讲清楚。我试了两种极空间的桥接和host模式。桥接模式。我按照教程走的。报错如图 截屏2024-12-05 01 46 52截屏2024-12-05 01 49 11截屏2024-12-05 01 47 02截屏2024-12-05 01 47 15

提到的第二个问题和日志,是我将网络模式改为host的情况,能连上音箱,但是没法使用语音控制。


评论 4 - 52fisher

我没讲清楚。我试了两种极空间的桥接和host模式。桥接模式。我按照教程走的。报错如图 截屏2024-12-05 01 46 52 截屏2024-12-05 01 49 11 截屏2024-12-05 01 47 02 截屏2024-12-05 01 47 15

提到的第二个问题和日志,是我将网络模式改为host的情况,能连上音箱,但是没法使用语音控制。

把外网访问端口改成5678


评论 5 - xiaohuobanhahaha

可以连接上网页控制了,但是语音控制仍然不行。已经按照FAQ问题集合 #99 重启docker。 这是日志, xiaomusic.txt


评论 6 - 52fisher

可以连接上网页控制了,但是语音控制仍然不行。已经按照FAQ问题集合 #99 重启docker。 这是日志, xiaomusic.txt

你的设置 hostname='192.168.31.165', port=8090, public_port=5678, 后台的ip 192.168.31.143 检查一下你的地址 有可能是你的ip地址改变了


评论 7 - xiaohuobanhahaha

可以连接上网页控制了,但是语音控制仍然不行。已经按照FAQ问题集合 #99 重启docker。 这是日志, xiaomusic.txt

你的设置 hostname='192.168.31.165', port=8090, public_port=5678, 后台的ip 192.168.31.143 检查一下你的地址 有可能是你的ip地址改变了

确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。


评论 8 - 52fisher

确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。

容器的网络模式改成bridge试试呢 没解决的话你加群明天再详聊吧


评论 9 - xiaohuobanhahaha

截屏2024-12-05 02 23 53截屏2024-12-05 02 24 49

确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。

容器的网络模式改成bridge试试呢 没解决的话你加群明天再详聊吧

辛苦了,这么晚还在回复。我一直用的bridge。大佬,群号多少,不行我明天群里问吧。


评论 10 - 52fisher

截屏2024-12-05 02 23 53 截屏2024-12-05 02 24 49

确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。

容器的网络模式改成bridge试试呢 没解决的话你加群明天再详聊吧

辛苦了,这么晚还在回复。我一直用的bridge。大佬,群号多少,不行我明天群里问吧。

readme


评论 11 - xiaohuobanhahaha

截屏2024-12-05 02 23 53 截屏2024-12-05 02 24 49

确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。

容器的网络模式改成bridge试试呢 没解决的话你加群明天再详聊吧

辛苦了,这么晚还在回复。我一直用的bridge。大佬,群号多少,不行我明天群里问吧。

readme

已自查解决。问题是账号问题。绑定设备的一定是创建者,不能是管理员。


链接到 GitHub Issue

',85)]))}const d=a(o,[["render",c]]);export{M as __pageData,d as default}; diff --git a/docs/.vitepress/dist/assets/issues_78.md.CH6fxnfx.js b/docs/.vitepress/dist/assets/issues_78.md.CH6fxnfx.js deleted file mode 100644 index be8331e..0000000 --- a/docs/.vitepress/dist/assets/issues_78.md.CH6fxnfx.js +++ /dev/null @@ -1,29 +0,0 @@ -import{_ as t,c as s,a0 as a,o as i}from"./chunks/framework.p2VkXzrt.js";const h=JSON.parse('{"title":"已支持配置自定义网络歌单,在这里分享你的歌单","description":"","frontmatter":{"title":"已支持配置自定义网络歌单,在这里分享你的歌单"},"headers":[],"relativePath":"issues/78.md","filePath":"issues/78.md","lastUpdated":null}'),r={name:"issues/78.md"};function p(n,e,o,l,u,d){return i(),s("div",null,e[0]||(e[0]=[a(`

已支持配置自定义网络歌单,在这里分享你的歌单

设置页面新增一个输入框配置json格式,可以定义配置音乐源,可以是电台或者其他的m3u8格式的。 再加一个输入框配置这个json文件的url,点击获取按钮把url对应的json内容填充到json输入框,方便直接使用别人分享的歌单。

比如这样的链接

已经测试能播放出来:

python3 micli.py play http://ngcdn001.cnr.cn/live/zgzs/index.m3u8

预计歌单格式是这样的, type 为 radio 作为电台的设定,会一直播放当前电台,不会播放下一首。

json
[
-  {
-    "name":"歌单1",
-    "musics":[
-      {
-        "name":"歌名1",
-        "url":"http://ngcdn001.cnr.cn/live/zgzs/index.m3u8",
-        "type":"radio"
-      },
-      {
-        "name":"歌名2",
-        "url":"https://lhttp.qtfm.cn/live/4915/64k.mp3"
-      }
-    ]
-  },
-  {
-    "name":"歌单2",
-    "musics":[
-      {
-        "name":"歌名3",
-        "url":"https://lhttp.qtfm.cn/live/4915/64k.mp3"
-      },
-      {
-        "name":"歌名4",
-        "url":"https://lhttp.qtfm.cn/live/4915/64k.mp3"
-      }
-    ]
-  }
-]

这里分享一个让 chatgpt 写 python 脚本来生成歌单的例子 https://chatgpt.com/share/6751c019-74c0-800a-a978-a20c636d4464

评论

评论 1 - hanxi

可以使用 gist 来配置和分享 json 文件,比如 https://gist.github.com/hanxi/dda82d964a28f8110f8fba81c3ff8314

点击 raw 得到 json 文件的链接 https://gist.githubusercontent.com/hanxi/dda82d964a28f8110f8fba81c3ff8314/raw/8787844d81c39dbfaad4e37954dd449d8bba5728/example.json

当然还可以用其他工具分享json文件,比如 github 和国内的 gitee 。


评论 2 - hanxi

已经有工具支持将 m3u 格式的电台文件转为网络歌单格式,见 /issues/88.html

欢迎有兴趣的朋友制作其他格式转换工具,比如网易歌单那一类的。


评论 3 - lazybabyz

按照教程设置后 播放列表选择m3u电台 再选择播放列表 歌曲,结果显示播放中 不断在转台 stdout: [08:58:02] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /static/default/index.html HTTP/1.1" 304 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /getsetting HTTP/1.1" 200 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24022 - "GET /getversion HTTP/1.1" 200 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /musiclist HTTP/1.1" 200 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24021 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /static/default/curplaylist?did=566731712 HTTP/1.1" 404 stdout: [08:58:05] [0.3.37] [INFO] 10.0.80.191:24022 - "GET /getvolume?did=566731712 HTTP/1.1" 500 stderr: [08:58:05] [0.3.37] [ERROR] Exception in ASGI application stderr: Traceback (most recent call last): stderr: File "/app/.venv/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 406, in run_asgi stderr: result = await app( # type: ignore[func-returns-value] stderr: File "/app/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 70, in call stderr: return await self.app(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/applications.py", line 1054, in call stderr: await super().call(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/applications.py", line 113, in call stderr: await self.middleware_stack(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 187, in call stderr: raise exc stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 165, in call stderr: await self.app(scope, receive, _send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/cors.py", line 85, in call stderr: await self.app(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 62, in call stderr: await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 62, in wrapped_app stderr: raise exc stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 51, in wrapped_app stderr: await app(scope, receive, sender) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 715, in call stderr: await self.middleware_stack(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 735, in app stderr: await route.handle(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 288, in handle stderr: await self.app(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 76, in app stderr: await wrap_app_handling_exceptions(app, request)(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 62, in wrapped_app stderr: raise exc stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 51, in wrapped_app stderr: await app(scope, receive, sender) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 73, in app stderr: response = await f(request) stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 301, in app stderr: raw_response = await run_endpoint_function( stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 212, in run_endpoint_function stderr: return await dependant.call(**values) stderr: File "/app/xiaomusic/httpserver.py", line 124, in getvolume stderr: volume = await xiaomusic.get_volume(did=did) stderr: File "/app/xiaomusic/xiaomusic.py", line 809, in get_volume stderr: return await self.devices[did].get_volume() stderr: File "/app/xiaomusic/xiaomusic.py", line 1400, in get_volume stderr: playing_info = await self.xiaomusic.mina_service.player_get_status( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 103, in player_get_status stderr: return await self.ubus_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 59, in ubus_request stderr: result = await self.mina_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request stderr: return await self.account.mi_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request stderr: raise Exception(f"Error {url}: {resp}") stderr: Exception: Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_YfG6nlPVAtwDHEgMeJXxcOiZQLrR57'}} stderr: [08:58:05] [0.3.37] [ERROR] h11_impl.py:411: Exception in ASGI application stderr: Traceback (most recent call last): stderr: File "/app/.venv/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 406, in run_asgi stderr: result = await app( # type: ignore[func-returns-value] stderr: File "/app/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 70, in call stderr: return await self.app(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/applications.py", line 1054, in call stderr: await super().call(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/applications.py", line 113, in call stderr: await self.middleware_stack(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 187, in call stderr: raise exc stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 165, in call stderr: await self.app(scope, receive, _send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/cors.py", line 85, in call stderr: await self.app(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 62, in call stderr: await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 62, in wrapped_app stderr: raise exc stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 51, in wrapped_app stderr: await app(scope, receive, sender) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 715, in call stderr: await self.middleware_stack(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 735, in app stderr: await route.handle(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 288, in handle stderr: await self.app(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 76, in app stderr: await wrap_app_handling_exceptions(app, request)(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 62, in wrapped_app stderr: raise exc stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 51, in wrapped_app stderr: await app(scope, receive, sender) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 73, in app stderr: response = await f(request) stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 301, in app stderr: raw_response = await run_endpoint_function( stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 212, in run_endpoint_function stderr: return await dependant.call(**values) stderr: File "/app/xiaomusic/httpserver.py", line 124, in getvolume stderr: volume = await xiaomusic.get_volume(did=did) stderr: File "/app/xiaomusic/xiaomusic.py", line 809, in get_volume stderr: return await self.devices[did].get_volume() stderr: File "/app/xiaomusic/xiaomusic.py", line 1400, in get_volume stderr: playing_info = await self.xiaomusic.mina_service.player_get_status( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 103, in player_get_status stderr: return await self.ubus_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 59, in ubus_request stderr: result = await self.mina_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request stderr: return await self.account.mi_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request stderr: raise Exception(f"Error {url}: {resp}") stderr: Exception: Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_YfG6nlPVAtwDHEgMeJXxcOiZQLrR57'}} stdout: [08:58:05] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:05] [0.3.37] [INFO] 10.0.80.191:24021 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:07] [0.3.37] [INFO] 10.0.80.191:24021 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:08] [0.3.37] [INFO] 10.0.80.191:24021 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:08] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stderr: [08:58:09] [0.3.37] [INFO] httpserver.py:177: docmd. did:566731712 cmd:播放列表m3u电台|80后音悦台 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:582: cancel_all_tasks no task stdout: [08:58:09] [0.3.37] [INFO] 10.0.80.191:24020 - "POST /cmd HTTP/1.1" 200 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:560: 收到消息:播放列表m3u电台|80后音悦台 控制面板:True did:566731712 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:648: 匹配到指令. opkey:播放列表 opvalue:play_music_list oparg:m3u电台|80后音悦台 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:716: 根据【m3u电台】找到播放列表【m3u电台】 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:991: 没打乱 m3u电台 ['80后音悦台', 'BBC Radio 1', 'BBC Radio 2', 'BBC Radio 3', 'BBC Radio 4', 'CNA938', 'FM 988', ' AFO Radio', 'BBC News', 'BBC Radio 1 Dance', 'BBC Radio 1 Relax', 'BBC Radio 1Xtra', 'BBC Radio 4 Extra', 'BBC Radio 5 Live', 'BBC Radio 6 Music', 'BCC中广新闻', 'BCC中广流行', 'BCC中广音乐', 'CNA亚洲新闻', 'CNR中国之声', 'CNR乡村之声', 'CNR交通广播', 'CNR台海之声', 'CNR文艺之声', 'CNR湾区之声', 'CNR神州之声', 'CNR经典音乐', 'CNR经济之声', 'CNR老年之声', 'CNR阅读之声', 'CNR音乐之声', 'CRI世界华声', 'CRI华语环球', 'CRI南海之声', 'CRI环球资讯', 'CRI英语资讯', 'CRI轻松调频', 'Capital FM', 'CityFM台之音', 'Cool Radio', 'Gold FM', 'Hit FM劲曲', 'Kiss FM', 'LBC News', 'LBC UK', 'Love FM', 'Love Radio', 'Money FM', 'NPR News', 'News Radio UK', 'One FM', 'Power FM', 'RFA', 'RFI', 'RTI中央广播', 'Times Radio', 'VOA环球英语', 'Yes FM', '上海故事广播', '上海流行音乐', '上海音乐广播', '上海音乐电台', '中文流行', '全国广播', '北京交通广播', '北京体育广播', '北京城市广播', '北京文艺广播', '北京新闻广播', '北京经典调频', '北京音乐广播', '南京音乐广播', '台中广播电台', '四川文艺广播', '国乐悠扬', '天籁之音 Hi-Fi', '天籁古典', '天籁国风', '安全百科', '山西文艺广播', '广东音乐之声', '广州汽车音乐', '摇滚天空', '有声文摘', '民谣蓝调', '民谣音乐台', '江苏故事广播', '江苏文艺广播', '江苏音乐广播', '河北文艺广播', '河北汽车音乐', '深圳音乐广播', '湖南文艺广播', '湖南旅游广播', '湖南音乐之声', '潮流音悦台', '经典FM', '美国之音', '陕西故事广播', '陕西音乐广播'] stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1422: 开始播放列表m3u电台 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1005: play. search_key: name:80后音悦台 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:663: 根据【80后音悦台】找到歌曲【80后音悦台】 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1088: cur_music 80后音悦台 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:388: get_music_url web music. name:80后音悦台, url:http://stream3.hndt.com/now/SFZeH2cb/playlist.m3u8 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:360: get_music_sec_url. name:80后音悦台 url:http://stream3.hndt.com/now/SFZeH2cb/playlist.m3u8 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:362: 电台不会有播放时长 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1437: group_force_stop_xiaoai ['be1daeaa-df03-4a27-8aff-404356abfa9a'] stderr: [08:58:09] [0.3.37] [ERROR] xiaomusic.py:1137: Execption Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_10DxZOintrGWhyETo4q9auU3VMNH6A'}} stderr: Traceback (most recent call last): stderr: File "/app/xiaomusic/xiaomusic.py", line 1131, in force_stop_xiaoai stderr: ret = await self.xiaomusic.mina_service.player_pause(device_id) stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 79, in player_pause stderr: return await self.ubus_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 59, in ubus_request stderr: result = await self.mina_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request stderr: return await self.account.mi_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request stderr: raise Exception(f"Error {url}: {resp}") stderr: Exception: Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_10DxZOintrGWhyETo4q9auU3VMNH6A'}} stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1440: group_force_stop_xiaoai ['be1daeaa-df03-4a27-8aff-404356abfa9a'] [None] stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1091: 播放 http://stream3.hndt.com/now/SFZeH2cb/playlist.m3u8 stderr: [08:58:10] [0.3.37] [ERROR] xiaomusic.py:1332: Execption Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_Bbkf4HLQiN7tjl1rGD2gpXuJ8yI0nP'}} stderr: Traceback (most recent call last): stderr: File "/app/xiaomusic/xiaomusic.py", line 1327, in play_one_url stderr: ret = await self.xiaomusic.mina_service.play_by_url(device_id, url) stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 125, in play_by_url stderr: return await self.ubus_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 59, in ubus_request stderr: result = await self.mina_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request stderr: return await self.account.mi_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request stderr: raise Exception(f"Error {url}: {resp}") stderr: Exception: Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_Bbkf4HLQiN7tjl1rGD2gpXuJ8yI0nP'}} stderr: [08:58:10] [0.3.37] [INFO] xiaomusic.py:1305: group_player_play http://stream3.hndt.com/now/SFZeH2cb/playlist.m3u8 ['be1daeaa-df03-4a27-8aff-404356abfa9a'] [None] stderr: [08:58:10] [0.3.37] [INFO] xiaomusic.py:1094: 播放 80后音悦台 失败


评论 4 - hanxi

设备掉线了


评论 5 - 201692929

怎么获取 他正在播放什么?或者是播放进度 ?播放列表?我想给他加进去 233333


评论 6 - hanxi

怎么获取 他正在播放什么?或者是播放进度 ?播放列表?我想给他加进去 233333

这个接口 /playingmusic


评论 7 - 114514thD

加不加"type":"radio"都会一直播放不切换到下一首歌是为什么呢


评论 8 - hanxi

加不加"type":"radio"都会一直播放不切换到下一首歌是为什么呢

发出来看看?


评论 9 - 114514thD

加不加"type":"radio"都会一直播放不切换到下一首歌是为什么呢

发出来看看?

本地开服务器,生成的m3u列表 格式如下 #EXTINF:247,周传雄 - 临别一眼.mp3 http://192.168.1.147:8000/%E5%91%A8%E4%BC%A0%E9%9B%84%20-%20%E4%B8%B4%E5%88%AB%E4%B8%80%E7%9C%BC.mp3 包含了时长信息 版本是0.3.46 potplayer里播放完全正常

仔细研究了一下,发现确实存在问题,不过是另一种情况,下面单说


评论 10 - 114514thD

加不加"type":"radio"都会一直播放不切换到下一首歌是为什么呢

发出来看看?

经过实验发现,本地生成的m3u用potplayer播放正常 image 转换为json(去掉"type":"radio")后用小爱播放也正常 image

但是alist链接就不正常,alist生成的m3u格式如下 #EXTM3U #EXTINF:-1,Let Me Hear.mp3 http://192.168.1.198:5244/d/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90%20%E9%9F%B3%E4%B9%90%E4%BA%91%E7%9B%98/Let%20Me%20Hear.mp3?sign=xxxx=:0 没有时长信息,但是用potplayer一播放就出现时长了 image 而用小爱播放就始终没有时长(切歌、等待都试过了) image 大佬你的示例链接(gist.github.com/hanxi/dda82d964a28f8110f8fba81c3ff8314)里的又是正常的,感觉可能是alist的流比较特殊。。 image


链接到 GitHub Issue

`,50)]))}const m=t(r,[["render",p]]);export{h as __pageData,m as default}; diff --git a/docs/.vitepress/dist/assets/issues_78.md.CH6fxnfx.lean.js b/docs/.vitepress/dist/assets/issues_78.md.CH6fxnfx.lean.js deleted file mode 100644 index be8331e..0000000 --- a/docs/.vitepress/dist/assets/issues_78.md.CH6fxnfx.lean.js +++ /dev/null @@ -1,29 +0,0 @@ -import{_ as t,c as s,a0 as a,o as i}from"./chunks/framework.p2VkXzrt.js";const h=JSON.parse('{"title":"已支持配置自定义网络歌单,在这里分享你的歌单","description":"","frontmatter":{"title":"已支持配置自定义网络歌单,在这里分享你的歌单"},"headers":[],"relativePath":"issues/78.md","filePath":"issues/78.md","lastUpdated":null}'),r={name:"issues/78.md"};function p(n,e,o,l,u,d){return i(),s("div",null,e[0]||(e[0]=[a(`

已支持配置自定义网络歌单,在这里分享你的歌单

设置页面新增一个输入框配置json格式,可以定义配置音乐源,可以是电台或者其他的m3u8格式的。 再加一个输入框配置这个json文件的url,点击获取按钮把url对应的json内容填充到json输入框,方便直接使用别人分享的歌单。

比如这样的链接

已经测试能播放出来:

python3 micli.py play http://ngcdn001.cnr.cn/live/zgzs/index.m3u8

预计歌单格式是这样的, type 为 radio 作为电台的设定,会一直播放当前电台,不会播放下一首。

json
[
-  {
-    "name":"歌单1",
-    "musics":[
-      {
-        "name":"歌名1",
-        "url":"http://ngcdn001.cnr.cn/live/zgzs/index.m3u8",
-        "type":"radio"
-      },
-      {
-        "name":"歌名2",
-        "url":"https://lhttp.qtfm.cn/live/4915/64k.mp3"
-      }
-    ]
-  },
-  {
-    "name":"歌单2",
-    "musics":[
-      {
-        "name":"歌名3",
-        "url":"https://lhttp.qtfm.cn/live/4915/64k.mp3"
-      },
-      {
-        "name":"歌名4",
-        "url":"https://lhttp.qtfm.cn/live/4915/64k.mp3"
-      }
-    ]
-  }
-]

这里分享一个让 chatgpt 写 python 脚本来生成歌单的例子 https://chatgpt.com/share/6751c019-74c0-800a-a978-a20c636d4464

评论

评论 1 - hanxi

可以使用 gist 来配置和分享 json 文件,比如 https://gist.github.com/hanxi/dda82d964a28f8110f8fba81c3ff8314

点击 raw 得到 json 文件的链接 https://gist.githubusercontent.com/hanxi/dda82d964a28f8110f8fba81c3ff8314/raw/8787844d81c39dbfaad4e37954dd449d8bba5728/example.json

当然还可以用其他工具分享json文件,比如 github 和国内的 gitee 。


评论 2 - hanxi

已经有工具支持将 m3u 格式的电台文件转为网络歌单格式,见 /issues/88.html

欢迎有兴趣的朋友制作其他格式转换工具,比如网易歌单那一类的。


评论 3 - lazybabyz

按照教程设置后 播放列表选择m3u电台 再选择播放列表 歌曲,结果显示播放中 不断在转台 stdout: [08:58:02] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /static/default/index.html HTTP/1.1" 304 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /getsetting HTTP/1.1" 200 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24022 - "GET /getversion HTTP/1.1" 200 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /musiclist HTTP/1.1" 200 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24021 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /static/default/curplaylist?did=566731712 HTTP/1.1" 404 stdout: [08:58:05] [0.3.37] [INFO] 10.0.80.191:24022 - "GET /getvolume?did=566731712 HTTP/1.1" 500 stderr: [08:58:05] [0.3.37] [ERROR] Exception in ASGI application stderr: Traceback (most recent call last): stderr: File "/app/.venv/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 406, in run_asgi stderr: result = await app( # type: ignore[func-returns-value] stderr: File "/app/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 70, in call stderr: return await self.app(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/applications.py", line 1054, in call stderr: await super().call(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/applications.py", line 113, in call stderr: await self.middleware_stack(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 187, in call stderr: raise exc stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 165, in call stderr: await self.app(scope, receive, _send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/cors.py", line 85, in call stderr: await self.app(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 62, in call stderr: await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 62, in wrapped_app stderr: raise exc stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 51, in wrapped_app stderr: await app(scope, receive, sender) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 715, in call stderr: await self.middleware_stack(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 735, in app stderr: await route.handle(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 288, in handle stderr: await self.app(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 76, in app stderr: await wrap_app_handling_exceptions(app, request)(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 62, in wrapped_app stderr: raise exc stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 51, in wrapped_app stderr: await app(scope, receive, sender) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 73, in app stderr: response = await f(request) stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 301, in app stderr: raw_response = await run_endpoint_function( stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 212, in run_endpoint_function stderr: return await dependant.call(**values) stderr: File "/app/xiaomusic/httpserver.py", line 124, in getvolume stderr: volume = await xiaomusic.get_volume(did=did) stderr: File "/app/xiaomusic/xiaomusic.py", line 809, in get_volume stderr: return await self.devices[did].get_volume() stderr: File "/app/xiaomusic/xiaomusic.py", line 1400, in get_volume stderr: playing_info = await self.xiaomusic.mina_service.player_get_status( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 103, in player_get_status stderr: return await self.ubus_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 59, in ubus_request stderr: result = await self.mina_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request stderr: return await self.account.mi_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request stderr: raise Exception(f"Error {url}: {resp}") stderr: Exception: Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_YfG6nlPVAtwDHEgMeJXxcOiZQLrR57'}} stderr: [08:58:05] [0.3.37] [ERROR] h11_impl.py:411: Exception in ASGI application stderr: Traceback (most recent call last): stderr: File "/app/.venv/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 406, in run_asgi stderr: result = await app( # type: ignore[func-returns-value] stderr: File "/app/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 70, in call stderr: return await self.app(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/applications.py", line 1054, in call stderr: await super().call(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/applications.py", line 113, in call stderr: await self.middleware_stack(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 187, in call stderr: raise exc stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 165, in call stderr: await self.app(scope, receive, _send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/cors.py", line 85, in call stderr: await self.app(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 62, in call stderr: await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 62, in wrapped_app stderr: raise exc stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 51, in wrapped_app stderr: await app(scope, receive, sender) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 715, in call stderr: await self.middleware_stack(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 735, in app stderr: await route.handle(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 288, in handle stderr: await self.app(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 76, in app stderr: await wrap_app_handling_exceptions(app, request)(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 62, in wrapped_app stderr: raise exc stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 51, in wrapped_app stderr: await app(scope, receive, sender) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 73, in app stderr: response = await f(request) stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 301, in app stderr: raw_response = await run_endpoint_function( stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 212, in run_endpoint_function stderr: return await dependant.call(**values) stderr: File "/app/xiaomusic/httpserver.py", line 124, in getvolume stderr: volume = await xiaomusic.get_volume(did=did) stderr: File "/app/xiaomusic/xiaomusic.py", line 809, in get_volume stderr: return await self.devices[did].get_volume() stderr: File "/app/xiaomusic/xiaomusic.py", line 1400, in get_volume stderr: playing_info = await self.xiaomusic.mina_service.player_get_status( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 103, in player_get_status stderr: return await self.ubus_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 59, in ubus_request stderr: result = await self.mina_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request stderr: return await self.account.mi_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request stderr: raise Exception(f"Error {url}: {resp}") stderr: Exception: Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_YfG6nlPVAtwDHEgMeJXxcOiZQLrR57'}} stdout: [08:58:05] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:05] [0.3.37] [INFO] 10.0.80.191:24021 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:07] [0.3.37] [INFO] 10.0.80.191:24021 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:08] [0.3.37] [INFO] 10.0.80.191:24021 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:08] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stderr: [08:58:09] [0.3.37] [INFO] httpserver.py:177: docmd. did:566731712 cmd:播放列表m3u电台|80后音悦台 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:582: cancel_all_tasks no task stdout: [08:58:09] [0.3.37] [INFO] 10.0.80.191:24020 - "POST /cmd HTTP/1.1" 200 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:560: 收到消息:播放列表m3u电台|80后音悦台 控制面板:True did:566731712 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:648: 匹配到指令. opkey:播放列表 opvalue:play_music_list oparg:m3u电台|80后音悦台 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:716: 根据【m3u电台】找到播放列表【m3u电台】 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:991: 没打乱 m3u电台 ['80后音悦台', 'BBC Radio 1', 'BBC Radio 2', 'BBC Radio 3', 'BBC Radio 4', 'CNA938', 'FM 988', ' AFO Radio', 'BBC News', 'BBC Radio 1 Dance', 'BBC Radio 1 Relax', 'BBC Radio 1Xtra', 'BBC Radio 4 Extra', 'BBC Radio 5 Live', 'BBC Radio 6 Music', 'BCC中广新闻', 'BCC中广流行', 'BCC中广音乐', 'CNA亚洲新闻', 'CNR中国之声', 'CNR乡村之声', 'CNR交通广播', 'CNR台海之声', 'CNR文艺之声', 'CNR湾区之声', 'CNR神州之声', 'CNR经典音乐', 'CNR经济之声', 'CNR老年之声', 'CNR阅读之声', 'CNR音乐之声', 'CRI世界华声', 'CRI华语环球', 'CRI南海之声', 'CRI环球资讯', 'CRI英语资讯', 'CRI轻松调频', 'Capital FM', 'CityFM台之音', 'Cool Radio', 'Gold FM', 'Hit FM劲曲', 'Kiss FM', 'LBC News', 'LBC UK', 'Love FM', 'Love Radio', 'Money FM', 'NPR News', 'News Radio UK', 'One FM', 'Power FM', 'RFA', 'RFI', 'RTI中央广播', 'Times Radio', 'VOA环球英语', 'Yes FM', '上海故事广播', '上海流行音乐', '上海音乐广播', '上海音乐电台', '中文流行', '全国广播', '北京交通广播', '北京体育广播', '北京城市广播', '北京文艺广播', '北京新闻广播', '北京经典调频', '北京音乐广播', '南京音乐广播', '台中广播电台', '四川文艺广播', '国乐悠扬', '天籁之音 Hi-Fi', '天籁古典', '天籁国风', '安全百科', '山西文艺广播', '广东音乐之声', '广州汽车音乐', '摇滚天空', '有声文摘', '民谣蓝调', '民谣音乐台', '江苏故事广播', '江苏文艺广播', '江苏音乐广播', '河北文艺广播', '河北汽车音乐', '深圳音乐广播', '湖南文艺广播', '湖南旅游广播', '湖南音乐之声', '潮流音悦台', '经典FM', '美国之音', '陕西故事广播', '陕西音乐广播'] stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1422: 开始播放列表m3u电台 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1005: play. search_key: name:80后音悦台 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:663: 根据【80后音悦台】找到歌曲【80后音悦台】 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1088: cur_music 80后音悦台 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:388: get_music_url web music. name:80后音悦台, url:http://stream3.hndt.com/now/SFZeH2cb/playlist.m3u8 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:360: get_music_sec_url. name:80后音悦台 url:http://stream3.hndt.com/now/SFZeH2cb/playlist.m3u8 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:362: 电台不会有播放时长 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1437: group_force_stop_xiaoai ['be1daeaa-df03-4a27-8aff-404356abfa9a'] stderr: [08:58:09] [0.3.37] [ERROR] xiaomusic.py:1137: Execption Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_10DxZOintrGWhyETo4q9auU3VMNH6A'}} stderr: Traceback (most recent call last): stderr: File "/app/xiaomusic/xiaomusic.py", line 1131, in force_stop_xiaoai stderr: ret = await self.xiaomusic.mina_service.player_pause(device_id) stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 79, in player_pause stderr: return await self.ubus_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 59, in ubus_request stderr: result = await self.mina_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request stderr: return await self.account.mi_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request stderr: raise Exception(f"Error {url}: {resp}") stderr: Exception: Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_10DxZOintrGWhyETo4q9auU3VMNH6A'}} stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1440: group_force_stop_xiaoai ['be1daeaa-df03-4a27-8aff-404356abfa9a'] [None] stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1091: 播放 http://stream3.hndt.com/now/SFZeH2cb/playlist.m3u8 stderr: [08:58:10] [0.3.37] [ERROR] xiaomusic.py:1332: Execption Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_Bbkf4HLQiN7tjl1rGD2gpXuJ8yI0nP'}} stderr: Traceback (most recent call last): stderr: File "/app/xiaomusic/xiaomusic.py", line 1327, in play_one_url stderr: ret = await self.xiaomusic.mina_service.play_by_url(device_id, url) stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 125, in play_by_url stderr: return await self.ubus_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 59, in ubus_request stderr: result = await self.mina_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request stderr: return await self.account.mi_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request stderr: raise Exception(f"Error {url}: {resp}") stderr: Exception: Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_Bbkf4HLQiN7tjl1rGD2gpXuJ8yI0nP'}} stderr: [08:58:10] [0.3.37] [INFO] xiaomusic.py:1305: group_player_play http://stream3.hndt.com/now/SFZeH2cb/playlist.m3u8 ['be1daeaa-df03-4a27-8aff-404356abfa9a'] [None] stderr: [08:58:10] [0.3.37] [INFO] xiaomusic.py:1094: 播放 80后音悦台 失败


评论 4 - hanxi

设备掉线了


评论 5 - 201692929

怎么获取 他正在播放什么?或者是播放进度 ?播放列表?我想给他加进去 233333


评论 6 - hanxi

怎么获取 他正在播放什么?或者是播放进度 ?播放列表?我想给他加进去 233333

这个接口 /playingmusic


评论 7 - 114514thD

加不加"type":"radio"都会一直播放不切换到下一首歌是为什么呢


评论 8 - hanxi

加不加"type":"radio"都会一直播放不切换到下一首歌是为什么呢

发出来看看?


评论 9 - 114514thD

加不加"type":"radio"都会一直播放不切换到下一首歌是为什么呢

发出来看看?

本地开服务器,生成的m3u列表 格式如下 #EXTINF:247,周传雄 - 临别一眼.mp3 http://192.168.1.147:8000/%E5%91%A8%E4%BC%A0%E9%9B%84%20-%20%E4%B8%B4%E5%88%AB%E4%B8%80%E7%9C%BC.mp3 包含了时长信息 版本是0.3.46 potplayer里播放完全正常

仔细研究了一下,发现确实存在问题,不过是另一种情况,下面单说


评论 10 - 114514thD

加不加"type":"radio"都会一直播放不切换到下一首歌是为什么呢

发出来看看?

经过实验发现,本地生成的m3u用potplayer播放正常 image 转换为json(去掉"type":"radio")后用小爱播放也正常 image

但是alist链接就不正常,alist生成的m3u格式如下 #EXTM3U #EXTINF:-1,Let Me Hear.mp3 http://192.168.1.198:5244/d/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90%20%E9%9F%B3%E4%B9%90%E4%BA%91%E7%9B%98/Let%20Me%20Hear.mp3?sign=xxxx=:0 没有时长信息,但是用potplayer一播放就出现时长了 image 而用小爱播放就始终没有时长(切歌、等待都试过了) image 大佬你的示例链接(gist.github.com/hanxi/dda82d964a28f8110f8fba81c3ff8314)里的又是正常的,感觉可能是alist的流比较特殊。。 image


链接到 GitHub Issue

`,50)]))}const m=t(r,[["render",p]]);export{h as __pageData,m as default}; diff --git a/docs/.vitepress/dist/assets/issues_88.md.VOyx9x2b.js b/docs/.vitepress/dist/assets/issues_88.md.VOyx9x2b.js deleted file mode 100644 index 9bd803f..0000000 --- a/docs/.vitepress/dist/assets/issues_88.md.VOyx9x2b.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as t,c as e,a0 as i,o}from"./chunks/framework.p2VkXzrt.js";const p=JSON.parse('{"title":"如何添加m3u格式文件的电台","description":"","frontmatter":{"title":"如何添加m3u格式文件的电台"},"headers":[],"relativePath":"issues/88.md","filePath":"issues/88.md","lastUpdated":null}'),r={name:"issues/88.md"};function s(h,a,l,u,m,n){return o(),e("div",null,a[0]||(a[0]=[i('

如何添加m3u格式文件的电台

比如可以找到这样的 m3u 电台文件: https://github.com/YueChan/Live/blob/main/Radio.m3u

  1. 复制文件内容,粘贴到 m3u 转换工具里,点击转换为 json 格式:

Screenshot_2024-06-29-11-28-58-904_com android chrome

  1. 然后复制 json 内容,粘贴到歌单内容里,点击保存,再返回首页:

Screenshot_2024-06-29-11-29-22-248_com android chrome

  1. 在首页点击刷新列表,选择所有电台,再点击播放列表歌曲:

Screenshot_2024-06-29-11-29-55-621_com android chrome

  1. 也可以用口令播放电台: 播放列表所有电台 ,或者口令: 播放歌曲北京城市广播

评论

评论 1 - guoxiangke

转换m3u链接: http://127.0.0.1:8090/static/m3u.html


评论 2 - guoxiangke

http://127.0.0.1:8090/playurl?did=mydid&url=https://a.com/test.m3u 如果能支持 这么播放m3u 就太完美了 希望能够支持,谢谢作者。


评论 3 - hanxi

http://127.0.0.1:8090/playurl?did=mydid&url=https://a.com/test.m3u 如果能支持 这么播放m3u 就太完美了 希望能够支持,谢谢作者。

不想破坏现有接口,可以考虑用插件的方式来实现。


评论 4 - lazybabyz

potplayer 测试 https://raw.githubusercontent.com/YueChan/Live/refs/heads/main/Radio.m3u 部分可以听 xiaomusic测试 https://github.com/YueChan/Live/blob/main/Radio.m3u 复制raw文件转换 全部失败不停转台

以下potplayer测试 可以听,xiaomusic测试 复制raw文件转换 全部失败不停转台

https://raw.githubusercontent.com/kaige-cai/live/refs/heads/main/radio.m3uhttps://raw.githubusercontent.com/imDazui/Tvlist-awesome-m3u-m3u8/master/m3u/广播电台2021.m3u


评论 5 - hanxi

检查一下是不是 ipv6 的地址?小米音箱不支持 ipv6


评论 6 - lazybabyz

检查一下是不是 ipv6 的地址?小米音箱不支持 ipv6

是我硬件设置的问题 重新安装了 解决!


链接到 GitHub Issue

',33)]))}const d=t(r,[["render",s]]);export{p as __pageData,d as default}; diff --git a/docs/.vitepress/dist/assets/issues_88.md.VOyx9x2b.lean.js b/docs/.vitepress/dist/assets/issues_88.md.VOyx9x2b.lean.js deleted file mode 100644 index 9bd803f..0000000 --- a/docs/.vitepress/dist/assets/issues_88.md.VOyx9x2b.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as t,c as e,a0 as i,o}from"./chunks/framework.p2VkXzrt.js";const p=JSON.parse('{"title":"如何添加m3u格式文件的电台","description":"","frontmatter":{"title":"如何添加m3u格式文件的电台"},"headers":[],"relativePath":"issues/88.md","filePath":"issues/88.md","lastUpdated":null}'),r={name:"issues/88.md"};function s(h,a,l,u,m,n){return o(),e("div",null,a[0]||(a[0]=[i('

如何添加m3u格式文件的电台

比如可以找到这样的 m3u 电台文件: https://github.com/YueChan/Live/blob/main/Radio.m3u

  1. 复制文件内容,粘贴到 m3u 转换工具里,点击转换为 json 格式:

Screenshot_2024-06-29-11-28-58-904_com android chrome

  1. 然后复制 json 内容,粘贴到歌单内容里,点击保存,再返回首页:

Screenshot_2024-06-29-11-29-22-248_com android chrome

  1. 在首页点击刷新列表,选择所有电台,再点击播放列表歌曲:

Screenshot_2024-06-29-11-29-55-621_com android chrome

  1. 也可以用口令播放电台: 播放列表所有电台 ,或者口令: 播放歌曲北京城市广播

评论

评论 1 - guoxiangke

转换m3u链接: http://127.0.0.1:8090/static/m3u.html


评论 2 - guoxiangke

http://127.0.0.1:8090/playurl?did=mydid&url=https://a.com/test.m3u 如果能支持 这么播放m3u 就太完美了 希望能够支持,谢谢作者。


评论 3 - hanxi

http://127.0.0.1:8090/playurl?did=mydid&url=https://a.com/test.m3u 如果能支持 这么播放m3u 就太完美了 希望能够支持,谢谢作者。

不想破坏现有接口,可以考虑用插件的方式来实现。


评论 4 - lazybabyz

potplayer 测试 https://raw.githubusercontent.com/YueChan/Live/refs/heads/main/Radio.m3u 部分可以听 xiaomusic测试 https://github.com/YueChan/Live/blob/main/Radio.m3u 复制raw文件转换 全部失败不停转台

以下potplayer测试 可以听,xiaomusic测试 复制raw文件转换 全部失败不停转台

https://raw.githubusercontent.com/kaige-cai/live/refs/heads/main/radio.m3uhttps://raw.githubusercontent.com/imDazui/Tvlist-awesome-m3u-m3u8/master/m3u/广播电台2021.m3u


评论 5 - hanxi

检查一下是不是 ipv6 的地址?小米音箱不支持 ipv6


评论 6 - lazybabyz

检查一下是不是 ipv6 的地址?小米音箱不支持 ipv6

是我硬件设置的问题 重新安装了 解决!


链接到 GitHub Issue

',33)]))}const d=t(r,[["render",s]]);export{p as __pageData,d as default}; diff --git a/docs/.vitepress/dist/assets/issues_94.md.GErTTh9P.js b/docs/.vitepress/dist/assets/issues_94.md.GErTTh9P.js deleted file mode 100644 index 2f10e82..0000000 --- a/docs/.vitepress/dist/assets/issues_94.md.GErTTh9P.js +++ /dev/null @@ -1,38 +0,0 @@ -import{_ as i,c as a,a0 as n,o as t}from"./chunks/framework.p2VkXzrt.js";const d=JSON.parse('{"title":"采用config.json配置方式","description":"","frontmatter":{"title":"采用config.json配置方式"},"headers":[],"relativePath":"issues/94.md","filePath":"issues/94.md","lastUpdated":null}'),h={name:"issues/94.md"};function p(l,s,k,e,E,o){return t(),a("div",null,s[0]||(s[0]=[n(`

采用config.json配置方式

docker 方式部署默认推荐使用环境变量的方式来配置参数,如果是自己用命令行启动,目前支持的参数配置比较少,但是是支持 --config 参数。

使用 pip 安装 xiaomusic 【0.1.83版本才支持 pip 安装】

shell
pip install xiaomusic

依赖的 ffmpeg 需要自己安装。

image

仓库中有个 config-example.json 文件,可以把这个文件拷贝为 config.json 然后修改 config.json 里的配置,再用下面的命令启动。

shell
xiaomusic --config ./config.json

默认的 config.json 模板如下:

json
{
-    "hardware": "L07A",
-    "account": "",
-    "password": "",
-    "mi_did": "",
-    "cookie": "",
-    "verbose": false,
-    "music_path": "music",
-    "conf_path": null,
-    "hostname": "192.168.2.5",
-    "port": 8090,
-    "proxy": null,
-    "search_prefix": "ytsearch:",
-    "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,
-    "use_music_api": false,
-    "log_file": "/tmp/xiaomusic.txt",
-    "fuzzy_match_cutoff": 0.6,
-    "enable_fuzzy_match": true
-}

如果采用 docker compose 启动想用 config.json 的配置方式,可以这样配:

yaml
services:
-  xiaomusic:
-    image: hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 8090:8090
-    volumes:
-      - ./music:/app/music
-      - ./config.json:/app/config.json
-    command: ['--config', '/app/config.json']

主要就是把 config.json 文件映射进容器和传 --config 参数。

评论

评论 1 - alitime

正需要,有配置文件方便多了


评论 2 - hanxi

ffmpeg 如果是用 apt install 这类系统工具安装的,默认会在 /usr/bin 目录下,ffmpeg_location 这个参数就需要设置为 /usr/bin .

目前 armv7 cpu 的 docker 镜像里的 ffmpeg 有问题,最好是用 pip 方式安装运行。


评论 3 - hanxi

ffmpeg 如果是用 apt install 这类系统工具安装的,默认会在 /usr/bin 目录下,ffmpeg_location 这个参数就需要设置为 /usr/bin .

目前 armv7 cpu 的 docker 镜像里的 ffmpeg 有问题,最好是用 pip 方式安装运行。

armv7 问题已经解决。


链接到 GitHub Issue

`,26)]))}const g=i(h,[["render",p]]);export{d as __pageData,g as default}; diff --git a/docs/.vitepress/dist/assets/issues_94.md.GErTTh9P.lean.js b/docs/.vitepress/dist/assets/issues_94.md.GErTTh9P.lean.js deleted file mode 100644 index 2f10e82..0000000 --- a/docs/.vitepress/dist/assets/issues_94.md.GErTTh9P.lean.js +++ /dev/null @@ -1,38 +0,0 @@ -import{_ as i,c as a,a0 as n,o as t}from"./chunks/framework.p2VkXzrt.js";const d=JSON.parse('{"title":"采用config.json配置方式","description":"","frontmatter":{"title":"采用config.json配置方式"},"headers":[],"relativePath":"issues/94.md","filePath":"issues/94.md","lastUpdated":null}'),h={name:"issues/94.md"};function p(l,s,k,e,E,o){return t(),a("div",null,s[0]||(s[0]=[n(`

采用config.json配置方式

docker 方式部署默认推荐使用环境变量的方式来配置参数,如果是自己用命令行启动,目前支持的参数配置比较少,但是是支持 --config 参数。

使用 pip 安装 xiaomusic 【0.1.83版本才支持 pip 安装】

shell
pip install xiaomusic

依赖的 ffmpeg 需要自己安装。

image

仓库中有个 config-example.json 文件,可以把这个文件拷贝为 config.json 然后修改 config.json 里的配置,再用下面的命令启动。

shell
xiaomusic --config ./config.json

默认的 config.json 模板如下:

json
{
-    "hardware": "L07A",
-    "account": "",
-    "password": "",
-    "mi_did": "",
-    "cookie": "",
-    "verbose": false,
-    "music_path": "music",
-    "conf_path": null,
-    "hostname": "192.168.2.5",
-    "port": 8090,
-    "proxy": null,
-    "search_prefix": "ytsearch:",
-    "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,
-    "use_music_api": false,
-    "log_file": "/tmp/xiaomusic.txt",
-    "fuzzy_match_cutoff": 0.6,
-    "enable_fuzzy_match": true
-}

如果采用 docker compose 启动想用 config.json 的配置方式,可以这样配:

yaml
services:
-  xiaomusic:
-    image: hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 8090:8090
-    volumes:
-      - ./music:/app/music
-      - ./config.json:/app/config.json
-    command: ['--config', '/app/config.json']

主要就是把 config.json 文件映射进容器和传 --config 参数。

评论

评论 1 - alitime

正需要,有配置文件方便多了


评论 2 - hanxi

ffmpeg 如果是用 apt install 这类系统工具安装的,默认会在 /usr/bin 目录下,ffmpeg_location 这个参数就需要设置为 /usr/bin .

目前 armv7 cpu 的 docker 镜像里的 ffmpeg 有问题,最好是用 pip 方式安装运行。


评论 3 - hanxi

ffmpeg 如果是用 apt install 这类系统工具安装的,默认会在 /usr/bin 目录下,ffmpeg_location 这个参数就需要设置为 /usr/bin .

目前 armv7 cpu 的 docker 镜像里的 ffmpeg 有问题,最好是用 pip 方式安装运行。

armv7 问题已经解决。


链接到 GitHub Issue

`,26)]))}const g=i(h,[["render",p]]);export{d as __pageData,g as default}; diff --git a/docs/.vitepress/dist/assets/issues_96.md.D6uW10Mi.js b/docs/.vitepress/dist/assets/issues_96.md.D6uW10Mi.js deleted file mode 100644 index e163012..0000000 --- a/docs/.vitepress/dist/assets/issues_96.md.D6uW10Mi.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as a,c as e,a0 as s,o as i}from"./chunks/framework.p2VkXzrt.js";const m=JSON.parse('{"title":"ios系统上的捷径配置","description":"","frontmatter":{"title":"ios系统上的捷径配置"},"headers":[],"relativePath":"issues/96.md","filePath":"issues/96.md","lastUpdated":null}'),o={name:"issues/96.md"};function r(c,t,d,l,n,p){return i(),e("div",null,t[0]||(t[0]=[s('

ios系统上的捷径配置

下面是播放音乐和关机两个示例。只要在 web 页面上能看到的功能,都有对应的 http 请求接口,都可以用来配置捷径。

mmexport1719767452647

mmexport1719767449742

评论

没有评论。 链接到 GitHub Issue

',6)]))}const u=a(o,[["render",r]]);export{m as __pageData,u as default}; diff --git a/docs/.vitepress/dist/assets/issues_96.md.D6uW10Mi.lean.js b/docs/.vitepress/dist/assets/issues_96.md.D6uW10Mi.lean.js deleted file mode 100644 index e163012..0000000 --- a/docs/.vitepress/dist/assets/issues_96.md.D6uW10Mi.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as a,c as e,a0 as s,o as i}from"./chunks/framework.p2VkXzrt.js";const m=JSON.parse('{"title":"ios系统上的捷径配置","description":"","frontmatter":{"title":"ios系统上的捷径配置"},"headers":[],"relativePath":"issues/96.md","filePath":"issues/96.md","lastUpdated":null}'),o={name:"issues/96.md"};function r(c,t,d,l,n,p){return i(),e("div",null,t[0]||(t[0]=[s('

ios系统上的捷径配置

下面是播放音乐和关机两个示例。只要在 web 页面上能看到的功能,都有对应的 http 请求接口,都可以用来配置捷径。

mmexport1719767452647

mmexport1719767449742

评论

没有评论。 链接到 GitHub Issue

',6)]))}const u=a(o,[["render",r]]);export{m as __pageData,u as default}; diff --git a/docs/.vitepress/dist/assets/issues_99.md.amwtZpFn.js b/docs/.vitepress/dist/assets/issues_99.md.amwtZpFn.js deleted file mode 100644 index 4304df4..0000000 --- a/docs/.vitepress/dist/assets/issues_99.md.amwtZpFn.js +++ /dev/null @@ -1,73 +0,0 @@ -import{_ as t,c as e,a0 as s,o as i}from"./chunks/framework.p2VkXzrt.js";const d=JSON.parse('{"title":"💬 FAQ问题集合","description":"","frontmatter":{"title":"💬 FAQ问题集合"},"headers":[],"relativePath":"issues/99.md","filePath":"issues/99.md","lastUpdated":null}'),o={name:"issues/99.md"};function n(l,a,p,c,r,u){return i(),e("div",null,a[0]||(a[0]=[s(`

💬 FAQ问题集合

NOTE

这个 issue 用来总结报错日志和对应的解决方法。

❓ XIAOMUSIC_HOSTNAME 怎么填

填写 docker 主机的 ip ,不是小爱音箱的ip,一般就是网页访问的后台地址里的 ip ,只要确保 ip 和小爱音箱在一个局域网内就行。

同时也支持 xx.xx.com 的域名格式,用于配置反代供外网访问,比如小爱音箱和 docker 主机不在同一个局域网内。

❓ Login Failed 登陆失败

表现就是 后台看不到设备列表 ,日志中会有对应的报错。

这个有专门的讨论,见 </issues/16.html> ,一般是因为开了加速代理,关代理再尝试即可。也可以试试在局域网设备里的米家app上退出再重新登录一下。

在小米官网 www.mi.com 登陆过人脸或滑块验证基本上能解决 99%的 login failed 问题。

来自 @yilikun 的友情提示:

  1. 关闭本地代理。
  2. 如果是nas运行的,网络由bridge改为host。
  3. 米家app重新登陆。
  4. mi.com官网重新登陆。

❓ 网页后台可以播放,语音控制无效

这种情况是拉取不到对话记录导致的。 如果是首次在网页后台保存 did 后需要重启一次容器。 其他情况可能是被限制拉取对话记录次数,也可以尝试重启容器。 还有一种情况是配错了唤醒口令,可以在小爱音箱app里查看对话记录,也可以查看 xiaomusic 的日志。默认口令前缀是【播放歌曲】,没有这个前缀是无法识别的,说播放音乐是没用的,除非自己设置其他口令词。 已知 M01/XMYX01JY 小米小爱音箱HD 获取对话记录的接口比较特殊,需要开启【特殊型号获取对话记录:】开关才能正常语音控制。

❓ 日志显示正在播放,却没有声音

可以点击播放链接按钮,看看默认的那个链接能否播放。

已知部分触屏版不能播放可以在后台设置 【型号兼容模式】为 true 试试。

其他情况可能是 XIAOMUSIC_HOSTNAME 配错了地址,不是 docker 主机地址会导致小爱音箱无法访问到,而且需要和小爱音箱在同一个局域网下的地址。还有可能是端口配错了,修改了默认 8090 端口映射,需要同步修改其他参数,可以翻阅端口修改的文档。

如果端口不是8090,首次启动没配好端口的话,需要手动修改setting.json文件里的端口,或者把setting.json文件删除重新配置,或者在后台修改监听端口后重启。

可以点击播放歌曲后,查看日志里的歌曲链接,放到浏览器里打开试试,不能访问说明是端口或者hostname问题,如果是异地访问,需要把 hostname 修改为外网ip或者域名,需要注意音箱只支持访问ipv4,不能是ipv6的公网。

如果是配了公网反代端口,注意区分是 http 还是 https ,如果是 https 的,配置 XIAOMUSIC_HOSTNAME 时需要加上 https:// 前缀。

❓ 无法播放 flac 格式歌曲

因设备差异和文件格式差异,已知部分设备不支持 flac 格式,比如 L05B L05C 。

❓ docker 镜像拉取失败

请更换镜像源或者使用代理。不同环境更换镜像源的方式不一样,可以网上搜索自己的 NAS 如何更换镜像源。

已经可以通过 DaoCloud 拉取镜像。

docker pull m.daocloud.io/docker.io/hanxi/xiaomusic:latest
-docker tag m.daocloud.io/docker.io/hanxi/xiaomusic:latest hanxi/xiaomusic:latest

❓ 启动失败,日志中出现 RuntimeError: can't start new thread

一般是 docker 版本太低,或者系统限制了 docker 使用的 cpu 数量,可以尝试升级 docker 到最新版本。

❓ DNS 解析错误

一般会出现下面这样的日志,表现就是设置页面看不到设备列表。

aiohttp.client_ _exceptions. ClientConnectorError: Cannot connect to host account.xiaomi.com:443 ssl:False [Temporary failure in name resolution]

可以尝试把主机的 DNS 设为 223.5.5.5 之后重启 docker 主机。

如果还是不行可以把 docker 的网络模式改成 host 模式。

❓ 点击播放后需要很久才开始播放的问题

这个问题新版本已经解决,如果还存在请反馈。

~目前0.3.x版本还存在这个问题没有完全解决,可以暂时回退到0.2.0版本继续使用。~

❓ 如何配置多个歌曲目录

yaml
services:
-  xiaomusic:
-    image: hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 8090:8090
-    volumes:
-      - /data/music1:/app/music/music1
-      - /data/music2:/app/music/music2
-      - /data/xiaomusic/conf:/app/conf

冒号左边的 /data/music1/data/music2 改成你的目录即可。如果你是 windows 的 docker ,可以改成 D:/music1D:/music2,盘符号开头,用 / 分割。

如果是 docker 部署的,建议不要去修改 web 后台里的音乐路径和配置路径等等所有路径除非你熟悉 docker 的目录映射机制。

❓ 能不能中文名

yaml
services:
-  xiaomusic:
-    image: hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 8090:8090
-    volumes:
-      - /data/music1:/app/music/歌曲目录1
-      - /data/music2:/app/music/歌曲目录2
-      - /data/xiaomusic/conf:/app/conf

❓ 能不能多层目录

可以,每层的每个目录会识别为一个播放列表。

❓ 是否需要手动获取 did

新版本不需要手动获取配置 did,不需要配置环境变量,直接在 web 后台填入小米账号密码保存后会自动获取到 did ,然后勾选对应的设备即可。

❓ 报错 601

报错日志大致如下:

txt
Exception: Error https://api2.mina.mi.com/remote/ubus: {"code":601,"message":"illegal argument exception","data":"IllegalArgumentException: ubus call format illegal!"}

原因是没有配置 did ,或者 did 配置错误。可以到设置页面选择正确的设备类型和 did 然后保存。

❓ 新功能没有生效

在设置页面重新保存一下,或者删除 setting.json 文件,重新在后台设置一次。

❓ 为什么会先说小爱音箱自带的回答,再说下载中或者过一会儿才播放本地歌曲

设计原理就是每秒不停的抓取对话记录,然后再打断小爱音箱自带的处理流程。整个过程下来会有延时,所以打断不会很及时,做不到无缝衔接。

评论

评论 1 - shissx

安装的最新版本,即使没有使用,日志一直在不停的刷新,示例: [10:20:36] [0.1.101] [DEBUG] Polling_event, timestamp: {'eeb70da5-baa9-4b56-b2f3-7ee01276a18a': 1720430457236} [10:20:36] [0.1.101] [DEBUG] Sleep 0.0003166699898429215, timestamp: {'eeb70da5-baa9-4b56-b2f3-7ee01276a18a': 1720430457236} [10:20:37] [0.1.101] [DEBUG] Listening new message, timestamp: {'eeb70da5-baa9-4b56-b2f3-7ee01276a18a

之前的版本没有这个问题,这个是设置错误?还是本来就如此呢?


评论 2 - hanxi

安装的最新版本,即使没有使用,日志一直在不停的刷新,示例: [10:20:36] [0.1.101] [DEBUG] Polling_event, timestamp: {'eeb70da5-baa9-4b56-b2f3-7ee01276a18a': 1720430457236} [10:20:36] [0.1.101] [DEBUG] Sleep 0.0003166699898429215, timestamp: {'eeb70da5-baa9-4b56-b2f3-7ee01276a18a': 1720430457236} [10:20:37] [0.1.101] [DEBUG] Listening new message, timestamp: {'eeb70da5-baa9-4b56-b2f3-7ee01276a18a

之前的版本没有这个问题,这个是设置错误?还是本来就如此呢?

正常现象,现在默认把调试日志打开了,可以在后台设置关闭调试日志的。


评论 3 - Dx0123

大佬,docker安装提示缺少很多module,我一个个在dockerfilie里加上,最后卡在miservice装不上了~


评论 4 - hanxi

@Dx0123 其实不用手动安装依赖的,直接一行应该就行。

RUN pip install -U xiaomusic

评论 5 - Dx0123

@Dx0123 其实不用手动安装依赖的,直接一行应该就行。

RUN pip install -U xiaomusic

我直接用pip安装好之后,执行仍然有缺少的依赖,和docker里缺的一样。截图的module安装了之后还会有其他依赖缺失 image


评论 6 - hanxi

@Dx0123 你的python版本是不是有问题?你参考下项目里的Dockerfile,用同一个From镜像试试。


评论 7 - hanxi

有时候指令已停止,可是一会儿,又自动播放下一首,根本就停不下来。需要重启容器才能正常

点关机按钮也不行吗?


评论 8 - hanxi

有时候指令已停止,可是一会儿,又自动播放下一首,根本就停不下来。需要重启容器才能正常

点关机按钮也不行吗?

是的,说关机,点关机,暂停都不行。只能重启容器。

有没有日志看看?


评论 9 - sqmcool

为什么我的没有显示设备? Snipaste_2024-09-14_15-51-00


评论 10 - hanxi

为什么我的没有显示设备? Snipaste_2024-09-14_15-51-00

应该是登陆失败,可以查看一下日志。


评论 11 - schppd

楼主您好,请问这个我需要怎么处理? 微信截图_20240915225040


评论 12 - hanxi

楼主您好,请问这个我需要怎么处理? 微信截图_20240915225040

删掉重新配置一下试试。


评论 13 - schppd

会不会跟网络不稳定有关系?我都弄了几次还是这样子

------------------ 原始邮件 ------------------ 发件人: "hanxi/xiaomusic" @.>; 发送时间: 2024年9月15日(星期天) 晚上10:57 @.>; @.@.>; 主题: Re: [hanxi/xiaomusic] FAQ问题集合 (Issue #99)

楼主您好,请问这个我需要怎么处理?

删掉重新配置一下试试。

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>


评论 14 - hanxi

会不会跟网络不稳定有关系?我都弄了几次还是这样子 ------------------ 原始邮件 ------------------ 发件人: "hanxi/xiaomusic" @.>; 发送时间: 2024年9月15日(星期天) 晚上10:57 @.>; @.@.>; 主题: Re: [hanxi/xiaomusic] FAQ问题集合 (Issue #99) 楼主您好,请问这个我需要怎么处理? 删掉重新配置一下试试。 — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>

有可能的,用代理试试。


评论 15 - guoxiangke

定制的时候,"全部"和”所有歌曲"的区别,帮助有需要的朋友: 歌单中 "全部" 指的是 所有歌单中歌曲,但不包括“歌单内容”配置(http://127.0.0.1:8090/static/setting.html)中的电台 "type": "radio",的 ”所有歌曲" 指的是下载的歌曲,在download文件夹里


评论 16 - agigogo

image 在docker里可以运行,但是没法播放设置页面中的播放链接,选中设备那里是空的,是不是没成功?怎么调整?


评论 17 - hanxi

image 在docker里可以运行,但是没法播放设置页面中的播放链接,选中设备那里是空的,是不是没成功?怎么调整?

设置页面输入小米的账号密码后,再勾选一个设备。


评论 18 - agigogo

image 在docker里可以运行,但是没法播放设置页面中的播放链接,选中设备那里是空的,是不是没成功?怎么调整?

设置页面输入小米的账号密码后,再勾选一个设备。

设置页面里没有勾选的选项

image 在docker里可以运行,但是没法播放设置页面中的播放链接,选中设备那里是空的,是不是没成功?怎么调整?

设置页面输入小米的账号密码后,再勾选一个设备。

image 设置页面没有可勾选项?


评论 19 - agigogo

image 在docker里可以运行,但是没法播放设置页面中的播放链接,选中设备那里是空的,是不是没成功?怎么调整?

设置页面输入小米的账号密码后,再勾选一个设备。

设置页面里没有勾选的选项

image 在docker里可以运行,但是没法播放设置页面中的播放链接,选中设备那里是空的,是不是没成功?怎么调整?

设置页面输入小米的账号密码后,再勾选一个设备。

image 设置页面没有可勾选项?

image

显示未检测到设备,设备型号是MDZ-25-DA


评论 20 - hanxi

@agigogo 应该是登陆失败了,局域网的手机重新登陆一下小爱音箱app吧。


评论 21 - agigogo

@agigogo 应该是登陆失败了,局域网的手机重新登陆一下小爱音箱app吧。

一直都是用米家APP来控制小爱音箱,那要下个小爱音响APP试一试


评论 22 - agigogo

@agigogo 应该是登陆失败了,局域网的手机重新登陆一下小爱音箱app吧。

搞定了,小爱音箱app重新绑定就可以了。真6~


评论 23 - Tueafternoon

一首歌结束不能自动切到下一首,随机播放模式,日志中显示下一曲定时器不见了....这个是咋回事啊


评论 24 - hanxi

一首歌结束不能自动切到下一首,随机播放模式,日志中显示下一曲定时器不见了....这个是咋回事啊

可能是音乐文件有问题,获取歌曲长度失败,你可以把歌曲文件上传一下给我测试。


评论 25 - hanxi

或者搜下日志里有没有 不会设置下一首歌的定时器 这个


评论 26 - Tueafternoon

或者搜下日志里有没有 不会设置下一首歌的定时器 这个

有这个,应该是我的文件格式问题,晚上我处理一下再试试


评论 27 - zealler9560

Screenshot_2024-10-31-23-28-57-903_com.android.chrome.jpg

istore系统可以拉取创建镜像,但是无法启动,错误提示见图一,求助大佬!路由器信息见图二Screenshot_2024-10-31-23-36-42-846-edit_com.android.chrome.jpg


评论 28 - adidas004

在群晖使用docker安装的,只能通过重装来升级到最新版本吗?有快捷的一键升级吗


评论 29 - hanxi

在群晖使用docker安装的,只能通过重装来升级到最新版本吗?有快捷的一键升级吗

不会命令行,就用这个工具吧。 https://github.com/onlyLTY/dockerCopilot


评论 30 - adidas004

谢谢您的工具,我刚去群晖的docker上有提示直接升级,还是非常的感觉你的回答

------------------ 原始邮件 ------------------ 发件人: @.>; 发送时间: 2024年11月11日(星期一) 下午4:20 收件人: @.>; 抄送: @.>; @.>; 主题: Re: [hanxi/xiaomusic] 💬 FAQ问题集合 (Issue #99)

在群晖使用docker安装的,只能通过重装来升级到最新版本吗?有快捷的一键升级吗

不会命令行,就用这个工具吧。 https://github.com/onlyLTY/dockerCopilot

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>


评论 31 - sinojelly

运行时遇到下面问题,请问要怎么排查?

2024/11/21 19:27:00  xiaomusic.py: error: unrecognized arguments: .venv/bin/python3 xiaomusic.py
-2024/11/21 19:27:00         [--enable_config_example]
-2024/11/21 19:27:00         [--ffmpeg_location FFMPEG_LOCATION]
-2024/11/21 19:27:00         [--config CONFIG]
-2024/11/21 19:27:00         [--verbose]
-2024/11/21 19:27:00         [--cookie COOKIE]
-2024/11/21 19:27:00         [--password PASSWORD]
-2024/11/21 19:27:00         [--account ACCOUNT]
-2024/11/21 19:27:00         [--hardware HARDWARE]
-2024/11/21 19:27:00         [--port PORT]
-2024/11/21 19:27:00         [-h]
-2024/11/21 19:27:00  usage: xiaomusic.py

评论 32 - hanxi

运行时遇到下面问题,请问要怎么排查?

2024/11/21 19:27:00  xiaomusic.py: error: unrecognized arguments: .venv/bin/python3 xiaomusic.py
-2024/11/21 19:27:00         [--enable_config_example]
-2024/11/21 19:27:00         [--ffmpeg_location FFMPEG_LOCATION]
-2024/11/21 19:27:00         [--config CONFIG]
-2024/11/21 19:27:00         [--verbose]
-2024/11/21 19:27:00         [--cookie COOKIE]
-2024/11/21 19:27:00         [--password PASSWORD]
-2024/11/21 19:27:00         [--account ACCOUNT]
-2024/11/21 19:27:00         [--hardware HARDWARE]
-2024/11/21 19:27:00         [--port PORT]
-2024/11/21 19:27:00         [-h]
-2024/11/21 19:27:00  usage: xiaomusic.py

看不出来


评论 33 - sinojelly

请问登录验证失败要怎么定位?小米登录邮箱,还是小米id 都报同样的错。

2024/11/25 0:50:44  [2024-11-25 00:50:44] [0.3.48] [ERROR] xiaomusic.py:259: /root/.mi.token file not exist
-2024/11/25 0:50:44  Exception: Error https://api2.mina.mi.com/admin/v2/device_list?master=0&requestId=app_ios_: Login failed
-2024/11/25 0:50:44      raise Exception(f"Error {url}: {resp}")
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request
-2024/11/25 0:50:44      return await self.account.mi_request(
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request
-2024/11/25 0:50:44      result = await self.mina_request("/admin/v2/device_list?master=" + str(master))
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 54, in device_list
-2024/11/25 0:50:44      hardware_data = await self.mina_service.device_list()
-2024/11/25 0:50:44    File "/app/xiaomusic/xiaomusic.py", line 232, in try_update_device_id
-2024/11/25 0:50:44  Traceback (most recent call last):
-2024/11/25 0:50:44  [2024-11-25 00:50:44] [0.3.48] [ERROR] xiaomusic.py:251: Execption Error https://api2.mina.mi.com/admin/v2/device_list?master=0&requestId=app_ios_xx: Login failed
-2024/11/25 0:50:44  Exception: {'qs': '%3Fsid%3Dmicoapi%26_json%3Dtrue', 'code': 70016, 'description': '登录验证失败', 'securityStatus': 0, '_sign': 'xxx', 'sid': 'micoapi', 'result': 'error', 'captchaUrl': None, 'callback': 'https://api2.mina.mi.com/sts', 'location': '', 'pwd': 0, 'child': 0, 'desc': '登录验证失败'}
-2024/11/25 0:50:44      raise Exception(resp)
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 69, in login

评论 34 - hanxi

请问登录验证失败要怎么定位?小米登录邮箱,还是小米id 都报同样的错。

2024/11/25 0:50:44  [2024-11-25 00:50:44] [0.3.48] [ERROR] xiaomusic.py:259: /root/.mi.token file not exist
-2024/11/25 0:50:44  Exception: Error https://api2.mina.mi.com/admin/v2/device_list?master=0&requestId=app_ios_: Login failed
-2024/11/25 0:50:44      raise Exception(f"Error {url}: {resp}")
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request
-2024/11/25 0:50:44      return await self.account.mi_request(
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request
-2024/11/25 0:50:44      result = await self.mina_request("/admin/v2/device_list?master=" + str(master))
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 54, in device_list
-2024/11/25 0:50:44      hardware_data = await self.mina_service.device_list()
-2024/11/25 0:50:44    File "/app/xiaomusic/xiaomusic.py", line 232, in try_update_device_id
-2024/11/25 0:50:44  Traceback (most recent call last):
-2024/11/25 0:50:44  [2024-11-25 00:50:44] [0.3.48] [ERROR] xiaomusic.py:251: Execption Error https://api2.mina.mi.com/admin/v2/device_list?master=0&requestId=app_ios_xx: Login failed
-2024/11/25 0:50:44  Exception: {'qs': '%3Fsid%3Dmicoapi%26_json%3Dtrue', 'code': 70016, 'description': '登录验证失败', 'securityStatus': 0, '_sign': 'xxx', 'sid': 'micoapi', 'result': 'error', 'captchaUrl': None, 'callback': 'https://api2.mina.mi.com/sts', 'location': '', 'pwd': 0, 'child': 0, 'desc': '登录验证失败'}
-2024/11/25 0:50:44      raise Exception(resp)
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 69, in login

上面给出了4个办法都试过了吗?局域网登陆mi.com了?


评论 35 - wusemao

设置web访问登录时,账号密码设置完之后登不进去了,账号名称用的中文的可以么


评论 36 - hanxi

设置web访问登录时,账号密码设置完之后登不进去了,账号名称用的中文的可以么

不确定是否可以,你可以考虑setting.json里的内容,不行就修改再重启。


评论 37 - quanmao

运行后会在音乐目录下生成一个tmp文件夹,如何指定路径,因为还有其应用访问音乐路径,不希望其识别tmp目录下的音乐。


评论 38 - hanxi

运行后会在音乐目录下生成一个tmp文件夹,如何指定路径,因为还有其应用访问音乐路径,不希望其识别tmp目录下的音乐。

忽略目录(逗号分割) 改成 @eaDir,tmp 即可。


评论 39 - quanmao

运行后会在音乐目录下生成一个tmp文件夹,如何指定路径,因为还有其应用访问音乐路径,不希望其识别tmp目录下的音乐。

忽略目录(逗号分割) 改成 @eaDir,tmp 即可。

抱歉,是我没有说清楚,是运行xiaomusic后会在音乐路径下生成tmp文件夹, 但我同时还在用navidrome,也会访问音乐目录,他会把tmp目录下的歌曲也扫描进去,所以想移动tmp目录。 navidrome没找到在哪里可以设置,忽略这个文件夹


评论 40 - hanxi

运行后会在音乐目录下生成一个tmp文件夹,如何指定路径,因为还有其应用访问音乐路径,不希望其识别tmp目录下的音乐。

忽略目录(逗号分割) 改成 @eaDir,tmp 即可。

抱歉,是我没有说清楚,是运行xiaomusic后会在音乐路径下生成tmp文件夹, 但我同时还在用navidrome,也会访问音乐目录,他会把tmp目录下的歌曲也扫描进去,所以想移动tmp目录。 navidrome没找到在哪里可以设置,忽略这个文件夹

提个新 issue 吧,有空加下配置项。


评论 41 - CallEdison

imageimage 问题一:能进控制面板,进不了设置页面,容器没有log生成,我昨天已经设置好了,现在功能能正常使用,但是进不了设置页面了问题二:昨天能进的时候发现本地下载目录有歌曲,但是设置里面的全部歌曲里面没有,搜索框搜索又能搜的到。


评论 42 - hanxi

image image 问题一:能进控制面板,进不了设置页面,容器没有log生成,我昨天已经设置好了,现在功能能正常使用,但是进不了设置页面了问题二:昨天能进的时候发现本地下载目录有歌曲,但是设置里面的全部歌曲里面没有,搜索框搜索又能搜的到。

问题一:打不开的地址是哪个? 问题二:可以点击刷新列表按钮试试。


评论 43 - huahua-er

是关闭了网络搜索了吗?现在的搜索只有本地数据没有网络歌曲了?


评论 44 - CallEdison

默认主题有歌曲

pure主题没有歌曲

xmusicPlayer也没有歌曲

 

Edison @.***

 

------------------ 原始邮件 ------------------ 发件人: @.>; 发送时间: 2024年11月30日(星期六) 上午6:26 收件人: @.>; 抄送: @.>; @.>; 主题: Re: [hanxi/xiaomusic] 💬 FAQ问题集合 (Issue #99)

是关闭了网络搜索了吗?现在的搜索只有本地数据没有网络歌曲了?

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>


评论 45 - hanxi

是关闭了网络搜索了吗?现在的搜索只有本地数据没有网络歌曲了?

等 yt-dlp 修复。


评论 46 - hanxi

默认主题有歌曲 pure主题没有歌曲 xmusicPlayer也没有歌曲   Edison @.***   ------------------ 原始邮件 ------------------ 发件人: @.>; 发送时间: 2024年11月30日(星期六) 上午6:26 收件人: @.>; 抄送: @.>; @.>; 主题: Re: [hanxi/xiaomusic] 💬 FAQ问题集合 (Issue #99) 是关闭了网络搜索了吗?现在的搜索只有本地数据没有网络歌曲了? — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>

需要刷新缓存


评论 47 - like1020

Screenshot_2024-12-03-06-58-52-853_com yjllq kito 请教一下,本地列表歌单里的歌曲即便设置为全部循环或随机播放,依然是不断地单曲循环,只能自己手动点下一首,请问是什么情况?


评论 48 - tchgtr

请问网络搜索功能修复了吗?感谢!


评论 49 - hanxi

请问网络搜索功能修复了吗?感谢!

修复了。


评论 50 - tchgtr

请问网络搜索功能修复了吗?感谢!

修复了。

我已经更新到最新版0.3.55,音箱是LX04触屏音箱,但是使用指令对小爱说“播放歌曲。。。“还是通过音箱绑定的QQ音乐播放,解绑了QQ音乐账号,就会提示先让我绑定账号(包括播放本地歌曲指令),感觉音箱还没跟docker连接上(已经重启过容器)。但是通过8090端口进入后台,搜索歌曲,播放,调节声音大小,单曲、随机所有的功能都可以通过按键实现,唯独不能通过语音跟小爱对话播放我指定的内容,请问这个是什么问题,感谢!


评论 51 - hanxi

请问网络搜索功能修复了吗?感谢!

修复了。

我已经更新到最新版0.3.55,音箱是LX04触屏音箱,但是使用指令对小爱说“播放歌曲。。。“还是通过音箱绑定的QQ音乐播放,解绑了QQ音乐账号,就会提示先让我绑定账号(包括播放本地歌曲指令),感觉音箱还没跟docker连接上(已经重启过容器)。但是通过8090端口进入后台,搜索歌曲,播放,调节声音大小,单曲、随机所有的功能都可以通过按键实现,唯独不能通过语音跟小爱对话播放我指定的内容,请问这个是什么问题,感谢!

贴下你的 setting.json 文件看看吧,把里面的账号密码删除。


评论 52 - tchgtr

请问网络搜索功能修复了吗?感谢!

修复了。

我已经更新到最新版0.3.55,音箱是LX04触屏音箱,但是使用指令对小爱说“播放歌曲。。。“还是通过音箱绑定的QQ音乐播放,解绑了QQ音乐账号,就会提示先让我绑定账号(包括播放本地歌曲指令),感觉音箱还没跟docker连接上(已经重启过容器)。但是通过8090端口进入后台,搜索歌曲,播放,调节声音大小,单曲、随机所有的功能都可以通过按键实现,唯独不能通过语音跟小爱对话播放我指定的内容,请问这个是什么问题,感谢!

贴下你的 setting.json 文件看看吧,把里面的账号密码删除。

{ "account": "", "password": "", "mi_did": "603807070", "miio_tts_command": "", "cookie": "", "verbose": false, "music_path": "music", "temp_path": "music/tmp", "download_path": "music/download", "conf_path": "conf", "cache_dir": "cache", "hostname": "192.168.31.159", "port": 8090, "public_port": 0, "proxy": "", "search_prefix": "bilisearch:", "ffmpeg_location": "./ffmpeg/bin", "active_cmd": "play,set_play_type_rnd,playlocal,play_music_list,play_music_list_index,stop_after_minute,stop", "exclude_dirs": "@eaDir,tmp", "music_path_depth": 10, "disable_httpauth": true, "httpauth_username": "", "httpauth_password": "", "music_list_url": "", "music_list_json": "", "custom_play_list_json": "", "disable_download": false, "key_word_dict": { "下一首": "play_next", "上一首": "play_prev", "单曲循环": "set_play_type_one", "全部循环": "set_play_type_all", "随机播放": "set_play_type_rnd", "单曲播放": "set_play_type_sin", "顺序播放": "set_play_type_seq", "分钟后关机": "stop_after_minute", "刷新列表": "gen_music_list", "加入收藏": "add_to_favorites", "收藏歌曲": "add_to_favorites", "取消收藏": "del_from_favorites", "播放列表第": "play_music_list_index", "播放本地歌曲": "playlocal", "本地播放歌曲": "playlocal", "播放歌曲": "play", "放歌曲": "play", "关机": "stop", "暂停": "stop", "停止": "stop", "停止播放": "stop", "播放列表": "play_music_list", "播放歌单": "play_music_list", "测试自定义口令": "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": "xiaomusic.log.txt", "fuzzy_match_cutoff": 0.6, "enable_fuzzy_match": true, "stop_tts_msg": "收到,再见", "enable_config_example": false, "keywords_playlocal": "播放本地歌曲,本地播放歌曲", "keywords_play": "播放歌曲,放歌曲", "keywords_stop": "关机,暂停,停止,停止播放", "keywords_playlist": "播放列表,播放歌单", "user_key_word_dict": { "测试自定义口令": "exec#code1("hello")", "测试链接": "exec#httpget("https://github.com/hanxi/xiaomusic\\")" }, "enable_force_stop": false, "devices": { "603807070": { "did": "603807070", "device_id": "60b8f875-4101-416a-9278-4d4170929b4d", "hardware": "LX04", "name": "小爱触屏音箱", "play_type": 1, "cur_music": "七里香", "cur_playlist": "临时搜索列表" } }, "group_list": "", "remove_id3tag": false, "convert_to_mp3": false, "delay_sec": 3, "continue_play": false, "pull_ask_sec": 1, "crontab_json": "", "enable_yt_dlp_cookies": false, "get_ask_by_mina": true, "play_type_one_tts_msg": "已经设置为单曲循环", "play_type_all_tts_msg": "已经设置为全部循环", "play_type_rnd_tts_msg": "已经设置为随机播放", "play_type_sin_tts_msg": "已经设置为单曲播放", "play_type_seq_tts_msg": "已经设置为顺序播放", "recently_added_playlist_len": 50 }


评论 53 - hanxi

get_ask_by_mina 【特殊型号获取对话记录】这个需要设置为 false


评论 54 - tchgtr

get_ask_by_mina 【特殊型号获取对话记录】这个需要设置为 false

已经重新设置并重启容器,尝试还是不行,会不会是这个触屏音箱不支持?


评论 55 - tchgtr

get_ask_by_mina 【特殊型号获取对话记录】这个需要设置为 false

已经重新设置并重启容器,尝试还是不行,会不会是这个触屏音箱不支持?

或者是在创建容器的时候,我的小米账号填写了我的手机号码,应该需要填写实际的小米账号?因为手机号码对应的小米账号,和输入对应的密码,都是可以登陆的,但是我不知道有什么区别,就把手机号码输入进去了


评论 56 - tchgtr

get_ask_by_mina 【特殊型号获取对话记录】这个需要设置为 false

已经重新设置并重启容器,尝试还是不行,会不会是这个触屏音箱不支持?

或者是在创建容器的时候,我的小米账号填写了我的手机号码,应该需要填写实际的小米账号?因为手机号码对应的小米账号,和输入对应的密码,都是可以登陆的,但是我不知道有什么区别,就把手机号码输入进去了

我重新创建容器,问题解决了,主要是两个地方改变了,一个是上面的小米账号的问题,另外一个是关于conf和music这两个文件夹,这次我都把他们放在/container/xiaomusic下,即/container/xiaomusic/conf(music),分别对应两个容器和主机app/conf(music),问题就解决了。之前我是分开在两个文件夹的。


评论 57 - tianting123

0 B的APE文件, 建议直接跳过播放


评论 58 - oklrc

新人请教:使用DOCKER镜像 或者 composer安装 如何升级到最新版本呢?是删除镜像再重新拉取吗


评论 59 - hanxi

新人请教:使用DOCKER镜像 或者 composer安装 如何升级到最新版本呢?是删除镜像再重新拉取吗

docker compose pull
-docker compose up -d

评论 60 - zhiquanchi

我在阿里云的服务器上运行的docker,我登录了小米账号,但是 操控面板 里面 不显示我的设备。音箱是pro LX06


链接到 GitHub Issue

`,292)]))}const m=t(o,[["render",n]]);export{d as __pageData,m as default}; diff --git a/docs/.vitepress/dist/assets/issues_99.md.amwtZpFn.lean.js b/docs/.vitepress/dist/assets/issues_99.md.amwtZpFn.lean.js deleted file mode 100644 index 4304df4..0000000 --- a/docs/.vitepress/dist/assets/issues_99.md.amwtZpFn.lean.js +++ /dev/null @@ -1,73 +0,0 @@ -import{_ as t,c as e,a0 as s,o as i}from"./chunks/framework.p2VkXzrt.js";const d=JSON.parse('{"title":"💬 FAQ问题集合","description":"","frontmatter":{"title":"💬 FAQ问题集合"},"headers":[],"relativePath":"issues/99.md","filePath":"issues/99.md","lastUpdated":null}'),o={name:"issues/99.md"};function n(l,a,p,c,r,u){return i(),e("div",null,a[0]||(a[0]=[s(`

💬 FAQ问题集合

NOTE

这个 issue 用来总结报错日志和对应的解决方法。

❓ XIAOMUSIC_HOSTNAME 怎么填

填写 docker 主机的 ip ,不是小爱音箱的ip,一般就是网页访问的后台地址里的 ip ,只要确保 ip 和小爱音箱在一个局域网内就行。

同时也支持 xx.xx.com 的域名格式,用于配置反代供外网访问,比如小爱音箱和 docker 主机不在同一个局域网内。

❓ Login Failed 登陆失败

表现就是 后台看不到设备列表 ,日志中会有对应的报错。

这个有专门的讨论,见 </issues/16.html> ,一般是因为开了加速代理,关代理再尝试即可。也可以试试在局域网设备里的米家app上退出再重新登录一下。

在小米官网 www.mi.com 登陆过人脸或滑块验证基本上能解决 99%的 login failed 问题。

来自 @yilikun 的友情提示:

  1. 关闭本地代理。
  2. 如果是nas运行的,网络由bridge改为host。
  3. 米家app重新登陆。
  4. mi.com官网重新登陆。

❓ 网页后台可以播放,语音控制无效

这种情况是拉取不到对话记录导致的。 如果是首次在网页后台保存 did 后需要重启一次容器。 其他情况可能是被限制拉取对话记录次数,也可以尝试重启容器。 还有一种情况是配错了唤醒口令,可以在小爱音箱app里查看对话记录,也可以查看 xiaomusic 的日志。默认口令前缀是【播放歌曲】,没有这个前缀是无法识别的,说播放音乐是没用的,除非自己设置其他口令词。 已知 M01/XMYX01JY 小米小爱音箱HD 获取对话记录的接口比较特殊,需要开启【特殊型号获取对话记录:】开关才能正常语音控制。

❓ 日志显示正在播放,却没有声音

可以点击播放链接按钮,看看默认的那个链接能否播放。

已知部分触屏版不能播放可以在后台设置 【型号兼容模式】为 true 试试。

其他情况可能是 XIAOMUSIC_HOSTNAME 配错了地址,不是 docker 主机地址会导致小爱音箱无法访问到,而且需要和小爱音箱在同一个局域网下的地址。还有可能是端口配错了,修改了默认 8090 端口映射,需要同步修改其他参数,可以翻阅端口修改的文档。

如果端口不是8090,首次启动没配好端口的话,需要手动修改setting.json文件里的端口,或者把setting.json文件删除重新配置,或者在后台修改监听端口后重启。

可以点击播放歌曲后,查看日志里的歌曲链接,放到浏览器里打开试试,不能访问说明是端口或者hostname问题,如果是异地访问,需要把 hostname 修改为外网ip或者域名,需要注意音箱只支持访问ipv4,不能是ipv6的公网。

如果是配了公网反代端口,注意区分是 http 还是 https ,如果是 https 的,配置 XIAOMUSIC_HOSTNAME 时需要加上 https:// 前缀。

❓ 无法播放 flac 格式歌曲

因设备差异和文件格式差异,已知部分设备不支持 flac 格式,比如 L05B L05C 。

❓ docker 镜像拉取失败

请更换镜像源或者使用代理。不同环境更换镜像源的方式不一样,可以网上搜索自己的 NAS 如何更换镜像源。

已经可以通过 DaoCloud 拉取镜像。

docker pull m.daocloud.io/docker.io/hanxi/xiaomusic:latest
-docker tag m.daocloud.io/docker.io/hanxi/xiaomusic:latest hanxi/xiaomusic:latest

❓ 启动失败,日志中出现 RuntimeError: can't start new thread

一般是 docker 版本太低,或者系统限制了 docker 使用的 cpu 数量,可以尝试升级 docker 到最新版本。

❓ DNS 解析错误

一般会出现下面这样的日志,表现就是设置页面看不到设备列表。

aiohttp.client_ _exceptions. ClientConnectorError: Cannot connect to host account.xiaomi.com:443 ssl:False [Temporary failure in name resolution]

可以尝试把主机的 DNS 设为 223.5.5.5 之后重启 docker 主机。

如果还是不行可以把 docker 的网络模式改成 host 模式。

❓ 点击播放后需要很久才开始播放的问题

这个问题新版本已经解决,如果还存在请反馈。

~目前0.3.x版本还存在这个问题没有完全解决,可以暂时回退到0.2.0版本继续使用。~

❓ 如何配置多个歌曲目录

yaml
services:
-  xiaomusic:
-    image: hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 8090:8090
-    volumes:
-      - /data/music1:/app/music/music1
-      - /data/music2:/app/music/music2
-      - /data/xiaomusic/conf:/app/conf

冒号左边的 /data/music1/data/music2 改成你的目录即可。如果你是 windows 的 docker ,可以改成 D:/music1D:/music2,盘符号开头,用 / 分割。

如果是 docker 部署的,建议不要去修改 web 后台里的音乐路径和配置路径等等所有路径除非你熟悉 docker 的目录映射机制。

❓ 能不能中文名

yaml
services:
-  xiaomusic:
-    image: hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 8090:8090
-    volumes:
-      - /data/music1:/app/music/歌曲目录1
-      - /data/music2:/app/music/歌曲目录2
-      - /data/xiaomusic/conf:/app/conf

❓ 能不能多层目录

可以,每层的每个目录会识别为一个播放列表。

❓ 是否需要手动获取 did

新版本不需要手动获取配置 did,不需要配置环境变量,直接在 web 后台填入小米账号密码保存后会自动获取到 did ,然后勾选对应的设备即可。

❓ 报错 601

报错日志大致如下:

txt
Exception: Error https://api2.mina.mi.com/remote/ubus: {"code":601,"message":"illegal argument exception","data":"IllegalArgumentException: ubus call format illegal!"}

原因是没有配置 did ,或者 did 配置错误。可以到设置页面选择正确的设备类型和 did 然后保存。

❓ 新功能没有生效

在设置页面重新保存一下,或者删除 setting.json 文件,重新在后台设置一次。

❓ 为什么会先说小爱音箱自带的回答,再说下载中或者过一会儿才播放本地歌曲

设计原理就是每秒不停的抓取对话记录,然后再打断小爱音箱自带的处理流程。整个过程下来会有延时,所以打断不会很及时,做不到无缝衔接。

评论

评论 1 - shissx

安装的最新版本,即使没有使用,日志一直在不停的刷新,示例: [10:20:36] [0.1.101] [DEBUG] Polling_event, timestamp: {'eeb70da5-baa9-4b56-b2f3-7ee01276a18a': 1720430457236} [10:20:36] [0.1.101] [DEBUG] Sleep 0.0003166699898429215, timestamp: {'eeb70da5-baa9-4b56-b2f3-7ee01276a18a': 1720430457236} [10:20:37] [0.1.101] [DEBUG] Listening new message, timestamp: {'eeb70da5-baa9-4b56-b2f3-7ee01276a18a

之前的版本没有这个问题,这个是设置错误?还是本来就如此呢?


评论 2 - hanxi

安装的最新版本,即使没有使用,日志一直在不停的刷新,示例: [10:20:36] [0.1.101] [DEBUG] Polling_event, timestamp: {'eeb70da5-baa9-4b56-b2f3-7ee01276a18a': 1720430457236} [10:20:36] [0.1.101] [DEBUG] Sleep 0.0003166699898429215, timestamp: {'eeb70da5-baa9-4b56-b2f3-7ee01276a18a': 1720430457236} [10:20:37] [0.1.101] [DEBUG] Listening new message, timestamp: {'eeb70da5-baa9-4b56-b2f3-7ee01276a18a

之前的版本没有这个问题,这个是设置错误?还是本来就如此呢?

正常现象,现在默认把调试日志打开了,可以在后台设置关闭调试日志的。


评论 3 - Dx0123

大佬,docker安装提示缺少很多module,我一个个在dockerfilie里加上,最后卡在miservice装不上了~


评论 4 - hanxi

@Dx0123 其实不用手动安装依赖的,直接一行应该就行。

RUN pip install -U xiaomusic

评论 5 - Dx0123

@Dx0123 其实不用手动安装依赖的,直接一行应该就行。

RUN pip install -U xiaomusic

我直接用pip安装好之后,执行仍然有缺少的依赖,和docker里缺的一样。截图的module安装了之后还会有其他依赖缺失 image


评论 6 - hanxi

@Dx0123 你的python版本是不是有问题?你参考下项目里的Dockerfile,用同一个From镜像试试。


评论 7 - hanxi

有时候指令已停止,可是一会儿,又自动播放下一首,根本就停不下来。需要重启容器才能正常

点关机按钮也不行吗?


评论 8 - hanxi

有时候指令已停止,可是一会儿,又自动播放下一首,根本就停不下来。需要重启容器才能正常

点关机按钮也不行吗?

是的,说关机,点关机,暂停都不行。只能重启容器。

有没有日志看看?


评论 9 - sqmcool

为什么我的没有显示设备? Snipaste_2024-09-14_15-51-00


评论 10 - hanxi

为什么我的没有显示设备? Snipaste_2024-09-14_15-51-00

应该是登陆失败,可以查看一下日志。


评论 11 - schppd

楼主您好,请问这个我需要怎么处理? 微信截图_20240915225040


评论 12 - hanxi

楼主您好,请问这个我需要怎么处理? 微信截图_20240915225040

删掉重新配置一下试试。


评论 13 - schppd

会不会跟网络不稳定有关系?我都弄了几次还是这样子

------------------ 原始邮件 ------------------ 发件人: "hanxi/xiaomusic" @.>; 发送时间: 2024年9月15日(星期天) 晚上10:57 @.>; @.@.>; 主题: Re: [hanxi/xiaomusic] FAQ问题集合 (Issue #99)

楼主您好,请问这个我需要怎么处理?

删掉重新配置一下试试。

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>


评论 14 - hanxi

会不会跟网络不稳定有关系?我都弄了几次还是这样子 ------------------ 原始邮件 ------------------ 发件人: "hanxi/xiaomusic" @.>; 发送时间: 2024年9月15日(星期天) 晚上10:57 @.>; @.@.>; 主题: Re: [hanxi/xiaomusic] FAQ问题集合 (Issue #99) 楼主您好,请问这个我需要怎么处理? 删掉重新配置一下试试。 — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>

有可能的,用代理试试。


评论 15 - guoxiangke

定制的时候,"全部"和”所有歌曲"的区别,帮助有需要的朋友: 歌单中 "全部" 指的是 所有歌单中歌曲,但不包括“歌单内容”配置(http://127.0.0.1:8090/static/setting.html)中的电台 "type": "radio",的 ”所有歌曲" 指的是下载的歌曲,在download文件夹里


评论 16 - agigogo

image 在docker里可以运行,但是没法播放设置页面中的播放链接,选中设备那里是空的,是不是没成功?怎么调整?


评论 17 - hanxi

image 在docker里可以运行,但是没法播放设置页面中的播放链接,选中设备那里是空的,是不是没成功?怎么调整?

设置页面输入小米的账号密码后,再勾选一个设备。


评论 18 - agigogo

image 在docker里可以运行,但是没法播放设置页面中的播放链接,选中设备那里是空的,是不是没成功?怎么调整?

设置页面输入小米的账号密码后,再勾选一个设备。

设置页面里没有勾选的选项

image 在docker里可以运行,但是没法播放设置页面中的播放链接,选中设备那里是空的,是不是没成功?怎么调整?

设置页面输入小米的账号密码后,再勾选一个设备。

image 设置页面没有可勾选项?


评论 19 - agigogo

image 在docker里可以运行,但是没法播放设置页面中的播放链接,选中设备那里是空的,是不是没成功?怎么调整?

设置页面输入小米的账号密码后,再勾选一个设备。

设置页面里没有勾选的选项

image 在docker里可以运行,但是没法播放设置页面中的播放链接,选中设备那里是空的,是不是没成功?怎么调整?

设置页面输入小米的账号密码后,再勾选一个设备。

image 设置页面没有可勾选项?

image

显示未检测到设备,设备型号是MDZ-25-DA


评论 20 - hanxi

@agigogo 应该是登陆失败了,局域网的手机重新登陆一下小爱音箱app吧。


评论 21 - agigogo

@agigogo 应该是登陆失败了,局域网的手机重新登陆一下小爱音箱app吧。

一直都是用米家APP来控制小爱音箱,那要下个小爱音响APP试一试


评论 22 - agigogo

@agigogo 应该是登陆失败了,局域网的手机重新登陆一下小爱音箱app吧。

搞定了,小爱音箱app重新绑定就可以了。真6~


评论 23 - Tueafternoon

一首歌结束不能自动切到下一首,随机播放模式,日志中显示下一曲定时器不见了....这个是咋回事啊


评论 24 - hanxi

一首歌结束不能自动切到下一首,随机播放模式,日志中显示下一曲定时器不见了....这个是咋回事啊

可能是音乐文件有问题,获取歌曲长度失败,你可以把歌曲文件上传一下给我测试。


评论 25 - hanxi

或者搜下日志里有没有 不会设置下一首歌的定时器 这个


评论 26 - Tueafternoon

或者搜下日志里有没有 不会设置下一首歌的定时器 这个

有这个,应该是我的文件格式问题,晚上我处理一下再试试


评论 27 - zealler9560

Screenshot_2024-10-31-23-28-57-903_com.android.chrome.jpg

istore系统可以拉取创建镜像,但是无法启动,错误提示见图一,求助大佬!路由器信息见图二Screenshot_2024-10-31-23-36-42-846-edit_com.android.chrome.jpg


评论 28 - adidas004

在群晖使用docker安装的,只能通过重装来升级到最新版本吗?有快捷的一键升级吗


评论 29 - hanxi

在群晖使用docker安装的,只能通过重装来升级到最新版本吗?有快捷的一键升级吗

不会命令行,就用这个工具吧。 https://github.com/onlyLTY/dockerCopilot


评论 30 - adidas004

谢谢您的工具,我刚去群晖的docker上有提示直接升级,还是非常的感觉你的回答

------------------ 原始邮件 ------------------ 发件人: @.>; 发送时间: 2024年11月11日(星期一) 下午4:20 收件人: @.>; 抄送: @.>; @.>; 主题: Re: [hanxi/xiaomusic] 💬 FAQ问题集合 (Issue #99)

在群晖使用docker安装的,只能通过重装来升级到最新版本吗?有快捷的一键升级吗

不会命令行,就用这个工具吧。 https://github.com/onlyLTY/dockerCopilot

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>


评论 31 - sinojelly

运行时遇到下面问题,请问要怎么排查?

2024/11/21 19:27:00  xiaomusic.py: error: unrecognized arguments: .venv/bin/python3 xiaomusic.py
-2024/11/21 19:27:00         [--enable_config_example]
-2024/11/21 19:27:00         [--ffmpeg_location FFMPEG_LOCATION]
-2024/11/21 19:27:00         [--config CONFIG]
-2024/11/21 19:27:00         [--verbose]
-2024/11/21 19:27:00         [--cookie COOKIE]
-2024/11/21 19:27:00         [--password PASSWORD]
-2024/11/21 19:27:00         [--account ACCOUNT]
-2024/11/21 19:27:00         [--hardware HARDWARE]
-2024/11/21 19:27:00         [--port PORT]
-2024/11/21 19:27:00         [-h]
-2024/11/21 19:27:00  usage: xiaomusic.py

评论 32 - hanxi

运行时遇到下面问题,请问要怎么排查?

2024/11/21 19:27:00  xiaomusic.py: error: unrecognized arguments: .venv/bin/python3 xiaomusic.py
-2024/11/21 19:27:00         [--enable_config_example]
-2024/11/21 19:27:00         [--ffmpeg_location FFMPEG_LOCATION]
-2024/11/21 19:27:00         [--config CONFIG]
-2024/11/21 19:27:00         [--verbose]
-2024/11/21 19:27:00         [--cookie COOKIE]
-2024/11/21 19:27:00         [--password PASSWORD]
-2024/11/21 19:27:00         [--account ACCOUNT]
-2024/11/21 19:27:00         [--hardware HARDWARE]
-2024/11/21 19:27:00         [--port PORT]
-2024/11/21 19:27:00         [-h]
-2024/11/21 19:27:00  usage: xiaomusic.py

看不出来


评论 33 - sinojelly

请问登录验证失败要怎么定位?小米登录邮箱,还是小米id 都报同样的错。

2024/11/25 0:50:44  [2024-11-25 00:50:44] [0.3.48] [ERROR] xiaomusic.py:259: /root/.mi.token file not exist
-2024/11/25 0:50:44  Exception: Error https://api2.mina.mi.com/admin/v2/device_list?master=0&requestId=app_ios_: Login failed
-2024/11/25 0:50:44      raise Exception(f"Error {url}: {resp}")
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request
-2024/11/25 0:50:44      return await self.account.mi_request(
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request
-2024/11/25 0:50:44      result = await self.mina_request("/admin/v2/device_list?master=" + str(master))
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 54, in device_list
-2024/11/25 0:50:44      hardware_data = await self.mina_service.device_list()
-2024/11/25 0:50:44    File "/app/xiaomusic/xiaomusic.py", line 232, in try_update_device_id
-2024/11/25 0:50:44  Traceback (most recent call last):
-2024/11/25 0:50:44  [2024-11-25 00:50:44] [0.3.48] [ERROR] xiaomusic.py:251: Execption Error https://api2.mina.mi.com/admin/v2/device_list?master=0&requestId=app_ios_xx: Login failed
-2024/11/25 0:50:44  Exception: {'qs': '%3Fsid%3Dmicoapi%26_json%3Dtrue', 'code': 70016, 'description': '登录验证失败', 'securityStatus': 0, '_sign': 'xxx', 'sid': 'micoapi', 'result': 'error', 'captchaUrl': None, 'callback': 'https://api2.mina.mi.com/sts', 'location': '', 'pwd': 0, 'child': 0, 'desc': '登录验证失败'}
-2024/11/25 0:50:44      raise Exception(resp)
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 69, in login

评论 34 - hanxi

请问登录验证失败要怎么定位?小米登录邮箱,还是小米id 都报同样的错。

2024/11/25 0:50:44  [2024-11-25 00:50:44] [0.3.48] [ERROR] xiaomusic.py:259: /root/.mi.token file not exist
-2024/11/25 0:50:44  Exception: Error https://api2.mina.mi.com/admin/v2/device_list?master=0&requestId=app_ios_: Login failed
-2024/11/25 0:50:44      raise Exception(f"Error {url}: {resp}")
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request
-2024/11/25 0:50:44      return await self.account.mi_request(
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request
-2024/11/25 0:50:44      result = await self.mina_request("/admin/v2/device_list?master=" + str(master))
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 54, in device_list
-2024/11/25 0:50:44      hardware_data = await self.mina_service.device_list()
-2024/11/25 0:50:44    File "/app/xiaomusic/xiaomusic.py", line 232, in try_update_device_id
-2024/11/25 0:50:44  Traceback (most recent call last):
-2024/11/25 0:50:44  [2024-11-25 00:50:44] [0.3.48] [ERROR] xiaomusic.py:251: Execption Error https://api2.mina.mi.com/admin/v2/device_list?master=0&requestId=app_ios_xx: Login failed
-2024/11/25 0:50:44  Exception: {'qs': '%3Fsid%3Dmicoapi%26_json%3Dtrue', 'code': 70016, 'description': '登录验证失败', 'securityStatus': 0, '_sign': 'xxx', 'sid': 'micoapi', 'result': 'error', 'captchaUrl': None, 'callback': 'https://api2.mina.mi.com/sts', 'location': '', 'pwd': 0, 'child': 0, 'desc': '登录验证失败'}
-2024/11/25 0:50:44      raise Exception(resp)
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 69, in login

上面给出了4个办法都试过了吗?局域网登陆mi.com了?


评论 35 - wusemao

设置web访问登录时,账号密码设置完之后登不进去了,账号名称用的中文的可以么


评论 36 - hanxi

设置web访问登录时,账号密码设置完之后登不进去了,账号名称用的中文的可以么

不确定是否可以,你可以考虑setting.json里的内容,不行就修改再重启。


评论 37 - quanmao

运行后会在音乐目录下生成一个tmp文件夹,如何指定路径,因为还有其应用访问音乐路径,不希望其识别tmp目录下的音乐。


评论 38 - hanxi

运行后会在音乐目录下生成一个tmp文件夹,如何指定路径,因为还有其应用访问音乐路径,不希望其识别tmp目录下的音乐。

忽略目录(逗号分割) 改成 @eaDir,tmp 即可。


评论 39 - quanmao

运行后会在音乐目录下生成一个tmp文件夹,如何指定路径,因为还有其应用访问音乐路径,不希望其识别tmp目录下的音乐。

忽略目录(逗号分割) 改成 @eaDir,tmp 即可。

抱歉,是我没有说清楚,是运行xiaomusic后会在音乐路径下生成tmp文件夹, 但我同时还在用navidrome,也会访问音乐目录,他会把tmp目录下的歌曲也扫描进去,所以想移动tmp目录。 navidrome没找到在哪里可以设置,忽略这个文件夹


评论 40 - hanxi

运行后会在音乐目录下生成一个tmp文件夹,如何指定路径,因为还有其应用访问音乐路径,不希望其识别tmp目录下的音乐。

忽略目录(逗号分割) 改成 @eaDir,tmp 即可。

抱歉,是我没有说清楚,是运行xiaomusic后会在音乐路径下生成tmp文件夹, 但我同时还在用navidrome,也会访问音乐目录,他会把tmp目录下的歌曲也扫描进去,所以想移动tmp目录。 navidrome没找到在哪里可以设置,忽略这个文件夹

提个新 issue 吧,有空加下配置项。


评论 41 - CallEdison

imageimage 问题一:能进控制面板,进不了设置页面,容器没有log生成,我昨天已经设置好了,现在功能能正常使用,但是进不了设置页面了问题二:昨天能进的时候发现本地下载目录有歌曲,但是设置里面的全部歌曲里面没有,搜索框搜索又能搜的到。


评论 42 - hanxi

image image 问题一:能进控制面板,进不了设置页面,容器没有log生成,我昨天已经设置好了,现在功能能正常使用,但是进不了设置页面了问题二:昨天能进的时候发现本地下载目录有歌曲,但是设置里面的全部歌曲里面没有,搜索框搜索又能搜的到。

问题一:打不开的地址是哪个? 问题二:可以点击刷新列表按钮试试。


评论 43 - huahua-er

是关闭了网络搜索了吗?现在的搜索只有本地数据没有网络歌曲了?


评论 44 - CallEdison

默认主题有歌曲

pure主题没有歌曲

xmusicPlayer也没有歌曲

 

Edison @.***

 

------------------ 原始邮件 ------------------ 发件人: @.>; 发送时间: 2024年11月30日(星期六) 上午6:26 收件人: @.>; 抄送: @.>; @.>; 主题: Re: [hanxi/xiaomusic] 💬 FAQ问题集合 (Issue #99)

是关闭了网络搜索了吗?现在的搜索只有本地数据没有网络歌曲了?

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>


评论 45 - hanxi

是关闭了网络搜索了吗?现在的搜索只有本地数据没有网络歌曲了?

等 yt-dlp 修复。


评论 46 - hanxi

默认主题有歌曲 pure主题没有歌曲 xmusicPlayer也没有歌曲   Edison @.***   ------------------ 原始邮件 ------------------ 发件人: @.>; 发送时间: 2024年11月30日(星期六) 上午6:26 收件人: @.>; 抄送: @.>; @.>; 主题: Re: [hanxi/xiaomusic] 💬 FAQ问题集合 (Issue #99) 是关闭了网络搜索了吗?现在的搜索只有本地数据没有网络歌曲了? — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>

需要刷新缓存


评论 47 - like1020

Screenshot_2024-12-03-06-58-52-853_com yjllq kito 请教一下,本地列表歌单里的歌曲即便设置为全部循环或随机播放,依然是不断地单曲循环,只能自己手动点下一首,请问是什么情况?


评论 48 - tchgtr

请问网络搜索功能修复了吗?感谢!


评论 49 - hanxi

请问网络搜索功能修复了吗?感谢!

修复了。


评论 50 - tchgtr

请问网络搜索功能修复了吗?感谢!

修复了。

我已经更新到最新版0.3.55,音箱是LX04触屏音箱,但是使用指令对小爱说“播放歌曲。。。“还是通过音箱绑定的QQ音乐播放,解绑了QQ音乐账号,就会提示先让我绑定账号(包括播放本地歌曲指令),感觉音箱还没跟docker连接上(已经重启过容器)。但是通过8090端口进入后台,搜索歌曲,播放,调节声音大小,单曲、随机所有的功能都可以通过按键实现,唯独不能通过语音跟小爱对话播放我指定的内容,请问这个是什么问题,感谢!


评论 51 - hanxi

请问网络搜索功能修复了吗?感谢!

修复了。

我已经更新到最新版0.3.55,音箱是LX04触屏音箱,但是使用指令对小爱说“播放歌曲。。。“还是通过音箱绑定的QQ音乐播放,解绑了QQ音乐账号,就会提示先让我绑定账号(包括播放本地歌曲指令),感觉音箱还没跟docker连接上(已经重启过容器)。但是通过8090端口进入后台,搜索歌曲,播放,调节声音大小,单曲、随机所有的功能都可以通过按键实现,唯独不能通过语音跟小爱对话播放我指定的内容,请问这个是什么问题,感谢!

贴下你的 setting.json 文件看看吧,把里面的账号密码删除。


评论 52 - tchgtr

请问网络搜索功能修复了吗?感谢!

修复了。

我已经更新到最新版0.3.55,音箱是LX04触屏音箱,但是使用指令对小爱说“播放歌曲。。。“还是通过音箱绑定的QQ音乐播放,解绑了QQ音乐账号,就会提示先让我绑定账号(包括播放本地歌曲指令),感觉音箱还没跟docker连接上(已经重启过容器)。但是通过8090端口进入后台,搜索歌曲,播放,调节声音大小,单曲、随机所有的功能都可以通过按键实现,唯独不能通过语音跟小爱对话播放我指定的内容,请问这个是什么问题,感谢!

贴下你的 setting.json 文件看看吧,把里面的账号密码删除。

{ "account": "", "password": "", "mi_did": "603807070", "miio_tts_command": "", "cookie": "", "verbose": false, "music_path": "music", "temp_path": "music/tmp", "download_path": "music/download", "conf_path": "conf", "cache_dir": "cache", "hostname": "192.168.31.159", "port": 8090, "public_port": 0, "proxy": "", "search_prefix": "bilisearch:", "ffmpeg_location": "./ffmpeg/bin", "active_cmd": "play,set_play_type_rnd,playlocal,play_music_list,play_music_list_index,stop_after_minute,stop", "exclude_dirs": "@eaDir,tmp", "music_path_depth": 10, "disable_httpauth": true, "httpauth_username": "", "httpauth_password": "", "music_list_url": "", "music_list_json": "", "custom_play_list_json": "", "disable_download": false, "key_word_dict": { "下一首": "play_next", "上一首": "play_prev", "单曲循环": "set_play_type_one", "全部循环": "set_play_type_all", "随机播放": "set_play_type_rnd", "单曲播放": "set_play_type_sin", "顺序播放": "set_play_type_seq", "分钟后关机": "stop_after_minute", "刷新列表": "gen_music_list", "加入收藏": "add_to_favorites", "收藏歌曲": "add_to_favorites", "取消收藏": "del_from_favorites", "播放列表第": "play_music_list_index", "播放本地歌曲": "playlocal", "本地播放歌曲": "playlocal", "播放歌曲": "play", "放歌曲": "play", "关机": "stop", "暂停": "stop", "停止": "stop", "停止播放": "stop", "播放列表": "play_music_list", "播放歌单": "play_music_list", "测试自定义口令": "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": "xiaomusic.log.txt", "fuzzy_match_cutoff": 0.6, "enable_fuzzy_match": true, "stop_tts_msg": "收到,再见", "enable_config_example": false, "keywords_playlocal": "播放本地歌曲,本地播放歌曲", "keywords_play": "播放歌曲,放歌曲", "keywords_stop": "关机,暂停,停止,停止播放", "keywords_playlist": "播放列表,播放歌单", "user_key_word_dict": { "测试自定义口令": "exec#code1("hello")", "测试链接": "exec#httpget("https://github.com/hanxi/xiaomusic\\")" }, "enable_force_stop": false, "devices": { "603807070": { "did": "603807070", "device_id": "60b8f875-4101-416a-9278-4d4170929b4d", "hardware": "LX04", "name": "小爱触屏音箱", "play_type": 1, "cur_music": "七里香", "cur_playlist": "临时搜索列表" } }, "group_list": "", "remove_id3tag": false, "convert_to_mp3": false, "delay_sec": 3, "continue_play": false, "pull_ask_sec": 1, "crontab_json": "", "enable_yt_dlp_cookies": false, "get_ask_by_mina": true, "play_type_one_tts_msg": "已经设置为单曲循环", "play_type_all_tts_msg": "已经设置为全部循环", "play_type_rnd_tts_msg": "已经设置为随机播放", "play_type_sin_tts_msg": "已经设置为单曲播放", "play_type_seq_tts_msg": "已经设置为顺序播放", "recently_added_playlist_len": 50 }


评论 53 - hanxi

get_ask_by_mina 【特殊型号获取对话记录】这个需要设置为 false


评论 54 - tchgtr

get_ask_by_mina 【特殊型号获取对话记录】这个需要设置为 false

已经重新设置并重启容器,尝试还是不行,会不会是这个触屏音箱不支持?


评论 55 - tchgtr

get_ask_by_mina 【特殊型号获取对话记录】这个需要设置为 false

已经重新设置并重启容器,尝试还是不行,会不会是这个触屏音箱不支持?

或者是在创建容器的时候,我的小米账号填写了我的手机号码,应该需要填写实际的小米账号?因为手机号码对应的小米账号,和输入对应的密码,都是可以登陆的,但是我不知道有什么区别,就把手机号码输入进去了


评论 56 - tchgtr

get_ask_by_mina 【特殊型号获取对话记录】这个需要设置为 false

已经重新设置并重启容器,尝试还是不行,会不会是这个触屏音箱不支持?

或者是在创建容器的时候,我的小米账号填写了我的手机号码,应该需要填写实际的小米账号?因为手机号码对应的小米账号,和输入对应的密码,都是可以登陆的,但是我不知道有什么区别,就把手机号码输入进去了

我重新创建容器,问题解决了,主要是两个地方改变了,一个是上面的小米账号的问题,另外一个是关于conf和music这两个文件夹,这次我都把他们放在/container/xiaomusic下,即/container/xiaomusic/conf(music),分别对应两个容器和主机app/conf(music),问题就解决了。之前我是分开在两个文件夹的。


评论 57 - tianting123

0 B的APE文件, 建议直接跳过播放


评论 58 - oklrc

新人请教:使用DOCKER镜像 或者 composer安装 如何升级到最新版本呢?是删除镜像再重新拉取吗


评论 59 - hanxi

新人请教:使用DOCKER镜像 或者 composer安装 如何升级到最新版本呢?是删除镜像再重新拉取吗

docker compose pull
-docker compose up -d

评论 60 - zhiquanchi

我在阿里云的服务器上运行的docker,我登录了小米账号,但是 操控面板 里面 不显示我的设备。音箱是pro LX06


链接到 GitHub Issue

`,292)]))}const m=t(o,[["render",n]]);export{d as __pageData,m as default}; diff --git a/docs/.vitepress/dist/assets/issues_index.md.Do1eTGGf.js b/docs/.vitepress/dist/assets/issues_index.md.Do1eTGGf.js deleted file mode 100644 index b3cc937..0000000 --- a/docs/.vitepress/dist/assets/issues_index.md.Do1eTGGf.js +++ /dev/null @@ -1,65 +0,0 @@ -import{_ as s,c as a,a0 as t,o as l}from"./chunks/framework.p2VkXzrt.js";const c=JSON.parse('{"title":"XiaoMusic: 无限听歌,解放小爱音箱","description":"","frontmatter":{},"headers":[],"relativePath":"issues/index.md","filePath":"issues/index.md","lastUpdated":null}'),h={name:"issues/index.md"};function e(n,i,p,k,r,o){return l(),a("div",null,i[0]||(i[0]=[t(`

XiaoMusic: 无限听歌,解放小爱音箱

GitHub LicenseDocker Image VersionDocker PullsPyPI - VersionPyPI - DownloadsPython Version from PEP 621 TOMLGitHub ReleaseVisitorsVisitors

使用小爱音箱播放音乐,音乐使用 yt-dlp 下载。

https://github.com/hanxi/xiaomusic

TIP

初次安装遇到问题请查阅 💬 FAQ问题集合 ,一般遇到的问题都已经有解决办法。

👋 最简配置运行

已经支持在 web 页面配置其他参数,docker 启动命令如下:

bash
docker run -p 8090:8090 -v /xiaomusic/music:/app/music -v /xiaomusic/conf:/app/conf hanxi/xiaomusic

🔥 国内:

bash
docker run -p 8090:8090 -v /xiaomusic/music:/app/music -v /xiaomusic/conf:/app/conf m.daocloud.io/docker.io/hanxi/xiaomusic

对应的 docker compose 配置如下:

yaml
services:
-  xiaomusic:
-    image: hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 8090:8090
-    volumes:
-      - /xiaomusic/music:/app/music
-      - /xiaomusic/conf:/app/conf

🔥 国内:

yaml
services:
-  xiaomusic:
-    image: m.daocloud.io/docker.io/hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 8090:8090
-    volumes:
-      - /xiaomusic/music:/app/music
-      - /xiaomusic/conf:/app/conf

其中 conf 目录为配置文件存放目录,music 目录为音乐存放目录,建议分开配置为不同的目录。

NOTE

上面配置的 /xiaomusic/music 和 /xiaomusic/conf 是 docker 主机里的 /xiaomusic 目录下的,可以修改为其他目录。如果报错找不到 /xiaomusic/music 目录,可以先执行 mkdir -p /xiaomusic/{music,conf} 命令新建目录。

docker 和 docker compose 二选一即可,启动成功后,在 web 页面可以配置其他参数,带有 * 号的配置是必须要配置的,其他的用不上时不用修改。初次配置时需要在页面上输入小米账号和密码保存后才能获取到设备列表。

TIP

目前安装步骤已经是最简化了,如果还是嫌安装麻烦,可以微信或者 QQ 约我远程安装,我一般周末和晚上才有时间,收个辛苦费 💰 50 元一次,安装失败不收费。

🔥 修改默认8090端口映射

方法1: 不修改监听端口 8090

【监听端口】保持为默认的 8090 不变,把【外网访问端口】改为 5678 。

yaml
services:
-  xiaomusic:
-    image: hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 5678:8090
-    volumes:
-      - /xiaomusic/music:/app/music
-      - /xiaomusic/conf:/app/conf
-    environment:
-      XIAOMUSIC_PUBLIC_PORT: 5678

XIAOMUSIC_PUBLIC_PORT 对应后台设置里的【外网访问端口】,修改后可以不用重启。

方法2: 修改监听端口 8090 为 5678

如果需要修改 8090 端口为其他端口,比如 5678,需要这样配,3个数字都需要是 5678 。见 https://github.com/hanxi/xiaomusic/issues/19

yaml
services:
-  xiaomusic:
-    image: hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 5678:5678
-    volumes:
-      - /xiaomusic/music:/app/music
-      - /xiaomusic/conf:/app/conf
-    environment:
-      XIAOMUSIC_PORT: 5678

如果不是首次修改端口,还需要修改 /xiaomusic/conf/setting.json 文件里的端口(也可以在后台修改监听端口后重启)。

遇到问题可以去 web 设置页面底部点击【下载日志文件】按钮,然后搜索一下日志文件内容确保里面没有账号密码信息后(有就删除这些敏感信息),然后在提 issues 反馈问题时把下载的日志文件带上。

IMPORTANT

XIAOMUSIC_PORT 也可以在后台配置,对应的是监听端口,修改后记得重启。

🤐 支持语音口令

  • 【播放歌曲】,播放本地的歌曲
  • 【播放歌曲+歌名】,比如:播放歌曲周杰伦晴天
  • 【上一首】
  • 【下一首】
  • 【单曲循环】
  • 【全部循环】
  • 【随机播放】
  • 【关机】,【停止播放】,两个效果是一样的。
  • 【刷新列表】,当复制了歌曲进 music 目录后,可以用这个口令刷新歌单。
  • 【播放列表+列表名】,比如:播放列表其他。
  • 【加入收藏】,把当前播放的歌曲加入收藏歌单。
  • 【取消收藏】,把当前播放的歌曲从收藏歌单里移除。
  • 【播放列表收藏】,这个用于播放收藏歌单。
  • 【播放本地歌曲+歌名】,这个口令和播放歌曲的区别是本地找不到也不会去下载。
  • 【播放列表第几个+列表名】,具体见: https://github.com/hanxi/xiaomusic/issues/158
  • 【搜索播放+关键词】,会搜索关键词作为临时搜索列表播放,比如说【搜索播放林俊杰】,会播放所有林俊杰的歌。
  • 【本地搜索播放+关键词】,跟搜索播放的区别是本地找不到也不会去下载。

TIP

隐藏玩法: 对小爱同学说播放歌曲小猪佩奇的故事,会先下载小猪佩奇的故事,然后再播放小猪佩奇的故事。

🛠️ pip 方式安装运行

shell
> pip install -U xiaomusic
-> xiaomusic --help
- __  __  _                   __  __                 _
- \\ \\/ / (_)   __ _    ___   |  \\/  |  _   _   ___  (_)   ___
-  \\  /  | |  / _\` |  / _ \\  | |\\/| | | | | | / __| | |  / __|
-  /  \\  | | | (_| | | (_) | | |  | | | |_| | \\__ \\ | | | (__
- /_/\\_\\ |_|  \\__,_|  \\___/  |_|  |_|  \\__,_| |___/ |_|  \\___|
-          XiaoMusic v0.3.37 by: github.com/hanxi
-
-usage: xiaomusic [-h] [--port PORT] [--hardware HARDWARE] [--account ACCOUNT]
-                 [--password PASSWORD] [--cookie COOKIE] [--verbose]
-                 [--config CONFIG] [--ffmpeg_location FFMPEG_LOCATION]
-
-options:
-  -h, --help            show this help message and exit
-  --port PORT           监听端口
-  --hardware HARDWARE   小爱音箱型号
-  --account ACCOUNT     xiaomi account
-  --password PASSWORD   xiaomi password
-  --cookie COOKIE       xiaomi cookie
-  --verbose             show info
-  --config CONFIG       config file path
-  --ffmpeg_location FFMPEG_LOCATION
-                        ffmpeg bin path
-> xiaomusic --config config.json

其中 config.json 文件可以参考 config-example.json 文件配置。见 https://github.com/hanxi/xiaomusic/issues/94

不修改默认端口 8090 的情况下,只需要执行 xiaomusic 即可启动。

🔩 开发环境运行

  • 使用 install_dependencies.sh 下载依赖
  • 使用 pdm 安装环境
  • 默认监听了端口 8090 , 使用其他端口自行修改。
shell
pdm run xiaomusic.py

如果是开发前端界面,可以通过 http://localhost:8090/docs 查看有什么接口。目前的 web 控制台非常简陋,欢迎有兴趣的朋友帮忙实现一个漂亮的前端,需要什么接口可以随时提需求。

🚦 代码提交规范

提交前请执行

pdm lintfmt

用于检查代码和格式化代码。

本地编译 Docker Image

shell
docker build -t xiaomusic .

技术栈

  • 后端代码使用 Python 语言编写。
  • HTTP 服务使用的是 FastAPI 框架,早期版本使用的是 Flask
  • 使用了 Docker ,在 NAS 上安装更方便。
  • 默认的前端主题使用了 jQuery 。

已测试支持的设备

型号名称
L06A小爱音箱
L07ARedmi小爱音箱 Play
S12/S12A/MDZ-25-DA小米AI音箱
LX5A小爱音箱 万能遥控版
LX05小爱音箱Play(2019款)
L15A小米AI音箱(第二代)
L16AXiaomi Sound
L17AXiaomi Sound Pro
LX06小爱音箱Pro
LX01小爱音箱mini
L05B小爱音箱Play
L05C小米小爱音箱Play 增强版
L09A小米音箱Art
LX04 X10A X08A已经支持的触屏版
X08C X08E X8F需要设置【型号兼容模式】选项为 true
M01/XMYX01JY小米小爱音箱HD 需要设置【特殊型号获取对话记录】选项为 true 才能语音播放

型号与产品名称对照可以在这里查询 https://home.miot-spec.com/s/xiaomi.wifispeaker

NOTE

如果你的设备支持播放,请反馈给我添加到支持列表里,谢谢。 目前应该所有设备类型都已经支持播放,有问题随时反馈。 其他触屏版不能播放可以设置【型号兼容模式】选项为 true 试试。见 https://github.com/hanxi/xiaomusic/issues/30

🎵 支持音乐格式

  • mp3
  • flac
  • wav
  • ape
  • ogg
  • m4a

NOTE

本地音乐会搜索目录下上面格式的文件,下载的歌曲是 mp3 格式的。 已知 L05B L05C LX06 L16A 不支持 flac 格式。 如果格式不能播放可以打开【转换为MP3】和【型号兼容模式】选项。具体见 https://github.com/hanxi/xiaomusic/issues/153#issuecomment-2328168689

💡 简易的控制面板

浏览器进入 http://192.168.2.5:8090

  • ip 是 XIAOMUSIC_HOSTNAME 设置的
  • 8090 是默认端口
  • 支持功能
    • 显示正在播放的歌曲
    • 模糊搜索本地歌曲
    • 播放列表
    • 删除歌曲
    • 设置页面
    • 配置网络歌单
    • 日志文件下载

采用新的设置页面之后,没有必须在启动前配置的环境变量了,除非是改默认的 8090 端口才需要配置环境变量。

🌏 网络歌单功能

可以配置一个 json 格式的歌单,支持电台和歌曲,也可以直接用别人分享的链接,同时配备了 m3u 文件格式转换工具,可以很方便的把 m3u 电台文件转换成网络歌单格式的 json 文件,具体用法见 https://github.com/hanxi/xiaomusic/issues/78

NOTE

欢迎有想法的朋友们制作更多的歌单转换工具。

🍺 更多其他可选配置

  • XIAOMUSIC_ACTIVE_CMD 环境变量,对应后台的 【允许唤醒的命令】,用于唤醒口令,配置成'play,random_play',在非播放状态下,只有这两个指令(播放歌曲和随机播放)可以触发,触发后,xiaomusic进入playing状态,其他指令则可以正常触发。具体见 https://github.com/hanxi/xiaomusic/pull/43
  • XIAOMUSIC_EXCLUDE_DIRS 配置歌曲目录里需要忽略的目录,对应后台的 【忽略目录】
  • XIAOMUSIC_MUSIC_PATH_DEPTH 配置歌曲目录搜索深度,对应后台的 【目录深度】,具体见 https://github.com/hanxi/xiaomusic/issues/76
  • XIAOMUSIC_DISABLE_HTTPAUTH 配置成 false 表示开启密码访问web控制台,对应后台的 【关闭密码验证】,具体见 https://github.com/hanxi/xiaomusic/issues/47
  • XIAOMUSIC_HTTPAUTH_USERNAME 配置 web 控制台用户,对应后台的 【控制台账户】
  • XIAOMUSIC_HTTPAUTH_PASSWORD 配置 web 控制台密码,对应后台的 【控制台密码】
  • XIAOMUSIC_CONF_PATH 用来存放配置文件的目录,对应后台的 【配置文件目录】,记得把目录映射到主机,默认为 /app/config ,具体见 https://github.com/hanxi/xiaomusic/issues/74
  • XIAOMUSIC_CACHE_DIR 用来音乐 tag 缓存,默认为 /app/cache,对应后台的 【缓存文件目录】。
  • XIAOMUSIC_DISABLE_DOWNLOAD 设为 true 时关闭下载功能,对应后台的 【关闭下载功能】,见 https://github.com/hanxi/xiaomusic/issues/82
  • XIAOMUSIC_USE_MUSIC_API 设为 true 时使用 player_play_music 接口播放音乐,对应后台的 【型号兼容模式】,用于兼容不能播放的型号,如果发现需要设置这个选项的时候请告知我加一下设备型号,方便以后不用设置。 见 https://github.com/hanxi/xiaomusic/issues/30
  • XIAOMUSIC_KEYWORDS_PLAY 用来播放歌曲的口令前缀,对应后台的 【播放歌曲口令】,默认是 "播放歌曲,放歌曲" ,可以用英文逗号分割配置多个
  • XIAOMUSIC_KEYWORDS_STOP 用来关机的口令,对应后台的 【停止口令】,默认是 "关机,暂停,停止" ,可以用英文逗号分割配置多个。
  • XIAOMUSIC_KEYWORDS_PLAYLOCAL 用来播放本地歌曲的口令前缀,对应后台的 【播放本地歌曲口令】,本地找不到时不会下载歌曲,默认是 "播放本地歌曲,本地播放歌曲" ,可以用英文逗号分割配置多个。
  • XIAOMUSIC_ENABLE_FUZZY_MATCH 设为 true 时开启模糊匹配(默认),设为 false 时关闭模糊匹配,对应后台的 【开启模糊搜索】,支持模糊匹配歌名和歌单名。 具体见 https://github.com/hanxi/xiaomusic/issues/52
  • XIAOMUSIC_FUZZY_MATCH_CUTOFF 设置模糊搜索匹配的最低相似度阈值(默认0.6,可以配0到1直接的小数),越小越模糊,越大越精准,对应后台的 【模糊匹配阈值】。具体见 https://github.com/hanxi/xiaomusic/issues/52
  • XIAOMUSIC_PUBLIC_PORT 用于设置外网端口,对应后台的 【外网访问端口】,当使用反向代理时可以设置为外网端口,XIAOMUSIC_HOSTNAME 设为外网IP或者域名即可。
  • XIAOMUSIC_DOWNLOAD_PATH 变量可以配置下载目录,默认为空,表示使用 music 目录为下载目录,对应后台的 【音乐下载目录】。设置这个目录必须是 music 的子目录,否则刷新列表后会找不到歌曲。具体见 https://github.com/hanxi/xiaomusic/issues/98
  • XIAOMUSIC_PROXY 用于配置国内使用 youtube 源下载歌曲时使用的代理,参数格式参考 yt-dlp 文档说明。 见 https://github.com/hanxi/xiaomusic/issues/2https://github.com/hanxi/xiaomusic/issues/11
  • MIIO_TTS_CMD 用于部分机型(如:L05C)使用 MiIO 支持 tts 能力,默认为空,命令选择见 MiService-fork 文档

⚠️ 安全提醒

IMPORTANT

  1. 如果配置了公网访问 xiaomusic ,请一定要开启密码登陆,并设置复杂的密码。且不要在公共场所的 WiFi 环境下使用,否则可能造成小米账号密码泄露。
  2. 强烈不建议将小爱音箱的小米账号绑定摄像头,代码难免会有 bug ,一旦小米账号密码泄露,可能监控录像也会泄露。

🤔 高级篇

📢 讨论区

❤️ 感谢

👉 其他教程

更多功能见 📝 文档汇总

🚨 免责声明

本项目仅供学习和研究目的,不得用于任何商业活动。用户在使用本项目时应遵守所在地区的法律法规,对于违法使用所导致的后果,本项目及作者不承担任何责任。 本项目可能存在未知的缺陷和风险(包括但不限于设备损坏和账号封禁等),使用者应自行承担使用本项目所产生的所有风险及责任。 作者不保证本项目的准确性、完整性、及时性、可靠性,也不承担任何因使用本项目而产生的任何损失或损害责任。 使用本项目即表示您已阅读并同意本免责声明的全部内容。

Star History

Star History Chart

赞赏

License

MIT License © 2024 涵曦

`,82)]))}const g=s(h,[["render",e]]);export{c as __pageData,g as default}; diff --git a/docs/.vitepress/dist/assets/issues_index.md.Do1eTGGf.lean.js b/docs/.vitepress/dist/assets/issues_index.md.Do1eTGGf.lean.js deleted file mode 100644 index b3cc937..0000000 --- a/docs/.vitepress/dist/assets/issues_index.md.Do1eTGGf.lean.js +++ /dev/null @@ -1,65 +0,0 @@ -import{_ as s,c as a,a0 as t,o as l}from"./chunks/framework.p2VkXzrt.js";const c=JSON.parse('{"title":"XiaoMusic: 无限听歌,解放小爱音箱","description":"","frontmatter":{},"headers":[],"relativePath":"issues/index.md","filePath":"issues/index.md","lastUpdated":null}'),h={name:"issues/index.md"};function e(n,i,p,k,r,o){return l(),a("div",null,i[0]||(i[0]=[t(`

XiaoMusic: 无限听歌,解放小爱音箱

GitHub LicenseDocker Image VersionDocker PullsPyPI - VersionPyPI - DownloadsPython Version from PEP 621 TOMLGitHub ReleaseVisitorsVisitors

使用小爱音箱播放音乐,音乐使用 yt-dlp 下载。

https://github.com/hanxi/xiaomusic

TIP

初次安装遇到问题请查阅 💬 FAQ问题集合 ,一般遇到的问题都已经有解决办法。

👋 最简配置运行

已经支持在 web 页面配置其他参数,docker 启动命令如下:

bash
docker run -p 8090:8090 -v /xiaomusic/music:/app/music -v /xiaomusic/conf:/app/conf hanxi/xiaomusic

🔥 国内:

bash
docker run -p 8090:8090 -v /xiaomusic/music:/app/music -v /xiaomusic/conf:/app/conf m.daocloud.io/docker.io/hanxi/xiaomusic

对应的 docker compose 配置如下:

yaml
services:
-  xiaomusic:
-    image: hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 8090:8090
-    volumes:
-      - /xiaomusic/music:/app/music
-      - /xiaomusic/conf:/app/conf

🔥 国内:

yaml
services:
-  xiaomusic:
-    image: m.daocloud.io/docker.io/hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 8090:8090
-    volumes:
-      - /xiaomusic/music:/app/music
-      - /xiaomusic/conf:/app/conf

其中 conf 目录为配置文件存放目录,music 目录为音乐存放目录,建议分开配置为不同的目录。

NOTE

上面配置的 /xiaomusic/music 和 /xiaomusic/conf 是 docker 主机里的 /xiaomusic 目录下的,可以修改为其他目录。如果报错找不到 /xiaomusic/music 目录,可以先执行 mkdir -p /xiaomusic/{music,conf} 命令新建目录。

docker 和 docker compose 二选一即可,启动成功后,在 web 页面可以配置其他参数,带有 * 号的配置是必须要配置的,其他的用不上时不用修改。初次配置时需要在页面上输入小米账号和密码保存后才能获取到设备列表。

TIP

目前安装步骤已经是最简化了,如果还是嫌安装麻烦,可以微信或者 QQ 约我远程安装,我一般周末和晚上才有时间,收个辛苦费 💰 50 元一次,安装失败不收费。

🔥 修改默认8090端口映射

方法1: 不修改监听端口 8090

【监听端口】保持为默认的 8090 不变,把【外网访问端口】改为 5678 。

yaml
services:
-  xiaomusic:
-    image: hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 5678:8090
-    volumes:
-      - /xiaomusic/music:/app/music
-      - /xiaomusic/conf:/app/conf
-    environment:
-      XIAOMUSIC_PUBLIC_PORT: 5678

XIAOMUSIC_PUBLIC_PORT 对应后台设置里的【外网访问端口】,修改后可以不用重启。

方法2: 修改监听端口 8090 为 5678

如果需要修改 8090 端口为其他端口,比如 5678,需要这样配,3个数字都需要是 5678 。见 https://github.com/hanxi/xiaomusic/issues/19

yaml
services:
-  xiaomusic:
-    image: hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 5678:5678
-    volumes:
-      - /xiaomusic/music:/app/music
-      - /xiaomusic/conf:/app/conf
-    environment:
-      XIAOMUSIC_PORT: 5678

如果不是首次修改端口,还需要修改 /xiaomusic/conf/setting.json 文件里的端口(也可以在后台修改监听端口后重启)。

遇到问题可以去 web 设置页面底部点击【下载日志文件】按钮,然后搜索一下日志文件内容确保里面没有账号密码信息后(有就删除这些敏感信息),然后在提 issues 反馈问题时把下载的日志文件带上。

IMPORTANT

XIAOMUSIC_PORT 也可以在后台配置,对应的是监听端口,修改后记得重启。

🤐 支持语音口令

  • 【播放歌曲】,播放本地的歌曲
  • 【播放歌曲+歌名】,比如:播放歌曲周杰伦晴天
  • 【上一首】
  • 【下一首】
  • 【单曲循环】
  • 【全部循环】
  • 【随机播放】
  • 【关机】,【停止播放】,两个效果是一样的。
  • 【刷新列表】,当复制了歌曲进 music 目录后,可以用这个口令刷新歌单。
  • 【播放列表+列表名】,比如:播放列表其他。
  • 【加入收藏】,把当前播放的歌曲加入收藏歌单。
  • 【取消收藏】,把当前播放的歌曲从收藏歌单里移除。
  • 【播放列表收藏】,这个用于播放收藏歌单。
  • 【播放本地歌曲+歌名】,这个口令和播放歌曲的区别是本地找不到也不会去下载。
  • 【播放列表第几个+列表名】,具体见: https://github.com/hanxi/xiaomusic/issues/158
  • 【搜索播放+关键词】,会搜索关键词作为临时搜索列表播放,比如说【搜索播放林俊杰】,会播放所有林俊杰的歌。
  • 【本地搜索播放+关键词】,跟搜索播放的区别是本地找不到也不会去下载。

TIP

隐藏玩法: 对小爱同学说播放歌曲小猪佩奇的故事,会先下载小猪佩奇的故事,然后再播放小猪佩奇的故事。

🛠️ pip 方式安装运行

shell
> pip install -U xiaomusic
-> xiaomusic --help
- __  __  _                   __  __                 _
- \\ \\/ / (_)   __ _    ___   |  \\/  |  _   _   ___  (_)   ___
-  \\  /  | |  / _\` |  / _ \\  | |\\/| | | | | | / __| | |  / __|
-  /  \\  | | | (_| | | (_) | | |  | | | |_| | \\__ \\ | | | (__
- /_/\\_\\ |_|  \\__,_|  \\___/  |_|  |_|  \\__,_| |___/ |_|  \\___|
-          XiaoMusic v0.3.37 by: github.com/hanxi
-
-usage: xiaomusic [-h] [--port PORT] [--hardware HARDWARE] [--account ACCOUNT]
-                 [--password PASSWORD] [--cookie COOKIE] [--verbose]
-                 [--config CONFIG] [--ffmpeg_location FFMPEG_LOCATION]
-
-options:
-  -h, --help            show this help message and exit
-  --port PORT           监听端口
-  --hardware HARDWARE   小爱音箱型号
-  --account ACCOUNT     xiaomi account
-  --password PASSWORD   xiaomi password
-  --cookie COOKIE       xiaomi cookie
-  --verbose             show info
-  --config CONFIG       config file path
-  --ffmpeg_location FFMPEG_LOCATION
-                        ffmpeg bin path
-> xiaomusic --config config.json

其中 config.json 文件可以参考 config-example.json 文件配置。见 https://github.com/hanxi/xiaomusic/issues/94

不修改默认端口 8090 的情况下,只需要执行 xiaomusic 即可启动。

🔩 开发环境运行

  • 使用 install_dependencies.sh 下载依赖
  • 使用 pdm 安装环境
  • 默认监听了端口 8090 , 使用其他端口自行修改。
shell
pdm run xiaomusic.py

如果是开发前端界面,可以通过 http://localhost:8090/docs 查看有什么接口。目前的 web 控制台非常简陋,欢迎有兴趣的朋友帮忙实现一个漂亮的前端,需要什么接口可以随时提需求。

🚦 代码提交规范

提交前请执行

pdm lintfmt

用于检查代码和格式化代码。

本地编译 Docker Image

shell
docker build -t xiaomusic .

技术栈

  • 后端代码使用 Python 语言编写。
  • HTTP 服务使用的是 FastAPI 框架,早期版本使用的是 Flask
  • 使用了 Docker ,在 NAS 上安装更方便。
  • 默认的前端主题使用了 jQuery 。

已测试支持的设备

型号名称
L06A小爱音箱
L07ARedmi小爱音箱 Play
S12/S12A/MDZ-25-DA小米AI音箱
LX5A小爱音箱 万能遥控版
LX05小爱音箱Play(2019款)
L15A小米AI音箱(第二代)
L16AXiaomi Sound
L17AXiaomi Sound Pro
LX06小爱音箱Pro
LX01小爱音箱mini
L05B小爱音箱Play
L05C小米小爱音箱Play 增强版
L09A小米音箱Art
LX04 X10A X08A已经支持的触屏版
X08C X08E X8F需要设置【型号兼容模式】选项为 true
M01/XMYX01JY小米小爱音箱HD 需要设置【特殊型号获取对话记录】选项为 true 才能语音播放

型号与产品名称对照可以在这里查询 https://home.miot-spec.com/s/xiaomi.wifispeaker

NOTE

如果你的设备支持播放,请反馈给我添加到支持列表里,谢谢。 目前应该所有设备类型都已经支持播放,有问题随时反馈。 其他触屏版不能播放可以设置【型号兼容模式】选项为 true 试试。见 https://github.com/hanxi/xiaomusic/issues/30

🎵 支持音乐格式

  • mp3
  • flac
  • wav
  • ape
  • ogg
  • m4a

NOTE

本地音乐会搜索目录下上面格式的文件,下载的歌曲是 mp3 格式的。 已知 L05B L05C LX06 L16A 不支持 flac 格式。 如果格式不能播放可以打开【转换为MP3】和【型号兼容模式】选项。具体见 https://github.com/hanxi/xiaomusic/issues/153#issuecomment-2328168689

💡 简易的控制面板

浏览器进入 http://192.168.2.5:8090

  • ip 是 XIAOMUSIC_HOSTNAME 设置的
  • 8090 是默认端口
  • 支持功能
    • 显示正在播放的歌曲
    • 模糊搜索本地歌曲
    • 播放列表
    • 删除歌曲
    • 设置页面
    • 配置网络歌单
    • 日志文件下载

采用新的设置页面之后,没有必须在启动前配置的环境变量了,除非是改默认的 8090 端口才需要配置环境变量。

🌏 网络歌单功能

可以配置一个 json 格式的歌单,支持电台和歌曲,也可以直接用别人分享的链接,同时配备了 m3u 文件格式转换工具,可以很方便的把 m3u 电台文件转换成网络歌单格式的 json 文件,具体用法见 https://github.com/hanxi/xiaomusic/issues/78

NOTE

欢迎有想法的朋友们制作更多的歌单转换工具。

🍺 更多其他可选配置

  • XIAOMUSIC_ACTIVE_CMD 环境变量,对应后台的 【允许唤醒的命令】,用于唤醒口令,配置成'play,random_play',在非播放状态下,只有这两个指令(播放歌曲和随机播放)可以触发,触发后,xiaomusic进入playing状态,其他指令则可以正常触发。具体见 https://github.com/hanxi/xiaomusic/pull/43
  • XIAOMUSIC_EXCLUDE_DIRS 配置歌曲目录里需要忽略的目录,对应后台的 【忽略目录】
  • XIAOMUSIC_MUSIC_PATH_DEPTH 配置歌曲目录搜索深度,对应后台的 【目录深度】,具体见 https://github.com/hanxi/xiaomusic/issues/76
  • XIAOMUSIC_DISABLE_HTTPAUTH 配置成 false 表示开启密码访问web控制台,对应后台的 【关闭密码验证】,具体见 https://github.com/hanxi/xiaomusic/issues/47
  • XIAOMUSIC_HTTPAUTH_USERNAME 配置 web 控制台用户,对应后台的 【控制台账户】
  • XIAOMUSIC_HTTPAUTH_PASSWORD 配置 web 控制台密码,对应后台的 【控制台密码】
  • XIAOMUSIC_CONF_PATH 用来存放配置文件的目录,对应后台的 【配置文件目录】,记得把目录映射到主机,默认为 /app/config ,具体见 https://github.com/hanxi/xiaomusic/issues/74
  • XIAOMUSIC_CACHE_DIR 用来音乐 tag 缓存,默认为 /app/cache,对应后台的 【缓存文件目录】。
  • XIAOMUSIC_DISABLE_DOWNLOAD 设为 true 时关闭下载功能,对应后台的 【关闭下载功能】,见 https://github.com/hanxi/xiaomusic/issues/82
  • XIAOMUSIC_USE_MUSIC_API 设为 true 时使用 player_play_music 接口播放音乐,对应后台的 【型号兼容模式】,用于兼容不能播放的型号,如果发现需要设置这个选项的时候请告知我加一下设备型号,方便以后不用设置。 见 https://github.com/hanxi/xiaomusic/issues/30
  • XIAOMUSIC_KEYWORDS_PLAY 用来播放歌曲的口令前缀,对应后台的 【播放歌曲口令】,默认是 "播放歌曲,放歌曲" ,可以用英文逗号分割配置多个
  • XIAOMUSIC_KEYWORDS_STOP 用来关机的口令,对应后台的 【停止口令】,默认是 "关机,暂停,停止" ,可以用英文逗号分割配置多个。
  • XIAOMUSIC_KEYWORDS_PLAYLOCAL 用来播放本地歌曲的口令前缀,对应后台的 【播放本地歌曲口令】,本地找不到时不会下载歌曲,默认是 "播放本地歌曲,本地播放歌曲" ,可以用英文逗号分割配置多个。
  • XIAOMUSIC_ENABLE_FUZZY_MATCH 设为 true 时开启模糊匹配(默认),设为 false 时关闭模糊匹配,对应后台的 【开启模糊搜索】,支持模糊匹配歌名和歌单名。 具体见 https://github.com/hanxi/xiaomusic/issues/52
  • XIAOMUSIC_FUZZY_MATCH_CUTOFF 设置模糊搜索匹配的最低相似度阈值(默认0.6,可以配0到1直接的小数),越小越模糊,越大越精准,对应后台的 【模糊匹配阈值】。具体见 https://github.com/hanxi/xiaomusic/issues/52
  • XIAOMUSIC_PUBLIC_PORT 用于设置外网端口,对应后台的 【外网访问端口】,当使用反向代理时可以设置为外网端口,XIAOMUSIC_HOSTNAME 设为外网IP或者域名即可。
  • XIAOMUSIC_DOWNLOAD_PATH 变量可以配置下载目录,默认为空,表示使用 music 目录为下载目录,对应后台的 【音乐下载目录】。设置这个目录必须是 music 的子目录,否则刷新列表后会找不到歌曲。具体见 https://github.com/hanxi/xiaomusic/issues/98
  • XIAOMUSIC_PROXY 用于配置国内使用 youtube 源下载歌曲时使用的代理,参数格式参考 yt-dlp 文档说明。 见 https://github.com/hanxi/xiaomusic/issues/2https://github.com/hanxi/xiaomusic/issues/11
  • MIIO_TTS_CMD 用于部分机型(如:L05C)使用 MiIO 支持 tts 能力,默认为空,命令选择见 MiService-fork 文档

⚠️ 安全提醒

IMPORTANT

  1. 如果配置了公网访问 xiaomusic ,请一定要开启密码登陆,并设置复杂的密码。且不要在公共场所的 WiFi 环境下使用,否则可能造成小米账号密码泄露。
  2. 强烈不建议将小爱音箱的小米账号绑定摄像头,代码难免会有 bug ,一旦小米账号密码泄露,可能监控录像也会泄露。

🤔 高级篇

📢 讨论区

❤️ 感谢

👉 其他教程

更多功能见 📝 文档汇总

🚨 免责声明

本项目仅供学习和研究目的,不得用于任何商业活动。用户在使用本项目时应遵守所在地区的法律法规,对于违法使用所导致的后果,本项目及作者不承担任何责任。 本项目可能存在未知的缺陷和风险(包括但不限于设备损坏和账号封禁等),使用者应自行承担使用本项目所产生的所有风险及责任。 作者不保证本项目的准确性、完整性、及时性、可靠性,也不承担任何因使用本项目而产生的任何损失或损害责任。 使用本项目即表示您已阅读并同意本免责声明的全部内容。

Star History

Star History Chart

赞赏

License

MIT License © 2024 涵曦

`,82)]))}const g=s(h,[["render",e]]);export{c as __pageData,g as default}; diff --git a/docs/.vitepress/dist/assets/style.DKcHOilP.css b/docs/.vitepress/dist/assets/style.DKcHOilP.css deleted file mode 100644 index 5bb0ea0..0000000 --- a/docs/.vitepress/dist/assets/style.DKcHOilP.css +++ /dev/null @@ -1 +0,0 @@ -@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-cyrillic.C5lxZ8CY.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-greek-ext.CqjqNYQ-.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-greek.BBVDIX6e.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-vietnamese.BjW4sHH5.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-latin-ext.4ZJIpNVo.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-latin.Di8DUHzh.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-cyrillic-ext.r48I6akx.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-cyrillic.By2_1cv3.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-greek-ext.1u6EdAuj.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-greek.DJ8dCoTZ.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-vietnamese.BSbpV94h.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-latin-ext.CN1xVJS-.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-latin.C2AdPX0b.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Punctuation SC;font-weight:400;src:local("PingFang SC Regular"),local("Noto Sans CJK SC"),local("Microsoft YaHei");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}@font-face{font-family:Punctuation SC;font-weight:500;src:local("PingFang SC Medium"),local("Noto Sans CJK SC"),local("Microsoft YaHei");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}@font-face{font-family:Punctuation SC;font-weight:600;src:local("PingFang SC Semibold"),local("Noto Sans CJK SC Bold"),local("Microsoft YaHei Bold");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}@font-face{font-family:Punctuation SC;font-weight:700;src:local("PingFang SC Semibold"),local("Noto Sans CJK SC Bold"),local("Microsoft YaHei Bold");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}:root{--vp-c-white: #ffffff;--vp-c-black: #000000;--vp-c-neutral: var(--vp-c-black);--vp-c-neutral-inverse: var(--vp-c-white)}.dark{--vp-c-neutral: var(--vp-c-white);--vp-c-neutral-inverse: var(--vp-c-black)}:root{--vp-c-gray-1: #dddde3;--vp-c-gray-2: #e4e4e9;--vp-c-gray-3: #ebebef;--vp-c-gray-soft: rgba(142, 150, 170, .14);--vp-c-indigo-1: #3451b2;--vp-c-indigo-2: #3a5ccc;--vp-c-indigo-3: #5672cd;--vp-c-indigo-soft: rgba(100, 108, 255, .14);--vp-c-purple-1: #6f42c1;--vp-c-purple-2: #7e4cc9;--vp-c-purple-3: #8e5cd9;--vp-c-purple-soft: rgba(159, 122, 234, .14);--vp-c-green-1: #18794e;--vp-c-green-2: #299764;--vp-c-green-3: #30a46c;--vp-c-green-soft: rgba(16, 185, 129, .14);--vp-c-yellow-1: #915930;--vp-c-yellow-2: #946300;--vp-c-yellow-3: #9f6a00;--vp-c-yellow-soft: rgba(234, 179, 8, .14);--vp-c-red-1: #b8272c;--vp-c-red-2: #d5393e;--vp-c-red-3: #e0575b;--vp-c-red-soft: rgba(244, 63, 94, .14);--vp-c-sponsor: #db2777}.dark{--vp-c-gray-1: #515c67;--vp-c-gray-2: #414853;--vp-c-gray-3: #32363f;--vp-c-gray-soft: rgba(101, 117, 133, .16);--vp-c-indigo-1: #a8b1ff;--vp-c-indigo-2: #5c73e7;--vp-c-indigo-3: #3e63dd;--vp-c-indigo-soft: rgba(100, 108, 255, .16);--vp-c-purple-1: #c8abfa;--vp-c-purple-2: #a879e6;--vp-c-purple-3: #8e5cd9;--vp-c-purple-soft: rgba(159, 122, 234, .16);--vp-c-green-1: #3dd68c;--vp-c-green-2: #30a46c;--vp-c-green-3: #298459;--vp-c-green-soft: rgba(16, 185, 129, .16);--vp-c-yellow-1: #f9b44e;--vp-c-yellow-2: #da8b17;--vp-c-yellow-3: #a46a0a;--vp-c-yellow-soft: rgba(234, 179, 8, .16);--vp-c-red-1: #f66f81;--vp-c-red-2: #f14158;--vp-c-red-3: #b62a3c;--vp-c-red-soft: rgba(244, 63, 94, .16)}:root{--vp-c-bg: #ffffff;--vp-c-bg-alt: #f6f6f7;--vp-c-bg-elv: #ffffff;--vp-c-bg-soft: #f6f6f7}.dark{--vp-c-bg: #1b1b1f;--vp-c-bg-alt: #161618;--vp-c-bg-elv: #202127;--vp-c-bg-soft: #202127}:root{--vp-c-border: #c2c2c4;--vp-c-divider: #e2e2e3;--vp-c-gutter: #e2e2e3}.dark{--vp-c-border: #3c3f44;--vp-c-divider: #2e2e32;--vp-c-gutter: #000000}:root{--vp-c-text-1: rgba(60, 60, 67);--vp-c-text-2: rgba(60, 60, 67, .78);--vp-c-text-3: rgba(60, 60, 67, .56)}.dark{--vp-c-text-1: rgba(255, 255, 245, .86);--vp-c-text-2: rgba(235, 235, 245, .6);--vp-c-text-3: rgba(235, 235, 245, .38)}:root{--vp-c-default-1: var(--vp-c-gray-1);--vp-c-default-2: var(--vp-c-gray-2);--vp-c-default-3: var(--vp-c-gray-3);--vp-c-default-soft: var(--vp-c-gray-soft);--vp-c-brand-1: var(--vp-c-indigo-1);--vp-c-brand-2: var(--vp-c-indigo-2);--vp-c-brand-3: var(--vp-c-indigo-3);--vp-c-brand-soft: var(--vp-c-indigo-soft);--vp-c-brand: var(--vp-c-brand-1);--vp-c-tip-1: var(--vp-c-brand-1);--vp-c-tip-2: var(--vp-c-brand-2);--vp-c-tip-3: var(--vp-c-brand-3);--vp-c-tip-soft: var(--vp-c-brand-soft);--vp-c-note-1: var(--vp-c-brand-1);--vp-c-note-2: var(--vp-c-brand-2);--vp-c-note-3: var(--vp-c-brand-3);--vp-c-note-soft: var(--vp-c-brand-soft);--vp-c-success-1: var(--vp-c-green-1);--vp-c-success-2: var(--vp-c-green-2);--vp-c-success-3: var(--vp-c-green-3);--vp-c-success-soft: var(--vp-c-green-soft);--vp-c-important-1: var(--vp-c-purple-1);--vp-c-important-2: var(--vp-c-purple-2);--vp-c-important-3: var(--vp-c-purple-3);--vp-c-important-soft: var(--vp-c-purple-soft);--vp-c-warning-1: var(--vp-c-yellow-1);--vp-c-warning-2: var(--vp-c-yellow-2);--vp-c-warning-3: var(--vp-c-yellow-3);--vp-c-warning-soft: var(--vp-c-yellow-soft);--vp-c-danger-1: var(--vp-c-red-1);--vp-c-danger-2: var(--vp-c-red-2);--vp-c-danger-3: var(--vp-c-red-3);--vp-c-danger-soft: var(--vp-c-red-soft);--vp-c-caution-1: var(--vp-c-red-1);--vp-c-caution-2: var(--vp-c-red-2);--vp-c-caution-3: var(--vp-c-red-3);--vp-c-caution-soft: var(--vp-c-red-soft)}:root{--vp-font-family-base: "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--vp-font-family-mono: ui-monospace, "Menlo", "Monaco", "Consolas", "Liberation Mono", "Courier New", monospace;font-optical-sizing:auto}:root:where(:lang(zh)){--vp-font-family-base: "Punctuation SC", "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"}:root{--vp-shadow-1: 0 1px 2px rgba(0, 0, 0, .04), 0 1px 2px rgba(0, 0, 0, .06);--vp-shadow-2: 0 3px 12px rgba(0, 0, 0, .07), 0 1px 4px rgba(0, 0, 0, .07);--vp-shadow-3: 0 12px 32px rgba(0, 0, 0, .1), 0 2px 6px rgba(0, 0, 0, .08);--vp-shadow-4: 0 14px 44px rgba(0, 0, 0, .12), 0 3px 9px rgba(0, 0, 0, .12);--vp-shadow-5: 0 18px 56px rgba(0, 0, 0, .16), 0 4px 12px rgba(0, 0, 0, .16)}:root{--vp-z-index-footer: 10;--vp-z-index-local-nav: 20;--vp-z-index-nav: 30;--vp-z-index-layout-top: 40;--vp-z-index-backdrop: 50;--vp-z-index-sidebar: 60}@media (min-width: 960px){:root{--vp-z-index-sidebar: 25}}:root{--vp-layout-max-width: 1440px}:root{--vp-header-anchor-symbol: "#"}:root{--vp-code-line-height: 1.7;--vp-code-font-size: .875em;--vp-code-color: var(--vp-c-brand-1);--vp-code-link-color: var(--vp-c-brand-1);--vp-code-link-hover-color: var(--vp-c-brand-2);--vp-code-bg: var(--vp-c-default-soft);--vp-code-block-color: var(--vp-c-text-2);--vp-code-block-bg: var(--vp-c-bg-alt);--vp-code-block-divider-color: var(--vp-c-gutter);--vp-code-lang-color: var(--vp-c-text-3);--vp-code-line-highlight-color: var(--vp-c-default-soft);--vp-code-line-number-color: var(--vp-c-text-3);--vp-code-line-diff-add-color: var(--vp-c-success-soft);--vp-code-line-diff-add-symbol-color: var(--vp-c-success-1);--vp-code-line-diff-remove-color: var(--vp-c-danger-soft);--vp-code-line-diff-remove-symbol-color: var(--vp-c-danger-1);--vp-code-line-warning-color: var(--vp-c-warning-soft);--vp-code-line-error-color: var(--vp-c-danger-soft);--vp-code-copy-code-border-color: var(--vp-c-divider);--vp-code-copy-code-bg: var(--vp-c-bg-soft);--vp-code-copy-code-hover-border-color: var(--vp-c-divider);--vp-code-copy-code-hover-bg: var(--vp-c-bg);--vp-code-copy-code-active-text: var(--vp-c-text-2);--vp-code-copy-copied-text-content: "Copied";--vp-code-tab-divider: var(--vp-code-block-divider-color);--vp-code-tab-text-color: var(--vp-c-text-2);--vp-code-tab-bg: var(--vp-code-block-bg);--vp-code-tab-hover-text-color: var(--vp-c-text-1);--vp-code-tab-active-text-color: var(--vp-c-text-1);--vp-code-tab-active-bar-color: var(--vp-c-brand-1)}:root{--vp-button-brand-border: transparent;--vp-button-brand-text: var(--vp-c-white);--vp-button-brand-bg: var(--vp-c-brand-3);--vp-button-brand-hover-border: transparent;--vp-button-brand-hover-text: var(--vp-c-white);--vp-button-brand-hover-bg: var(--vp-c-brand-2);--vp-button-brand-active-border: transparent;--vp-button-brand-active-text: var(--vp-c-white);--vp-button-brand-active-bg: var(--vp-c-brand-1);--vp-button-alt-border: transparent;--vp-button-alt-text: var(--vp-c-text-1);--vp-button-alt-bg: var(--vp-c-default-3);--vp-button-alt-hover-border: transparent;--vp-button-alt-hover-text: var(--vp-c-text-1);--vp-button-alt-hover-bg: var(--vp-c-default-2);--vp-button-alt-active-border: transparent;--vp-button-alt-active-text: var(--vp-c-text-1);--vp-button-alt-active-bg: var(--vp-c-default-1);--vp-button-sponsor-border: var(--vp-c-text-2);--vp-button-sponsor-text: var(--vp-c-text-2);--vp-button-sponsor-bg: transparent;--vp-button-sponsor-hover-border: var(--vp-c-sponsor);--vp-button-sponsor-hover-text: var(--vp-c-sponsor);--vp-button-sponsor-hover-bg: transparent;--vp-button-sponsor-active-border: var(--vp-c-sponsor);--vp-button-sponsor-active-text: var(--vp-c-sponsor);--vp-button-sponsor-active-bg: transparent}:root{--vp-custom-block-font-size: 14px;--vp-custom-block-code-font-size: 13px;--vp-custom-block-info-border: transparent;--vp-custom-block-info-text: var(--vp-c-text-1);--vp-custom-block-info-bg: var(--vp-c-default-soft);--vp-custom-block-info-code-bg: var(--vp-c-default-soft);--vp-custom-block-note-border: transparent;--vp-custom-block-note-text: var(--vp-c-text-1);--vp-custom-block-note-bg: var(--vp-c-default-soft);--vp-custom-block-note-code-bg: var(--vp-c-default-soft);--vp-custom-block-tip-border: transparent;--vp-custom-block-tip-text: var(--vp-c-text-1);--vp-custom-block-tip-bg: var(--vp-c-tip-soft);--vp-custom-block-tip-code-bg: var(--vp-c-tip-soft);--vp-custom-block-important-border: transparent;--vp-custom-block-important-text: var(--vp-c-text-1);--vp-custom-block-important-bg: var(--vp-c-important-soft);--vp-custom-block-important-code-bg: var(--vp-c-important-soft);--vp-custom-block-warning-border: transparent;--vp-custom-block-warning-text: var(--vp-c-text-1);--vp-custom-block-warning-bg: var(--vp-c-warning-soft);--vp-custom-block-warning-code-bg: var(--vp-c-warning-soft);--vp-custom-block-danger-border: transparent;--vp-custom-block-danger-text: var(--vp-c-text-1);--vp-custom-block-danger-bg: var(--vp-c-danger-soft);--vp-custom-block-danger-code-bg: var(--vp-c-danger-soft);--vp-custom-block-caution-border: transparent;--vp-custom-block-caution-text: var(--vp-c-text-1);--vp-custom-block-caution-bg: var(--vp-c-caution-soft);--vp-custom-block-caution-code-bg: var(--vp-c-caution-soft);--vp-custom-block-details-border: var(--vp-custom-block-info-border);--vp-custom-block-details-text: var(--vp-custom-block-info-text);--vp-custom-block-details-bg: var(--vp-custom-block-info-bg);--vp-custom-block-details-code-bg: var(--vp-custom-block-info-code-bg)}:root{--vp-input-border-color: var(--vp-c-border);--vp-input-bg-color: var(--vp-c-bg-alt);--vp-input-switch-bg-color: var(--vp-c-default-soft)}:root{--vp-nav-height: 64px;--vp-nav-bg-color: var(--vp-c-bg);--vp-nav-screen-bg-color: var(--vp-c-bg);--vp-nav-logo-height: 24px}.hide-nav{--vp-nav-height: 0px}.hide-nav .VPSidebar{--vp-nav-height: 22px}:root{--vp-local-nav-bg-color: var(--vp-c-bg)}:root{--vp-sidebar-width: 272px;--vp-sidebar-bg-color: var(--vp-c-bg-alt)}:root{--vp-backdrop-bg-color: rgba(0, 0, 0, .6)}:root{--vp-home-hero-name-color: var(--vp-c-brand-1);--vp-home-hero-name-background: transparent;--vp-home-hero-image-background-image: none;--vp-home-hero-image-filter: none}:root{--vp-badge-info-border: transparent;--vp-badge-info-text: var(--vp-c-text-2);--vp-badge-info-bg: var(--vp-c-default-soft);--vp-badge-tip-border: transparent;--vp-badge-tip-text: var(--vp-c-tip-1);--vp-badge-tip-bg: var(--vp-c-tip-soft);--vp-badge-warning-border: transparent;--vp-badge-warning-text: var(--vp-c-warning-1);--vp-badge-warning-bg: var(--vp-c-warning-soft);--vp-badge-danger-border: transparent;--vp-badge-danger-text: var(--vp-c-danger-1);--vp-badge-danger-bg: var(--vp-c-danger-soft)}:root{--vp-carbon-ads-text-color: var(--vp-c-text-1);--vp-carbon-ads-poweredby-color: var(--vp-c-text-2);--vp-carbon-ads-bg-color: var(--vp-c-bg-soft);--vp-carbon-ads-hover-text-color: var(--vp-c-brand-1);--vp-carbon-ads-hover-poweredby-color: var(--vp-c-text-1)}:root{--vp-local-search-bg: var(--vp-c-bg);--vp-local-search-result-bg: var(--vp-c-bg);--vp-local-search-result-border: var(--vp-c-divider);--vp-local-search-result-selected-bg: var(--vp-c-bg);--vp-local-search-result-selected-border: var(--vp-c-brand-1);--vp-local-search-highlight-bg: var(--vp-c-brand-1);--vp-local-search-highlight-text: var(--vp-c-neutral-inverse)}@media (prefers-reduced-motion: reduce){*,:before,:after{animation-delay:-1ms!important;animation-duration:1ms!important;animation-iteration-count:1!important;background-attachment:initial!important;scroll-behavior:auto!important;transition-duration:0s!important;transition-delay:0s!important}}*,:before,:after{box-sizing:border-box}html{line-height:1.4;font-size:16px;-webkit-text-size-adjust:100%}html.dark{color-scheme:dark}body{margin:0;width:100%;min-width:320px;min-height:100vh;line-height:24px;font-family:var(--vp-font-family-base);font-size:16px;font-weight:400;color:var(--vp-c-text-1);background-color:var(--vp-c-bg);font-synthesis:style;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}main{display:block}h1,h2,h3,h4,h5,h6{margin:0;line-height:24px;font-size:16px;font-weight:400}p{margin:0}strong,b{font-weight:600}a,area,button,[role=button],input,label,select,summary,textarea{touch-action:manipulation}a{color:inherit;text-decoration:inherit}ol,ul{list-style:none;margin:0;padding:0}blockquote{margin:0}pre,code,kbd,samp{font-family:var(--vp-font-family-mono)}img,svg,video,canvas,audio,iframe,embed,object{display:block}figure{margin:0}img,video{max-width:100%;height:auto}button,input,optgroup,select,textarea{border:0;padding:0;line-height:inherit;color:inherit}button{padding:0;font-family:inherit;background-color:transparent;background-image:none}button:enabled,[role=button]:enabled{cursor:pointer}button:focus,button:focus-visible{outline:1px dotted;outline:4px auto -webkit-focus-ring-color}button:focus:not(:focus-visible){outline:none!important}input:focus,textarea:focus,select:focus{outline:none}table{border-collapse:collapse}input{background-color:transparent}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:var(--vp-c-text-3)}input::-ms-input-placeholder,textarea::-ms-input-placeholder{color:var(--vp-c-text-3)}input::placeholder,textarea::placeholder{color:var(--vp-c-text-3)}input::-webkit-outer-spin-button,input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}input[type=number]{-moz-appearance:textfield}textarea{resize:vertical}select{-webkit-appearance:none}fieldset{margin:0;padding:0}h1,h2,h3,h4,h5,h6,li,p{overflow-wrap:break-word}vite-error-overlay{z-index:9999}mjx-container{overflow-x:auto}mjx-container>svg{display:inline-block;margin:auto}[class^=vpi-],[class*=" vpi-"],.vp-icon{width:1em;height:1em}[class^=vpi-].bg,[class*=" vpi-"].bg,.vp-icon.bg{background-size:100% 100%;background-color:transparent}[class^=vpi-]:not(.bg),[class*=" vpi-"]:not(.bg),.vp-icon:not(.bg){-webkit-mask:var(--icon) no-repeat;mask:var(--icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit}.vpi-align-left{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M21 6H3M15 12H3M17 18H3'/%3E%3C/svg%3E")}.vpi-arrow-right,.vpi-arrow-down,.vpi-arrow-left,.vpi-arrow-up{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M5 12h14M12 5l7 7-7 7'/%3E%3C/svg%3E")}.vpi-chevron-right,.vpi-chevron-down,.vpi-chevron-left,.vpi-chevron-up{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E")}.vpi-chevron-down,.vpi-arrow-down{transform:rotate(90deg)}.vpi-chevron-left,.vpi-arrow-left{transform:rotate(180deg)}.vpi-chevron-up,.vpi-arrow-up{transform:rotate(-90deg)}.vpi-square-pen{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7'/%3E%3Cpath d='M18.375 2.625a2.121 2.121 0 1 1 3 3L12 15l-4 1 1-4Z'/%3E%3C/svg%3E")}.vpi-plus{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M5 12h14M12 5v14'/%3E%3C/svg%3E")}.vpi-sun{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='4'/%3E%3Cpath d='M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41'/%3E%3C/svg%3E")}.vpi-moon{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z'/%3E%3C/svg%3E")}.vpi-more-horizontal{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='1'/%3E%3Ccircle cx='19' cy='12' r='1'/%3E%3Ccircle cx='5' cy='12' r='1'/%3E%3C/svg%3E")}.vpi-languages{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m5 8 6 6M4 14l6-6 2-3M2 5h12M7 2h1M22 22l-5-10-5 10M14 18h6'/%3E%3C/svg%3E")}.vpi-heart{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z'/%3E%3C/svg%3E")}.vpi-search{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E")}.vpi-layout-list{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='7' height='7' x='3' y='3' rx='1'/%3E%3Crect width='7' height='7' x='3' y='14' rx='1'/%3E%3Cpath d='M14 4h7M14 9h7M14 15h7M14 20h7'/%3E%3C/svg%3E")}.vpi-delete{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M20 5H9l-7 7 7 7h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2ZM18 9l-6 6M12 9l6 6'/%3E%3C/svg%3E")}.vpi-corner-down-left{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m9 10-5 5 5 5'/%3E%3Cpath d='M20 4v7a4 4 0 0 1-4 4H4'/%3E%3C/svg%3E")}:root{--vp-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3C/svg%3E");--vp-icon-copied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3Cpath d='m9 14 2 2 4-4'/%3E%3C/svg%3E")}.visually-hidden{position:absolute;width:1px;height:1px;white-space:nowrap;clip:rect(0 0 0 0);clip-path:inset(50%);overflow:hidden}.custom-block{border:1px solid transparent;border-radius:8px;padding:16px 16px 8px;line-height:24px;font-size:var(--vp-custom-block-font-size);color:var(--vp-c-text-2)}.custom-block.info{border-color:var(--vp-custom-block-info-border);color:var(--vp-custom-block-info-text);background-color:var(--vp-custom-block-info-bg)}.custom-block.info a,.custom-block.info code{color:var(--vp-c-brand-1)}.custom-block.info a:hover,.custom-block.info a:hover>code{color:var(--vp-c-brand-2)}.custom-block.info code{background-color:var(--vp-custom-block-info-code-bg)}.custom-block.note{border-color:var(--vp-custom-block-note-border);color:var(--vp-custom-block-note-text);background-color:var(--vp-custom-block-note-bg)}.custom-block.note a,.custom-block.note code{color:var(--vp-c-brand-1)}.custom-block.note a:hover,.custom-block.note a:hover>code{color:var(--vp-c-brand-2)}.custom-block.note code{background-color:var(--vp-custom-block-note-code-bg)}.custom-block.tip{border-color:var(--vp-custom-block-tip-border);color:var(--vp-custom-block-tip-text);background-color:var(--vp-custom-block-tip-bg)}.custom-block.tip a,.custom-block.tip code{color:var(--vp-c-tip-1)}.custom-block.tip a:hover,.custom-block.tip a:hover>code{color:var(--vp-c-tip-2)}.custom-block.tip code{background-color:var(--vp-custom-block-tip-code-bg)}.custom-block.important{border-color:var(--vp-custom-block-important-border);color:var(--vp-custom-block-important-text);background-color:var(--vp-custom-block-important-bg)}.custom-block.important a,.custom-block.important code{color:var(--vp-c-important-1)}.custom-block.important a:hover,.custom-block.important a:hover>code{color:var(--vp-c-important-2)}.custom-block.important code{background-color:var(--vp-custom-block-important-code-bg)}.custom-block.warning{border-color:var(--vp-custom-block-warning-border);color:var(--vp-custom-block-warning-text);background-color:var(--vp-custom-block-warning-bg)}.custom-block.warning a,.custom-block.warning code{color:var(--vp-c-warning-1)}.custom-block.warning a:hover,.custom-block.warning a:hover>code{color:var(--vp-c-warning-2)}.custom-block.warning code{background-color:var(--vp-custom-block-warning-code-bg)}.custom-block.danger{border-color:var(--vp-custom-block-danger-border);color:var(--vp-custom-block-danger-text);background-color:var(--vp-custom-block-danger-bg)}.custom-block.danger a,.custom-block.danger code{color:var(--vp-c-danger-1)}.custom-block.danger a:hover,.custom-block.danger a:hover>code{color:var(--vp-c-danger-2)}.custom-block.danger code{background-color:var(--vp-custom-block-danger-code-bg)}.custom-block.caution{border-color:var(--vp-custom-block-caution-border);color:var(--vp-custom-block-caution-text);background-color:var(--vp-custom-block-caution-bg)}.custom-block.caution a,.custom-block.caution code{color:var(--vp-c-caution-1)}.custom-block.caution a:hover,.custom-block.caution a:hover>code{color:var(--vp-c-caution-2)}.custom-block.caution code{background-color:var(--vp-custom-block-caution-code-bg)}.custom-block.details{border-color:var(--vp-custom-block-details-border);color:var(--vp-custom-block-details-text);background-color:var(--vp-custom-block-details-bg)}.custom-block.details a{color:var(--vp-c-brand-1)}.custom-block.details a:hover,.custom-block.details a:hover>code{color:var(--vp-c-brand-2)}.custom-block.details code{background-color:var(--vp-custom-block-details-code-bg)}.custom-block-title{font-weight:600}.custom-block p+p{margin:8px 0}.custom-block.details summary{margin:0 0 8px;font-weight:700;cursor:pointer;-webkit-user-select:none;user-select:none}.custom-block.details summary+p{margin:8px 0}.custom-block a{color:inherit;font-weight:600;text-decoration:underline;text-underline-offset:2px;transition:opacity .25s}.custom-block a:hover{opacity:.75}.custom-block code{font-size:var(--vp-custom-block-code-font-size)}.custom-block.custom-block th,.custom-block.custom-block blockquote>p{font-size:var(--vp-custom-block-font-size);color:inherit}.dark .vp-code span{color:var(--shiki-dark, inherit)}html:not(.dark) .vp-code span{color:var(--shiki-light, inherit)}.vp-code-group{margin-top:16px}.vp-code-group .tabs{position:relative;display:flex;margin-right:-24px;margin-left:-24px;padding:0 12px;background-color:var(--vp-code-tab-bg);overflow-x:auto;overflow-y:hidden;box-shadow:inset 0 -1px var(--vp-code-tab-divider)}@media (min-width: 640px){.vp-code-group .tabs{margin-right:0;margin-left:0;border-radius:8px 8px 0 0}}.vp-code-group .tabs input{position:fixed;opacity:0;pointer-events:none}.vp-code-group .tabs label{position:relative;display:inline-block;border-bottom:1px solid transparent;padding:0 12px;line-height:48px;font-size:14px;font-weight:500;color:var(--vp-code-tab-text-color);white-space:nowrap;cursor:pointer;transition:color .25s}.vp-code-group .tabs label:after{position:absolute;right:8px;bottom:-1px;left:8px;z-index:1;height:2px;border-radius:2px;content:"";background-color:transparent;transition:background-color .25s}.vp-code-group label:hover{color:var(--vp-code-tab-hover-text-color)}.vp-code-group input:checked+label{color:var(--vp-code-tab-active-text-color)}.vp-code-group input:checked+label:after{background-color:var(--vp-code-tab-active-bar-color)}.vp-code-group div[class*=language-],.vp-block{display:none;margin-top:0!important;border-top-left-radius:0!important;border-top-right-radius:0!important}.vp-code-group div[class*=language-].active,.vp-block.active{display:block}.vp-block{padding:20px 24px}.vp-doc h1,.vp-doc h2,.vp-doc h3,.vp-doc h4,.vp-doc h5,.vp-doc h6{position:relative;font-weight:600;outline:none}.vp-doc h1{letter-spacing:-.02em;line-height:40px;font-size:28px}.vp-doc h2{margin:48px 0 16px;border-top:1px solid var(--vp-c-divider);padding-top:24px;letter-spacing:-.02em;line-height:32px;font-size:24px}.vp-doc h3{margin:32px 0 0;letter-spacing:-.01em;line-height:28px;font-size:20px}.vp-doc h4{margin:24px 0 0;letter-spacing:-.01em;line-height:24px;font-size:18px}.vp-doc .header-anchor{position:absolute;top:0;left:0;margin-left:-.87em;font-weight:500;-webkit-user-select:none;user-select:none;opacity:0;text-decoration:none;transition:color .25s,opacity .25s}.vp-doc .header-anchor:before{content:var(--vp-header-anchor-symbol)}.vp-doc h1:hover .header-anchor,.vp-doc h1 .header-anchor:focus,.vp-doc h2:hover .header-anchor,.vp-doc h2 .header-anchor:focus,.vp-doc h3:hover .header-anchor,.vp-doc h3 .header-anchor:focus,.vp-doc h4:hover .header-anchor,.vp-doc h4 .header-anchor:focus,.vp-doc h5:hover .header-anchor,.vp-doc h5 .header-anchor:focus,.vp-doc h6:hover .header-anchor,.vp-doc h6 .header-anchor:focus{opacity:1}@media (min-width: 768px){.vp-doc h1{letter-spacing:-.02em;line-height:40px;font-size:32px}}.vp-doc h2 .header-anchor{top:24px}.vp-doc p,.vp-doc summary{margin:16px 0}.vp-doc p{line-height:28px}.vp-doc blockquote{margin:16px 0;border-left:2px solid var(--vp-c-divider);padding-left:16px;transition:border-color .5s;color:var(--vp-c-text-2)}.vp-doc blockquote>p{margin:0;font-size:16px;transition:color .5s}.vp-doc a{font-weight:500;color:var(--vp-c-brand-1);text-decoration:underline;text-underline-offset:2px;transition:color .25s,opacity .25s}.vp-doc a:hover{color:var(--vp-c-brand-2)}.vp-doc strong{font-weight:600}.vp-doc ul,.vp-doc ol{padding-left:1.25rem;margin:16px 0}.vp-doc ul{list-style:disc}.vp-doc ol{list-style:decimal}.vp-doc li+li{margin-top:8px}.vp-doc li>ol,.vp-doc li>ul{margin:8px 0 0}.vp-doc table{display:block;border-collapse:collapse;margin:20px 0;overflow-x:auto}.vp-doc tr{background-color:var(--vp-c-bg);border-top:1px solid var(--vp-c-divider);transition:background-color .5s}.vp-doc tr:nth-child(2n){background-color:var(--vp-c-bg-soft)}.vp-doc th,.vp-doc td{border:1px solid var(--vp-c-divider);padding:8px 16px}.vp-doc th{text-align:left;font-size:14px;font-weight:600;color:var(--vp-c-text-2);background-color:var(--vp-c-bg-soft)}.vp-doc td{font-size:14px}.vp-doc hr{margin:16px 0;border:none;border-top:1px solid var(--vp-c-divider)}.vp-doc .custom-block{margin:16px 0}.vp-doc .custom-block p{margin:8px 0;line-height:24px}.vp-doc .custom-block p:first-child{margin:0}.vp-doc .custom-block div[class*=language-]{margin:8px 0;border-radius:8px}.vp-doc .custom-block div[class*=language-] code{font-weight:400;background-color:transparent}.vp-doc .custom-block .vp-code-group .tabs{margin:0;border-radius:8px 8px 0 0}.vp-doc :not(pre,h1,h2,h3,h4,h5,h6)>code{font-size:var(--vp-code-font-size);color:var(--vp-code-color)}.vp-doc :not(pre)>code{border-radius:4px;padding:3px 6px;background-color:var(--vp-code-bg);transition:color .25s,background-color .5s}.vp-doc a>code{color:var(--vp-code-link-color)}.vp-doc a:hover>code{color:var(--vp-code-link-hover-color)}.vp-doc h1>code,.vp-doc h2>code,.vp-doc h3>code,.vp-doc h4>code{font-size:.9em}.vp-doc div[class*=language-],.vp-block{position:relative;margin:16px -24px;background-color:var(--vp-code-block-bg);overflow-x:auto;transition:background-color .5s}@media (min-width: 640px){.vp-doc div[class*=language-],.vp-block{border-radius:8px;margin:16px 0}}@media (max-width: 639px){.vp-doc li div[class*=language-]{border-radius:8px 0 0 8px}}.vp-doc div[class*=language-]+div[class*=language-],.vp-doc div[class$=-api]+div[class*=language-],.vp-doc div[class*=language-]+div[class$=-api]>div[class*=language-]{margin-top:-8px}.vp-doc [class*=language-] pre,.vp-doc [class*=language-] code{direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}.vp-doc [class*=language-] pre{position:relative;z-index:1;margin:0;padding:20px 0;background:transparent;overflow-x:auto}.vp-doc [class*=language-] code{display:block;padding:0 24px;width:fit-content;min-width:100%;line-height:var(--vp-code-line-height);font-size:var(--vp-code-font-size);color:var(--vp-code-block-color);transition:color .5s}.vp-doc [class*=language-] code .highlighted{background-color:var(--vp-code-line-highlight-color);transition:background-color .5s;margin:0 -24px;padding:0 24px;width:calc(100% + 48px);display:inline-block}.vp-doc [class*=language-] code .highlighted.error{background-color:var(--vp-code-line-error-color)}.vp-doc [class*=language-] code .highlighted.warning{background-color:var(--vp-code-line-warning-color)}.vp-doc [class*=language-] code .diff{transition:background-color .5s;margin:0 -24px;padding:0 24px;width:calc(100% + 48px);display:inline-block}.vp-doc [class*=language-] code .diff:before{position:absolute;left:10px}.vp-doc [class*=language-] .has-focused-lines .line:not(.has-focus){filter:blur(.095rem);opacity:.4;transition:filter .35s,opacity .35s}.vp-doc [class*=language-] .has-focused-lines .line:not(.has-focus){opacity:.7;transition:filter .35s,opacity .35s}.vp-doc [class*=language-]:hover .has-focused-lines .line:not(.has-focus){filter:blur(0);opacity:1}.vp-doc [class*=language-] code .diff.remove{background-color:var(--vp-code-line-diff-remove-color);opacity:.7}.vp-doc [class*=language-] code .diff.remove:before{content:"-";color:var(--vp-code-line-diff-remove-symbol-color)}.vp-doc [class*=language-] code .diff.add{background-color:var(--vp-code-line-diff-add-color)}.vp-doc [class*=language-] code .diff.add:before{content:"+";color:var(--vp-code-line-diff-add-symbol-color)}.vp-doc div[class*=language-].line-numbers-mode{padding-left:32px}.vp-doc .line-numbers-wrapper{position:absolute;top:0;bottom:0;left:0;z-index:3;border-right:1px solid var(--vp-code-block-divider-color);padding-top:20px;width:32px;text-align:center;font-family:var(--vp-font-family-mono);line-height:var(--vp-code-line-height);font-size:var(--vp-code-font-size);color:var(--vp-code-line-number-color);transition:border-color .5s,color .5s}.vp-doc [class*=language-]>button.copy{direction:ltr;position:absolute;top:12px;right:12px;z-index:3;border:1px solid var(--vp-code-copy-code-border-color);border-radius:4px;width:40px;height:40px;background-color:var(--vp-code-copy-code-bg);opacity:0;cursor:pointer;background-image:var(--vp-icon-copy);background-position:50%;background-size:20px;background-repeat:no-repeat;transition:border-color .25s,background-color .25s,opacity .25s}.vp-doc [class*=language-]:hover>button.copy,.vp-doc [class*=language-]>button.copy:focus{opacity:1}.vp-doc [class*=language-]>button.copy:hover,.vp-doc [class*=language-]>button.copy.copied{border-color:var(--vp-code-copy-code-hover-border-color);background-color:var(--vp-code-copy-code-hover-bg)}.vp-doc [class*=language-]>button.copy.copied,.vp-doc [class*=language-]>button.copy:hover.copied{border-radius:0 4px 4px 0;background-color:var(--vp-code-copy-code-hover-bg);background-image:var(--vp-icon-copied)}.vp-doc [class*=language-]>button.copy.copied:before,.vp-doc [class*=language-]>button.copy:hover.copied:before{position:relative;top:-1px;transform:translate(calc(-100% - 1px));display:flex;justify-content:center;align-items:center;border:1px solid var(--vp-code-copy-code-hover-border-color);border-right:0;border-radius:4px 0 0 4px;padding:0 10px;width:fit-content;height:40px;text-align:center;font-size:12px;font-weight:500;color:var(--vp-code-copy-code-active-text);background-color:var(--vp-code-copy-code-hover-bg);white-space:nowrap;content:var(--vp-code-copy-copied-text-content)}.vp-doc [class*=language-]>span.lang{position:absolute;top:2px;right:8px;z-index:2;font-size:12px;font-weight:500;-webkit-user-select:none;user-select:none;color:var(--vp-code-lang-color);transition:color .4s,opacity .4s}.vp-doc [class*=language-]:hover>button.copy+span.lang,.vp-doc [class*=language-]>button.copy:focus+span.lang{opacity:0}.vp-doc .VPTeamMembers{margin-top:24px}.vp-doc .VPTeamMembers.small.count-1 .container{margin:0!important;max-width:calc((100% - 24px)/2)!important}.vp-doc .VPTeamMembers.small.count-2 .container,.vp-doc .VPTeamMembers.small.count-3 .container{max-width:100%!important}.vp-doc .VPTeamMembers.medium.count-1 .container{margin:0!important;max-width:calc((100% - 24px)/2)!important}:is(.vp-external-link-icon,.vp-doc a[href*="://"],.vp-doc a[target=_blank]):not(.no-icon):after{display:inline-block;margin-top:-1px;margin-left:4px;width:11px;height:11px;background:currentColor;color:var(--vp-c-text-3);flex-shrink:0;--icon: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M0 0h24v24H0V0z' fill='none' /%3E%3Cpath d='M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5H9z' /%3E%3C/svg%3E");-webkit-mask-image:var(--icon);mask-image:var(--icon)}.vp-external-link-icon:after{content:""}.external-link-icon-enabled :is(.vp-doc a[href*="://"],.vp-doc a[target=_blank]):after{content:"";color:currentColor}.vp-sponsor{border-radius:16px;overflow:hidden}.vp-sponsor.aside{border-radius:12px}.vp-sponsor-section+.vp-sponsor-section{margin-top:4px}.vp-sponsor-tier{margin:0 0 4px!important;text-align:center;letter-spacing:1px!important;line-height:24px;width:100%;font-weight:600;color:var(--vp-c-text-2);background-color:var(--vp-c-bg-soft)}.vp-sponsor.normal .vp-sponsor-tier{padding:13px 0 11px;font-size:14px}.vp-sponsor.aside .vp-sponsor-tier{padding:9px 0 7px;font-size:12px}.vp-sponsor-grid+.vp-sponsor-tier{margin-top:4px}.vp-sponsor-grid{display:flex;flex-wrap:wrap;gap:4px}.vp-sponsor-grid.xmini .vp-sponsor-grid-link{height:64px}.vp-sponsor-grid.xmini .vp-sponsor-grid-image{max-width:64px;max-height:22px}.vp-sponsor-grid.mini .vp-sponsor-grid-link{height:72px}.vp-sponsor-grid.mini .vp-sponsor-grid-image{max-width:96px;max-height:24px}.vp-sponsor-grid.small .vp-sponsor-grid-link{height:96px}.vp-sponsor-grid.small .vp-sponsor-grid-image{max-width:96px;max-height:24px}.vp-sponsor-grid.medium .vp-sponsor-grid-link{height:112px}.vp-sponsor-grid.medium .vp-sponsor-grid-image{max-width:120px;max-height:36px}.vp-sponsor-grid.big .vp-sponsor-grid-link{height:184px}.vp-sponsor-grid.big .vp-sponsor-grid-image{max-width:192px;max-height:56px}.vp-sponsor-grid[data-vp-grid="2"] .vp-sponsor-grid-item{width:calc((100% - 4px)/2)}.vp-sponsor-grid[data-vp-grid="3"] .vp-sponsor-grid-item{width:calc((100% - 4px * 2) / 3)}.vp-sponsor-grid[data-vp-grid="4"] .vp-sponsor-grid-item{width:calc((100% - 12px)/4)}.vp-sponsor-grid[data-vp-grid="5"] .vp-sponsor-grid-item{width:calc((100% - 16px)/5)}.vp-sponsor-grid[data-vp-grid="6"] .vp-sponsor-grid-item{width:calc((100% - 4px * 5) / 6)}.vp-sponsor-grid-item{flex-shrink:0;width:100%;background-color:var(--vp-c-bg-soft);transition:background-color .25s}.vp-sponsor-grid-item:hover{background-color:var(--vp-c-default-soft)}.vp-sponsor-grid-item:hover .vp-sponsor-grid-image{filter:grayscale(0) invert(0)}.vp-sponsor-grid-item.empty:hover{background-color:var(--vp-c-bg-soft)}.dark .vp-sponsor-grid-item:hover{background-color:var(--vp-c-white)}.dark .vp-sponsor-grid-item.empty:hover{background-color:var(--vp-c-bg-soft)}.vp-sponsor-grid-link{display:flex}.vp-sponsor-grid-box{display:flex;justify-content:center;align-items:center;width:100%}.vp-sponsor-grid-image{max-width:100%;filter:grayscale(1);transition:filter .25s}.dark .vp-sponsor-grid-image{filter:grayscale(1) invert(1)}.VPBadge{display:inline-block;margin-left:2px;border:1px solid transparent;border-radius:12px;padding:0 10px;line-height:22px;font-size:12px;font-weight:500;transform:translateY(-2px)}.VPBadge.small{padding:0 6px;line-height:18px;font-size:10px;transform:translateY(-8px)}.VPDocFooter .VPBadge{display:none}.vp-doc h1>.VPBadge{margin-top:4px;vertical-align:top}.vp-doc h2>.VPBadge{margin-top:3px;padding:0 8px;vertical-align:top}.vp-doc h3>.VPBadge{vertical-align:middle}.vp-doc h4>.VPBadge,.vp-doc h5>.VPBadge,.vp-doc h6>.VPBadge{vertical-align:middle;line-height:18px}.VPBadge.info{border-color:var(--vp-badge-info-border);color:var(--vp-badge-info-text);background-color:var(--vp-badge-info-bg)}.VPBadge.tip{border-color:var(--vp-badge-tip-border);color:var(--vp-badge-tip-text);background-color:var(--vp-badge-tip-bg)}.VPBadge.warning{border-color:var(--vp-badge-warning-border);color:var(--vp-badge-warning-text);background-color:var(--vp-badge-warning-bg)}.VPBadge.danger{border-color:var(--vp-badge-danger-border);color:var(--vp-badge-danger-text);background-color:var(--vp-badge-danger-bg)}.VPBackdrop[data-v-54a304ca]{position:fixed;top:0;right:0;bottom:0;left:0;z-index:var(--vp-z-index-backdrop);background:var(--vp-backdrop-bg-color);transition:opacity .5s}.VPBackdrop.fade-enter-from[data-v-54a304ca],.VPBackdrop.fade-leave-to[data-v-54a304ca]{opacity:0}.VPBackdrop.fade-leave-active[data-v-54a304ca]{transition-duration:.25s}@media (min-width: 1280px){.VPBackdrop[data-v-54a304ca]{display:none}}.NotFound[data-v-6ff51ddd]{padding:64px 24px 96px;text-align:center}@media (min-width: 768px){.NotFound[data-v-6ff51ddd]{padding:96px 32px 168px}}.code[data-v-6ff51ddd]{line-height:64px;font-size:64px;font-weight:600}.title[data-v-6ff51ddd]{padding-top:12px;letter-spacing:2px;line-height:20px;font-size:20px;font-weight:700}.divider[data-v-6ff51ddd]{margin:24px auto 18px;width:64px;height:1px;background-color:var(--vp-c-divider)}.quote[data-v-6ff51ddd]{margin:0 auto;max-width:256px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.action[data-v-6ff51ddd]{padding-top:20px}.link[data-v-6ff51ddd]{display:inline-block;border:1px solid var(--vp-c-brand-1);border-radius:16px;padding:3px 16px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:border-color .25s,color .25s}.link[data-v-6ff51ddd]:hover{border-color:var(--vp-c-brand-2);color:var(--vp-c-brand-2)}.root[data-v-53c99d69]{position:relative;z-index:1}.nested[data-v-53c99d69]{padding-right:16px;padding-left:16px}.outline-link[data-v-53c99d69]{display:block;line-height:32px;font-size:14px;font-weight:400;color:var(--vp-c-text-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .5s}.outline-link[data-v-53c99d69]:hover,.outline-link.active[data-v-53c99d69]{color:var(--vp-c-text-1);transition:color .25s}.outline-link.nested[data-v-53c99d69]{padding-left:13px}.VPDocAsideOutline[data-v-f610f197]{display:none}.VPDocAsideOutline.has-outline[data-v-f610f197]{display:block}.content[data-v-f610f197]{position:relative;border-left:1px solid var(--vp-c-divider);padding-left:16px;font-size:13px;font-weight:500}.outline-marker[data-v-f610f197]{position:absolute;top:32px;left:-1px;z-index:0;opacity:0;width:2px;border-radius:2px;height:18px;background-color:var(--vp-c-brand-1);transition:top .25s cubic-bezier(0,1,.5,1),background-color .5s,opacity .25s}.outline-title[data-v-f610f197]{line-height:32px;font-size:14px;font-weight:600}.VPDocAside[data-v-cb998dce]{display:flex;flex-direction:column;flex-grow:1}.spacer[data-v-cb998dce]{flex-grow:1}.VPDocAside[data-v-cb998dce] .spacer+.VPDocAsideSponsors,.VPDocAside[data-v-cb998dce] .spacer+.VPDocAsideCarbonAds{margin-top:24px}.VPDocAside[data-v-cb998dce] .VPDocAsideSponsors+.VPDocAsideCarbonAds{margin-top:16px}.VPLastUpdated[data-v-1bb0c8a8]{line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}@media (min-width: 640px){.VPLastUpdated[data-v-1bb0c8a8]{line-height:32px;font-size:14px;font-weight:500}}.VPDocFooter[data-v-1bcd8184]{margin-top:64px}.edit-info[data-v-1bcd8184]{padding-bottom:18px}@media (min-width: 640px){.edit-info[data-v-1bcd8184]{display:flex;justify-content:space-between;align-items:center;padding-bottom:14px}}.edit-link-button[data-v-1bcd8184]{display:flex;align-items:center;border:0;line-height:32px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:color .25s}.edit-link-button[data-v-1bcd8184]:hover{color:var(--vp-c-brand-2)}.edit-link-icon[data-v-1bcd8184]{margin-right:8px}.prev-next[data-v-1bcd8184]{border-top:1px solid var(--vp-c-divider);padding-top:24px;display:grid;grid-row-gap:8px}@media (min-width: 640px){.prev-next[data-v-1bcd8184]{grid-template-columns:repeat(2,1fr);grid-column-gap:16px}}.pager-link[data-v-1bcd8184]{display:block;border:1px solid var(--vp-c-divider);border-radius:8px;padding:11px 16px 13px;width:100%;height:100%;transition:border-color .25s}.pager-link[data-v-1bcd8184]:hover{border-color:var(--vp-c-brand-1)}.pager-link.next[data-v-1bcd8184]{margin-left:auto;text-align:right}.desc[data-v-1bcd8184]{display:block;line-height:20px;font-size:12px;font-weight:500;color:var(--vp-c-text-2)}.title[data-v-1bcd8184]{display:block;line-height:20px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:color .25s}.VPDoc[data-v-e6f2a212]{padding:32px 24px 96px;width:100%}@media (min-width: 768px){.VPDoc[data-v-e6f2a212]{padding:48px 32px 128px}}@media (min-width: 960px){.VPDoc[data-v-e6f2a212]{padding:48px 32px 0}.VPDoc:not(.has-sidebar) .container[data-v-e6f2a212]{display:flex;justify-content:center;max-width:992px}.VPDoc:not(.has-sidebar) .content[data-v-e6f2a212]{max-width:752px}}@media (min-width: 1280px){.VPDoc .container[data-v-e6f2a212]{display:flex;justify-content:center}.VPDoc .aside[data-v-e6f2a212]{display:block}}@media (min-width: 1440px){.VPDoc:not(.has-sidebar) .content[data-v-e6f2a212]{max-width:784px}.VPDoc:not(.has-sidebar) .container[data-v-e6f2a212]{max-width:1104px}}.container[data-v-e6f2a212]{margin:0 auto;width:100%}.aside[data-v-e6f2a212]{position:relative;display:none;order:2;flex-grow:1;padding-left:32px;width:100%;max-width:256px}.left-aside[data-v-e6f2a212]{order:1;padding-left:unset;padding-right:32px}.aside-container[data-v-e6f2a212]{position:fixed;top:0;padding-top:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + var(--vp-doc-top-height, 0px) + 48px);width:224px;height:100vh;overflow-x:hidden;overflow-y:auto;scrollbar-width:none}.aside-container[data-v-e6f2a212]::-webkit-scrollbar{display:none}.aside-curtain[data-v-e6f2a212]{position:fixed;bottom:0;z-index:10;width:224px;height:32px;background:linear-gradient(transparent,var(--vp-c-bg) 70%)}.aside-content[data-v-e6f2a212]{display:flex;flex-direction:column;min-height:calc(100vh - (var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px));padding-bottom:32px}.content[data-v-e6f2a212]{position:relative;margin:0 auto;width:100%}@media (min-width: 960px){.content[data-v-e6f2a212]{padding:0 32px 128px}}@media (min-width: 1280px){.content[data-v-e6f2a212]{order:1;margin:0;min-width:640px}}.content-container[data-v-e6f2a212]{margin:0 auto}.VPDoc.has-aside .content-container[data-v-e6f2a212]{max-width:688px}.VPButton[data-v-93dc4167]{display:inline-block;border:1px solid transparent;text-align:center;font-weight:600;white-space:nowrap;transition:color .25s,border-color .25s,background-color .25s}.VPButton[data-v-93dc4167]:active{transition:color .1s,border-color .1s,background-color .1s}.VPButton.medium[data-v-93dc4167]{border-radius:20px;padding:0 20px;line-height:38px;font-size:14px}.VPButton.big[data-v-93dc4167]{border-radius:24px;padding:0 24px;line-height:46px;font-size:16px}.VPButton.brand[data-v-93dc4167]{border-color:var(--vp-button-brand-border);color:var(--vp-button-brand-text);background-color:var(--vp-button-brand-bg)}.VPButton.brand[data-v-93dc4167]:hover{border-color:var(--vp-button-brand-hover-border);color:var(--vp-button-brand-hover-text);background-color:var(--vp-button-brand-hover-bg)}.VPButton.brand[data-v-93dc4167]:active{border-color:var(--vp-button-brand-active-border);color:var(--vp-button-brand-active-text);background-color:var(--vp-button-brand-active-bg)}.VPButton.alt[data-v-93dc4167]{border-color:var(--vp-button-alt-border);color:var(--vp-button-alt-text);background-color:var(--vp-button-alt-bg)}.VPButton.alt[data-v-93dc4167]:hover{border-color:var(--vp-button-alt-hover-border);color:var(--vp-button-alt-hover-text);background-color:var(--vp-button-alt-hover-bg)}.VPButton.alt[data-v-93dc4167]:active{border-color:var(--vp-button-alt-active-border);color:var(--vp-button-alt-active-text);background-color:var(--vp-button-alt-active-bg)}.VPButton.sponsor[data-v-93dc4167]{border-color:var(--vp-button-sponsor-border);color:var(--vp-button-sponsor-text);background-color:var(--vp-button-sponsor-bg)}.VPButton.sponsor[data-v-93dc4167]:hover{border-color:var(--vp-button-sponsor-hover-border);color:var(--vp-button-sponsor-hover-text);background-color:var(--vp-button-sponsor-hover-bg)}.VPButton.sponsor[data-v-93dc4167]:active{border-color:var(--vp-button-sponsor-active-border);color:var(--vp-button-sponsor-active-text);background-color:var(--vp-button-sponsor-active-bg)}html:not(.dark) .VPImage.dark[data-v-ab19afbb]{display:none}.dark .VPImage.light[data-v-ab19afbb]{display:none}.VPHero[data-v-b10c5094]{margin-top:calc((var(--vp-nav-height) + var(--vp-layout-top-height, 0px)) * -1);padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px) 24px 48px}@media (min-width: 640px){.VPHero[data-v-b10c5094]{padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 48px 64px}}@media (min-width: 960px){.VPHero[data-v-b10c5094]{padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 64px 64px}}.container[data-v-b10c5094]{display:flex;flex-direction:column;margin:0 auto;max-width:1152px}@media (min-width: 960px){.container[data-v-b10c5094]{flex-direction:row}}.main[data-v-b10c5094]{position:relative;z-index:10;order:2;flex-grow:1;flex-shrink:0}.VPHero.has-image .container[data-v-b10c5094]{text-align:center}@media (min-width: 960px){.VPHero.has-image .container[data-v-b10c5094]{text-align:left}}@media (min-width: 960px){.main[data-v-b10c5094]{order:1;width:calc((100% / 3) * 2)}.VPHero.has-image .main[data-v-b10c5094]{max-width:592px}}.name[data-v-b10c5094],.text[data-v-b10c5094]{max-width:392px;letter-spacing:-.4px;line-height:40px;font-size:32px;font-weight:700;white-space:pre-wrap}.VPHero.has-image .name[data-v-b10c5094],.VPHero.has-image .text[data-v-b10c5094]{margin:0 auto}.name[data-v-b10c5094]{color:var(--vp-home-hero-name-color)}.clip[data-v-b10c5094]{background:var(--vp-home-hero-name-background);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:var(--vp-home-hero-name-color)}@media (min-width: 640px){.name[data-v-b10c5094],.text[data-v-b10c5094]{max-width:576px;line-height:56px;font-size:48px}}@media (min-width: 960px){.name[data-v-b10c5094],.text[data-v-b10c5094]{line-height:64px;font-size:56px}.VPHero.has-image .name[data-v-b10c5094],.VPHero.has-image .text[data-v-b10c5094]{margin:0}}.tagline[data-v-b10c5094]{padding-top:8px;max-width:392px;line-height:28px;font-size:18px;font-weight:500;white-space:pre-wrap;color:var(--vp-c-text-2)}.VPHero.has-image .tagline[data-v-b10c5094]{margin:0 auto}@media (min-width: 640px){.tagline[data-v-b10c5094]{padding-top:12px;max-width:576px;line-height:32px;font-size:20px}}@media (min-width: 960px){.tagline[data-v-b10c5094]{line-height:36px;font-size:24px}.VPHero.has-image .tagline[data-v-b10c5094]{margin:0}}.actions[data-v-b10c5094]{display:flex;flex-wrap:wrap;margin:-6px;padding-top:24px}.VPHero.has-image .actions[data-v-b10c5094]{justify-content:center}@media (min-width: 640px){.actions[data-v-b10c5094]{padding-top:32px}}@media (min-width: 960px){.VPHero.has-image .actions[data-v-b10c5094]{justify-content:flex-start}}.action[data-v-b10c5094]{flex-shrink:0;padding:6px}.image[data-v-b10c5094]{order:1;margin:-76px -24px -48px}@media (min-width: 640px){.image[data-v-b10c5094]{margin:-108px -24px -48px}}@media (min-width: 960px){.image[data-v-b10c5094]{flex-grow:1;order:2;margin:0;min-height:100%}}.image-container[data-v-b10c5094]{position:relative;margin:0 auto;width:320px;height:320px}@media (min-width: 640px){.image-container[data-v-b10c5094]{width:392px;height:392px}}@media (min-width: 960px){.image-container[data-v-b10c5094]{display:flex;justify-content:center;align-items:center;width:100%;height:100%;transform:translate(-32px,-32px)}}.image-bg[data-v-b10c5094]{position:absolute;top:50%;left:50%;border-radius:50%;width:192px;height:192px;background-image:var(--vp-home-hero-image-background-image);filter:var(--vp-home-hero-image-filter);transform:translate(-50%,-50%)}@media (min-width: 640px){.image-bg[data-v-b10c5094]{width:256px;height:256px}}@media (min-width: 960px){.image-bg[data-v-b10c5094]{width:320px;height:320px}}[data-v-b10c5094] .image-src{position:absolute;top:50%;left:50%;max-width:192px;max-height:192px;transform:translate(-50%,-50%)}@media (min-width: 640px){[data-v-b10c5094] .image-src{max-width:256px;max-height:256px}}@media (min-width: 960px){[data-v-b10c5094] .image-src{max-width:320px;max-height:320px}}.VPFeature[data-v-bd37d1a2]{display:block;border:1px solid var(--vp-c-bg-soft);border-radius:12px;height:100%;background-color:var(--vp-c-bg-soft);transition:border-color .25s,background-color .25s}.VPFeature.link[data-v-bd37d1a2]:hover{border-color:var(--vp-c-brand-1)}.box[data-v-bd37d1a2]{display:flex;flex-direction:column;padding:24px;height:100%}.box[data-v-bd37d1a2]>.VPImage{margin-bottom:20px}.icon[data-v-bd37d1a2]{display:flex;justify-content:center;align-items:center;margin-bottom:20px;border-radius:6px;background-color:var(--vp-c-default-soft);width:48px;height:48px;font-size:24px;transition:background-color .25s}.title[data-v-bd37d1a2]{line-height:24px;font-size:16px;font-weight:600}.details[data-v-bd37d1a2]{flex-grow:1;padding-top:8px;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.link-text[data-v-bd37d1a2]{padding-top:8px}.link-text-value[data-v-bd37d1a2]{display:flex;align-items:center;font-size:14px;font-weight:500;color:var(--vp-c-brand-1)}.link-text-icon[data-v-bd37d1a2]{margin-left:6px}.VPFeatures[data-v-b1eea84a]{position:relative;padding:0 24px}@media (min-width: 640px){.VPFeatures[data-v-b1eea84a]{padding:0 48px}}@media (min-width: 960px){.VPFeatures[data-v-b1eea84a]{padding:0 64px}}.container[data-v-b1eea84a]{margin:0 auto;max-width:1152px}.items[data-v-b1eea84a]{display:flex;flex-wrap:wrap;margin:-8px}.item[data-v-b1eea84a]{padding:8px;width:100%}@media (min-width: 640px){.item.grid-2[data-v-b1eea84a],.item.grid-4[data-v-b1eea84a],.item.grid-6[data-v-b1eea84a]{width:50%}}@media (min-width: 768px){.item.grid-2[data-v-b1eea84a],.item.grid-4[data-v-b1eea84a]{width:50%}.item.grid-3[data-v-b1eea84a],.item.grid-6[data-v-b1eea84a]{width:calc(100% / 3)}}@media (min-width: 960px){.item.grid-4[data-v-b1eea84a]{width:25%}}.container[data-v-c141a4bd]{margin:auto;width:100%;max-width:1280px;padding:0 24px}@media (min-width: 640px){.container[data-v-c141a4bd]{padding:0 48px}}@media (min-width: 960px){.container[data-v-c141a4bd]{width:100%;padding:0 64px}}.vp-doc[data-v-c141a4bd] .VPHomeSponsors,.vp-doc[data-v-c141a4bd] .VPTeamPage{margin-left:var(--vp-offset, calc(50% - 50vw) );margin-right:var(--vp-offset, calc(50% - 50vw) )}.vp-doc[data-v-c141a4bd] .VPHomeSponsors h2{border-top:none;letter-spacing:normal}.vp-doc[data-v-c141a4bd] .VPHomeSponsors a,.vp-doc[data-v-c141a4bd] .VPTeamPage a{text-decoration:none}.VPHome[data-v-07b1ad08]{margin-bottom:96px}@media (min-width: 768px){.VPHome[data-v-07b1ad08]{margin-bottom:128px}}.VPContent[data-v-9a6c75ad]{flex-grow:1;flex-shrink:0;margin:var(--vp-layout-top-height, 0px) auto 0;width:100%}.VPContent.is-home[data-v-9a6c75ad]{width:100%;max-width:100%}.VPContent.has-sidebar[data-v-9a6c75ad]{margin:0}@media (min-width: 960px){.VPContent[data-v-9a6c75ad]{padding-top:var(--vp-nav-height)}.VPContent.has-sidebar[data-v-9a6c75ad]{margin:var(--vp-layout-top-height, 0px) 0 0;padding-left:var(--vp-sidebar-width)}}@media (min-width: 1440px){.VPContent.has-sidebar[data-v-9a6c75ad]{padding-right:calc((100vw - var(--vp-layout-max-width)) / 2);padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.VPFooter[data-v-566314d4]{position:relative;z-index:var(--vp-z-index-footer);border-top:1px solid var(--vp-c-gutter);padding:32px 24px;background-color:var(--vp-c-bg)}.VPFooter.has-sidebar[data-v-566314d4]{display:none}.VPFooter[data-v-566314d4] a{text-decoration-line:underline;text-underline-offset:2px;transition:color .25s}.VPFooter[data-v-566314d4] a:hover{color:var(--vp-c-text-1)}@media (min-width: 768px){.VPFooter[data-v-566314d4]{padding:32px}}.container[data-v-566314d4]{margin:0 auto;max-width:var(--vp-layout-max-width);text-align:center}.message[data-v-566314d4],.copyright[data-v-566314d4]{line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.VPLocalNavOutlineDropdown[data-v-883964e0]{padding:12px 20px 11px}@media (min-width: 960px){.VPLocalNavOutlineDropdown[data-v-883964e0]{padding:12px 36px 11px}}.VPLocalNavOutlineDropdown button[data-v-883964e0]{display:block;font-size:12px;font-weight:500;line-height:24px;color:var(--vp-c-text-2);transition:color .5s;position:relative}.VPLocalNavOutlineDropdown button[data-v-883964e0]:hover{color:var(--vp-c-text-1);transition:color .25s}.VPLocalNavOutlineDropdown button.open[data-v-883964e0]{color:var(--vp-c-text-1)}.icon[data-v-883964e0]{display:inline-block;vertical-align:middle;margin-left:2px;font-size:14px;transform:rotate(0);transition:transform .25s}@media (min-width: 960px){.VPLocalNavOutlineDropdown button[data-v-883964e0]{font-size:14px}.icon[data-v-883964e0]{font-size:16px}}.open>.icon[data-v-883964e0]{transform:rotate(90deg)}.items[data-v-883964e0]{position:absolute;top:40px;right:16px;left:16px;display:grid;gap:1px;border:1px solid var(--vp-c-border);border-radius:8px;background-color:var(--vp-c-gutter);max-height:calc(var(--vp-vh, 100vh) - 86px);overflow:hidden auto;box-shadow:var(--vp-shadow-3)}@media (min-width: 960px){.items[data-v-883964e0]{right:auto;left:calc(var(--vp-sidebar-width) + 32px);width:320px}}.header[data-v-883964e0]{background-color:var(--vp-c-bg-soft)}.top-link[data-v-883964e0]{display:block;padding:0 16px;line-height:48px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1)}.outline[data-v-883964e0]{padding:8px 0;background-color:var(--vp-c-bg-soft)}.flyout-enter-active[data-v-883964e0]{transition:all .2s ease-out}.flyout-leave-active[data-v-883964e0]{transition:all .15s ease-in}.flyout-enter-from[data-v-883964e0],.flyout-leave-to[data-v-883964e0]{opacity:0;transform:translateY(-16px)}.VPLocalNav[data-v-2488c25a]{position:sticky;top:0;left:0;z-index:var(--vp-z-index-local-nav);border-bottom:1px solid var(--vp-c-gutter);padding-top:var(--vp-layout-top-height, 0px);width:100%;background-color:var(--vp-local-nav-bg-color)}.VPLocalNav.fixed[data-v-2488c25a]{position:fixed}@media (min-width: 960px){.VPLocalNav[data-v-2488c25a]{top:var(--vp-nav-height)}.VPLocalNav.has-sidebar[data-v-2488c25a]{padding-left:var(--vp-sidebar-width)}.VPLocalNav.empty[data-v-2488c25a]{display:none}}@media (min-width: 1280px){.VPLocalNav[data-v-2488c25a]{display:none}}@media (min-width: 1440px){.VPLocalNav.has-sidebar[data-v-2488c25a]{padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.container[data-v-2488c25a]{display:flex;justify-content:space-between;align-items:center}.menu[data-v-2488c25a]{display:flex;align-items:center;padding:12px 24px 11px;line-height:24px;font-size:12px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.menu[data-v-2488c25a]:hover{color:var(--vp-c-text-1);transition:color .25s}@media (min-width: 768px){.menu[data-v-2488c25a]{padding:0 32px}}@media (min-width: 960px){.menu[data-v-2488c25a]{display:none}}.menu-icon[data-v-2488c25a]{margin-right:8px;font-size:14px}.VPOutlineDropdown[data-v-2488c25a]{padding:12px 24px 11px}@media (min-width: 768px){.VPOutlineDropdown[data-v-2488c25a]{padding:12px 32px 11px}}.VPSwitch[data-v-b4ccac88]{position:relative;border-radius:11px;display:block;width:40px;height:22px;flex-shrink:0;border:1px solid var(--vp-input-border-color);background-color:var(--vp-input-switch-bg-color);transition:border-color .25s!important}.VPSwitch[data-v-b4ccac88]:hover{border-color:var(--vp-c-brand-1)}.check[data-v-b4ccac88]{position:absolute;top:1px;left:1px;width:18px;height:18px;border-radius:50%;background-color:var(--vp-c-neutral-inverse);box-shadow:var(--vp-shadow-1);transition:transform .25s!important}.icon[data-v-b4ccac88]{position:relative;display:block;width:18px;height:18px;border-radius:50%;overflow:hidden}.icon[data-v-b4ccac88] [class^=vpi-]{position:absolute;top:3px;left:3px;width:12px;height:12px;color:var(--vp-c-text-2)}.dark .icon[data-v-b4ccac88] [class^=vpi-]{color:var(--vp-c-text-1);transition:opacity .25s!important}.sun[data-v-be9742d9]{opacity:1}.moon[data-v-be9742d9],.dark .sun[data-v-be9742d9]{opacity:0}.dark .moon[data-v-be9742d9]{opacity:1}.dark .VPSwitchAppearance[data-v-be9742d9] .check{transform:translate(18px)}.VPNavBarAppearance[data-v-3f90c1a5]{display:none}@media (min-width: 1280px){.VPNavBarAppearance[data-v-3f90c1a5]{display:flex;align-items:center}}.VPMenuGroup+.VPMenuLink[data-v-7eeeb2dc]{margin:12px -12px 0;border-top:1px solid var(--vp-c-divider);padding:12px 12px 0}.link[data-v-7eeeb2dc]{display:block;border-radius:6px;padding:0 12px;line-height:32px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);white-space:nowrap;transition:background-color .25s,color .25s}.link[data-v-7eeeb2dc]:hover{color:var(--vp-c-brand-1);background-color:var(--vp-c-default-soft)}.link.active[data-v-7eeeb2dc]{color:var(--vp-c-brand-1)}.VPMenuGroup[data-v-a6b0397c]{margin:12px -12px 0;border-top:1px solid var(--vp-c-divider);padding:12px 12px 0}.VPMenuGroup[data-v-a6b0397c]:first-child{margin-top:0;border-top:0;padding-top:0}.VPMenuGroup+.VPMenuGroup[data-v-a6b0397c]{margin-top:12px;border-top:1px solid var(--vp-c-divider)}.title[data-v-a6b0397c]{padding:0 12px;line-height:32px;font-size:14px;font-weight:600;color:var(--vp-c-text-2);white-space:nowrap;transition:color .25s}.VPMenu[data-v-20ed86d6]{border-radius:12px;padding:12px;min-width:128px;border:1px solid var(--vp-c-divider);background-color:var(--vp-c-bg-elv);box-shadow:var(--vp-shadow-3);transition:background-color .5s;max-height:calc(100vh - var(--vp-nav-height));overflow-y:auto}.VPMenu[data-v-20ed86d6] .group{margin:0 -12px;padding:0 12px 12px}.VPMenu[data-v-20ed86d6] .group+.group{border-top:1px solid var(--vp-c-divider);padding:11px 12px 12px}.VPMenu[data-v-20ed86d6] .group:last-child{padding-bottom:0}.VPMenu[data-v-20ed86d6] .group+.item{border-top:1px solid var(--vp-c-divider);padding:11px 16px 0}.VPMenu[data-v-20ed86d6] .item{padding:0 16px;white-space:nowrap}.VPMenu[data-v-20ed86d6] .label{flex-grow:1;line-height:28px;font-size:12px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.VPMenu[data-v-20ed86d6] .action{padding-left:24px}.VPFlyout[data-v-bfe7971f]{position:relative}.VPFlyout[data-v-bfe7971f]:hover{color:var(--vp-c-brand-1);transition:color .25s}.VPFlyout:hover .text[data-v-bfe7971f]{color:var(--vp-c-text-2)}.VPFlyout:hover .icon[data-v-bfe7971f]{fill:var(--vp-c-text-2)}.VPFlyout.active .text[data-v-bfe7971f]{color:var(--vp-c-brand-1)}.VPFlyout.active:hover .text[data-v-bfe7971f]{color:var(--vp-c-brand-2)}.button[aria-expanded=false]+.menu[data-v-bfe7971f]{opacity:0;visibility:hidden;transform:translateY(0)}.VPFlyout:hover .menu[data-v-bfe7971f],.button[aria-expanded=true]+.menu[data-v-bfe7971f]{opacity:1;visibility:visible;transform:translateY(0)}.button[data-v-bfe7971f]{display:flex;align-items:center;padding:0 12px;height:var(--vp-nav-height);color:var(--vp-c-text-1);transition:color .5s}.text[data-v-bfe7971f]{display:flex;align-items:center;line-height:var(--vp-nav-height);font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.option-icon[data-v-bfe7971f]{margin-right:0;font-size:16px}.text-icon[data-v-bfe7971f]{margin-left:4px;font-size:14px}.icon[data-v-bfe7971f]{font-size:20px;transition:fill .25s}.menu[data-v-bfe7971f]{position:absolute;top:calc(var(--vp-nav-height) / 2 + 20px);right:0;opacity:0;visibility:hidden;transition:opacity .25s,visibility .25s,transform .25s}.VPSocialLink[data-v-60a9a2d3]{display:flex;justify-content:center;align-items:center;width:36px;height:36px;color:var(--vp-c-text-2);transition:color .5s}.VPSocialLink[data-v-60a9a2d3]:hover{color:var(--vp-c-text-1);transition:color .25s}.VPSocialLink[data-v-60a9a2d3]>svg,.VPSocialLink[data-v-60a9a2d3]>[class^=vpi-social-]{width:20px;height:20px;fill:currentColor}.VPSocialLinks[data-v-e71e869c]{display:flex;justify-content:center}.VPNavBarExtra[data-v-f953d92f]{display:none;margin-right:-12px}@media (min-width: 768px){.VPNavBarExtra[data-v-f953d92f]{display:block}}@media (min-width: 1280px){.VPNavBarExtra[data-v-f953d92f]{display:none}}.trans-title[data-v-f953d92f]{padding:0 24px 0 12px;line-height:32px;font-size:14px;font-weight:700;color:var(--vp-c-text-1)}.item.appearance[data-v-f953d92f],.item.social-links[data-v-f953d92f]{display:flex;align-items:center;padding:0 12px}.item.appearance[data-v-f953d92f]{min-width:176px}.appearance-action[data-v-f953d92f]{margin-right:-2px}.social-links-list[data-v-f953d92f]{margin:-4px -8px}.VPNavBarHamburger[data-v-6bee1efd]{display:flex;justify-content:center;align-items:center;width:48px;height:var(--vp-nav-height)}@media (min-width: 768px){.VPNavBarHamburger[data-v-6bee1efd]{display:none}}.container[data-v-6bee1efd]{position:relative;width:16px;height:14px;overflow:hidden}.VPNavBarHamburger:hover .top[data-v-6bee1efd]{top:0;left:0;transform:translate(4px)}.VPNavBarHamburger:hover .middle[data-v-6bee1efd]{top:6px;left:0;transform:translate(0)}.VPNavBarHamburger:hover .bottom[data-v-6bee1efd]{top:12px;left:0;transform:translate(8px)}.VPNavBarHamburger.active .top[data-v-6bee1efd]{top:6px;transform:translate(0) rotate(225deg)}.VPNavBarHamburger.active .middle[data-v-6bee1efd]{top:6px;transform:translate(16px)}.VPNavBarHamburger.active .bottom[data-v-6bee1efd]{top:6px;transform:translate(0) rotate(135deg)}.VPNavBarHamburger.active:hover .top[data-v-6bee1efd],.VPNavBarHamburger.active:hover .middle[data-v-6bee1efd],.VPNavBarHamburger.active:hover .bottom[data-v-6bee1efd]{background-color:var(--vp-c-text-2);transition:top .25s,background-color .25s,transform .25s}.top[data-v-6bee1efd],.middle[data-v-6bee1efd],.bottom[data-v-6bee1efd]{position:absolute;width:16px;height:2px;background-color:var(--vp-c-text-1);transition:top .25s,background-color .5s,transform .25s}.top[data-v-6bee1efd]{top:0;left:0;transform:translate(0)}.middle[data-v-6bee1efd]{top:6px;left:0;transform:translate(8px)}.bottom[data-v-6bee1efd]{top:12px;left:0;transform:translate(4px)}.VPNavBarMenuLink[data-v-815115f5]{display:flex;align-items:center;padding:0 12px;line-height:var(--vp-nav-height);font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.VPNavBarMenuLink.active[data-v-815115f5],.VPNavBarMenuLink[data-v-815115f5]:hover{color:var(--vp-c-brand-1)}.VPNavBarMenu[data-v-afb2845e]{display:none}@media (min-width: 768px){.VPNavBarMenu[data-v-afb2845e]{display:flex}}/*! @docsearch/css 3.8.0 | MIT License | © Algolia, Inc. and contributors | https://docsearch.algolia.com */:root{--docsearch-primary-color:#5468ff;--docsearch-text-color:#1c1e21;--docsearch-spacing:12px;--docsearch-icon-stroke-width:1.4;--docsearch-highlight-color:var(--docsearch-primary-color);--docsearch-muted-color:#969faf;--docsearch-container-background:rgba(101,108,133,.8);--docsearch-logo-color:#5468ff;--docsearch-modal-width:560px;--docsearch-modal-height:600px;--docsearch-modal-background:#f5f6f7;--docsearch-modal-shadow:inset 1px 1px 0 0 hsla(0,0%,100%,.5),0 3px 8px 0 #555a64;--docsearch-searchbox-height:56px;--docsearch-searchbox-background:#ebedf0;--docsearch-searchbox-focus-background:#fff;--docsearch-searchbox-shadow:inset 0 0 0 2px var(--docsearch-primary-color);--docsearch-hit-height:56px;--docsearch-hit-color:#444950;--docsearch-hit-active-color:#fff;--docsearch-hit-background:#fff;--docsearch-hit-shadow:0 1px 3px 0 #d4d9e1;--docsearch-key-gradient:linear-gradient(-225deg,#d5dbe4,#f8f8f8);--docsearch-key-shadow:inset 0 -2px 0 0 #cdcde6,inset 0 0 1px 1px #fff,0 1px 2px 1px rgba(30,35,90,.4);--docsearch-key-pressed-shadow:inset 0 -2px 0 0 #cdcde6,inset 0 0 1px 1px #fff,0 1px 1px 0 rgba(30,35,90,.4);--docsearch-footer-height:44px;--docsearch-footer-background:#fff;--docsearch-footer-shadow:0 -1px 0 0 #e0e3e8,0 -3px 6px 0 rgba(69,98,155,.12)}html[data-theme=dark]{--docsearch-text-color:#f5f6f7;--docsearch-container-background:rgba(9,10,17,.8);--docsearch-modal-background:#15172a;--docsearch-modal-shadow:inset 1px 1px 0 0 #2c2e40,0 3px 8px 0 #000309;--docsearch-searchbox-background:#090a11;--docsearch-searchbox-focus-background:#000;--docsearch-hit-color:#bec3c9;--docsearch-hit-shadow:none;--docsearch-hit-background:#090a11;--docsearch-key-gradient:linear-gradient(-26.5deg,#565872,#31355b);--docsearch-key-shadow:inset 0 -2px 0 0 #282d55,inset 0 0 1px 1px #51577d,0 2px 2px 0 rgba(3,4,9,.3);--docsearch-key-pressed-shadow:inset 0 -2px 0 0 #282d55,inset 0 0 1px 1px #51577d,0 1px 1px 0 #0304094d;--docsearch-footer-background:#1e2136;--docsearch-footer-shadow:inset 0 1px 0 0 rgba(73,76,106,.5),0 -4px 8px 0 rgba(0,0,0,.2);--docsearch-logo-color:#fff;--docsearch-muted-color:#7f8497}.DocSearch-Button{align-items:center;background:var(--docsearch-searchbox-background);border:0;border-radius:40px;color:var(--docsearch-muted-color);cursor:pointer;display:flex;font-weight:500;height:36px;justify-content:space-between;margin:0 0 0 16px;padding:0 8px;-webkit-user-select:none;user-select:none}.DocSearch-Button:active,.DocSearch-Button:focus,.DocSearch-Button:hover{background:var(--docsearch-searchbox-focus-background);box-shadow:var(--docsearch-searchbox-shadow);color:var(--docsearch-text-color);outline:none}.DocSearch-Button-Container{align-items:center;display:flex}.DocSearch-Search-Icon{stroke-width:1.6}.DocSearch-Button .DocSearch-Search-Icon{color:var(--docsearch-text-color)}.DocSearch-Button-Placeholder{font-size:1rem;padding:0 12px 0 6px}.DocSearch-Button-Keys{display:flex;min-width:calc(40px + .8em)}.DocSearch-Button-Key{align-items:center;background:var(--docsearch-key-gradient);border:0;border-radius:3px;box-shadow:var(--docsearch-key-shadow);color:var(--docsearch-muted-color);display:flex;height:18px;justify-content:center;margin-right:.4em;padding:0 0 2px;position:relative;top:-1px;width:20px}.DocSearch-Button-Key--pressed{box-shadow:var(--docsearch-key-pressed-shadow);transform:translate3d(0,1px,0)}@media (max-width:768px){.DocSearch-Button-Keys,.DocSearch-Button-Placeholder{display:none}}.DocSearch--active{overflow:hidden!important}.DocSearch-Container,.DocSearch-Container *{box-sizing:border-box}.DocSearch-Container{background-color:var(--docsearch-container-background);height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:200}.DocSearch-Container a{text-decoration:none}.DocSearch-Link{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;color:var(--docsearch-highlight-color);cursor:pointer;font:inherit;margin:0;padding:0}.DocSearch-Modal{background:var(--docsearch-modal-background);border-radius:6px;box-shadow:var(--docsearch-modal-shadow);flex-direction:column;margin:60px auto auto;max-width:var(--docsearch-modal-width);position:relative}.DocSearch-SearchBar{display:flex;padding:var(--docsearch-spacing) var(--docsearch-spacing) 0}.DocSearch-Form{align-items:center;background:var(--docsearch-searchbox-focus-background);border-radius:4px;box-shadow:var(--docsearch-searchbox-shadow);display:flex;height:var(--docsearch-searchbox-height);margin:0;padding:0 var(--docsearch-spacing);position:relative;width:100%}.DocSearch-Input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;color:var(--docsearch-text-color);flex:1;font:inherit;font-size:1.2em;height:100%;outline:none;padding:0 0 0 8px;width:80%}.DocSearch-Input::placeholder{color:var(--docsearch-muted-color);opacity:1}.DocSearch-Input::-webkit-search-cancel-button,.DocSearch-Input::-webkit-search-decoration,.DocSearch-Input::-webkit-search-results-button,.DocSearch-Input::-webkit-search-results-decoration{display:none}.DocSearch-LoadingIndicator,.DocSearch-MagnifierLabel,.DocSearch-Reset{margin:0;padding:0}.DocSearch-MagnifierLabel,.DocSearch-Reset{align-items:center;color:var(--docsearch-highlight-color);display:flex;justify-content:center}.DocSearch-Container--Stalled .DocSearch-MagnifierLabel,.DocSearch-LoadingIndicator{display:none}.DocSearch-Container--Stalled .DocSearch-LoadingIndicator{align-items:center;color:var(--docsearch-highlight-color);display:flex;justify-content:center}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Reset{animation:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:var(--docsearch-icon-color);cursor:pointer;right:0;stroke-width:var(--docsearch-icon-stroke-width)}}.DocSearch-Reset{animation:fade-in .1s ease-in forwards;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:var(--docsearch-icon-color);cursor:pointer;padding:2px;right:0;stroke-width:var(--docsearch-icon-stroke-width)}.DocSearch-Reset[hidden]{display:none}.DocSearch-Reset:hover{color:var(--docsearch-highlight-color)}.DocSearch-LoadingIndicator svg,.DocSearch-MagnifierLabel svg{height:24px;width:24px}.DocSearch-Cancel{display:none}.DocSearch-Dropdown{max-height:calc(var(--docsearch-modal-height) - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height));min-height:var(--docsearch-spacing);overflow-y:auto;overflow-y:overlay;padding:0 var(--docsearch-spacing);scrollbar-color:var(--docsearch-muted-color) var(--docsearch-modal-background);scrollbar-width:thin}.DocSearch-Dropdown::-webkit-scrollbar{width:12px}.DocSearch-Dropdown::-webkit-scrollbar-track{background:transparent}.DocSearch-Dropdown::-webkit-scrollbar-thumb{background-color:var(--docsearch-muted-color);border:3px solid var(--docsearch-modal-background);border-radius:20px}.DocSearch-Dropdown ul{list-style:none;margin:0;padding:0}.DocSearch-Label{font-size:.75em;line-height:1.6em}.DocSearch-Help,.DocSearch-Label{color:var(--docsearch-muted-color)}.DocSearch-Help{font-size:.9em;margin:0;-webkit-user-select:none;user-select:none}.DocSearch-Title{font-size:1.2em}.DocSearch-Logo a{display:flex}.DocSearch-Logo svg{color:var(--docsearch-logo-color);margin-left:8px}.DocSearch-Hits:last-of-type{margin-bottom:24px}.DocSearch-Hits mark{background:none;color:var(--docsearch-highlight-color)}.DocSearch-HitsFooter{color:var(--docsearch-muted-color);display:flex;font-size:.85em;justify-content:center;margin-bottom:var(--docsearch-spacing);padding:var(--docsearch-spacing)}.DocSearch-HitsFooter a{border-bottom:1px solid;color:inherit}.DocSearch-Hit{border-radius:4px;display:flex;padding-bottom:4px;position:relative}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit--deleting{transition:none}}.DocSearch-Hit--deleting{opacity:0;transition:all .25s linear}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit--favoriting{transition:none}}.DocSearch-Hit--favoriting{transform:scale(0);transform-origin:top center;transition:all .25s linear;transition-delay:.25s}.DocSearch-Hit a{background:var(--docsearch-hit-background);border-radius:4px;box-shadow:var(--docsearch-hit-shadow);display:block;padding-left:var(--docsearch-spacing);width:100%}.DocSearch-Hit-source{background:var(--docsearch-modal-background);color:var(--docsearch-highlight-color);font-size:.85em;font-weight:600;line-height:32px;margin:0 -4px;padding:8px 4px 0;position:sticky;top:0;z-index:10}.DocSearch-Hit-Tree{color:var(--docsearch-muted-color);height:var(--docsearch-hit-height);opacity:.5;stroke-width:var(--docsearch-icon-stroke-width);width:24px}.DocSearch-Hit[aria-selected=true] a{background-color:var(--docsearch-highlight-color)}.DocSearch-Hit[aria-selected=true] mark{text-decoration:underline}.DocSearch-Hit-Container{align-items:center;color:var(--docsearch-hit-color);display:flex;flex-direction:row;height:var(--docsearch-hit-height);padding:0 var(--docsearch-spacing) 0 0}.DocSearch-Hit-icon{height:20px;width:20px}.DocSearch-Hit-action,.DocSearch-Hit-icon{color:var(--docsearch-muted-color);stroke-width:var(--docsearch-icon-stroke-width)}.DocSearch-Hit-action{align-items:center;display:flex;height:22px;width:22px}.DocSearch-Hit-action svg{display:block;height:18px;width:18px}.DocSearch-Hit-action+.DocSearch-Hit-action{margin-left:6px}.DocSearch-Hit-action-button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:inherit;cursor:pointer;padding:2px}svg.DocSearch-Hit-Select-Icon{display:none}.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Select-Icon{display:block}.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{background:#0003;transition:background-color .1s ease-in}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{transition:none}}.DocSearch-Hit-action-button:focus path,.DocSearch-Hit-action-button:hover path{fill:#fff}.DocSearch-Hit-content-wrapper{display:flex;flex:1 1 auto;flex-direction:column;font-weight:500;justify-content:center;line-height:1.2em;margin:0 8px;overflow-x:hidden;position:relative;text-overflow:ellipsis;white-space:nowrap;width:80%}.DocSearch-Hit-title{font-size:.9em}.DocSearch-Hit-path{color:var(--docsearch-muted-color);font-size:.75em}.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Tree,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-action,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-icon,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-path,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-text,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-title,.DocSearch-Hit[aria-selected=true] mark{color:var(--docsearch-hit-active-color)!important}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{background:#0003;transition:none}}.DocSearch-ErrorScreen,.DocSearch-NoResults,.DocSearch-StartScreen{font-size:.9em;margin:0 auto;padding:36px 0;text-align:center;width:80%}.DocSearch-Screen-Icon{color:var(--docsearch-muted-color);padding-bottom:12px}.DocSearch-NoResults-Prefill-List{display:inline-block;padding-bottom:24px;text-align:left}.DocSearch-NoResults-Prefill-List ul{display:inline-block;padding:8px 0 0}.DocSearch-NoResults-Prefill-List li{list-style-position:inside;list-style-type:"» "}.DocSearch-Prefill{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:1em;color:var(--docsearch-highlight-color);cursor:pointer;display:inline-block;font-size:1em;font-weight:700;padding:0}.DocSearch-Prefill:focus,.DocSearch-Prefill:hover{outline:none;text-decoration:underline}.DocSearch-Footer{align-items:center;background:var(--docsearch-footer-background);border-radius:0 0 8px 8px;box-shadow:var(--docsearch-footer-shadow);display:flex;flex-direction:row-reverse;flex-shrink:0;height:var(--docsearch-footer-height);justify-content:space-between;padding:0 var(--docsearch-spacing);position:relative;-webkit-user-select:none;user-select:none;width:100%;z-index:300}.DocSearch-Commands{color:var(--docsearch-muted-color);display:flex;list-style:none;margin:0;padding:0}.DocSearch-Commands li{align-items:center;display:flex}.DocSearch-Commands li:not(:last-of-type){margin-right:.8em}.DocSearch-Commands-Key{align-items:center;background:var(--docsearch-key-gradient);border:0;border-radius:2px;box-shadow:var(--docsearch-key-shadow);color:var(--docsearch-muted-color);display:flex;height:18px;justify-content:center;margin-right:.4em;padding:0 0 1px;width:20px}.DocSearch-VisuallyHiddenForAccessibility{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}@media (max-width:768px){:root{--docsearch-spacing:10px;--docsearch-footer-height:40px}.DocSearch-Dropdown{height:100%}.DocSearch-Container{height:100vh;height:-webkit-fill-available;height:calc(var(--docsearch-vh, 1vh)*100);position:absolute}.DocSearch-Footer{border-radius:0;bottom:0;position:absolute}.DocSearch-Hit-content-wrapper{display:flex;position:relative;width:80%}.DocSearch-Modal{border-radius:0;box-shadow:none;height:100vh;height:-webkit-fill-available;height:calc(var(--docsearch-vh, 1vh)*100);margin:0;max-width:100%;width:100%}.DocSearch-Dropdown{max-height:calc(var(--docsearch-vh, 1vh)*100 - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height))}.DocSearch-Cancel{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;color:var(--docsearch-highlight-color);cursor:pointer;display:inline-block;flex:none;font:inherit;font-size:1em;font-weight:500;margin-left:var(--docsearch-spacing);outline:none;overflow:hidden;padding:0;-webkit-user-select:none;user-select:none;white-space:nowrap}.DocSearch-Commands,.DocSearch-Hit-Tree{display:none}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}[class*=DocSearch]{--docsearch-primary-color: var(--vp-c-brand-1);--docsearch-highlight-color: var(--docsearch-primary-color);--docsearch-text-color: var(--vp-c-text-1);--docsearch-muted-color: var(--vp-c-text-2);--docsearch-searchbox-shadow: none;--docsearch-searchbox-background: transparent;--docsearch-searchbox-focus-background: transparent;--docsearch-key-gradient: transparent;--docsearch-key-shadow: none;--docsearch-modal-background: var(--vp-c-bg-soft);--docsearch-footer-background: var(--vp-c-bg)}.dark [class*=DocSearch]{--docsearch-modal-shadow: none;--docsearch-footer-shadow: none;--docsearch-logo-color: var(--vp-c-text-2);--docsearch-hit-background: var(--vp-c-default-soft);--docsearch-hit-color: var(--vp-c-text-2);--docsearch-hit-shadow: none}.DocSearch-Button{display:flex;justify-content:center;align-items:center;margin:0;padding:0;width:48px;height:55px;background:transparent;transition:border-color .25s}.DocSearch-Button:hover{background:transparent}.DocSearch-Button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}.DocSearch-Button-Key--pressed{transform:none;box-shadow:none}.DocSearch-Button:focus:not(:focus-visible){outline:none!important}@media (min-width: 768px){.DocSearch-Button{justify-content:flex-start;border:1px solid transparent;border-radius:8px;padding:0 10px 0 12px;width:100%;height:40px;background-color:var(--vp-c-bg-alt)}.DocSearch-Button:hover{border-color:var(--vp-c-brand-1);background:var(--vp-c-bg-alt)}}.DocSearch-Button .DocSearch-Button-Container{display:flex;align-items:center}.DocSearch-Button .DocSearch-Search-Icon{position:relative;width:16px;height:16px;color:var(--vp-c-text-1);fill:currentColor;transition:color .5s}.DocSearch-Button:hover .DocSearch-Search-Icon{color:var(--vp-c-text-1)}@media (min-width: 768px){.DocSearch-Button .DocSearch-Search-Icon{top:1px;margin-right:8px;width:14px;height:14px;color:var(--vp-c-text-2)}}.DocSearch-Button .DocSearch-Button-Placeholder{display:none;margin-top:2px;padding:0 16px 0 0;font-size:13px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.DocSearch-Button:hover .DocSearch-Button-Placeholder{color:var(--vp-c-text-1)}@media (min-width: 768px){.DocSearch-Button .DocSearch-Button-Placeholder{display:inline-block}}.DocSearch-Button .DocSearch-Button-Keys{direction:ltr;display:none;min-width:auto}@media (min-width: 768px){.DocSearch-Button .DocSearch-Button-Keys{display:flex;align-items:center}}.DocSearch-Button .DocSearch-Button-Key{display:block;margin:2px 0 0;border:1px solid var(--vp-c-divider);border-right:none;border-radius:4px 0 0 4px;padding-left:6px;min-width:0;width:auto;height:22px;line-height:22px;font-family:var(--vp-font-family-base);font-size:12px;font-weight:500;transition:color .5s,border-color .5s}.DocSearch-Button .DocSearch-Button-Key+.DocSearch-Button-Key{border-right:1px solid var(--vp-c-divider);border-left:none;border-radius:0 4px 4px 0;padding-left:2px;padding-right:6px}.DocSearch-Button .DocSearch-Button-Key:first-child{font-size:0!important}.DocSearch-Button .DocSearch-Button-Key:first-child:after{content:"Ctrl";font-size:12px;letter-spacing:normal;color:var(--docsearch-muted-color)}.mac .DocSearch-Button .DocSearch-Button-Key:first-child:after{content:"⌘"}.DocSearch-Button .DocSearch-Button-Key:first-child>*{display:none}.DocSearch-Search-Icon{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke-width='1.6' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' d='m14.386 14.386 4.088 4.088-4.088-4.088A7.533 7.533 0 1 1 3.733 3.733a7.533 7.533 0 0 1 10.653 10.653z'/%3E%3C/svg%3E")}.VPNavBarSearch{display:flex;align-items:center}@media (min-width: 768px){.VPNavBarSearch{flex-grow:1;padding-left:24px}}@media (min-width: 960px){.VPNavBarSearch{padding-left:32px}}.dark .DocSearch-Footer{border-top:1px solid var(--vp-c-divider)}.DocSearch-Form{border:1px solid var(--vp-c-brand-1);background-color:var(--vp-c-white)}.dark .DocSearch-Form{background-color:var(--vp-c-default-soft)}.DocSearch-Screen-Icon>svg{margin:auto}.VPNavBarSocialLinks[data-v-ef6192dc]{display:none}@media (min-width: 1280px){.VPNavBarSocialLinks[data-v-ef6192dc]{display:flex;align-items:center}}.title[data-v-9f43907a]{display:flex;align-items:center;border-bottom:1px solid transparent;width:100%;height:var(--vp-nav-height);font-size:16px;font-weight:600;color:var(--vp-c-text-1);transition:opacity .25s}@media (min-width: 960px){.title[data-v-9f43907a]{flex-shrink:0}.VPNavBarTitle.has-sidebar .title[data-v-9f43907a]{border-bottom-color:var(--vp-c-divider)}}[data-v-9f43907a] .logo{margin-right:8px;height:var(--vp-nav-logo-height)}.VPNavBarTranslations[data-v-acee064b]{display:none}@media (min-width: 1280px){.VPNavBarTranslations[data-v-acee064b]{display:flex;align-items:center}}.title[data-v-acee064b]{padding:0 24px 0 12px;line-height:32px;font-size:14px;font-weight:700;color:var(--vp-c-text-1)}.VPNavBar[data-v-9fd4d1dd]{position:relative;height:var(--vp-nav-height);pointer-events:none;white-space:nowrap;transition:background-color .25s}.VPNavBar.screen-open[data-v-9fd4d1dd]{transition:none;background-color:var(--vp-nav-bg-color);border-bottom:1px solid var(--vp-c-divider)}.VPNavBar[data-v-9fd4d1dd]:not(.home){background-color:var(--vp-nav-bg-color)}@media (min-width: 960px){.VPNavBar[data-v-9fd4d1dd]:not(.home){background-color:transparent}.VPNavBar[data-v-9fd4d1dd]:not(.has-sidebar):not(.home.top){background-color:var(--vp-nav-bg-color)}}.wrapper[data-v-9fd4d1dd]{padding:0 8px 0 24px}@media (min-width: 768px){.wrapper[data-v-9fd4d1dd]{padding:0 32px}}@media (min-width: 960px){.VPNavBar.has-sidebar .wrapper[data-v-9fd4d1dd]{padding:0}}.container[data-v-9fd4d1dd]{display:flex;justify-content:space-between;margin:0 auto;max-width:calc(var(--vp-layout-max-width) - 64px);height:var(--vp-nav-height);pointer-events:none}.container>.title[data-v-9fd4d1dd],.container>.content[data-v-9fd4d1dd]{pointer-events:none}.container[data-v-9fd4d1dd] *{pointer-events:auto}@media (min-width: 960px){.VPNavBar.has-sidebar .container[data-v-9fd4d1dd]{max-width:100%}}.title[data-v-9fd4d1dd]{flex-shrink:0;height:calc(var(--vp-nav-height) - 1px);transition:background-color .5s}@media (min-width: 960px){.VPNavBar.has-sidebar .title[data-v-9fd4d1dd]{position:absolute;top:0;left:0;z-index:2;padding:0 32px;width:var(--vp-sidebar-width);height:var(--vp-nav-height);background-color:transparent}}@media (min-width: 1440px){.VPNavBar.has-sidebar .title[data-v-9fd4d1dd]{padding-left:max(32px,calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));width:calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px)}}.content[data-v-9fd4d1dd]{flex-grow:1}@media (min-width: 960px){.VPNavBar.has-sidebar .content[data-v-9fd4d1dd]{position:relative;z-index:1;padding-right:32px;padding-left:var(--vp-sidebar-width)}}@media (min-width: 1440px){.VPNavBar.has-sidebar .content[data-v-9fd4d1dd]{padding-right:calc((100vw - var(--vp-layout-max-width)) / 2 + 32px);padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.content-body[data-v-9fd4d1dd]{display:flex;justify-content:flex-end;align-items:center;height:var(--vp-nav-height);transition:background-color .5s}@media (min-width: 960px){.VPNavBar:not(.home.top) .content-body[data-v-9fd4d1dd]{position:relative;background-color:var(--vp-nav-bg-color)}.VPNavBar:not(.has-sidebar):not(.home.top) .content-body[data-v-9fd4d1dd]{background-color:transparent}}@media (max-width: 767px){.content-body[data-v-9fd4d1dd]{column-gap:.5rem}}.menu+.translations[data-v-9fd4d1dd]:before,.menu+.appearance[data-v-9fd4d1dd]:before,.menu+.social-links[data-v-9fd4d1dd]:before,.translations+.appearance[data-v-9fd4d1dd]:before,.appearance+.social-links[data-v-9fd4d1dd]:before{margin-right:8px;margin-left:8px;width:1px;height:24px;background-color:var(--vp-c-divider);content:""}.menu+.appearance[data-v-9fd4d1dd]:before,.translations+.appearance[data-v-9fd4d1dd]:before{margin-right:16px}.appearance+.social-links[data-v-9fd4d1dd]:before{margin-left:16px}.social-links[data-v-9fd4d1dd]{margin-right:-8px}.divider[data-v-9fd4d1dd]{width:100%;height:1px}@media (min-width: 960px){.VPNavBar.has-sidebar .divider[data-v-9fd4d1dd]{padding-left:var(--vp-sidebar-width)}}@media (min-width: 1440px){.VPNavBar.has-sidebar .divider[data-v-9fd4d1dd]{padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.divider-line[data-v-9fd4d1dd]{width:100%;height:1px;transition:background-color .5s}.VPNavBar:not(.home) .divider-line[data-v-9fd4d1dd]{background-color:var(--vp-c-gutter)}@media (min-width: 960px){.VPNavBar:not(.home.top) .divider-line[data-v-9fd4d1dd]{background-color:var(--vp-c-gutter)}.VPNavBar:not(.has-sidebar):not(.home.top) .divider[data-v-9fd4d1dd]{background-color:var(--vp-c-gutter)}}.VPNavScreenAppearance[data-v-a3e2920d]{display:flex;justify-content:space-between;align-items:center;border-radius:8px;padding:12px 14px 12px 16px;background-color:var(--vp-c-bg-soft)}.text[data-v-a3e2920d]{line-height:24px;font-size:12px;font-weight:500;color:var(--vp-c-text-2)}.VPNavScreenMenuLink[data-v-fa963d97]{display:block;border-bottom:1px solid var(--vp-c-divider);padding:12px 0 11px;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:border-color .25s,color .25s}.VPNavScreenMenuLink[data-v-fa963d97]:hover{color:var(--vp-c-brand-1)}.VPNavScreenMenuGroupLink[data-v-e04f3e85]{display:block;margin-left:12px;line-height:32px;font-size:14px;font-weight:400;color:var(--vp-c-text-1);transition:color .25s}.VPNavScreenMenuGroupLink[data-v-e04f3e85]:hover{color:var(--vp-c-brand-1)}.VPNavScreenMenuGroupSection[data-v-f60dbfa7]{display:block}.title[data-v-f60dbfa7]{line-height:32px;font-size:13px;font-weight:700;color:var(--vp-c-text-2);transition:color .25s}.VPNavScreenMenuGroup[data-v-d99bfeec]{border-bottom:1px solid var(--vp-c-divider);height:48px;overflow:hidden;transition:border-color .5s}.VPNavScreenMenuGroup .items[data-v-d99bfeec]{visibility:hidden}.VPNavScreenMenuGroup.open .items[data-v-d99bfeec]{visibility:visible}.VPNavScreenMenuGroup.open[data-v-d99bfeec]{padding-bottom:10px;height:auto}.VPNavScreenMenuGroup.open .button[data-v-d99bfeec]{padding-bottom:6px;color:var(--vp-c-brand-1)}.VPNavScreenMenuGroup.open .button-icon[data-v-d99bfeec]{transform:rotate(45deg)}.button[data-v-d99bfeec]{display:flex;justify-content:space-between;align-items:center;padding:12px 4px 11px 0;width:100%;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.button[data-v-d99bfeec]:hover{color:var(--vp-c-brand-1)}.button-icon[data-v-d99bfeec]{transition:transform .25s}.group[data-v-d99bfeec]:first-child{padding-top:0}.group+.group[data-v-d99bfeec],.group+.item[data-v-d99bfeec]{padding-top:4px}.VPNavScreenTranslations[data-v-516e4bc3]{height:24px;overflow:hidden}.VPNavScreenTranslations.open[data-v-516e4bc3]{height:auto}.title[data-v-516e4bc3]{display:flex;align-items:center;font-size:14px;font-weight:500;color:var(--vp-c-text-1)}.icon[data-v-516e4bc3]{font-size:16px}.icon.lang[data-v-516e4bc3]{margin-right:8px}.icon.chevron[data-v-516e4bc3]{margin-left:4px}.list[data-v-516e4bc3]{padding:4px 0 0 24px}.link[data-v-516e4bc3]{line-height:32px;font-size:13px;color:var(--vp-c-text-1)}.VPNavScreen[data-v-2dd6d0c7]{position:fixed;top:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px));right:0;bottom:0;left:0;padding:0 32px;width:100%;background-color:var(--vp-nav-screen-bg-color);overflow-y:auto;transition:background-color .25s;pointer-events:auto}.VPNavScreen.fade-enter-active[data-v-2dd6d0c7],.VPNavScreen.fade-leave-active[data-v-2dd6d0c7]{transition:opacity .25s}.VPNavScreen.fade-enter-active .container[data-v-2dd6d0c7],.VPNavScreen.fade-leave-active .container[data-v-2dd6d0c7]{transition:transform .25s ease}.VPNavScreen.fade-enter-from[data-v-2dd6d0c7],.VPNavScreen.fade-leave-to[data-v-2dd6d0c7]{opacity:0}.VPNavScreen.fade-enter-from .container[data-v-2dd6d0c7],.VPNavScreen.fade-leave-to .container[data-v-2dd6d0c7]{transform:translateY(-8px)}@media (min-width: 768px){.VPNavScreen[data-v-2dd6d0c7]{display:none}}.container[data-v-2dd6d0c7]{margin:0 auto;padding:24px 0 96px;max-width:288px}.menu+.translations[data-v-2dd6d0c7],.menu+.appearance[data-v-2dd6d0c7],.translations+.appearance[data-v-2dd6d0c7]{margin-top:24px}.menu+.social-links[data-v-2dd6d0c7]{margin-top:16px}.appearance+.social-links[data-v-2dd6d0c7]{margin-top:16px}.VPNav[data-v-7ad780c2]{position:relative;top:var(--vp-layout-top-height, 0px);left:0;z-index:var(--vp-z-index-nav);width:100%;pointer-events:none;transition:background-color .5s}@media (min-width: 960px){.VPNav[data-v-7ad780c2]{position:fixed}}.VPSidebarItem.level-0[data-v-edd2eed8]{padding-bottom:24px}.VPSidebarItem.collapsed.level-0[data-v-edd2eed8]{padding-bottom:10px}.item[data-v-edd2eed8]{position:relative;display:flex;width:100%}.VPSidebarItem.collapsible>.item[data-v-edd2eed8]{cursor:pointer}.indicator[data-v-edd2eed8]{position:absolute;top:6px;bottom:6px;left:-17px;width:2px;border-radius:2px;transition:background-color .25s}.VPSidebarItem.level-2.is-active>.item>.indicator[data-v-edd2eed8],.VPSidebarItem.level-3.is-active>.item>.indicator[data-v-edd2eed8],.VPSidebarItem.level-4.is-active>.item>.indicator[data-v-edd2eed8],.VPSidebarItem.level-5.is-active>.item>.indicator[data-v-edd2eed8]{background-color:var(--vp-c-brand-1)}.link[data-v-edd2eed8]{display:flex;align-items:center;flex-grow:1}.text[data-v-edd2eed8]{flex-grow:1;padding:4px 0;line-height:24px;font-size:14px;transition:color .25s}.VPSidebarItem.level-0 .text[data-v-edd2eed8]{font-weight:700;color:var(--vp-c-text-1)}.VPSidebarItem.level-1 .text[data-v-edd2eed8],.VPSidebarItem.level-2 .text[data-v-edd2eed8],.VPSidebarItem.level-3 .text[data-v-edd2eed8],.VPSidebarItem.level-4 .text[data-v-edd2eed8],.VPSidebarItem.level-5 .text[data-v-edd2eed8]{font-weight:500;color:var(--vp-c-text-2)}.VPSidebarItem.level-0.is-link>.item>.link:hover .text[data-v-edd2eed8],.VPSidebarItem.level-1.is-link>.item>.link:hover .text[data-v-edd2eed8],.VPSidebarItem.level-2.is-link>.item>.link:hover .text[data-v-edd2eed8],.VPSidebarItem.level-3.is-link>.item>.link:hover .text[data-v-edd2eed8],.VPSidebarItem.level-4.is-link>.item>.link:hover .text[data-v-edd2eed8],.VPSidebarItem.level-5.is-link>.item>.link:hover .text[data-v-edd2eed8]{color:var(--vp-c-brand-1)}.VPSidebarItem.level-0.has-active>.item>.text[data-v-edd2eed8],.VPSidebarItem.level-1.has-active>.item>.text[data-v-edd2eed8],.VPSidebarItem.level-2.has-active>.item>.text[data-v-edd2eed8],.VPSidebarItem.level-3.has-active>.item>.text[data-v-edd2eed8],.VPSidebarItem.level-4.has-active>.item>.text[data-v-edd2eed8],.VPSidebarItem.level-5.has-active>.item>.text[data-v-edd2eed8],.VPSidebarItem.level-0.has-active>.item>.link>.text[data-v-edd2eed8],.VPSidebarItem.level-1.has-active>.item>.link>.text[data-v-edd2eed8],.VPSidebarItem.level-2.has-active>.item>.link>.text[data-v-edd2eed8],.VPSidebarItem.level-3.has-active>.item>.link>.text[data-v-edd2eed8],.VPSidebarItem.level-4.has-active>.item>.link>.text[data-v-edd2eed8],.VPSidebarItem.level-5.has-active>.item>.link>.text[data-v-edd2eed8]{color:var(--vp-c-text-1)}.VPSidebarItem.level-0.is-active>.item .link>.text[data-v-edd2eed8],.VPSidebarItem.level-1.is-active>.item .link>.text[data-v-edd2eed8],.VPSidebarItem.level-2.is-active>.item .link>.text[data-v-edd2eed8],.VPSidebarItem.level-3.is-active>.item .link>.text[data-v-edd2eed8],.VPSidebarItem.level-4.is-active>.item .link>.text[data-v-edd2eed8],.VPSidebarItem.level-5.is-active>.item .link>.text[data-v-edd2eed8]{color:var(--vp-c-brand-1)}.caret[data-v-edd2eed8]{display:flex;justify-content:center;align-items:center;margin-right:-7px;width:32px;height:32px;color:var(--vp-c-text-3);cursor:pointer;transition:color .25s;flex-shrink:0}.item:hover .caret[data-v-edd2eed8]{color:var(--vp-c-text-2)}.item:hover .caret[data-v-edd2eed8]:hover{color:var(--vp-c-text-1)}.caret-icon[data-v-edd2eed8]{font-size:18px;transform:rotate(90deg);transition:transform .25s}.VPSidebarItem.collapsed .caret-icon[data-v-edd2eed8]{transform:rotate(0)}.VPSidebarItem.level-1 .items[data-v-edd2eed8],.VPSidebarItem.level-2 .items[data-v-edd2eed8],.VPSidebarItem.level-3 .items[data-v-edd2eed8],.VPSidebarItem.level-4 .items[data-v-edd2eed8],.VPSidebarItem.level-5 .items[data-v-edd2eed8]{border-left:1px solid var(--vp-c-divider);padding-left:16px}.VPSidebarItem.collapsed .items[data-v-edd2eed8]{display:none}.no-transition[data-v-51288d80] .caret-icon{transition:none}.group+.group[data-v-51288d80]{border-top:1px solid var(--vp-c-divider);padding-top:10px}@media (min-width: 960px){.group[data-v-51288d80]{padding-top:10px;width:calc(var(--vp-sidebar-width) - 64px)}}.VPSidebar[data-v-42c4c606]{position:fixed;top:var(--vp-layout-top-height, 0px);bottom:0;left:0;z-index:var(--vp-z-index-sidebar);padding:32px 32px 96px;width:calc(100vw - 64px);max-width:320px;background-color:var(--vp-sidebar-bg-color);opacity:0;box-shadow:var(--vp-c-shadow-3);overflow-x:hidden;overflow-y:auto;transform:translate(-100%);transition:opacity .5s,transform .25s ease;overscroll-behavior:contain}.VPSidebar.open[data-v-42c4c606]{opacity:1;visibility:visible;transform:translate(0);transition:opacity .25s,transform .5s cubic-bezier(.19,1,.22,1)}.dark .VPSidebar[data-v-42c4c606]{box-shadow:var(--vp-shadow-1)}@media (min-width: 960px){.VPSidebar[data-v-42c4c606]{padding-top:var(--vp-nav-height);width:var(--vp-sidebar-width);max-width:100%;background-color:var(--vp-sidebar-bg-color);opacity:1;visibility:visible;box-shadow:none;transform:translate(0)}}@media (min-width: 1440px){.VPSidebar[data-v-42c4c606]{padding-left:max(32px,calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));width:calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px)}}@media (min-width: 960px){.curtain[data-v-42c4c606]{position:sticky;top:-64px;left:0;z-index:1;margin-top:calc(var(--vp-nav-height) * -1);margin-right:-32px;margin-left:-32px;height:var(--vp-nav-height);background-color:var(--vp-sidebar-bg-color)}}.nav[data-v-42c4c606]{outline:0}.VPSkipLink[data-v-c8291ffa]{top:8px;left:8px;padding:8px 16px;z-index:999;border-radius:8px;font-size:12px;font-weight:700;text-decoration:none;color:var(--vp-c-brand-1);box-shadow:var(--vp-shadow-3);background-color:var(--vp-c-bg)}.VPSkipLink[data-v-c8291ffa]:focus{height:auto;width:auto;clip:auto;clip-path:none}@media (min-width: 1280px){.VPSkipLink[data-v-c8291ffa]{top:14px;left:16px}}.Layout[data-v-d8b57b2d]{display:flex;flex-direction:column;min-height:100vh}.VPHomeSponsors[data-v-3dc26e1d]{border-top:1px solid var(--vp-c-gutter);padding-top:88px!important}.VPHomeSponsors[data-v-3dc26e1d]{margin:96px 0}@media (min-width: 768px){.VPHomeSponsors[data-v-3dc26e1d]{margin:128px 0}}.VPHomeSponsors[data-v-3dc26e1d]{padding:0 24px}@media (min-width: 768px){.VPHomeSponsors[data-v-3dc26e1d]{padding:0 48px}}@media (min-width: 960px){.VPHomeSponsors[data-v-3dc26e1d]{padding:0 64px}}.container[data-v-3dc26e1d]{margin:0 auto;max-width:1152px}.love[data-v-3dc26e1d]{margin:0 auto;width:fit-content;font-size:28px;color:var(--vp-c-text-3)}.icon[data-v-3dc26e1d]{display:inline-block}.message[data-v-3dc26e1d]{margin:0 auto;padding-top:10px;max-width:320px;text-align:center;line-height:24px;font-size:16px;font-weight:500;color:var(--vp-c-text-2)}.sponsors[data-v-3dc26e1d]{padding-top:32px}.action[data-v-3dc26e1d]{padding-top:40px;text-align:center}.VPTeamPage[data-v-a5329171]{margin:96px 0}@media (min-width: 768px){.VPTeamPage[data-v-a5329171]{margin:128px 0}}.VPHome .VPTeamPageTitle[data-v-a5329171-s]{border-top:1px solid var(--vp-c-gutter);padding-top:88px!important}.VPTeamPageSection+.VPTeamPageSection[data-v-a5329171-s],.VPTeamMembers+.VPTeamPageSection[data-v-a5329171-s]{margin-top:64px}.VPTeamMembers+.VPTeamMembers[data-v-a5329171-s]{margin-top:24px}@media (min-width: 768px){.VPTeamPageTitle+.VPTeamPageSection[data-v-a5329171-s]{margin-top:16px}.VPTeamPageSection+.VPTeamPageSection[data-v-a5329171-s],.VPTeamMembers+.VPTeamPageSection[data-v-a5329171-s]{margin-top:96px}}.VPTeamMembers[data-v-a5329171-s]{padding:0 24px}@media (min-width: 768px){.VPTeamMembers[data-v-a5329171-s]{padding:0 48px}}@media (min-width: 960px){.VPTeamMembers[data-v-a5329171-s]{padding:0 64px}}.VPTeamPageTitle[data-v-46c5e327]{padding:48px 32px;text-align:center}@media (min-width: 768px){.VPTeamPageTitle[data-v-46c5e327]{padding:64px 48px 48px}}@media (min-width: 960px){.VPTeamPageTitle[data-v-46c5e327]{padding:80px 64px 48px}}.title[data-v-46c5e327]{letter-spacing:0;line-height:44px;font-size:36px;font-weight:500}@media (min-width: 768px){.title[data-v-46c5e327]{letter-spacing:-.5px;line-height:56px;font-size:48px}}.lead[data-v-46c5e327]{margin:0 auto;max-width:512px;padding-top:12px;line-height:24px;font-size:16px;font-weight:500;color:var(--vp-c-text-2)}@media (min-width: 768px){.lead[data-v-46c5e327]{max-width:592px;letter-spacing:.15px;line-height:28px;font-size:20px}}.VPTeamPageSection[data-v-3bf2e850]{padding:0 32px}@media (min-width: 768px){.VPTeamPageSection[data-v-3bf2e850]{padding:0 48px}}@media (min-width: 960px){.VPTeamPageSection[data-v-3bf2e850]{padding:0 64px}}.title[data-v-3bf2e850]{position:relative;margin:0 auto;max-width:1152px;text-align:center;color:var(--vp-c-text-2)}.title-line[data-v-3bf2e850]{position:absolute;top:16px;left:0;width:100%;height:1px;background-color:var(--vp-c-divider)}.title-text[data-v-3bf2e850]{position:relative;display:inline-block;padding:0 24px;letter-spacing:0;line-height:32px;font-size:20px;font-weight:500;background-color:var(--vp-c-bg)}.lead[data-v-3bf2e850]{margin:0 auto;max-width:480px;padding-top:12px;text-align:center;line-height:24px;font-size:16px;font-weight:500;color:var(--vp-c-text-2)}.members[data-v-3bf2e850]{padding-top:40px}.VPTeamMembersItem[data-v-acff304e]{display:flex;flex-direction:column;gap:2px;border-radius:12px;width:100%;height:100%;overflow:hidden}.VPTeamMembersItem.small .profile[data-v-acff304e]{padding:32px}.VPTeamMembersItem.small .data[data-v-acff304e]{padding-top:20px}.VPTeamMembersItem.small .avatar[data-v-acff304e]{width:64px;height:64px}.VPTeamMembersItem.small .name[data-v-acff304e]{line-height:24px;font-size:16px}.VPTeamMembersItem.small .affiliation[data-v-acff304e]{padding-top:4px;line-height:20px;font-size:14px}.VPTeamMembersItem.small .desc[data-v-acff304e]{padding-top:12px;line-height:20px;font-size:14px}.VPTeamMembersItem.small .links[data-v-acff304e]{margin:0 -16px -20px;padding:10px 0 0}.VPTeamMembersItem.medium .profile[data-v-acff304e]{padding:48px 32px}.VPTeamMembersItem.medium .data[data-v-acff304e]{padding-top:24px;text-align:center}.VPTeamMembersItem.medium .avatar[data-v-acff304e]{width:96px;height:96px}.VPTeamMembersItem.medium .name[data-v-acff304e]{letter-spacing:.15px;line-height:28px;font-size:20px}.VPTeamMembersItem.medium .affiliation[data-v-acff304e]{padding-top:4px;font-size:16px}.VPTeamMembersItem.medium .desc[data-v-acff304e]{padding-top:16px;max-width:288px;font-size:16px}.VPTeamMembersItem.medium .links[data-v-acff304e]{margin:0 -16px -12px;padding:16px 12px 0}.profile[data-v-acff304e]{flex-grow:1;background-color:var(--vp-c-bg-soft)}.data[data-v-acff304e]{text-align:center}.avatar[data-v-acff304e]{position:relative;flex-shrink:0;margin:0 auto;border-radius:50%;box-shadow:var(--vp-shadow-3)}.avatar-img[data-v-acff304e]{position:absolute;top:0;right:0;bottom:0;left:0;border-radius:50%;object-fit:cover}.name[data-v-acff304e]{margin:0;font-weight:600}.affiliation[data-v-acff304e]{margin:0;font-weight:500;color:var(--vp-c-text-2)}.org.link[data-v-acff304e]{color:var(--vp-c-text-2);transition:color .25s}.org.link[data-v-acff304e]:hover{color:var(--vp-c-brand-1)}.desc[data-v-acff304e]{margin:0 auto}.desc[data-v-acff304e] a{font-weight:500;color:var(--vp-c-brand-1);text-decoration-style:dotted;transition:color .25s}.links[data-v-acff304e]{display:flex;justify-content:center;height:56px}.sp-link[data-v-acff304e]{display:flex;justify-content:center;align-items:center;text-align:center;padding:16px;font-size:14px;font-weight:500;color:var(--vp-c-sponsor);background-color:var(--vp-c-bg-soft);transition:color .25s,background-color .25s}.sp .sp-link.link[data-v-acff304e]:hover,.sp .sp-link.link[data-v-acff304e]:focus{outline:none;color:var(--vp-c-white);background-color:var(--vp-c-sponsor)}.sp-icon[data-v-acff304e]{margin-right:8px;font-size:16px}.VPTeamMembers.small .container[data-v-bf782009]{grid-template-columns:repeat(auto-fit,minmax(224px,1fr))}.VPTeamMembers.small.count-1 .container[data-v-bf782009]{max-width:276px}.VPTeamMembers.small.count-2 .container[data-v-bf782009]{max-width:576px}.VPTeamMembers.small.count-3 .container[data-v-bf782009]{max-width:876px}.VPTeamMembers.medium .container[data-v-bf782009]{grid-template-columns:repeat(auto-fit,minmax(256px,1fr))}@media (min-width: 375px){.VPTeamMembers.medium .container[data-v-bf782009]{grid-template-columns:repeat(auto-fit,minmax(288px,1fr))}}.VPTeamMembers.medium.count-1 .container[data-v-bf782009]{max-width:368px}.VPTeamMembers.medium.count-2 .container[data-v-bf782009]{max-width:760px}.container[data-v-bf782009]{display:grid;gap:24px;margin:0 auto;max-width:1152px} diff --git a/docs/.vitepress/dist/assets/test1_api-examples.md.LfndiNnB.js b/docs/.vitepress/dist/assets/test1_api-examples.md.LfndiNnB.js deleted file mode 100644 index 061774a..0000000 --- a/docs/.vitepress/dist/assets/test1_api-examples.md.LfndiNnB.js +++ /dev/null @@ -1,16 +0,0 @@ -import{u as p,c as h,a0 as o,j as a,t,k as i,a as s,o as d}from"./chunks/framework.p2VkXzrt.js";const f=JSON.parse('{"title":"Runtime API Examples","description":"","frontmatter":{"outline":"deep"},"headers":[],"relativePath":"test1/api-examples.md","filePath":"test1/api-examples.md","lastUpdated":null}'),g={name:"test1/api-examples.md"},E=Object.assign(g,{setup(m){const{site:k,theme:n,page:l,frontmatter:r}=p();return(u,e)=>(d(),h("div",null,[e[0]||(e[0]=o(`

Runtime API Examples

This page demonstrates usage of some of the runtime APIs provided by VitePress.

The main useData() API can be used to access site, theme, and page data for the current page. It works in both .md and .vue files:

md
<script setup>
-import { useData } from 'vitepress'
-
-const { theme, page, frontmatter } = useData()
-</script>
-
-## Results
-
-### Theme Data
-<pre>{{ theme }}</pre>
-
-### Page Data
-<pre>{{ page }}</pre>
-
-### Page Frontmatter
-<pre>{{ frontmatter }}</pre>

Results

Theme Data

`,6)),a("pre",null,t(i(n)),1),e[1]||(e[1]=a("h3",{id:"page-data",tabindex:"-1"},[s("Page Data "),a("a",{class:"header-anchor",href:"#page-data","aria-label":'Permalink to "Page Data"',target:"_self"},"​")],-1)),a("pre",null,t(i(l)),1),e[2]||(e[2]=a("h3",{id:"page-frontmatter",tabindex:"-1"},[s("Page Frontmatter "),a("a",{class:"header-anchor",href:"#page-frontmatter","aria-label":'Permalink to "Page Frontmatter"',target:"_self"},"​")],-1)),a("pre",null,t(i(r)),1),e[3]||(e[3]=a("h2",{id:"more",tabindex:"-1"},[s("More "),a("a",{class:"header-anchor",href:"#more","aria-label":'Permalink to "More"',target:"_self"},"​")],-1)),e[4]||(e[4]=a("p",null,[s("Check out the documentation for the "),a("a",{href:"https://vitepress.dev/reference/runtime-api#usedata",target:"_self"},"full list of runtime APIs"),s(".")],-1))]))}});export{f as __pageData,E as default}; diff --git a/docs/.vitepress/dist/assets/test1_api-examples.md.LfndiNnB.lean.js b/docs/.vitepress/dist/assets/test1_api-examples.md.LfndiNnB.lean.js deleted file mode 100644 index 061774a..0000000 --- a/docs/.vitepress/dist/assets/test1_api-examples.md.LfndiNnB.lean.js +++ /dev/null @@ -1,16 +0,0 @@ -import{u as p,c as h,a0 as o,j as a,t,k as i,a as s,o as d}from"./chunks/framework.p2VkXzrt.js";const f=JSON.parse('{"title":"Runtime API Examples","description":"","frontmatter":{"outline":"deep"},"headers":[],"relativePath":"test1/api-examples.md","filePath":"test1/api-examples.md","lastUpdated":null}'),g={name:"test1/api-examples.md"},E=Object.assign(g,{setup(m){const{site:k,theme:n,page:l,frontmatter:r}=p();return(u,e)=>(d(),h("div",null,[e[0]||(e[0]=o(`

Runtime API Examples

This page demonstrates usage of some of the runtime APIs provided by VitePress.

The main useData() API can be used to access site, theme, and page data for the current page. It works in both .md and .vue files:

md
<script setup>
-import { useData } from 'vitepress'
-
-const { theme, page, frontmatter } = useData()
-</script>
-
-## Results
-
-### Theme Data
-<pre>{{ theme }}</pre>
-
-### Page Data
-<pre>{{ page }}</pre>
-
-### Page Frontmatter
-<pre>{{ frontmatter }}</pre>

Results

Theme Data

`,6)),a("pre",null,t(i(n)),1),e[1]||(e[1]=a("h3",{id:"page-data",tabindex:"-1"},[s("Page Data "),a("a",{class:"header-anchor",href:"#page-data","aria-label":'Permalink to "Page Data"',target:"_self"},"​")],-1)),a("pre",null,t(i(l)),1),e[2]||(e[2]=a("h3",{id:"page-frontmatter",tabindex:"-1"},[s("Page Frontmatter "),a("a",{class:"header-anchor",href:"#page-frontmatter","aria-label":'Permalink to "Page Frontmatter"',target:"_self"},"​")],-1)),a("pre",null,t(i(r)),1),e[3]||(e[3]=a("h2",{id:"more",tabindex:"-1"},[s("More "),a("a",{class:"header-anchor",href:"#more","aria-label":'Permalink to "More"',target:"_self"},"​")],-1)),e[4]||(e[4]=a("p",null,[s("Check out the documentation for the "),a("a",{href:"https://vitepress.dev/reference/runtime-api#usedata",target:"_self"},"full list of runtime APIs"),s(".")],-1))]))}});export{f as __pageData,E as default}; diff --git a/docs/.vitepress/dist/assets/test1_index.md.Bma_WtEx.js b/docs/.vitepress/dist/assets/test1_index.md.Bma_WtEx.js deleted file mode 100644 index 0dd3f3f..0000000 --- a/docs/.vitepress/dist/assets/test1_index.md.Bma_WtEx.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as t,c as a,a0 as o,o as d}from"./chunks/framework.p2VkXzrt.js";const f=JSON.parse('{"title":"note","description":"","frontmatter":{},"headers":[],"relativePath":"test1/index.md","filePath":"test1/index.md","lastUpdated":null}'),n={name:"test1/index.md"};function r(s,e,c,i,l,p){return d(),a("div",null,e[0]||(e[0]=[o('

note

note文件夹中创建/删除md文件,侧边栏数据会被自动更新。

Create / delete md file in the note folder, and the sidebar data will be updated automatically.

',3)]))}const u=t(n,[["render",r]]);export{f as __pageData,u as default}; diff --git a/docs/.vitepress/dist/assets/test1_index.md.Bma_WtEx.lean.js b/docs/.vitepress/dist/assets/test1_index.md.Bma_WtEx.lean.js deleted file mode 100644 index 0dd3f3f..0000000 --- a/docs/.vitepress/dist/assets/test1_index.md.Bma_WtEx.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as t,c as a,a0 as o,o as d}from"./chunks/framework.p2VkXzrt.js";const f=JSON.parse('{"title":"note","description":"","frontmatter":{},"headers":[],"relativePath":"test1/index.md","filePath":"test1/index.md","lastUpdated":null}'),n={name:"test1/index.md"};function r(s,e,c,i,l,p){return d(),a("div",null,e[0]||(e[0]=[o('

note

note文件夹中创建/删除md文件,侧边栏数据会被自动更新。

Create / delete md file in the note folder, and the sidebar data will be updated automatically.

',3)]))}const u=t(n,[["render",r]]);export{f as __pageData,u as default}; diff --git a/docs/.vitepress/dist/assets/test1_markdown-examples.md.D-SZaDZl.js b/docs/.vitepress/dist/assets/test1_markdown-examples.md.D-SZaDZl.js deleted file mode 100644 index ea6649f..0000000 --- a/docs/.vitepress/dist/assets/test1_markdown-examples.md.D-SZaDZl.js +++ /dev/null @@ -1,33 +0,0 @@ -import{_ as a,c as i,a0 as n,o as t}from"./chunks/framework.p2VkXzrt.js";const E=JSON.parse('{"title":"Markdown Extension Examples","description":"","frontmatter":{},"headers":[],"relativePath":"test1/markdown-examples.md","filePath":"test1/markdown-examples.md","lastUpdated":null}'),e={name:"test1/markdown-examples.md"};function l(p,s,h,k,r,o){return t(),i("div",null,s[0]||(s[0]=[n(`

Markdown Extension Examples

This page demonstrates some of the built-in markdown extensions provided by VitePress.

Syntax Highlighting

VitePress provides Syntax Highlighting powered by Shiki, with additional features like line-highlighting:

Input

md
\`\`\`js{4}
-export default {
-  data () {
-    return {
-      msg: 'Highlighted!'
-    }
-  }
-}
-\`\`\`

Output

js
export default {
-  data () {
-    return {
-      msg: 'Highlighted!'
-    }
-  }
-}

Custom Containers

Input

md
::: info
-This is an info box.
-:::
-
-::: tip
-This is a tip.
-:::
-
-::: warning
-This is a warning.
-:::
-
-::: danger
-This is a dangerous warning.
-:::
-
-::: details
-This is a details block.
-:::

Output

INFO

This is an info box.

TIP

This is a tip.

WARNING

This is a warning.

DANGER

This is a dangerous warning.

Details

This is a details block.

More

Check out the documentation for the full list of markdown extensions.

`,19)]))}const c=a(e,[["render",l]]);export{E as __pageData,c as default}; diff --git a/docs/.vitepress/dist/assets/test1_markdown-examples.md.D-SZaDZl.lean.js b/docs/.vitepress/dist/assets/test1_markdown-examples.md.D-SZaDZl.lean.js deleted file mode 100644 index ea6649f..0000000 --- a/docs/.vitepress/dist/assets/test1_markdown-examples.md.D-SZaDZl.lean.js +++ /dev/null @@ -1,33 +0,0 @@ -import{_ as a,c as i,a0 as n,o as t}from"./chunks/framework.p2VkXzrt.js";const E=JSON.parse('{"title":"Markdown Extension Examples","description":"","frontmatter":{},"headers":[],"relativePath":"test1/markdown-examples.md","filePath":"test1/markdown-examples.md","lastUpdated":null}'),e={name:"test1/markdown-examples.md"};function l(p,s,h,k,r,o){return t(),i("div",null,s[0]||(s[0]=[n(`

Markdown Extension Examples

This page demonstrates some of the built-in markdown extensions provided by VitePress.

Syntax Highlighting

VitePress provides Syntax Highlighting powered by Shiki, with additional features like line-highlighting:

Input

md
\`\`\`js{4}
-export default {
-  data () {
-    return {
-      msg: 'Highlighted!'
-    }
-  }
-}
-\`\`\`

Output

js
export default {
-  data () {
-    return {
-      msg: 'Highlighted!'
-    }
-  }
-}

Custom Containers

Input

md
::: info
-This is an info box.
-:::
-
-::: tip
-This is a tip.
-:::
-
-::: warning
-This is a warning.
-:::
-
-::: danger
-This is a dangerous warning.
-:::
-
-::: details
-This is a details block.
-:::

Output

INFO

This is an info box.

TIP

This is a tip.

WARNING

This is a warning.

DANGER

This is a dangerous warning.

Details

This is a details block.

More

Check out the documentation for the full list of markdown extensions.

`,19)]))}const c=a(e,[["render",l]]);export{E as __pageData,c as default}; diff --git a/docs/.vitepress/dist/hashmap.json b/docs/.vitepress/dist/hashmap.json deleted file mode 100644 index 407445c..0000000 --- a/docs/.vitepress/dist/hashmap.json +++ /dev/null @@ -1 +0,0 @@ -{"index.md":"ByDiiWcV","issues_101.md":"DBQG9jJP","issues_105.md":"8_yuCS-D","issues_182.md":"CpqPtztd","issues_19.md":"DB81ml38","issues_210.md":"C4LG69AC","issues_211.md":"DUZAr3Vg","issues_212.md":"j95HTJwO","issues_269.md":"CwdSqsHN","issues_285.md":"0b7MF6MA","issues_294.md":"CrOAwh-t","issues_297.md":"CtkkBK4R","issues_78.md":"CH6fxnfx","issues_88.md":"VOyx9x2b","issues_94.md":"GErTTh9P","issues_96.md":"D6uW10Mi","issues_99.md":"amwtZpFn","issues_index.md":"Do1eTGGf","test1_api-examples.md":"LfndiNnB","test1_index.md":"Bma_WtEx","test1_markdown-examples.md":"D-SZaDZl"} diff --git a/docs/.vitepress/dist/index.html b/docs/.vitepress/dist/index.html deleted file mode 100644 index a1eecf9..0000000 --- a/docs/.vitepress/dist/index.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - XiaoMusic - - - - - - - - - - - - - - -
Skip to content

XiaoMusic

无限听歌,解放小爱音箱

使用小爱音箱播放音乐,音乐使用 yt-dlp 下载

- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/issues/101.html b/docs/.vitepress/dist/issues/101.html deleted file mode 100644 index 8ec6bee..0000000 --- a/docs/.vitepress/dist/issues/101.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - 群晖docker安装 xiaomusic | XiaoMusic - - - - - - - - - - - - - - -
Skip to content

群晖docker安装 xiaomusic

由于现在群晖已经无法正常下载 docker 里的镜像了,绕了好多弯;现在用 ssh 服务命令拉取镜像来创建容器; 1、ssh 输入账号密码进入群晖 2、输入 sudo -i 再次输入密码进入 root 权限 3、输入 docker search xiaomusic来查找到该镜像名; 4、然后输入 docker pull xiaomusic 试试是否能安装,如果不能;就得在命令前加个代理地址;下面列了一些代理地址可以一个个的试 docker.fxxk.dedyn.iodocker.io registry-docker-hub-latest-9vgc.onrender.com docker.chenby.cn dockerproxy.com hub.uuuadc.top docker.jsdelivr.fyi docker.registry.cyou dockerhub.anzu.vip

我是用的最下面这个成功的 docker pull dockerhub.anzu.vip/xiaomusic:latest 5、安装完成后就进入群晖 DOCKER 配置 xiaomusic image MI_HARDWARE=型号 前面第4 步骤获取的 XIAOMUSIC_SEARCH=搜索方式,我填写的bilisearch: 意思是通过 bilibili 搜索 MI_DID=前面第4 步骤获取的 MI_USER=小米账号 MI_PASS=小米密码 XIAOMUSIC_FUZZY_MATCH_CUTOFF=模糊匹配,最小为 0.1 最大为 1,越小越模糊,越大越精准

6、配置端口 image (1) 7、映射路径 image (2)

评论

评论 1 - kiwi5656

MI_DID=前面第4 步骤获取的,第4步骤在哪?


评论 2 - hanxi

MI_DID=前面第4 步骤获取的,第4步骤在哪?

不用设置 MI_DID


评论 3 - hanxi

国内 docker 镜像

docker pull m.daocloud.io/docker.io/hanxi/xiaomusic:latest
-docker tag m.daocloud.io/docker.io/hanxi/xiaomusic:latest hanxi/xiaomusic:latest

评论 4 - ginitaimeiyty

如果手头上有能科学上网的机器,直接把群辉的代理服务器IP填写成可以科学上网的机器IP+端口,翻墙软件打开允许局域网连接就可以


链接到 GitHub Issue

- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/issues/105.html b/docs/.vitepress/dist/issues/105.html deleted file mode 100644 index e1bd09b..0000000 --- a/docs/.vitepress/dist/issues/105.html +++ /dev/null @@ -1,213 +0,0 @@ - - - - - - 【插件】自定义口令功能 | XiaoMusic - - - - - - - - - - - - - - -
Skip to content

【插件】自定义口令功能

自定义口令配置需要配置到 config.json 文件里,使用 config.json 方式启动。参考 </issues/94.html> 。

口令的配置方式见 config-example.json 文件。口令对应的代码需要写到 plugins/ 目录下面,如果是容器启动,则需要把这个目录挂载出来。

config.json 格式是下面这样的。

json
{
-    "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\")"
-    }
-}

配置自定义口令时,只需要配置 user_key_word_dict 即可,会自动插入到 key_word_dict 里的。配置格式是:

 "测试自定义口令": "exec#code1(\"hello\")",

其中 "测试自定义口令" 就是对小爱音箱说的,"exec#code1(\"hello\")" 就是要执行的插件代码,代码以 exec# 开头,后面紧跟着执行代码。这里 code1 是一个插件函数,插件函数需要在 plugin 目录里实现,一个文件只会导出一个与文件名相同的插件函数。所以 code1 函数是在 plugin/code1.py 里实现的。

async def code1(arg1):
-    global log, xiaomusic
-    log.info(f"code1:{arg1}")
-    await xiaomusic.do_tts("你好,我是自定义的测试口令")

这里只是演示了打印日志和让小爱音箱说话。还有一个示例插件是 httpget ,可以用来访问 url 。

比如下面这样配置的话,当对小爱音箱说测试链接时,会去访问 url ,可以用来很多其他的事情。

"测试链接": "exec#httpget(\"https://github.com/hanxi/xiaomusic\")

最后还需要在 active_cmd 中配上口令用于唤醒:

  "active_cmd": "play,set_random_play,playlocal,play_music_list,play_music_list_index,stop_after_minute,stop,测试自定义口令",

感兴趣的可以体验一下,写了有什么好玩的插件也可以在这里分享,或者提 pr 合并进官方库里作为自带插件。

评论

评论 1 - carson512

如果开启服务的状态下 如何唤醒才能调用原有的播放QQ 网易云?而特定唤醒词调用xiaoai播放?


评论 2 - hanxi

如果开启服务的状态下 如何唤醒才能调用原有的播放QQ 网易云?而特定唤醒词调用xiaoai播放?

不使用 xiaomusic 的唤醒词就会调用音箱自带的,比如说播放音乐


评论 3 - shellingford37

[23:26:12] [0.3.30] [INFO] xiaomusic.py:531: 收到消息:测试自定义口令 控制面板:False did:290874427
-[23:26:12] [0.3.30] [INFO] xiaomusic.py:577: 完全匹配指令. query:测试自定义口令 opvalue:exec#code1("hello")
-[23:26:12] [0.3.30] [INFO] code1.py:3: code1:hello
-[23:26:12] [0.3.30] [ERROR] xiaomusic.py:542: Execption XiaoMusic.do_tts() missing 1 required positional argument: 'value'
-Traceback (most recent call last):
-  File "/app/xiaomusic/xiaomusic.py", line 540, in do_check_cmd
-    await func(did=did, arg1=oparg)
-  File "/app/xiaomusic/xiaomusic.py", line 890, in exec
-    await self.plugin_manager.execute_plugin(code)
-  File "/app/xiaomusic/plugin.py", line 66, in execute_plugin
-    await coroutine
-  File "/app/plugins/code1.py", line 4, in code1
-    await xiaomusic.do_tts("你好,我是自定义的测试口令")
-TypeError: XiaoMusic.do_tts() missing 1 required positional argument: 'value'

我用code1的代码执行报错,有大佬知道为什么吗?


评论 4 - hanxi

@shellingford37 重构后漏改了,修复了。


评论 5 - guoxiangke

先说播放歌曲,再说 测试自定义口令 就行


评论 6 - CZJCC

想请教下插件那个功能,如何把用户的语音输入作为参数内容传到自定义函数里


评论 7 - hanxi

想请教下插件那个功能,如何把用户的语音输入作为参数内容传到自定义函数里

现在获取不到,等我加个接口获取吧。


评论 8 - CZJCC

666,支持以后我可以贡献一个接入通义模型的插件


评论 9 - hanxi

@CZJCC 你可以更新看看 plugins/code1.py 的测试代码,我测试了是可以拿到语音输入的原始内容的。


评论 10 - hanxi

文档更新了下,active_cmd 也需要配置一下才能正常唤醒。


评论 11 - CZJCC

@CZJCC 你可以更新看看 plugins/code1.py 的测试代码,我测试了是可以拿到语音输入的原始内容的。

我原先设想的事用户的话术是”通义提问为什么地球是圆的“,指令匹配的时候通义提问前缀匹配到类似于code1方法,为什么地球是圆的作为参数传入这个函数,但我看现在是拿历史记录实现的


评论 12 - hanxi

是的,插件函数里面再切割一下前缀就行。last_record就是当前的那条语音数据。


评论 13 - hanxi

@CZJCC 你可以更新看看 plugins/code1.py 的测试代码,我测试了是可以拿到语音输入的原始内容的。

我原先设想的事用户的话术是”通义提问为什么地球是圆的“,指令匹配的时候通义提问前缀匹配到类似于code1方法,为什么地球是圆的作为参数传入这个函数,但我看现在是拿历史记录实现的

是的,这样比较简单,交给插件里面处理也比较自由。


评论 14 - mogeqian

key_word_dict中的“播放歌曲”口令是不能修改的是吧?因为以前用小爱同学播放歌曲说习惯了,总是触发xiaomusic自动下载歌曲,我想把口令改成“查找歌曲”,这样我当说播放歌曲的时候就调用网易云音乐或者QQ音乐,当我说查找歌曲的时候就先看本地有没有歌曲,没有话就自动下载到本地。 当我先按照config-example.json的模板写好如下配置并重命名为config.json

  "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": [
-    "分钟后关机",
-    "查找歌曲",
-    "下一首",
-    "单曲循环",
-    "全部循环",
-    "随机播放",
-    "关机",
-    "刷新列表",
-    "播放列表",
-    "播放本地歌曲",
-    "本地播放歌曲",
-    "下载歌曲",
-    "暂停",
-    "停止",
-    "停止播放",
-    "测试自定义口令",
-    "测试链接"
-  ],

用以下命令安装docker docker run --name xiaomusic -p 5488:5488 -v /mnt/sharedata/audiodata/musci/xiaomusic:/app/music -v /mnt/data_sdb1/docker/xiaomusic/config.json:/app/config.json -e XIAOMUSIC_PORT=5488 hanxi/xiaomusic --config /app/config.json 日志里提示的依然是:

key_word_dict={'播放歌曲': 'play', '播放本地歌曲': 'playlocal', '关机': 'stop', '下一首': 'play_next', '上一首': 'play_prev', '单曲循环': 'set_play_type_one', '全部循环': 'set_play_type_all', '随机播放': 'set_random_play', '分钟后关机': 'stop_after_minute', '播放列表': 'play_music_list', '刷新列表': 'gen_music_list', '加入收藏': 'add_to_favorites', '收藏歌曲': 'add_to_favorites', '取消收藏': 'del_from_favorites', '播放列表第': 'play_music_list_index', '本地播放歌曲': 'playlocal', '查找歌曲': 'play', '下载歌曲': 'play', '暂停': 'stop', '停止': 'stop', '停止播放': 'stop', '播放歌单': 'play_music_list', '测试自定义口令': 'exec#code1("hello")', '测试链接': 'exec#httpget("https://github.com/hanxi/xiaomusic")'}, key_match_order=['分钟后关机', '播放歌曲', '下一首', '上一首', '单曲循环', '全部循环', '随机播放', '关机', '刷新列表', '播放列表第', '播放列表', '加入收藏', '收藏歌曲', '取消收藏', '播放本地歌曲', '本地播放歌曲', '查找歌曲', '下载歌曲', '暂停', '停止', '停止播放', '播放歌单', '测试自定义口令', '测试链接']

似乎自定义的口令只能以插入的方式添加上去,并不能替换掉原来的口令


评论 15 - hanxi

key_word_dict中的“播放歌曲”口令是不能修改的是吧?因为以前用小爱同学播放歌曲说习惯了,总是触发xiaomusic自动下载歌曲,我想把口令改成“查找歌曲”,这样我当说播放歌曲的时候就调用网易云音乐或者QQ音乐,当我说查找歌曲的时候就先看本地有没有歌曲,没有话就自动下载到本地。 当我先按照config-example.json的模板写好如下配置并重命名为config.json

  "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": [
-    "分钟后关机",
-    "查找歌曲",
-    "下一首",
-    "单曲循环",
-    "全部循环",
-    "随机播放",
-    "关机",
-    "刷新列表",
-    "播放列表",
-    "播放本地歌曲",
-    "本地播放歌曲",
-    "下载歌曲",
-    "暂停",
-    "停止",
-    "停止播放",
-    "测试自定义口令",
-    "测试链接"
-  ],

用以下命令安装docker docker run --name xiaomusic -p 5488:5488 -v /mnt/sharedata/audiodata/musci/xiaomusic:/app/music -v /mnt/data_sdb1/docker/xiaomusic/config.json:/app/config.json -e XIAOMUSIC_PORT=5488 hanxi/xiaomusic --config /app/config.json 日志里提示的依然是:

key_word_dict={'播放歌曲': 'play', '播放本地歌曲': 'playlocal', '关机': 'stop', '下一首': 'play_next', '上一首': 'play_prev', '单曲循环': 'set_play_type_one', '全部循环': 'set_play_type_all', '随机播放': 'set_random_play', '分钟后关机': 'stop_after_minute', '播放列表': 'play_music_list', '刷新列表': 'gen_music_list', '加入收藏': 'add_to_favorites', '收藏歌曲': 'add_to_favorites', '取消收藏': 'del_from_favorites', '播放列表第': 'play_music_list_index', '本地播放歌曲': 'playlocal', '查找歌曲': 'play', '下载歌曲': 'play', '暂停': 'stop', '停止': 'stop', '停止播放': 'stop', '播放歌单': 'play_music_list', '测试自定义口令': 'exec#code1("hello")', '测试链接': 'exec#httpget("https://github.com/hanxi/xiaomusic")'}, key_match_order=['分钟后关机', '播放歌曲', '下一首', '上一首', '单曲循环', '全部循环', '随机播放', '关机', '刷新列表', '播放列表第', '播放列表', '加入收藏', '收藏歌曲', '取消收藏', '播放本地歌曲', '本地播放歌曲', '查找歌曲', '下载歌曲', '暂停', '停止', '停止播放', '播放歌单', '测试自定义口令', '测试链接']

似乎自定义的口令只能以插入的方式添加上去,并不能替换掉原来的口令

可以在网页后台设置页面改。


评论 16 - mogeqian

不行,后台设置如图 QQ截图20241111181411 日志如下:

[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:1130: update_config_from_setting ok. data:Config(account='**', password='**', mi_did='726577518,570867755', miio_tts_command='', cookie='', verbose=False, music_path='music', download_path='music/download', conf_path='conf', cache_dir='cache', hostname='192.168.22.4', port=8090, public_port=0, proxy='', 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='', custom_play_list_json='', disable_download=False, key_word_dict={'播放歌曲': 'play', '播放本地歌曲': 'playlocal', '关机': 'stop', '下一首': 'play_next', '上一首': 'play_prev', '单曲循环': 'set_play_type_one', '全部循环': 'set_play_type_all', '随机播放': 'set_random_play', '分钟后关机': 'stop_after_minute', '播放列表': 'play_music_list', '刷新列表': 'gen_music_list', '加入收藏': 'add_to_favorites', '收藏歌曲': 'add_to_favorites', '取消收藏': 'del_from_favorites', '播放列表第': 'play_music_list_index', '本地播放歌曲': 'playlocal', '查找歌曲': 'play', '下载歌曲': 'play', '暂停': 'stop', '停止': 'stop', '停止播放': 'stop', '播放歌单': 'play_music_list', '测试自定义口令': '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=False, keywords_playlocal='播放本地歌曲,本地播放歌曲', keywords_play='查找歌曲,下载歌曲', keywords_stop='关机,暂停,停止,停止播放', keywords_playlist='播放列表,播放歌单', user_key_word_dict={'测试自定义口令': 'exec#code1("hello")', '测试链接': 'exec#httpget("https://github.com/hanxi/xiaomusic")'}, enable_force_stop=False, devices={'726577518': Device(did='726577518', device_id='******', hardware='LX06', name='小爱音箱Pro', play_type='', cur_music='', cur_playlist='全部'), '570867755': Device(did='570867755', device_id='*********', hardware='L15A', name='小米AI音箱(第二代)', play_type='', cur_music='', cur_playlist='全部')}, group_list='', remove_id3tag=False, convert_to_mp3=False, delay_sec=3, continue_play=False, pull_ask_sec=1, crontab_json='', enable_yt_dlp_cookies=False, get_ask_by_mina=False)
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:1133: 语音控制已启动, 用【分钟后关机/播放歌曲/下一首/上一首/单曲循环/全部循环/随机播放/关机/刷新列表/播放列表第/播放列表/加入收藏/收藏歌曲/取消收藏/播放本地歌曲/本地播放歌曲/查找歌曲/下载歌曲/暂停/停止/停止播放/播放歌单/测试自定义口令/测试链接】开头来控制
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:543: 协程时间循环未启动
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:1250: 没打乱 全部 ['乌兰托娅 火红的萨日朗', '凤凰传奇麝香夫人'] ... ['罗大佑童年', '阿嬷'] with len: 7
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:1250: 没打乱 全部 ['乌兰托娅 火红的萨日朗', '凤凰传奇麝香夫人'] ... ['罗大佑童年', '阿嬷'] with len: 7
-[2024-11-11 18:08:04] [0.3.46] [INFO] analytics.py:28: analytics init ok
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:104: Startup OK. Config(account='***', password='***', mi_did='726577518,570867755', miio_tts_command='', cookie='', verbose=False, music_path='music', download_path='music/download', conf_path='conf', cache_dir='cache', hostname='192.168.22.4', port=8090, public_port=0, proxy='', 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='', custom_play_list_json='', disable_download=False, key_word_dict={'播放歌曲': 'play', '播放本地歌曲': 'playlocal', '关机': 'stop', '下一首': 'play_next', '上一首': 'play_prev', '单曲循环': 'set_play_type_one', '全部循环': 'set_play_type_all', '随机播放': 'set_random_play', '分钟后关机': 'stop_after_minute', '播放列表': 'play_music_list', '刷新列表': 'gen_music_list', '加入收藏': 'add_to_favorites', '收藏歌曲': 'add_to_favorites', '取消收藏': 'del_from_favorites', '播放列表第': 'play_music_list_index', '本地播放歌曲': 'playlocal', '查找歌曲': 'play', '下载歌曲': 'play', '暂停': 'stop', '停止': 'stop', '停止播放': 'stop', '播放歌单': 'play_music_list', '测试自定义口令': '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=False, keywords_playlocal='播放本地歌曲,本地播放歌曲', keywords_play='查找歌曲,下载歌曲', keywords_stop='关机,暂停,停止,停止播放', keywords_playlist='播放列表,播放歌单', user_key_word_dict={'测试自定义口令': 'exec#code1("hello")', '测试链接': 'exec#httpget("https://github.com/hanxi/xiaomusic")'}, enable_force_stop=False, devices={'726577518': Device(did='726577518', device_id='*****', hardware='LX06', name='小爱音箱Pro', play_type='', cur_music='', cur_playlist='全部'), '570867755': Device(did='570867755', device_id='*********', hardware='L15A', name='小米AI音箱(第二代)', play_type='', cur_music='', cur_playlist='全部')}, group_list='', remove_id3tag=False, convert_to_mp3=False, delay_sec=3, continue_play=False, pull_ask_sec=1, crontab_json='', enable_yt_dlp_cookies=False, get_ask_by_mina=False)
-[2024-11-11 18:08:04] [0.3.46] [INFO] httpserver.py:111: disable_httpauth:True
-[18:08:04] [0.3.46] [INFO] Started server process [1]
-[18:08:04] [0.3.46] [INFO] Waiting for application startup.
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:541: 启动后台构建 tag cache
-[18:08:04] [0.3.46] [INFO] Application startup complete.
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:513: 已从【cache/tag_cache.json】加载 tag cache
-[18:08:04] [0.3.46] [INFO] Uvicorn running on http://['0.0.0.0', '::']:8090 (Press CTRL+C to quit)
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:527: 保存:tag cache 已保存到【cache/tag_cache.json】
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:577: tag 更新完成
-[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:248: 选中的设备: {'726577518': Device(did='726577518', device_id='******', hardware='LX06', name='小爱音箱Pro', play_type='', cur_music='', cur_playlist='全部'), '570867755': Device(did='570867755', device_id='*********', hardware='L15A', name='小米AI音箱(第二代)', play_type='', cur_music='', cur_playlist='全部')}
-[18:08:34] [0.3.46] [INFO] 172.20.0.1:35058 - "GET /static/default/setting.html HTTP/1.1" 304
-[18:08:34] [0.3.46] [INFO] 172.20.0.1:35058 - "GET /getversion HTTP/1.1" 200

使用的docker-compose命令安装 services: xiaomusic: image: hanxi/xiaomusic container_name: xiaomusic restart: unless-stopped ports: - 8090:8090 volumes: - /mnt/sharedata/audiodata/musci/xiaomusic:/app/music - /mnt/data_sdb1/docker/xiaomusic/config.json:/app/config.json command: ['--config', '/app/config.json']

根据日志的提示,'播放歌曲': 'play'依然存在,只是增加了 '查找歌曲': 'play', '下载歌曲': 'play', 这两个关于play的自定义口令,所以实际上play有三条口令 “播放歌曲、查找歌曲、下载歌曲”,能否删除掉'播放歌曲': 'play'这个系统默认的口令?只使用 '查找歌曲': 'play', '下载歌曲': 'play', 这两个关于play的自定义口令


评论 17 - hanxi

@mogeqian 另外提个 issue 吧,现在应该是不支持删除默认的口令。


评论 18 - mogeqian

好的,已经重开了一个issue #259


链接到 GitHub Issue

- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/issues/182.html b/docs/.vitepress/dist/issues/182.html deleted file mode 100644 index c0659a4..0000000 --- a/docs/.vitepress/dist/issues/182.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - 定时任务配置格式 | XiaoMusic - - - - - - - - - - - - - - -
Skip to content

定时任务配置格式

支持采用 crontab 的格式配置定时任务,已经支持下面的任务类型:

  • stop 关机
  • play 播放歌曲
  • play_music_list 播放列表
  • tts 文字转语音
  • refresh_music_list 刷新播放列表
  • set_volume 设置音量
  • set_play_type 设置播放类型,单曲循环 0 , 全部循环 1 , 随机播放 2 , 单曲播放 3 , 顺序播放 4

示例

json
[
-    {
-        "expression": "0 8 * * 0-4",
-        "name": "play",
-        "did": "123456789",
-        "arg1": "周杰伦晴天"
-    },
-    {
-        "expression": "10 8 * * 0-4",
-        "name": "stop",
-        "did": "123456789"
-    },
-    {
-        "expression": "0 9 * * *",
-        "name": "play",
-        "did": "123456789",
-        "arg1": "周杰伦晴天"
-    },
-    {
-        "expression": "0 10 * * *",
-        "name": "play_music_list",
-        "did": "123456789",
-        "arg1": "周杰伦"
-    },
-    {
-        "expression": "30 10 * * *",
-        "name": "play_music_list",
-        "did": "123456789",
-        "arg1": "周杰伦|晴天"
-    },
-    {
-        "expression": "0 7 * * *",
-        "name": "tts",
-        "did": "123456789",
-        "arg1": "早上好!该起床了!"
-    },
-    {
-        "expression": "0 3 * * *",
-        "name": "refresh_music_list"
-    },
-    {
-        "expression": "* * * * *",
-        "name": "set_volume",
-        "did": "123456789",
-        "arg1": "25"
-    },
-    {
-        "expression": "* * * * *",
-        "name": "set_play_type",
-        "did": "123456789",
-        "arg1": "2"
-    }
-]

示例中的意思是:

  • 周一到周五每天 8 点播放歌曲 "周杰伦晴天"
  • 周一到周五每天 8 点 10 分执行关机指令
  • 每天 9 点播放歌曲 "周杰伦晴天"
  • 每天 10 点播放列表 "周杰伦"
  • 每天 10 点 30 分播放列表 "周杰伦" 里的 "晴天"
  • 每天 7 点发出语音 "早上好!该起床了!"
  • 每天 3 点刷新播放列表,用于自动更新目录下的歌曲到播放列表里。
  • 每分钟设置音量为 25
  • 每分钟设置为随机播放

注意星期一是0,星期二是1,星期日是6。 (0-6 or mon,tue,wed,thu,fri,sat,sun) The first weekday is always monday.

参数意思

  • expression 的格式是标准的 crontab 的格式,用于配置任务的执行时机,如何配置可以直接用下面的 crontab ai 工具生成
  • name 是任务名,目前只支持上面那几种。
  • did 是小爱音箱的设备ID,就是设置页面的音箱型号后面的那串数字。
  • arg1 根据任务不同而不同。
    • play 的 arg1 表示要播放的歌曲名。
    • play_music_list 的 arg1 表示要播放的播放目录名,可以加 | 符合加上目录下面的歌曲名,也可不加。
    • tts 的 arg1 表示要说的语音文字。

评论

评论 1 - hanxi

0.3.38版本功能。


链接到 GitHub Issue

- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/issues/19.html b/docs/.vitepress/dist/issues/19.html deleted file mode 100644 index 0b62071..0000000 --- a/docs/.vitepress/dist/issues/19.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - 如何修改默认的8090端口 | XiaoMusic - - - - - - - - - - - - - - -
Skip to content

如何修改默认的8090端口

docker-compose 修改映射端口会播放失败

ports:
-      - 80:8090

从日志看继续调用了 http://10.0.0.4:8090 而不是修改映射的80,还原成

ports:
-      - 8090:8090

则一切正常

xiaomusic    | [BiliBiliSearch] Playlist 安河桥北: Downloading 1 items of 1
-xiaomusic    | [download] Downloading item 1 of 1
-xiaomusic    | [BiliBili] Extracting URL: http://www.bilibili.com/video/av319943893
-xiaomusic    | [BiliBili] 319943893: Downloading webpage
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | [BiliBili] BV1tw411X7Rr: Extracting videos in anthology
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | [BiliBili] 319943893: Extracting chapters
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | [BiliBili] Format(s) 1080P 高码率, 1080P 高清, 720P 高清, 4K 超清 are missing; you have to login or become premium member to download them. Use --cookies-from-browser or --cookies for the authentication. See  https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp  for how to manually pass cookies
-xiaomusic    | [info] BV1tw411X7Rr: Downloading 1 format(s): 30280
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-xiaomusic    | [download] Destination: music/安河桥北.m4a
-[download]   0.3% of    5.61MiB at    6.24MiB/s ETA 00:0010.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-[download]   0.5% of    5.61MiB at    2.42MiB/s ETA 00:0210.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-[download]   1.1% of    5.61MiB at    1.50MiB/s ETA 00:0310.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-[download]   4.4% of    5.61MiB at    2.35MiB/s ETA 00:0210.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-[download]   8.9% of    5.61MiB at    3.59MiB/s ETA 00:0110.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
-[download] 100% of    5.61MiB in 00:00:00 at 10.83MiB/s  
-xiaomusic    | [ExtractAudio] Destination: music/安河桥北.mp3
-xiaomusic    | Deleting original file music/安河桥北.m4a (pass -k to keep)
-xiaomusic    | [download] Finished downloading playlist: 安河桥北
-xiaomusic    | [02/21/24 15:29:29] INFO     播放                               xiaomusic.py:461
-xiaomusic    |                              http://10.0.0.4:8090/music/%E5%AE%                 
-xiaomusic    |                              89%E6%B2%B3%E6%A1%A5%E5%8C%97.mp3                  
-xiaomusic    |                     INFO     已经开始播放了                     xiaomusic.py:464
-xiaomusic    |                     INFO     歌曲music/安河桥北.mp3的时长251秒  xiaomusic.py:371
-xiaomusic    |                     INFO     251秒后将会播放下一首              xiaomusic.py:385
-xiaomusic    |                     INFO     匹配到指令. opkey:set_volume#      xiaomusic.py:441
-xiaomusic    |                              opvalue:set_volume oparg:24

评论

评论 1 - hanxi

需要添加环境变量

environment:
-    XIAOMUSIC_PORT:80
-ports:
-      - 80:80

评论 2 - newrookie001

需要添加环境变量

environment:
-    XIAOMUSIC_PORT:80
-ports:
-      - 80:80

自己走了点弯路,半天才搞明白。补充说明:

environment: XIAOMUSIC_PORT: 5678 #就是“5678”可以根据自己要求设置,但要求上下的5678都设置成一个 ports: - 5678:5678


评论 3 - hanxi

如果换端口,需要3个数字一致,比如

environment:
-    XIAOMUSIC_PORT:6874
-ports:
-      - 6874:6874

评论 4 - hanxi

文档类型的我都打开下,方便其他人看到。


评论 5 - flymin

docker-compose 中对应关系应该是

yaml
ports:
-    - aaaa:bbbb
-environment:
-    XIAOMUSIC_PORT: bbbb  # 配置文件中的 port,后台:监听端口(修改后需要重启)
-    XIAOMUSIC_PUBLIC_PORT: aaaa # 配置文件中的 public_port,后台:外网访问端口(0表示跟监听端口一致)

以上,docker 环境中基本不存在需要修改 bbbb 的情况,也就是不用设置 XIAOMUSIC_PORT。如果需要修改端口,只需要修改两处 aaaa 如果使用反向代理,则转发 localhost:aaaa,XIAOMUSIC_PUBLIC_PORT 设置成代理的监听端口 cccc

另外,setting 文件存在会覆盖环境变量。启动过之后需要直接修改 settings.json 或者在后台修改


链接到 GitHub Issue

- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/issues/210.html b/docs/.vitepress/dist/issues/210.html deleted file mode 100644 index aae1380..0000000 --- a/docs/.vitepress/dist/issues/210.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - yt-dlp cookies 文件上传功能 | XiaoMusic - - - - - - - - - - - - - - -
Skip to content

yt-dlp cookies 文件上传功能

此功能用于解决 yt-dlp 下载资源失败时使用,比如 ip 被 B站或者 youtube 加入黑名单后才需要使用。

上传的文件用于 yt-dlp 的 --cookies 参数。

--cookies FILE   Netscape formatted file to read cookies from
-                      and dump cookie jar in

获取 cookies.txt 文件

  1. 下载插件 Get cookies.txt LOCALLY
  2. 给予插件访问权限和无痕模式允许使用 image
  3. 打开无痕窗口
  4. 打开 youtube.com
  5. 登陆 youtube.com
  6. 打开新标签页
  7. 关闭 youtube.com 的标签页
  8. 保存 cookies.txt image

原因见 https://github.com/yt-dlp/yt-dlp/wiki/Extractors#exporting-youtube-cookies

上传 cookies.txt

  1. 打开设置页面
  2. 设置启用yt-dlp-cookies 选项为 true Screenshot_2024-09-29-22-31-40-134_com android chrome-edit
  3. 点击保存
  4. 再点击选择文件,选择前面保存好的 cookies.txt 文件,点击上传。 Screenshot_2024-09-29-22-33-21-361_com android chrome-edit

后续用途

  1. 语音下载歌曲都会带上前面上传的 cookies.txt 文件去搜索下载歌曲。
  2. 歌曲批量下载工具下载歌曲或者歌单时也会带上 cookies.txt 文件。

评论

评论 1 - kingfly2016

0.3.37的版本并没有发现可以开启yt-dlp-cookies 并上传cookies文件的地方,尝试把导出的cookies.txt手工上传到conf目录下,没有生效. 屏幕截图_11-10-2024_183725_192 168 6 202


评论 2 - hanxi

需要等38版本,或者用测试版本,镜像名后面加 :main


评论 3 - kingfly2016

需要等38版本,或者用测试版本,镜像名后面加 :main

谢谢


链接到 GitHub Issue

- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/issues/211.html b/docs/.vitepress/dist/issues/211.html deleted file mode 100644 index 112aed7..0000000 --- a/docs/.vitepress/dist/issues/211.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - 📝 文档汇总 | XiaoMusic - - - - - - - - - - - - - - -
Skip to content

📝 文档汇总

1️⃣ 基础文档

2️⃣ 进阶文档

3️⃣ 其他安装文档

NOTE

下面教程可能比较旧,只供参考

评论

评论 1 - sghuenn

redmi小爱触屏音箱8,仍然需要打开“型号兼容模式”才能播放 打开兼容模式后的问题有:每首歌曲播放完毕后都要再从头播放4、5秒才播放下一曲;语音命令小爱同学播放下一曲,它会从头开始播放当前歌曲。


评论 2 - hanxi

redmi小爱触屏音箱8,仍然需要打开“型号兼容模式”才能播放 打开兼容模式后的问题有:每首歌曲播放完毕后都要再从头播放4、5秒才播放下一曲;语音命令小爱同学播放下一曲,它会从头开始播放当前歌曲。

播放歌曲的接口应该是有点问题,等有设备有开发能力的人来搞吧。


评论 3 - zhoukk37

想问下如何利用反向代理,完成使得小爱外网访问nas呢,您能提供一下关键词,我自己去检索学下一下吗


评论 4 - hanxi

想问下如何利用反向代理,完成使得小爱外网访问nas呢,您能提供一下关键词,我自己去检索学下一下吗

内网穿透,frp能实现,就是把局域网的端口映射成公网的端口。


链接到 GitHub Issue

- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/issues/212.html b/docs/.vitepress/dist/issues/212.html deleted file mode 100644 index b95dc79..0000000 --- a/docs/.vitepress/dist/issues/212.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - 如何批量下载歌曲 | XiaoMusic - - - - - - - - - - - - - - -
Skip to content

如何批量下载歌曲

批量下载歌曲依赖的是 yt-dlp 批量下载播放列表里的视频并转为 mp3 实现的。

先进入到歌曲下载工具页面:

默认主题 => 设置 => 歌曲下载工具

Screenshot_2024-09-29-22-36-12-178_com android chrome-edit

已经测试过 B 站和 youtube 两种播放列表,播放列表的链接是有要求,不能有其他多余参数。

比如 B 站的是这样的

https://m.bilibili.com/video/BV1WUsDezE88

youtube 的是这样的

https://m.youtube.com/playlist?list=PLUD2d-pqyvT6_ztf31hx-5SsUUvY5UsQn

输入歌单名字是用于保存的文件夹名字,最好不是已经存在的名字,每次下载歌单都取个新名字比较合适。

已知 youtube 需要上传无痕模式下的 cookies.txt 文件才能正常下载。具体步骤见 /issues/210.html 。

也支持单独下载一个链接只有一首歌曲的。

评论

评论 1 - lazybabyz

默认主题 => 设置 =>没有显示找到 歌曲下载工具, 有一个 歌单地址 歌单内容: 输入B站测试地址显示返回无效

aaa


评论 2 - hanxi

等0.3.38版本。


链接到 GitHub Issue

- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/issues/269.html b/docs/.vitepress/dist/issues/269.html deleted file mode 100644 index ae5b885..0000000 --- a/docs/.vitepress/dist/issues/269.html +++ /dev/null @@ -1,371 +0,0 @@ - - - - - - 如何添加 网易云音乐playlist | XiaoMusic - - - - - - - - - - - - - - -
Skip to content

如何添加 网易云音乐playlist

利用 NeteaseCloudMusicApi 获取歌单和播放地址 我在你基础上改了一下,但是我的逻辑不合理 https://github.com/dissipator/xiaomusic

评论

评论 1 - hanxi

建议通过插件实现或者新增一个页面工具把歌单导出 json 。 歌单的 json 格式见 /issues/78.html


评论 2 - qiujie8092916

利用 NeteaseCloudMusicApi 获取歌单和播放地址 我在你基础上改了一下,但是我的逻辑不合理 dissipator/xiaomusic

老哥在实现插件了吗?我也急需播放歌单 我诉求是:我用网易云音乐单独新建了一个歌单,我往里面扔歌曲,以更新歌单。希望创建一个自定义的语音命令,让小爱同学随机播放这个歌单里的音乐。然后我通过在米家里执行,比如触发了「我回来了」的智能场景时,就让小爱音箱执行这个自定义的语音命令,就会自动播放我新建的这个歌单里的音乐了。 现在的小爱音箱虽然能勉强实现,但是很垃圾。随机播放的随机性有问题,并且只能选择歌单里的 30 首歌。 (老哥如果没实现的话,我可以尝试搞搞)


评论 3 - dissipator

我都能直接利用这个直接播放NeteaseCloudMusicApi这上的歌了。在配合unblk,无敌。我就是没设备,还在路上 通过插件实现,非常好,就是不知道怎么开发,有文档我可以尝试一下。


评论 4 - dissipator

建议通过插件实现或者新增一个页面工具把歌单导出 json 。 歌单的 json 格式见 #78

配置处直接填了api的接口 http://127.0.0.1:3000/playlist/detail?id=12758992226 我是直接再你歌单保存上强改的。

python
async def downloadjson(data: UrlInfo, Verifcation=Depends(verification)):
-    log.info(data)
-    url = data.url
-    content = "[{"
-    host = f"{url.split('/')[0]}//{url.split('/')[2]}"
-    try:
-        ret = "OK"
-        jsons = await downloadfile(url,"json")
-        # print(jsons)
-        list_name = jsons['playlist']['name']
-        content += '"name":"'+list_name+'","musics":['
-        for song in jsons['playlist']['tracks']:
-            content += f"{{\"name\":\"{song['name']}\",\"url\": \"{host}/song/url?br=999000&proxy=http:%2F%2F127.0.0.1:8080&realIP=211.161.244.70&id={song['id']}\"}}"
-    except Exception as e:
-        log.exception(f"Execption {e}")
-        ret = "Download JSON file failed."
-    content = content[:-1] + "]}]"

照着你的说明。而且能成功播发

python
@app.get("/musicinfo")
-async def musicinfo(
-    name: str, musictag: bool = False, Verifcation=Depends(verification)
-):
-    url = xiaomusic.get_music_url(name)
-    if("song/url" in url):
-        jsons = await downloadfile(url,"json")
-        url = jsons['data'][0]['url']

播放处加了一个判断


评论 5 - hanxi

建议通过插件实现或者新增一个页面工具把歌单导出 json 。 歌单的 json 格式见 #78

你的修改我看了,不太通用。生成json,再用现有的接口提交json更通用。


评论 6 - dissipator

是的,不通用。最好是用插件实现。

  1. 就是不知道你插件的逻辑。
  2. 如果用插件就考虑直接读取网易账号下所有歌单。然后选择一个导入,或者全部导入。
  3. 等你完善文档后我可以尝试写一个。同理,qq等其他平台的歌单也就都可以弄了

评论 7 - hanxi

等有空我写个修改歌单内容的插件示例吧。


评论 8 - dissipator

import requests
-
-def getmy_playlist(type="netease",api_host="http://127.0.0.1/api", playlist_id=None,uid=None):
-    """
-    Purpose: 
-    """
-    global log, xiaomusic
-    if type == "netease":
-        if uid:
-            api_url = f"{api_host}/user/playlist?uid={uid}"
-            # 发起请求
-            response = requests.get(api_url, timeout=5)  # 增加超时以避免长时间挂起
-            response.raise_for_status()  # 如果响应不是200,引发HTTPError异常
-            # log.info(f"getmy_playlist url:{api_url} response:{response.text}")
-
-            music_list = response.json()
-            for item in music_list['playlist']:
-                list_name = item.get("name")
-
-                log.info(f"getmy_playlist name:{list_name}")
-                # if item.get("id") in [12709941656,]:
-                songs_url = f"{api_host}/playlist/detail?id={item['id']}"
-                # 发起请求
-                response = requests.get(songs_url, timeout=5)  # 增加超时以避免长时间挂起
-                response.raise_for_status()  # 如果响应不是200,引发HTTPError异常
-                # log.info(f"getmy_playlist url:{api_url} response:{response.text}")
-                musics = response.json()
-                one_music_list = []
-                for music in musics['playlist']['tracks']:
-                    if (not music):
-                        continue
-                    # try:
-                    name = music['name']
-                    picUrl = music['al']['picUrl']
-                    artist = music['ar'][0]['name']
-                    album = music['al']['name']
-
-                    name = music.get("name")
-                    url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"
-                    if (not name) or (not url):
-                        continue
-                    xiaomusic.all_music[name] = url
-                    xiaomusic.all_music_tags[name] =  {
-                            "title": name,
-                            "artist": artist,
-                            "album": album,
-                            "year": "",
-                            "genre": "",
-                            "picture": picUrl,
-                            "lyrics": ""
-                        }
-                    
-                    one_music_list.append(name)
-                log.debug(f"getmy_playlist name:{list_name}")
-                log.debug(one_music_list)
-                # 歌曲名字相同会覆盖
-                xiaomusic.music_list[list_name] = one_music_list
-            xiaomusic.try_save_tag_cache()
-            log.debug(xiaomusic.all_music)
-            log.debug(xiaomusic.music_list)
-
-            return
-        if playlist_id:
-            songs_url = f"{api_host}/playlist/detail?id={playlist_id}"
-            # 发起请求
-            response = requests.get(songs_url, timeout=5)  # 增加超时以避免长时间挂起
-            response.raise_for_status()  # 如果响应不是200,引发HTTPError异常
-            # log.info(f"getmy_playlist url:{api_url} response:{response.text}")
-            musics = response.json()
-            list_name = musics['playlist']['name']
-            one_music_list = []
-            for music in musics['playlist']['tracks']:
-                if (not music):
-                    continue
-                # try:
-                name = music['name']
-                picUrl = music['al']['picUrl']
-                artist = music['ar'][0]['name']
-                album = music['al']['name']
-
-                name = music.get("name")
-                url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"
-                if (not name) or (not url):
-                    continue
-                xiaomusic.all_music[name] = url
-                xiaomusic.all_music_tags[name] =  {
-                        "title": name,
-                        "artist": artist,
-                        "album": album,
-                        "year": "",
-                        "genre": "",
-                        "picture": picUrl,
-                        "lyrics": ""
-                    }
-                one_music_list.append(name)
-            log.debug(f"getmy_playlist name:{list_name}")
-            log.debug(one_music_list)
-            # 歌曲名字相同会覆盖
-            xiaomusic.music_list[list_name] = one_music_list
-            xiaomusic.try_save_tag_cache()
-            log.debug(xiaomusic.all_music)
-            log.debug(xiaomusic.music_list)
-            return
-    else:
-        log.error(f"getmy_playlist type:{type} not support")

评论 9 - dissipator

等有空我写个修改歌单内容的插件示例吧。

不用拉,插件我已经写出来了 image


评论 10 - hanxi

发下你的 setting.json 配置吧,方便他人知道怎么配。


评论 11 - dissipator

我还在测试,设备到了能用了再发吧


评论 12 - guitarbug

也需要网易歌单功能, 坐等教程


评论 13 - dissipator

成功了

image

最好是在setting.json里去配置,我测试老是不匹配,在setting.json里去配置终于成功。可以直接调用 NeteaseCloudMusicApi 接口,甚至使用UnblockNeteaseMusic 解锁灰色,但是代码需要小调整。

setting.json

json
{
-  "account": "",
-  "password": "",
-  "mi_did": "",
-  "miio_tts_command": "",
-  "cookie": "",
-  "verbose": false,
-  "music_path": "music",
-  "download_path": "music/download",
-  "conf_path": "conf",
-  "cache_dir": "cache",
-  "hostname": "192.168.2.5",
-  "port": 8090,
-  "public_port": 0,
-  "proxy": "",
-  "search_prefix": "bilisearch:",
-  "ffmpeg_location": "./ffmpeg/bin",
-  "active_cmd": "play,set_random_play,playlocal,play_music_list,play_music_list_index,stop_after_minute,stop,获取歌单",
-  "exclude_dirs": "@eaDir,tmp",
-  "music_path_depth": 10,
-  "disable_httpauth": true,
-  "httpauth_username": "",
-  "httpauth_password": "",
-  "music_list_url": "",
-  "music_list_json": "",
-  "custom_play_list_json": "",
-  "disable_download": false,
-  "key_word_dict": {
-    "播放歌曲": "play",
-    "播放本地歌曲": "playlocal",
-    "关机": "stop",
-    "下一首": "play_next",
-    "上一首": "play_prev",
-    "单曲循环": "set_play_type_one",
-    "全部循环": "set_play_type_all",
-    "随机播放": "set_random_play",
-    "分钟后关机": "stop_after_minute",
-    "播放列表": "play_music_list",
-    "刷新列表": "gen_music_list",
-    "加入收藏": "add_to_favorites",
-    "收藏歌曲": "add_to_favorites",
-    "取消收藏": "del_from_favorites",
-    "播放列表第": "play_music_list_index",
-    "本地播放歌曲": "playlocal",
-    "放歌曲": "play",
-    "暂停": "stop",
-    "停止": "stop",
-    "停止播放": "stop",
-    "播放歌单": "play_music_list",
-    "测试自定义口令": "exec#code1(\"hello\")",
-    "测试链接": "exec#httpget(\"https://github.com/hanxi/xiaomusic\")",
-    "获取歌单": "exec#getmy_playlist(playlist_id=12758992225)"
-  },
-  "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": false,
-  "keywords_playlocal": "播放本地歌曲,本地播放歌曲",
-  "keywords_play": "播放歌曲,放歌曲",
-  "keywords_stop": "关机,暂停,停止,停止播放",
-  "keywords_playlist": "播放列表,播放歌单",
-  "user_key_word_dict": {
-    "测试自定义口令": "exec#code1(\"hello\")",
-    "测试链接": "exec#httpget(\"https://github.com/hanxi/xiaomusic\")",
-    "获取歌单": "exec#getmy_playlist(playlist_id=12758992225)"
-  },
-  "enable_force_stop": false,
-  "devices": {
-    " ": {
-      "did": " ",
-      "device_id": " -17c6-4204- - ",
-      "hardware": "L05C",
-      "name": "小黑你好",
-      "play_type": "",
-      "cur_music": "",
-      "cur_playlist": ""
-    }
-  },
-  "group_list": "",
-  "remove_id3tag": false,
-  "convert_to_mp3": false,
-  "delay_sec": 3,
-  "continue_play": false,
-  "pull_ask_sec": 1,
-  "crontab_json": "",
-  "enable_yt_dlp_cookies": false,
-  "get_ask_by_mina": false
-}

getmy_playlist.py

python
import requests
-
-async def getmy_playlist(type="netease",api_host="http://127.0.0.1/api", playlist_id=None,uid=None):
-    """
-    Purpose: 
-    """
-    global log, xiaomusic
-    if type == "netease":
-        if uid:
-            api_url = f"{api_host}/user/playlist?uid={uid}"
-            # 发起请求
-            response = requests.get(api_url, timeout=5) # 增加超时以避免长时间挂起
-            response.raise_for_status() # 如果响应不是200,引发HTTPError异常
-            # log.info(f"getmy_playlist url:{api_url} response:{response.text}")
-
-            music_list = response.json()
-            for item in music_list['playlist']:
-                list_name = item.get("name")
-
-                log.info(f"getmy_playlist name:{list_name}")
-                # if item.get("id") in [12709941656,]:
-                songs_url = f"{api_host}/playlist/detail?id={item['id']}"
-                # 发起请求
-                response = requests.get(songs_url, timeout=5) # 增加超时以避免长时间挂起
-                response.raise_for_status() # 如果响应不是200,引发HTTPError异常
-                # log.info(f"getmy_playlist url:{api_url} response:{response.text}")
-                musics = response.json()
-                one_music_list = []
-                for music in musics['playlist']['tracks']:
-                    if (not music):
-                        continue
-                    # try:
-                    name = music['name']
-                    picUrl = music['al']['picUrl']
-                    artist = music['ar'][0]['name']
-                    album = music['al']['name']
-
-                    name = music.get("name")
-                    url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"
-                    if (not name) or (not url):
-                        continue
-                    xiaomusic.all_music[name] = url
-                    xiaomusic.all_music_tags[name] = {
-                        "title": name,
-                        "artist": artist,
-                        "album": album,
-                        "year": "",
-                        "genre": "",
-                        "picture": picUrl,
-                        "lyrics": ""
-                        }
-
-                    one_music_list.append(name)
-                    log.debug(f"getmy_playlist name:{list_name}")
-                log.debug(one_music_list)
-                # 歌曲名字相同会覆盖
-                xiaomusic.music_list[list_name] = one_music_list
-                xiaomusic.try_save_tag_cache()
-                log.debug(xiaomusic.all_music)
-                log.debug(xiaomusic.music_list)
-            return
-        if playlist_id:
-            songs_url = f"{api_host}/playlist/detail?id={playlist_id}"
-            # 发起请求
-            response = requests.get(songs_url, timeout=5) # 增加超时以避免长时间挂起
-            response.raise_for_status() # 如果响应不是200,引发HTTPError异常
-            
-            musics = response.json()
-            list_name = musics['playlist']['name']
-            log.info(f"getmy_playlist list_name:{list_name} ")
-            one_music_list = []
-            for music in musics['playlist']['tracks']:
-                if (not music):
-                    continue
-                # try:
-                name = music['name']
-                picUrl = music['al']['picUrl']
-                artist = music['ar'][0]['name']
-                album = music['al']['name']
-
-                name = music.get("name")
-                url = f"{api_host}/song/url?id={music['id']}&br=350000&proxy=HTTP:%2F%2F127.0.0.1:8080"
-                if (not name) or (not url):
-                  continue
-                xiaomusic.all_music[name] = url
-                xiaomusic.all_music_tags[name] = {
-                    "title": name,
-                    "artist": artist,
-                    "album": album,
-                    "year": "",
-                    "genre": "",
-                    "picture": picUrl,
-                    "lyrics": ""
-                }
-                one_music_list.append(name)
-            log.debug(f"getmy_playlist name:{list_name}")
-            log.debug(one_music_list)
-            # 歌曲名字相同会覆盖
-            xiaomusic.music_list[list_name] = one_music_list
-            xiaomusic.try_save_tag_cache()
-            return
-    else:
-        log.error(f"getmy_playlist type:{type} not support")

评论 14 - dissipator

imageimage


评论 15 - dludream

NeteaseCloudMusicApi似乎获取不到播放地址?其他信息倒是有。

2024-11-28_151952


评论 16 - hanxi

看代码是另一个接口获取url的: url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"


评论 17 - dludream

看代码是另一个接口获取url的: url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"

是的,我用的是这个接口,https://registry.hub.docker.com/r/gnehs/neteasecloudmusicapi-docker/

可能网易修改了api,这些获取不了播放地址了。

我倒不是要这个功能,只是感兴趣看看。我不获取,我直接用yt把歌全下载下来播放。


评论 18 - dissipator

这个不是网易的接口,是NeteaseCloudMusicApi 接口。proxy=HTTP:%2F%2F127.0.0.1:8080"是UnblockNeteaseMusic 解锁灰色; 完整的使用方式和docker 可以到 https://github.com/dissipator/xiaomusic/tree/dev 看README.md;目前没有教程。本人就在群2,有问题可以找我。


链接到 GitHub Issue

- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/issues/285.html b/docs/.vitepress/dist/issues/285.html deleted file mode 100644 index 2190e82..0000000 --- a/docs/.vitepress/dist/issues/285.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - 相关工具推荐 | XiaoMusic - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/issues/294.html b/docs/.vitepress/dist/issues/294.html deleted file mode 100644 index c358c1e..0000000 --- a/docs/.vitepress/dist/issues/294.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - 关于M01型号的注意事项 | XiaoMusic - - - - - - - - - - - - - - -
Skip to content

关于M01型号的注意事项

M01:在0.3.55版本【型号兼容模式】与【特殊型号获取对话记录】都设为false或true,都可以语音了。 如果【型号兼容模式】为 true,默认UI显示播放中,但音箱没声音。

型号:S12A、LX04、S12 在米家APP可以联动,比如客厅有人自定义指令:播放歌曲、关机...等 而M01无论【型号兼容模式】与【特殊型号获取对话记录】设为false或true,都无法执行任何自定义指令…

IMG_6460

评论

评论 1 - hanxi

M01 保持默认设置应该是能语音和播放的吧。自定义指令的功能应该是 M01 本身就不支持,可能属于放弃维护的产品吧。


评论 2 - bj803

M01 保持默认设置应该是能语音和播放的吧。自定义指令的功能应该是 M01 本身就不支持,可能属于放弃维护的产品吧。

刚试了用:pause是可以暂停(不过只暂2分钟左右又自己播放了)


评论 3 - hanxi

只能网页里点关机按钮,或者语音关机。所有型号都一样。


评论 4 - bj803

型号:S12A、LX04、S12

只能网页里点关机按钮,或者语音关机。所有型号都一样。

在型号:S12A、LX04、S12 除了能网页里点关机按钮或者语音开关机外,能在米家APP自定义指令进行播放与关机(例如当客厅客有人自定义指令"播放歌曲",无人自定义指令"关机"。M01自定义指令"pause"可以暂停,其他口令不行。


评论 5 - bj803

刚又试了一下,用自定义play stop、power off可以停止播放关机,用自定义play music可以播放音乐,可能M01不需要下划线


评论 6 - hanxi

感谢你的反馈。


链接到 GitHub Issue

- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/issues/297.html b/docs/.vitepress/dist/issues/297.html deleted file mode 100644 index 9dc093d..0000000 --- a/docs/.vitepress/dist/issues/297.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - xiaomusic极空间安装教程(2024/12/4更新) | XiaoMusic - - - - - - - - - - - - - - -
Skip to content

xiaomusic极空间安装教程(2024-12-4更新)

本教程同步更新于最新版的xiaomusic

看不懂/嫌麻烦/懒 但有点小钱,找 hanxi 预约微信或者 QQ 远程安装,他便宜,收费50一次,作法不成功不要钱

ARM架构自己想办法获取镜像 点名Z2PRO

获取镜像

科学环境:

  1. 搜索框 中输入 hanxi/xiaomusic,在搜索的结果中直接选择第一个,点击下载

图片

  1. 在新弹出的版本选择窗口中,根据你的情况选择。 图片

版本说明

  • 获取 最新版 直接点击 下载 即可,建议使用默认的 latest
  • 获取 特定版本 点击此处可查看 用于回退出现功能不兼容、恶性bug等情况,一般建议反馈开发者,修复很及时,尽量不要回退版本 请输入如 v0.3.55
  • 获取 实验版本(已修复部分bug但未推送)请输入 main
  1. 接着弹出如图所示的页面,耐心等待下载完成。 图片

  2. 下载完成后切换到 本地镜像 选项卡

剩余步骤与国内环境相同,见 部署镜像

国内环境:

  1. 打开docker,在左侧的菜单中选择 镜像 切换到 仓库 选项卡,点击 自定义拉取 按钮 图片
  2. 在弹出的对话框中输入 m.daocloud.io/docker.io/hanxi/xiaomusic ,点击 拉取 按钮 图片
  3. 下载完成后切换到 本地镜像 选项卡

部署镜像

  1. 找到刚才已经拉取好的镜像,单击选中,点击 添加到容器图片
  2. 在弹出的 创建容器 菜单中,切换到 文件夹路径 选项卡中,按图中的提示进行配置。 图片

注意:

  • 装载路径中的 配置文件目录音乐目录 必须进行配置。
  • 如有多个音乐目录,请按照下面的格式进行配置
文件/文件夹装载路径
/data/music1/app/music/music1
/data/music2/app/music/music2
  1. 切换到 端口 选项卡,修改成与你的极空间 不冲突 的本地端口号,如 5678 (示例按照本地端口号5678来进行配置,下同)

友情提醒: 尽量不要修改容器端口号,否则要到配置文件目录修改对应的setting.json文件中的配置,会增加很多麻烦

图片

  1. 切换到 环境 选项卡,将XIAOMUSIC_HOSTNAME 修改为你的 极空间的IP地址

友情提醒:

  1. 此处不可忽略,否则后续播放音乐会出现问题
  2. 不要尝试修改XIAOMUSIC_PORT!除非你没有看上一条的友情提醒
  3. 不要在此处配置ACCOUNTPASSWORD,没有过风控仍然无法使用!上古时代的教程不要再看了,容易走火入魔!

图片

  1. 点击 应用按钮,此时容器已经配置完成了,切换到左侧的 容器概况 菜单,可查看容器详情 图片

进入xiaomusic网页端进行配置

1.请关闭代理,打开浏览器,地址栏输入 极空间IP:本地端口号192.168.2.5:5678,打开网页后点击 默认主题

图片

注意:

  • 不要复制此处的地址,必须输入极空间的IP地址。不知道的建议上咸鱼50块换个不锈钢盆
  • 不要输入容器的端口号8090,极空间不能使用这个端口号。
  1. 点击 设置 按钮进入设置页面 图片

  2. 输入小米账号小米密码XIAOMUSIC_HOSTNAME(IP或域名):外网访问端口,滑到页面最下方点击 保存图片图片

注意:

  • 小米账号非手机号,请在手机设置-个人中心中查看小米ID
  • 密码不要输错,账号密码错误在上面会弹出提醒,不要假装看不见上面的提醒文字
  • XIAOMUSIC_HOSTNAME(IP或域名): 可以输入当前页面的IP地址(在地址栏),不要在此处输入端口号!!!,如果域名需要使用https协议,请加上https://

4.如果以上步骤没错,你将在设置中心看见设备列表 图片

  1. 回到首页,出现设备列表,切换对应设备即可畅享 图片

评论

评论 1 - xiaohuobanhahaha

xiaomusic.txt

截屏2024-12-05 00 43 24 an'zh 无法使用语音播放歌曲,小爱s12a。极空间z4pro。 1. 按照教程,点击播放本地歌曲,提示hostname和设置的端口映射不匹配。映射5678,容器端口8090. 2.网络host模式,能够本地播放,无法使用语音控制,提示“下载app”。日志已上传

评论 2 - 52fisher

xiaomusic.txt截屏2024-12-05 00 43 24 an'zh 无法使用语音播放歌曲,小爱s12a。极空间z4pro。 1. 按照教程,点击播放本地歌曲,提示hostname和设置的端口映射不匹配。映射5678,容器端口8090. 2.网络host模式,能够本地播放,无法使用语音控制,提示“下载app”。日志已上传

既然你映射的5678,为什么你又在那把监听端口改成11999? 我的教程里面全程没有写要修改监听端口


评论 3 - xiaohuobanhahaha

我没讲清楚。我试了两种极空间的桥接和host模式。桥接模式。我按照教程走的。报错如图 截屏2024-12-05 01 46 52截屏2024-12-05 01 49 11截屏2024-12-05 01 47 02截屏2024-12-05 01 47 15

提到的第二个问题和日志,是我将网络模式改为host的情况,能连上音箱,但是没法使用语音控制。


评论 4 - 52fisher

我没讲清楚。我试了两种极空间的桥接和host模式。桥接模式。我按照教程走的。报错如图 截屏2024-12-05 01 46 52 截屏2024-12-05 01 49 11 截屏2024-12-05 01 47 02 截屏2024-12-05 01 47 15

提到的第二个问题和日志,是我将网络模式改为host的情况,能连上音箱,但是没法使用语音控制。

把外网访问端口改成5678


评论 5 - xiaohuobanhahaha

可以连接上网页控制了,但是语音控制仍然不行。已经按照FAQ问题集合 #99 重启docker。 这是日志, xiaomusic.txt


评论 6 - 52fisher

可以连接上网页控制了,但是语音控制仍然不行。已经按照FAQ问题集合 #99 重启docker。 这是日志, xiaomusic.txt

你的设置 hostname='192.168.31.165', port=8090, public_port=5678, 后台的ip 192.168.31.143 检查一下你的地址 有可能是你的ip地址改变了


评论 7 - xiaohuobanhahaha

可以连接上网页控制了,但是语音控制仍然不行。已经按照FAQ问题集合 #99 重启docker。 这是日志, xiaomusic.txt

你的设置 hostname='192.168.31.165', port=8090, public_port=5678, 后台的ip 192.168.31.143 检查一下你的地址 有可能是你的ip地址改变了

确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。


评论 8 - 52fisher

确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。

容器的网络模式改成bridge试试呢 没解决的话你加群明天再详聊吧


评论 9 - xiaohuobanhahaha

截屏2024-12-05 02 23 53截屏2024-12-05 02 24 49

确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。

容器的网络模式改成bridge试试呢 没解决的话你加群明天再详聊吧

辛苦了,这么晚还在回复。我一直用的bridge。大佬,群号多少,不行我明天群里问吧。


评论 10 - 52fisher

截屏2024-12-05 02 23 53 截屏2024-12-05 02 24 49

确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。

容器的网络模式改成bridge试试呢 没解决的话你加群明天再详聊吧

辛苦了,这么晚还在回复。我一直用的bridge。大佬,群号多少,不行我明天群里问吧。

readme


评论 11 - xiaohuobanhahaha

截屏2024-12-05 02 23 53 截屏2024-12-05 02 24 49

确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。

容器的网络模式改成bridge试试呢 没解决的话你加群明天再详聊吧

辛苦了,这么晚还在回复。我一直用的bridge。大佬,群号多少,不行我明天群里问吧。

readme

已自查解决。问题是账号问题。绑定设备的一定是创建者,不能是管理员。


链接到 GitHub Issue

- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/issues/78.html b/docs/.vitepress/dist/issues/78.html deleted file mode 100644 index 881be62..0000000 --- a/docs/.vitepress/dist/issues/78.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - 已支持配置自定义网络歌单,在这里分享你的歌单 | XiaoMusic - - - - - - - - - - - - - - -
Skip to content

已支持配置自定义网络歌单,在这里分享你的歌单

设置页面新增一个输入框配置json格式,可以定义配置音乐源,可以是电台或者其他的m3u8格式的。 再加一个输入框配置这个json文件的url,点击获取按钮把url对应的json内容填充到json输入框,方便直接使用别人分享的歌单。

比如这样的链接

已经测试能播放出来:

python3 micli.py play http://ngcdn001.cnr.cn/live/zgzs/index.m3u8

预计歌单格式是这样的, type 为 radio 作为电台的设定,会一直播放当前电台,不会播放下一首。

json
[
-  {
-    "name":"歌单1",
-    "musics":[
-      {
-        "name":"歌名1",
-        "url":"http://ngcdn001.cnr.cn/live/zgzs/index.m3u8",
-        "type":"radio"
-      },
-      {
-        "name":"歌名2",
-        "url":"https://lhttp.qtfm.cn/live/4915/64k.mp3"
-      }
-    ]
-  },
-  {
-    "name":"歌单2",
-    "musics":[
-      {
-        "name":"歌名3",
-        "url":"https://lhttp.qtfm.cn/live/4915/64k.mp3"
-      },
-      {
-        "name":"歌名4",
-        "url":"https://lhttp.qtfm.cn/live/4915/64k.mp3"
-      }
-    ]
-  }
-]

这里分享一个让 chatgpt 写 python 脚本来生成歌单的例子 https://chatgpt.com/share/6751c019-74c0-800a-a978-a20c636d4464

评论

评论 1 - hanxi

可以使用 gist 来配置和分享 json 文件,比如 https://gist.github.com/hanxi/dda82d964a28f8110f8fba81c3ff8314

点击 raw 得到 json 文件的链接 https://gist.githubusercontent.com/hanxi/dda82d964a28f8110f8fba81c3ff8314/raw/8787844d81c39dbfaad4e37954dd449d8bba5728/example.json

当然还可以用其他工具分享json文件,比如 github 和国内的 gitee 。


评论 2 - hanxi

已经有工具支持将 m3u 格式的电台文件转为网络歌单格式,见 /issues/88.html

欢迎有兴趣的朋友制作其他格式转换工具,比如网易歌单那一类的。


评论 3 - lazybabyz

按照教程设置后 播放列表选择m3u电台 再选择播放列表 歌曲,结果显示播放中 不断在转台 stdout: [08:58:02] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /static/default/index.html HTTP/1.1" 304 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /getsetting HTTP/1.1" 200 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24022 - "GET /getversion HTTP/1.1" 200 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /musiclist HTTP/1.1" 200 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24021 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /static/default/curplaylist?did=566731712 HTTP/1.1" 404 stdout: [08:58:05] [0.3.37] [INFO] 10.0.80.191:24022 - "GET /getvolume?did=566731712 HTTP/1.1" 500 stderr: [08:58:05] [0.3.37] [ERROR] Exception in ASGI application stderr: Traceback (most recent call last): stderr: File "/app/.venv/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 406, in run_asgi stderr: result = await app( # type: ignore[func-returns-value] stderr: File "/app/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 70, in call stderr: return await self.app(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/applications.py", line 1054, in call stderr: await super().call(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/applications.py", line 113, in call stderr: await self.middleware_stack(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 187, in call stderr: raise exc stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 165, in call stderr: await self.app(scope, receive, _send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/cors.py", line 85, in call stderr: await self.app(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 62, in call stderr: await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 62, in wrapped_app stderr: raise exc stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 51, in wrapped_app stderr: await app(scope, receive, sender) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 715, in call stderr: await self.middleware_stack(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 735, in app stderr: await route.handle(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 288, in handle stderr: await self.app(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 76, in app stderr: await wrap_app_handling_exceptions(app, request)(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 62, in wrapped_app stderr: raise exc stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 51, in wrapped_app stderr: await app(scope, receive, sender) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 73, in app stderr: response = await f(request) stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 301, in app stderr: raw_response = await run_endpoint_function( stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 212, in run_endpoint_function stderr: return await dependant.call(**values) stderr: File "/app/xiaomusic/httpserver.py", line 124, in getvolume stderr: volume = await xiaomusic.get_volume(did=did) stderr: File "/app/xiaomusic/xiaomusic.py", line 809, in get_volume stderr: return await self.devices[did].get_volume() stderr: File "/app/xiaomusic/xiaomusic.py", line 1400, in get_volume stderr: playing_info = await self.xiaomusic.mina_service.player_get_status( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 103, in player_get_status stderr: return await self.ubus_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 59, in ubus_request stderr: result = await self.mina_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request stderr: return await self.account.mi_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request stderr: raise Exception(f"Error {url}: {resp}") stderr: Exception: Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_YfG6nlPVAtwDHEgMeJXxcOiZQLrR57'}} stderr: [08:58:05] [0.3.37] [ERROR] h11_impl.py:411: Exception in ASGI application stderr: Traceback (most recent call last): stderr: File "/app/.venv/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 406, in run_asgi stderr: result = await app( # type: ignore[func-returns-value] stderr: File "/app/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 70, in call stderr: return await self.app(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/applications.py", line 1054, in call stderr: await super().call(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/applications.py", line 113, in call stderr: await self.middleware_stack(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 187, in call stderr: raise exc stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 165, in call stderr: await self.app(scope, receive, _send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/cors.py", line 85, in call stderr: await self.app(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 62, in call stderr: await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 62, in wrapped_app stderr: raise exc stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 51, in wrapped_app stderr: await app(scope, receive, sender) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 715, in call stderr: await self.middleware_stack(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 735, in app stderr: await route.handle(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 288, in handle stderr: await self.app(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 76, in app stderr: await wrap_app_handling_exceptions(app, request)(scope, receive, send) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 62, in wrapped_app stderr: raise exc stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 51, in wrapped_app stderr: await app(scope, receive, sender) stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 73, in app stderr: response = await f(request) stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 301, in app stderr: raw_response = await run_endpoint_function( stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 212, in run_endpoint_function stderr: return await dependant.call(**values) stderr: File "/app/xiaomusic/httpserver.py", line 124, in getvolume stderr: volume = await xiaomusic.get_volume(did=did) stderr: File "/app/xiaomusic/xiaomusic.py", line 809, in get_volume stderr: return await self.devices[did].get_volume() stderr: File "/app/xiaomusic/xiaomusic.py", line 1400, in get_volume stderr: playing_info = await self.xiaomusic.mina_service.player_get_status( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 103, in player_get_status stderr: return await self.ubus_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 59, in ubus_request stderr: result = await self.mina_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request stderr: return await self.account.mi_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request stderr: raise Exception(f"Error {url}: {resp}") stderr: Exception: Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_YfG6nlPVAtwDHEgMeJXxcOiZQLrR57'}} stdout: [08:58:05] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:05] [0.3.37] [INFO] 10.0.80.191:24021 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:07] [0.3.37] [INFO] 10.0.80.191:24021 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:08] [0.3.37] [INFO] 10.0.80.191:24021 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stdout: [08:58:08] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /playingmusic?did=566731712 HTTP/1.1" 200 stderr: [08:58:09] [0.3.37] [INFO] httpserver.py:177: docmd. did:566731712 cmd:播放列表m3u电台|80后音悦台 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:582: cancel_all_tasks no task stdout: [08:58:09] [0.3.37] [INFO] 10.0.80.191:24020 - "POST /cmd HTTP/1.1" 200 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:560: 收到消息:播放列表m3u电台|80后音悦台 控制面板:True did:566731712 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:648: 匹配到指令. opkey:播放列表 opvalue:play_music_list oparg:m3u电台|80后音悦台 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:716: 根据【m3u电台】找到播放列表【m3u电台】 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:991: 没打乱 m3u电台 ['80后音悦台', 'BBC Radio 1', 'BBC Radio 2', 'BBC Radio 3', 'BBC Radio 4', 'CNA938', 'FM 988', ' AFO Radio', 'BBC News', 'BBC Radio 1 Dance', 'BBC Radio 1 Relax', 'BBC Radio 1Xtra', 'BBC Radio 4 Extra', 'BBC Radio 5 Live', 'BBC Radio 6 Music', 'BCC中广新闻', 'BCC中广流行', 'BCC中广音乐', 'CNA亚洲新闻', 'CNR中国之声', 'CNR乡村之声', 'CNR交通广播', 'CNR台海之声', 'CNR文艺之声', 'CNR湾区之声', 'CNR神州之声', 'CNR经典音乐', 'CNR经济之声', 'CNR老年之声', 'CNR阅读之声', 'CNR音乐之声', 'CRI世界华声', 'CRI华语环球', 'CRI南海之声', 'CRI环球资讯', 'CRI英语资讯', 'CRI轻松调频', 'Capital FM', 'CityFM台之音', 'Cool Radio', 'Gold FM', 'Hit FM劲曲', 'Kiss FM', 'LBC News', 'LBC UK', 'Love FM', 'Love Radio', 'Money FM', 'NPR News', 'News Radio UK', 'One FM', 'Power FM', 'RFA', 'RFI', 'RTI中央广播', 'Times Radio', 'VOA环球英语', 'Yes FM', '上海故事广播', '上海流行音乐', '上海音乐广播', '上海音乐电台', '中文流行', '全国广播', '北京交通广播', '北京体育广播', '北京城市广播', '北京文艺广播', '北京新闻广播', '北京经典调频', '北京音乐广播', '南京音乐广播', '台中广播电台', '四川文艺广播', '国乐悠扬', '天籁之音 Hi-Fi', '天籁古典', '天籁国风', '安全百科', '山西文艺广播', '广东音乐之声', '广州汽车音乐', '摇滚天空', '有声文摘', '民谣蓝调', '民谣音乐台', '江苏故事广播', '江苏文艺广播', '江苏音乐广播', '河北文艺广播', '河北汽车音乐', '深圳音乐广播', '湖南文艺广播', '湖南旅游广播', '湖南音乐之声', '潮流音悦台', '经典FM', '美国之音', '陕西故事广播', '陕西音乐广播'] stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1422: 开始播放列表m3u电台 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1005: play. search_key: name:80后音悦台 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:663: 根据【80后音悦台】找到歌曲【80后音悦台】 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1088: cur_music 80后音悦台 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:388: get_music_url web music. name:80后音悦台, url:http://stream3.hndt.com/now/SFZeH2cb/playlist.m3u8 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:360: get_music_sec_url. name:80后音悦台 url:http://stream3.hndt.com/now/SFZeH2cb/playlist.m3u8 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:362: 电台不会有播放时长 stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1437: group_force_stop_xiaoai ['be1daeaa-df03-4a27-8aff-404356abfa9a'] stderr: [08:58:09] [0.3.37] [ERROR] xiaomusic.py:1137: Execption Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_10DxZOintrGWhyETo4q9auU3VMNH6A'}} stderr: Traceback (most recent call last): stderr: File "/app/xiaomusic/xiaomusic.py", line 1131, in force_stop_xiaoai stderr: ret = await self.xiaomusic.mina_service.player_pause(device_id) stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 79, in player_pause stderr: return await self.ubus_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 59, in ubus_request stderr: result = await self.mina_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request stderr: return await self.account.mi_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request stderr: raise Exception(f"Error {url}: {resp}") stderr: Exception: Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_10DxZOintrGWhyETo4q9auU3VMNH6A'}} stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1440: group_force_stop_xiaoai ['be1daeaa-df03-4a27-8aff-404356abfa9a'] [None] stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1091: 播放 http://stream3.hndt.com/now/SFZeH2cb/playlist.m3u8 stderr: [08:58:10] [0.3.37] [ERROR] xiaomusic.py:1332: Execption Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_Bbkf4HLQiN7tjl1rGD2gpXuJ8yI0nP'}} stderr: Traceback (most recent call last): stderr: File "/app/xiaomusic/xiaomusic.py", line 1327, in play_one_url stderr: ret = await self.xiaomusic.mina_service.play_by_url(device_id, url) stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 125, in play_by_url stderr: return await self.ubus_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 59, in ubus_request stderr: result = await self.mina_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request stderr: return await self.account.mi_request( stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request stderr: raise Exception(f"Error {url}: {resp}") stderr: Exception: Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_Bbkf4HLQiN7tjl1rGD2gpXuJ8yI0nP'}} stderr: [08:58:10] [0.3.37] [INFO] xiaomusic.py:1305: group_player_play http://stream3.hndt.com/now/SFZeH2cb/playlist.m3u8 ['be1daeaa-df03-4a27-8aff-404356abfa9a'] [None] stderr: [08:58:10] [0.3.37] [INFO] xiaomusic.py:1094: 播放 80后音悦台 失败


评论 4 - hanxi

设备掉线了


评论 5 - 201692929

怎么获取 他正在播放什么?或者是播放进度 ?播放列表?我想给他加进去 233333


评论 6 - hanxi

怎么获取 他正在播放什么?或者是播放进度 ?播放列表?我想给他加进去 233333

这个接口 /playingmusic


评论 7 - 114514thD

加不加"type":"radio"都会一直播放不切换到下一首歌是为什么呢


评论 8 - hanxi

加不加"type":"radio"都会一直播放不切换到下一首歌是为什么呢

发出来看看?


评论 9 - 114514thD

加不加"type":"radio"都会一直播放不切换到下一首歌是为什么呢

发出来看看?

本地开服务器,生成的m3u列表 格式如下 #EXTINF:247,周传雄 - 临别一眼.mp3 http://192.168.1.147:8000/%E5%91%A8%E4%BC%A0%E9%9B%84%20-%20%E4%B8%B4%E5%88%AB%E4%B8%80%E7%9C%BC.mp3 包含了时长信息 版本是0.3.46 potplayer里播放完全正常

仔细研究了一下,发现确实存在问题,不过是另一种情况,下面单说


评论 10 - 114514thD

加不加"type":"radio"都会一直播放不切换到下一首歌是为什么呢

发出来看看?

经过实验发现,本地生成的m3u用potplayer播放正常 image 转换为json(去掉"type":"radio")后用小爱播放也正常 image

但是alist链接就不正常,alist生成的m3u格式如下 #EXTM3U #EXTINF:-1,Let Me Hear.mp3 http://192.168.1.198:5244/d/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90%20%E9%9F%B3%E4%B9%90%E4%BA%91%E7%9B%98/Let%20Me%20Hear.mp3?sign=xxxx=:0 没有时长信息,但是用potplayer一播放就出现时长了 image 而用小爱播放就始终没有时长(切歌、等待都试过了) image 大佬你的示例链接(gist.github.com/hanxi/dda82d964a28f8110f8fba81c3ff8314)里的又是正常的,感觉可能是alist的流比较特殊。。 image


链接到 GitHub Issue

- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/issues/88.html b/docs/.vitepress/dist/issues/88.html deleted file mode 100644 index eb53c51..0000000 --- a/docs/.vitepress/dist/issues/88.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - 如何添加m3u格式文件的电台 | XiaoMusic - - - - - - - - - - - - - - -
Skip to content

如何添加m3u格式文件的电台

比如可以找到这样的 m3u 电台文件: https://github.com/YueChan/Live/blob/main/Radio.m3u

  1. 复制文件内容,粘贴到 m3u 转换工具里,点击转换为 json 格式:

Screenshot_2024-06-29-11-28-58-904_com android chrome

  1. 然后复制 json 内容,粘贴到歌单内容里,点击保存,再返回首页:

Screenshot_2024-06-29-11-29-22-248_com android chrome

  1. 在首页点击刷新列表,选择所有电台,再点击播放列表歌曲:

Screenshot_2024-06-29-11-29-55-621_com android chrome

  1. 也可以用口令播放电台: 播放列表所有电台 ,或者口令: 播放歌曲北京城市广播

评论

评论 1 - guoxiangke

转换m3u链接: http://127.0.0.1:8090/static/m3u.html


评论 2 - guoxiangke

http://127.0.0.1:8090/playurl?did=mydid&url=https://a.com/test.m3u 如果能支持 这么播放m3u 就太完美了 希望能够支持,谢谢作者。


评论 3 - hanxi

http://127.0.0.1:8090/playurl?did=mydid&url=https://a.com/test.m3u 如果能支持 这么播放m3u 就太完美了 希望能够支持,谢谢作者。

不想破坏现有接口,可以考虑用插件的方式来实现。


评论 4 - lazybabyz

potplayer 测试 https://raw.githubusercontent.com/YueChan/Live/refs/heads/main/Radio.m3u 部分可以听 xiaomusic测试 https://github.com/YueChan/Live/blob/main/Radio.m3u 复制raw文件转换 全部失败不停转台

以下potplayer测试 可以听,xiaomusic测试 复制raw文件转换 全部失败不停转台

https://raw.githubusercontent.com/kaige-cai/live/refs/heads/main/radio.m3uhttps://raw.githubusercontent.com/imDazui/Tvlist-awesome-m3u-m3u8/master/m3u/广播电台2021.m3u


评论 5 - hanxi

检查一下是不是 ipv6 的地址?小米音箱不支持 ipv6


评论 6 - lazybabyz

检查一下是不是 ipv6 的地址?小米音箱不支持 ipv6

是我硬件设置的问题 重新安装了 解决!


链接到 GitHub Issue

- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/issues/94.html b/docs/.vitepress/dist/issues/94.html deleted file mode 100644 index 91ac304..0000000 --- a/docs/.vitepress/dist/issues/94.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - 采用config.json配置方式 | XiaoMusic - - - - - - - - - - - - - - -
Skip to content

采用config.json配置方式

docker 方式部署默认推荐使用环境变量的方式来配置参数,如果是自己用命令行启动,目前支持的参数配置比较少,但是是支持 --config 参数。

使用 pip 安装 xiaomusic 【0.1.83版本才支持 pip 安装】

shell
pip install xiaomusic

依赖的 ffmpeg 需要自己安装。

image

仓库中有个 config-example.json 文件,可以把这个文件拷贝为 config.json 然后修改 config.json 里的配置,再用下面的命令启动。

shell
xiaomusic --config ./config.json

默认的 config.json 模板如下:

json
{
-    "hardware": "L07A",
-    "account": "",
-    "password": "",
-    "mi_did": "",
-    "cookie": "",
-    "verbose": false,
-    "music_path": "music",
-    "conf_path": null,
-    "hostname": "192.168.2.5",
-    "port": 8090,
-    "proxy": null,
-    "search_prefix": "ytsearch:",
-    "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,
-    "use_music_api": false,
-    "log_file": "/tmp/xiaomusic.txt",
-    "fuzzy_match_cutoff": 0.6,
-    "enable_fuzzy_match": true
-}

如果采用 docker compose 启动想用 config.json 的配置方式,可以这样配:

yaml
services:
-  xiaomusic:
-    image: hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 8090:8090
-    volumes:
-      - ./music:/app/music
-      - ./config.json:/app/config.json
-    command: ['--config', '/app/config.json']

主要就是把 config.json 文件映射进容器和传 --config 参数。

评论

评论 1 - alitime

正需要,有配置文件方便多了


评论 2 - hanxi

ffmpeg 如果是用 apt install 这类系统工具安装的,默认会在 /usr/bin 目录下,ffmpeg_location 这个参数就需要设置为 /usr/bin .

目前 armv7 cpu 的 docker 镜像里的 ffmpeg 有问题,最好是用 pip 方式安装运行。


评论 3 - hanxi

ffmpeg 如果是用 apt install 这类系统工具安装的,默认会在 /usr/bin 目录下,ffmpeg_location 这个参数就需要设置为 /usr/bin .

目前 armv7 cpu 的 docker 镜像里的 ffmpeg 有问题,最好是用 pip 方式安装运行。

armv7 问题已经解决。


链接到 GitHub Issue

- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/issues/96.html b/docs/.vitepress/dist/issues/96.html deleted file mode 100644 index 8b9d391..0000000 --- a/docs/.vitepress/dist/issues/96.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - ios系统上的捷径配置 | XiaoMusic - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/issues/99.html b/docs/.vitepress/dist/issues/99.html deleted file mode 100644 index 19beb22..0000000 --- a/docs/.vitepress/dist/issues/99.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - 💬 FAQ问题集合 | XiaoMusic - - - - - - - - - - - - - - -
Skip to content

💬 FAQ问题集合

NOTE

这个 issue 用来总结报错日志和对应的解决方法。

❓ XIAOMUSIC_HOSTNAME 怎么填

填写 docker 主机的 ip ,不是小爱音箱的ip,一般就是网页访问的后台地址里的 ip ,只要确保 ip 和小爱音箱在一个局域网内就行。

同时也支持 xx.xx.com 的域名格式,用于配置反代供外网访问,比如小爱音箱和 docker 主机不在同一个局域网内。

❓ Login Failed 登陆失败

表现就是 后台看不到设备列表 ,日志中会有对应的报错。

这个有专门的讨论,见 </issues/16.html> ,一般是因为开了加速代理,关代理再尝试即可。也可以试试在局域网设备里的米家app上退出再重新登录一下。

在小米官网 www.mi.com 登陆过人脸或滑块验证基本上能解决 99%的 login failed 问题。

来自 @yilikun 的友情提示:

  1. 关闭本地代理。
  2. 如果是nas运行的,网络由bridge改为host。
  3. 米家app重新登陆。
  4. mi.com官网重新登陆。

❓ 网页后台可以播放,语音控制无效

这种情况是拉取不到对话记录导致的。 如果是首次在网页后台保存 did 后需要重启一次容器。 其他情况可能是被限制拉取对话记录次数,也可以尝试重启容器。 还有一种情况是配错了唤醒口令,可以在小爱音箱app里查看对话记录,也可以查看 xiaomusic 的日志。默认口令前缀是【播放歌曲】,没有这个前缀是无法识别的,说播放音乐是没用的,除非自己设置其他口令词。 已知 M01/XMYX01JY 小米小爱音箱HD 获取对话记录的接口比较特殊,需要开启【特殊型号获取对话记录:】开关才能正常语音控制。

❓ 日志显示正在播放,却没有声音

可以点击播放链接按钮,看看默认的那个链接能否播放。

已知部分触屏版不能播放可以在后台设置 【型号兼容模式】为 true 试试。

其他情况可能是 XIAOMUSIC_HOSTNAME 配错了地址,不是 docker 主机地址会导致小爱音箱无法访问到,而且需要和小爱音箱在同一个局域网下的地址。还有可能是端口配错了,修改了默认 8090 端口映射,需要同步修改其他参数,可以翻阅端口修改的文档。

如果端口不是8090,首次启动没配好端口的话,需要手动修改setting.json文件里的端口,或者把setting.json文件删除重新配置,或者在后台修改监听端口后重启。

可以点击播放歌曲后,查看日志里的歌曲链接,放到浏览器里打开试试,不能访问说明是端口或者hostname问题,如果是异地访问,需要把 hostname 修改为外网ip或者域名,需要注意音箱只支持访问ipv4,不能是ipv6的公网。

如果是配了公网反代端口,注意区分是 http 还是 https ,如果是 https 的,配置 XIAOMUSIC_HOSTNAME 时需要加上 https:// 前缀。

❓ 无法播放 flac 格式歌曲

因设备差异和文件格式差异,已知部分设备不支持 flac 格式,比如 L05B L05C 。

❓ docker 镜像拉取失败

请更换镜像源或者使用代理。不同环境更换镜像源的方式不一样,可以网上搜索自己的 NAS 如何更换镜像源。

已经可以通过 DaoCloud 拉取镜像。

docker pull m.daocloud.io/docker.io/hanxi/xiaomusic:latest
-docker tag m.daocloud.io/docker.io/hanxi/xiaomusic:latest hanxi/xiaomusic:latest

❓ 启动失败,日志中出现 RuntimeError: can't start new thread

一般是 docker 版本太低,或者系统限制了 docker 使用的 cpu 数量,可以尝试升级 docker 到最新版本。

❓ DNS 解析错误

一般会出现下面这样的日志,表现就是设置页面看不到设备列表。

aiohttp.client_ _exceptions. ClientConnectorError: Cannot connect to host account.xiaomi.com:443 ssl:False [Temporary failure in name resolution]

可以尝试把主机的 DNS 设为 223.5.5.5 之后重启 docker 主机。

如果还是不行可以把 docker 的网络模式改成 host 模式。

❓ 点击播放后需要很久才开始播放的问题

这个问题新版本已经解决,如果还存在请反馈。

~目前0.3.x版本还存在这个问题没有完全解决,可以暂时回退到0.2.0版本继续使用。~

❓ 如何配置多个歌曲目录

yaml
services:
-  xiaomusic:
-    image: hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 8090:8090
-    volumes:
-      - /data/music1:/app/music/music1
-      - /data/music2:/app/music/music2
-      - /data/xiaomusic/conf:/app/conf

冒号左边的 /data/music1/data/music2 改成你的目录即可。如果你是 windows 的 docker ,可以改成 D:/music1D:/music2,盘符号开头,用 / 分割。

如果是 docker 部署的,建议不要去修改 web 后台里的音乐路径和配置路径等等所有路径除非你熟悉 docker 的目录映射机制。

❓ 能不能中文名

yaml
services:
-  xiaomusic:
-    image: hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 8090:8090
-    volumes:
-      - /data/music1:/app/music/歌曲目录1
-      - /data/music2:/app/music/歌曲目录2
-      - /data/xiaomusic/conf:/app/conf

❓ 能不能多层目录

可以,每层的每个目录会识别为一个播放列表。

❓ 是否需要手动获取 did

新版本不需要手动获取配置 did,不需要配置环境变量,直接在 web 后台填入小米账号密码保存后会自动获取到 did ,然后勾选对应的设备即可。

❓ 报错 601

报错日志大致如下:

txt
Exception: Error https://api2.mina.mi.com/remote/ubus: {"code":601,"message":"illegal argument exception","data":"IllegalArgumentException: ubus call format illegal!"}

原因是没有配置 did ,或者 did 配置错误。可以到设置页面选择正确的设备类型和 did 然后保存。

❓ 新功能没有生效

在设置页面重新保存一下,或者删除 setting.json 文件,重新在后台设置一次。

❓ 为什么会先说小爱音箱自带的回答,再说下载中或者过一会儿才播放本地歌曲

设计原理就是每秒不停的抓取对话记录,然后再打断小爱音箱自带的处理流程。整个过程下来会有延时,所以打断不会很及时,做不到无缝衔接。

评论

评论 1 - shissx

安装的最新版本,即使没有使用,日志一直在不停的刷新,示例: [10:20:36] [0.1.101] [DEBUG] Polling_event, timestamp: {'eeb70da5-baa9-4b56-b2f3-7ee01276a18a': 1720430457236} [10:20:36] [0.1.101] [DEBUG] Sleep 0.0003166699898429215, timestamp: {'eeb70da5-baa9-4b56-b2f3-7ee01276a18a': 1720430457236} [10:20:37] [0.1.101] [DEBUG] Listening new message, timestamp: {'eeb70da5-baa9-4b56-b2f3-7ee01276a18a

之前的版本没有这个问题,这个是设置错误?还是本来就如此呢?


评论 2 - hanxi

安装的最新版本,即使没有使用,日志一直在不停的刷新,示例: [10:20:36] [0.1.101] [DEBUG] Polling_event, timestamp: {'eeb70da5-baa9-4b56-b2f3-7ee01276a18a': 1720430457236} [10:20:36] [0.1.101] [DEBUG] Sleep 0.0003166699898429215, timestamp: {'eeb70da5-baa9-4b56-b2f3-7ee01276a18a': 1720430457236} [10:20:37] [0.1.101] [DEBUG] Listening new message, timestamp: {'eeb70da5-baa9-4b56-b2f3-7ee01276a18a

之前的版本没有这个问题,这个是设置错误?还是本来就如此呢?

正常现象,现在默认把调试日志打开了,可以在后台设置关闭调试日志的。


评论 3 - Dx0123

大佬,docker安装提示缺少很多module,我一个个在dockerfilie里加上,最后卡在miservice装不上了~


评论 4 - hanxi

@Dx0123 其实不用手动安装依赖的,直接一行应该就行。

RUN pip install -U xiaomusic

评论 5 - Dx0123

@Dx0123 其实不用手动安装依赖的,直接一行应该就行。

RUN pip install -U xiaomusic

我直接用pip安装好之后,执行仍然有缺少的依赖,和docker里缺的一样。截图的module安装了之后还会有其他依赖缺失 image


评论 6 - hanxi

@Dx0123 你的python版本是不是有问题?你参考下项目里的Dockerfile,用同一个From镜像试试。


评论 7 - hanxi

有时候指令已停止,可是一会儿,又自动播放下一首,根本就停不下来。需要重启容器才能正常

点关机按钮也不行吗?


评论 8 - hanxi

有时候指令已停止,可是一会儿,又自动播放下一首,根本就停不下来。需要重启容器才能正常

点关机按钮也不行吗?

是的,说关机,点关机,暂停都不行。只能重启容器。

有没有日志看看?


评论 9 - sqmcool

为什么我的没有显示设备? Snipaste_2024-09-14_15-51-00


评论 10 - hanxi

为什么我的没有显示设备? Snipaste_2024-09-14_15-51-00

应该是登陆失败,可以查看一下日志。


评论 11 - schppd

楼主您好,请问这个我需要怎么处理? 微信截图_20240915225040


评论 12 - hanxi

楼主您好,请问这个我需要怎么处理? 微信截图_20240915225040

删掉重新配置一下试试。


评论 13 - schppd

会不会跟网络不稳定有关系?我都弄了几次还是这样子

------------------ 原始邮件 ------------------ 发件人: "hanxi/xiaomusic" @.>; 发送时间: 2024年9月15日(星期天) 晚上10:57 @.>; @.@.>; 主题: Re: [hanxi/xiaomusic] FAQ问题集合 (Issue #99)

楼主您好,请问这个我需要怎么处理?

删掉重新配置一下试试。

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>


评论 14 - hanxi

会不会跟网络不稳定有关系?我都弄了几次还是这样子 ------------------ 原始邮件 ------------------ 发件人: "hanxi/xiaomusic" @.>; 发送时间: 2024年9月15日(星期天) 晚上10:57 @.>; @.@.>; 主题: Re: [hanxi/xiaomusic] FAQ问题集合 (Issue #99) 楼主您好,请问这个我需要怎么处理? 删掉重新配置一下试试。 — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>

有可能的,用代理试试。


评论 15 - guoxiangke

定制的时候,"全部"和”所有歌曲"的区别,帮助有需要的朋友: 歌单中 "全部" 指的是 所有歌单中歌曲,但不包括“歌单内容”配置(http://127.0.0.1:8090/static/setting.html)中的电台 "type": "radio",的 ”所有歌曲" 指的是下载的歌曲,在download文件夹里


评论 16 - agigogo

image 在docker里可以运行,但是没法播放设置页面中的播放链接,选中设备那里是空的,是不是没成功?怎么调整?


评论 17 - hanxi

image 在docker里可以运行,但是没法播放设置页面中的播放链接,选中设备那里是空的,是不是没成功?怎么调整?

设置页面输入小米的账号密码后,再勾选一个设备。


评论 18 - agigogo

image 在docker里可以运行,但是没法播放设置页面中的播放链接,选中设备那里是空的,是不是没成功?怎么调整?

设置页面输入小米的账号密码后,再勾选一个设备。

设置页面里没有勾选的选项

image 在docker里可以运行,但是没法播放设置页面中的播放链接,选中设备那里是空的,是不是没成功?怎么调整?

设置页面输入小米的账号密码后,再勾选一个设备。

image 设置页面没有可勾选项?


评论 19 - agigogo

image 在docker里可以运行,但是没法播放设置页面中的播放链接,选中设备那里是空的,是不是没成功?怎么调整?

设置页面输入小米的账号密码后,再勾选一个设备。

设置页面里没有勾选的选项

image 在docker里可以运行,但是没法播放设置页面中的播放链接,选中设备那里是空的,是不是没成功?怎么调整?

设置页面输入小米的账号密码后,再勾选一个设备。

image 设置页面没有可勾选项?

image

显示未检测到设备,设备型号是MDZ-25-DA


评论 20 - hanxi

@agigogo 应该是登陆失败了,局域网的手机重新登陆一下小爱音箱app吧。


评论 21 - agigogo

@agigogo 应该是登陆失败了,局域网的手机重新登陆一下小爱音箱app吧。

一直都是用米家APP来控制小爱音箱,那要下个小爱音响APP试一试


评论 22 - agigogo

@agigogo 应该是登陆失败了,局域网的手机重新登陆一下小爱音箱app吧。

搞定了,小爱音箱app重新绑定就可以了。真6~


评论 23 - Tueafternoon

一首歌结束不能自动切到下一首,随机播放模式,日志中显示下一曲定时器不见了....这个是咋回事啊


评论 24 - hanxi

一首歌结束不能自动切到下一首,随机播放模式,日志中显示下一曲定时器不见了....这个是咋回事啊

可能是音乐文件有问题,获取歌曲长度失败,你可以把歌曲文件上传一下给我测试。


评论 25 - hanxi

或者搜下日志里有没有 不会设置下一首歌的定时器 这个


评论 26 - Tueafternoon

或者搜下日志里有没有 不会设置下一首歌的定时器 这个

有这个,应该是我的文件格式问题,晚上我处理一下再试试


评论 27 - zealler9560

Screenshot_2024-10-31-23-28-57-903_com.android.chrome.jpg

istore系统可以拉取创建镜像,但是无法启动,错误提示见图一,求助大佬!路由器信息见图二Screenshot_2024-10-31-23-36-42-846-edit_com.android.chrome.jpg


评论 28 - adidas004

在群晖使用docker安装的,只能通过重装来升级到最新版本吗?有快捷的一键升级吗


评论 29 - hanxi

在群晖使用docker安装的,只能通过重装来升级到最新版本吗?有快捷的一键升级吗

不会命令行,就用这个工具吧。 https://github.com/onlyLTY/dockerCopilot


评论 30 - adidas004

谢谢您的工具,我刚去群晖的docker上有提示直接升级,还是非常的感觉你的回答

------------------ 原始邮件 ------------------ 发件人: @.>; 发送时间: 2024年11月11日(星期一) 下午4:20 收件人: @.>; 抄送: @.>; @.>; 主题: Re: [hanxi/xiaomusic] 💬 FAQ问题集合 (Issue #99)

在群晖使用docker安装的,只能通过重装来升级到最新版本吗?有快捷的一键升级吗

不会命令行,就用这个工具吧。 https://github.com/onlyLTY/dockerCopilot

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>


评论 31 - sinojelly

运行时遇到下面问题,请问要怎么排查?

2024/11/21 19:27:00  xiaomusic.py: error: unrecognized arguments: .venv/bin/python3 xiaomusic.py
-2024/11/21 19:27:00         [--enable_config_example]
-2024/11/21 19:27:00         [--ffmpeg_location FFMPEG_LOCATION]
-2024/11/21 19:27:00         [--config CONFIG]
-2024/11/21 19:27:00         [--verbose]
-2024/11/21 19:27:00         [--cookie COOKIE]
-2024/11/21 19:27:00         [--password PASSWORD]
-2024/11/21 19:27:00         [--account ACCOUNT]
-2024/11/21 19:27:00         [--hardware HARDWARE]
-2024/11/21 19:27:00         [--port PORT]
-2024/11/21 19:27:00         [-h]
-2024/11/21 19:27:00  usage: xiaomusic.py

评论 32 - hanxi

运行时遇到下面问题,请问要怎么排查?

2024/11/21 19:27:00  xiaomusic.py: error: unrecognized arguments: .venv/bin/python3 xiaomusic.py
-2024/11/21 19:27:00         [--enable_config_example]
-2024/11/21 19:27:00         [--ffmpeg_location FFMPEG_LOCATION]
-2024/11/21 19:27:00         [--config CONFIG]
-2024/11/21 19:27:00         [--verbose]
-2024/11/21 19:27:00         [--cookie COOKIE]
-2024/11/21 19:27:00         [--password PASSWORD]
-2024/11/21 19:27:00         [--account ACCOUNT]
-2024/11/21 19:27:00         [--hardware HARDWARE]
-2024/11/21 19:27:00         [--port PORT]
-2024/11/21 19:27:00         [-h]
-2024/11/21 19:27:00  usage: xiaomusic.py

看不出来


评论 33 - sinojelly

请问登录验证失败要怎么定位?小米登录邮箱,还是小米id 都报同样的错。

2024/11/25 0:50:44  [2024-11-25 00:50:44] [0.3.48] [ERROR] xiaomusic.py:259: /root/.mi.token file not exist
-2024/11/25 0:50:44  Exception: Error https://api2.mina.mi.com/admin/v2/device_list?master=0&requestId=app_ios_: Login failed
-2024/11/25 0:50:44      raise Exception(f"Error {url}: {resp}")
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request
-2024/11/25 0:50:44      return await self.account.mi_request(
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request
-2024/11/25 0:50:44      result = await self.mina_request("/admin/v2/device_list?master=" + str(master))
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 54, in device_list
-2024/11/25 0:50:44      hardware_data = await self.mina_service.device_list()
-2024/11/25 0:50:44    File "/app/xiaomusic/xiaomusic.py", line 232, in try_update_device_id
-2024/11/25 0:50:44  Traceback (most recent call last):
-2024/11/25 0:50:44  [2024-11-25 00:50:44] [0.3.48] [ERROR] xiaomusic.py:251: Execption Error https://api2.mina.mi.com/admin/v2/device_list?master=0&requestId=app_ios_xx: Login failed
-2024/11/25 0:50:44  Exception: {'qs': '%3Fsid%3Dmicoapi%26_json%3Dtrue', 'code': 70016, 'description': '登录验证失败', 'securityStatus': 0, '_sign': 'xxx', 'sid': 'micoapi', 'result': 'error', 'captchaUrl': None, 'callback': 'https://api2.mina.mi.com/sts', 'location': '', 'pwd': 0, 'child': 0, 'desc': '登录验证失败'}
-2024/11/25 0:50:44      raise Exception(resp)
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 69, in login

评论 34 - hanxi

请问登录验证失败要怎么定位?小米登录邮箱,还是小米id 都报同样的错。

2024/11/25 0:50:44  [2024-11-25 00:50:44] [0.3.48] [ERROR] xiaomusic.py:259: /root/.mi.token file not exist
-2024/11/25 0:50:44  Exception: Error https://api2.mina.mi.com/admin/v2/device_list?master=0&requestId=app_ios_: Login failed
-2024/11/25 0:50:44      raise Exception(f"Error {url}: {resp}")
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request
-2024/11/25 0:50:44      return await self.account.mi_request(
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request
-2024/11/25 0:50:44      result = await self.mina_request("/admin/v2/device_list?master=" + str(master))
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 54, in device_list
-2024/11/25 0:50:44      hardware_data = await self.mina_service.device_list()
-2024/11/25 0:50:44    File "/app/xiaomusic/xiaomusic.py", line 232, in try_update_device_id
-2024/11/25 0:50:44  Traceback (most recent call last):
-2024/11/25 0:50:44  [2024-11-25 00:50:44] [0.3.48] [ERROR] xiaomusic.py:251: Execption Error https://api2.mina.mi.com/admin/v2/device_list?master=0&requestId=app_ios_xx: Login failed
-2024/11/25 0:50:44  Exception: {'qs': '%3Fsid%3Dmicoapi%26_json%3Dtrue', 'code': 70016, 'description': '登录验证失败', 'securityStatus': 0, '_sign': 'xxx', 'sid': 'micoapi', 'result': 'error', 'captchaUrl': None, 'callback': 'https://api2.mina.mi.com/sts', 'location': '', 'pwd': 0, 'child': 0, 'desc': '登录验证失败'}
-2024/11/25 0:50:44      raise Exception(resp)
-2024/11/25 0:50:44    File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 69, in login

上面给出了4个办法都试过了吗?局域网登陆mi.com了?


评论 35 - wusemao

设置web访问登录时,账号密码设置完之后登不进去了,账号名称用的中文的可以么


评论 36 - hanxi

设置web访问登录时,账号密码设置完之后登不进去了,账号名称用的中文的可以么

不确定是否可以,你可以考虑setting.json里的内容,不行就修改再重启。


评论 37 - quanmao

运行后会在音乐目录下生成一个tmp文件夹,如何指定路径,因为还有其应用访问音乐路径,不希望其识别tmp目录下的音乐。


评论 38 - hanxi

运行后会在音乐目录下生成一个tmp文件夹,如何指定路径,因为还有其应用访问音乐路径,不希望其识别tmp目录下的音乐。

忽略目录(逗号分割) 改成 @eaDir,tmp 即可。


评论 39 - quanmao

运行后会在音乐目录下生成一个tmp文件夹,如何指定路径,因为还有其应用访问音乐路径,不希望其识别tmp目录下的音乐。

忽略目录(逗号分割) 改成 @eaDir,tmp 即可。

抱歉,是我没有说清楚,是运行xiaomusic后会在音乐路径下生成tmp文件夹, 但我同时还在用navidrome,也会访问音乐目录,他会把tmp目录下的歌曲也扫描进去,所以想移动tmp目录。 navidrome没找到在哪里可以设置,忽略这个文件夹


评论 40 - hanxi

运行后会在音乐目录下生成一个tmp文件夹,如何指定路径,因为还有其应用访问音乐路径,不希望其识别tmp目录下的音乐。

忽略目录(逗号分割) 改成 @eaDir,tmp 即可。

抱歉,是我没有说清楚,是运行xiaomusic后会在音乐路径下生成tmp文件夹, 但我同时还在用navidrome,也会访问音乐目录,他会把tmp目录下的歌曲也扫描进去,所以想移动tmp目录。 navidrome没找到在哪里可以设置,忽略这个文件夹

提个新 issue 吧,有空加下配置项。


评论 41 - CallEdison

imageimage 问题一:能进控制面板,进不了设置页面,容器没有log生成,我昨天已经设置好了,现在功能能正常使用,但是进不了设置页面了问题二:昨天能进的时候发现本地下载目录有歌曲,但是设置里面的全部歌曲里面没有,搜索框搜索又能搜的到。


评论 42 - hanxi

image image 问题一:能进控制面板,进不了设置页面,容器没有log生成,我昨天已经设置好了,现在功能能正常使用,但是进不了设置页面了问题二:昨天能进的时候发现本地下载目录有歌曲,但是设置里面的全部歌曲里面没有,搜索框搜索又能搜的到。

问题一:打不开的地址是哪个? 问题二:可以点击刷新列表按钮试试。


评论 43 - huahua-er

是关闭了网络搜索了吗?现在的搜索只有本地数据没有网络歌曲了?


评论 44 - CallEdison

默认主题有歌曲

pure主题没有歌曲

xmusicPlayer也没有歌曲

 

Edison @.***

 

------------------ 原始邮件 ------------------ 发件人: @.>; 发送时间: 2024年11月30日(星期六) 上午6:26 收件人: @.>; 抄送: @.>; @.>; 主题: Re: [hanxi/xiaomusic] 💬 FAQ问题集合 (Issue #99)

是关闭了网络搜索了吗?现在的搜索只有本地数据没有网络歌曲了?

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>


评论 45 - hanxi

是关闭了网络搜索了吗?现在的搜索只有本地数据没有网络歌曲了?

等 yt-dlp 修复。


评论 46 - hanxi

默认主题有歌曲 pure主题没有歌曲 xmusicPlayer也没有歌曲   Edison @.***   ------------------ 原始邮件 ------------------ 发件人: @.>; 发送时间: 2024年11月30日(星期六) 上午6:26 收件人: @.>; 抄送: @.>; @.>; 主题: Re: [hanxi/xiaomusic] 💬 FAQ问题集合 (Issue #99) 是关闭了网络搜索了吗?现在的搜索只有本地数据没有网络歌曲了? — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>

需要刷新缓存


评论 47 - like1020

Screenshot_2024-12-03-06-58-52-853_com yjllq kito 请教一下,本地列表歌单里的歌曲即便设置为全部循环或随机播放,依然是不断地单曲循环,只能自己手动点下一首,请问是什么情况?


评论 48 - tchgtr

请问网络搜索功能修复了吗?感谢!


评论 49 - hanxi

请问网络搜索功能修复了吗?感谢!

修复了。


评论 50 - tchgtr

请问网络搜索功能修复了吗?感谢!

修复了。

我已经更新到最新版0.3.55,音箱是LX04触屏音箱,但是使用指令对小爱说“播放歌曲。。。“还是通过音箱绑定的QQ音乐播放,解绑了QQ音乐账号,就会提示先让我绑定账号(包括播放本地歌曲指令),感觉音箱还没跟docker连接上(已经重启过容器)。但是通过8090端口进入后台,搜索歌曲,播放,调节声音大小,单曲、随机所有的功能都可以通过按键实现,唯独不能通过语音跟小爱对话播放我指定的内容,请问这个是什么问题,感谢!


评论 51 - hanxi

请问网络搜索功能修复了吗?感谢!

修复了。

我已经更新到最新版0.3.55,音箱是LX04触屏音箱,但是使用指令对小爱说“播放歌曲。。。“还是通过音箱绑定的QQ音乐播放,解绑了QQ音乐账号,就会提示先让我绑定账号(包括播放本地歌曲指令),感觉音箱还没跟docker连接上(已经重启过容器)。但是通过8090端口进入后台,搜索歌曲,播放,调节声音大小,单曲、随机所有的功能都可以通过按键实现,唯独不能通过语音跟小爱对话播放我指定的内容,请问这个是什么问题,感谢!

贴下你的 setting.json 文件看看吧,把里面的账号密码删除。


评论 52 - tchgtr

请问网络搜索功能修复了吗?感谢!

修复了。

我已经更新到最新版0.3.55,音箱是LX04触屏音箱,但是使用指令对小爱说“播放歌曲。。。“还是通过音箱绑定的QQ音乐播放,解绑了QQ音乐账号,就会提示先让我绑定账号(包括播放本地歌曲指令),感觉音箱还没跟docker连接上(已经重启过容器)。但是通过8090端口进入后台,搜索歌曲,播放,调节声音大小,单曲、随机所有的功能都可以通过按键实现,唯独不能通过语音跟小爱对话播放我指定的内容,请问这个是什么问题,感谢!

贴下你的 setting.json 文件看看吧,把里面的账号密码删除。

{ "account": "", "password": "", "mi_did": "603807070", "miio_tts_command": "", "cookie": "", "verbose": false, "music_path": "music", "temp_path": "music/tmp", "download_path": "music/download", "conf_path": "conf", "cache_dir": "cache", "hostname": "192.168.31.159", "port": 8090, "public_port": 0, "proxy": "", "search_prefix": "bilisearch:", "ffmpeg_location": "./ffmpeg/bin", "active_cmd": "play,set_play_type_rnd,playlocal,play_music_list,play_music_list_index,stop_after_minute,stop", "exclude_dirs": "@eaDir,tmp", "music_path_depth": 10, "disable_httpauth": true, "httpauth_username": "", "httpauth_password": "", "music_list_url": "", "music_list_json": "", "custom_play_list_json": "", "disable_download": false, "key_word_dict": { "下一首": "play_next", "上一首": "play_prev", "单曲循环": "set_play_type_one", "全部循环": "set_play_type_all", "随机播放": "set_play_type_rnd", "单曲播放": "set_play_type_sin", "顺序播放": "set_play_type_seq", "分钟后关机": "stop_after_minute", "刷新列表": "gen_music_list", "加入收藏": "add_to_favorites", "收藏歌曲": "add_to_favorites", "取消收藏": "del_from_favorites", "播放列表第": "play_music_list_index", "播放本地歌曲": "playlocal", "本地播放歌曲": "playlocal", "播放歌曲": "play", "放歌曲": "play", "关机": "stop", "暂停": "stop", "停止": "stop", "停止播放": "stop", "播放列表": "play_music_list", "播放歌单": "play_music_list", "测试自定义口令": "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": "xiaomusic.log.txt", "fuzzy_match_cutoff": 0.6, "enable_fuzzy_match": true, "stop_tts_msg": "收到,再见", "enable_config_example": false, "keywords_playlocal": "播放本地歌曲,本地播放歌曲", "keywords_play": "播放歌曲,放歌曲", "keywords_stop": "关机,暂停,停止,停止播放", "keywords_playlist": "播放列表,播放歌单", "user_key_word_dict": { "测试自定义口令": "exec#code1("hello")", "测试链接": "exec#httpget("https://github.com/hanxi/xiaomusic\")" }, "enable_force_stop": false, "devices": { "603807070": { "did": "603807070", "device_id": "60b8f875-4101-416a-9278-4d4170929b4d", "hardware": "LX04", "name": "小爱触屏音箱", "play_type": 1, "cur_music": "七里香", "cur_playlist": "临时搜索列表" } }, "group_list": "", "remove_id3tag": false, "convert_to_mp3": false, "delay_sec": 3, "continue_play": false, "pull_ask_sec": 1, "crontab_json": "", "enable_yt_dlp_cookies": false, "get_ask_by_mina": true, "play_type_one_tts_msg": "已经设置为单曲循环", "play_type_all_tts_msg": "已经设置为全部循环", "play_type_rnd_tts_msg": "已经设置为随机播放", "play_type_sin_tts_msg": "已经设置为单曲播放", "play_type_seq_tts_msg": "已经设置为顺序播放", "recently_added_playlist_len": 50 }


评论 53 - hanxi

get_ask_by_mina 【特殊型号获取对话记录】这个需要设置为 false


评论 54 - tchgtr

get_ask_by_mina 【特殊型号获取对话记录】这个需要设置为 false

已经重新设置并重启容器,尝试还是不行,会不会是这个触屏音箱不支持?


评论 55 - tchgtr

get_ask_by_mina 【特殊型号获取对话记录】这个需要设置为 false

已经重新设置并重启容器,尝试还是不行,会不会是这个触屏音箱不支持?

或者是在创建容器的时候,我的小米账号填写了我的手机号码,应该需要填写实际的小米账号?因为手机号码对应的小米账号,和输入对应的密码,都是可以登陆的,但是我不知道有什么区别,就把手机号码输入进去了


评论 56 - tchgtr

get_ask_by_mina 【特殊型号获取对话记录】这个需要设置为 false

已经重新设置并重启容器,尝试还是不行,会不会是这个触屏音箱不支持?

或者是在创建容器的时候,我的小米账号填写了我的手机号码,应该需要填写实际的小米账号?因为手机号码对应的小米账号,和输入对应的密码,都是可以登陆的,但是我不知道有什么区别,就把手机号码输入进去了

我重新创建容器,问题解决了,主要是两个地方改变了,一个是上面的小米账号的问题,另外一个是关于conf和music这两个文件夹,这次我都把他们放在/container/xiaomusic下,即/container/xiaomusic/conf(music),分别对应两个容器和主机app/conf(music),问题就解决了。之前我是分开在两个文件夹的。


评论 57 - tianting123

0 B的APE文件, 建议直接跳过播放


评论 58 - oklrc

新人请教:使用DOCKER镜像 或者 composer安装 如何升级到最新版本呢?是删除镜像再重新拉取吗


评论 59 - hanxi

新人请教:使用DOCKER镜像 或者 composer安装 如何升级到最新版本呢?是删除镜像再重新拉取吗

docker compose pull
-docker compose up -d

评论 60 - zhiquanchi

我在阿里云的服务器上运行的docker,我登录了小米账号,但是 操控面板 里面 不显示我的设备。音箱是pro LX06


链接到 GitHub Issue

- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/issues/index.html b/docs/.vitepress/dist/issues/index.html deleted file mode 100644 index 8f5c2a5..0000000 --- a/docs/.vitepress/dist/issues/index.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - XiaoMusic: 无限听歌,解放小爱音箱 | XiaoMusic - - - - - - - - - - - - - - -
Skip to content

XiaoMusic: 无限听歌,解放小爱音箱

GitHub LicenseDocker Image VersionDocker PullsPyPI - VersionPyPI - DownloadsPython Version from PEP 621 TOMLGitHub ReleaseVisitorsVisitors

使用小爱音箱播放音乐,音乐使用 yt-dlp 下载。

https://github.com/hanxi/xiaomusic

TIP

初次安装遇到问题请查阅 💬 FAQ问题集合 ,一般遇到的问题都已经有解决办法。

👋 最简配置运行

已经支持在 web 页面配置其他参数,docker 启动命令如下:

bash
docker run -p 8090:8090 -v /xiaomusic/music:/app/music -v /xiaomusic/conf:/app/conf hanxi/xiaomusic

🔥 国内:

bash
docker run -p 8090:8090 -v /xiaomusic/music:/app/music -v /xiaomusic/conf:/app/conf m.daocloud.io/docker.io/hanxi/xiaomusic

对应的 docker compose 配置如下:

yaml
services:
-  xiaomusic:
-    image: hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 8090:8090
-    volumes:
-      - /xiaomusic/music:/app/music
-      - /xiaomusic/conf:/app/conf

🔥 国内:

yaml
services:
-  xiaomusic:
-    image: m.daocloud.io/docker.io/hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 8090:8090
-    volumes:
-      - /xiaomusic/music:/app/music
-      - /xiaomusic/conf:/app/conf

其中 conf 目录为配置文件存放目录,music 目录为音乐存放目录,建议分开配置为不同的目录。

NOTE

上面配置的 /xiaomusic/music 和 /xiaomusic/conf 是 docker 主机里的 /xiaomusic 目录下的,可以修改为其他目录。如果报错找不到 /xiaomusic/music 目录,可以先执行 mkdir -p /xiaomusic/{music,conf} 命令新建目录。

docker 和 docker compose 二选一即可,启动成功后,在 web 页面可以配置其他参数,带有 * 号的配置是必须要配置的,其他的用不上时不用修改。初次配置时需要在页面上输入小米账号和密码保存后才能获取到设备列表。

TIP

目前安装步骤已经是最简化了,如果还是嫌安装麻烦,可以微信或者 QQ 约我远程安装,我一般周末和晚上才有时间,收个辛苦费 💰 50 元一次,安装失败不收费。

🔥 修改默认8090端口映射

方法1: 不修改监听端口 8090

【监听端口】保持为默认的 8090 不变,把【外网访问端口】改为 5678 。

yaml
services:
-  xiaomusic:
-    image: hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 5678:8090
-    volumes:
-      - /xiaomusic/music:/app/music
-      - /xiaomusic/conf:/app/conf
-    environment:
-      XIAOMUSIC_PUBLIC_PORT: 5678

XIAOMUSIC_PUBLIC_PORT 对应后台设置里的【外网访问端口】,修改后可以不用重启。

方法2: 修改监听端口 8090 为 5678

如果需要修改 8090 端口为其他端口,比如 5678,需要这样配,3个数字都需要是 5678 。见 https://github.com/hanxi/xiaomusic/issues/19

yaml
services:
-  xiaomusic:
-    image: hanxi/xiaomusic
-    container_name: xiaomusic
-    restart: unless-stopped
-    ports:
-      - 5678:5678
-    volumes:
-      - /xiaomusic/music:/app/music
-      - /xiaomusic/conf:/app/conf
-    environment:
-      XIAOMUSIC_PORT: 5678

如果不是首次修改端口,还需要修改 /xiaomusic/conf/setting.json 文件里的端口(也可以在后台修改监听端口后重启)。

遇到问题可以去 web 设置页面底部点击【下载日志文件】按钮,然后搜索一下日志文件内容确保里面没有账号密码信息后(有就删除这些敏感信息),然后在提 issues 反馈问题时把下载的日志文件带上。

IMPORTANT

XIAOMUSIC_PORT 也可以在后台配置,对应的是监听端口,修改后记得重启。

🤐 支持语音口令

  • 【播放歌曲】,播放本地的歌曲
  • 【播放歌曲+歌名】,比如:播放歌曲周杰伦晴天
  • 【上一首】
  • 【下一首】
  • 【单曲循环】
  • 【全部循环】
  • 【随机播放】
  • 【关机】,【停止播放】,两个效果是一样的。
  • 【刷新列表】,当复制了歌曲进 music 目录后,可以用这个口令刷新歌单。
  • 【播放列表+列表名】,比如:播放列表其他。
  • 【加入收藏】,把当前播放的歌曲加入收藏歌单。
  • 【取消收藏】,把当前播放的歌曲从收藏歌单里移除。
  • 【播放列表收藏】,这个用于播放收藏歌单。
  • 【播放本地歌曲+歌名】,这个口令和播放歌曲的区别是本地找不到也不会去下载。
  • 【播放列表第几个+列表名】,具体见: https://github.com/hanxi/xiaomusic/issues/158
  • 【搜索播放+关键词】,会搜索关键词作为临时搜索列表播放,比如说【搜索播放林俊杰】,会播放所有林俊杰的歌。
  • 【本地搜索播放+关键词】,跟搜索播放的区别是本地找不到也不会去下载。

TIP

隐藏玩法: 对小爱同学说播放歌曲小猪佩奇的故事,会先下载小猪佩奇的故事,然后再播放小猪佩奇的故事。

🛠️ pip 方式安装运行

shell
> pip install -U xiaomusic
-> xiaomusic --help
- __  __  _                   __  __                 _
- \ \/ / (_)   __ _    ___   |  \/  |  _   _   ___  (_)   ___
-  \  /  | |  / _` |  / _ \  | |\/| | | | | | / __| | |  / __|
-  /  \  | | | (_| | | (_) | | |  | | | |_| | \__ \ | | | (__
- /_/\_\ |_|  \__,_|  \___/  |_|  |_|  \__,_| |___/ |_|  \___|
-          XiaoMusic v0.3.37 by: github.com/hanxi
-
-usage: xiaomusic [-h] [--port PORT] [--hardware HARDWARE] [--account ACCOUNT]
-                 [--password PASSWORD] [--cookie COOKIE] [--verbose]
-                 [--config CONFIG] [--ffmpeg_location FFMPEG_LOCATION]
-
-options:
-  -h, --help            show this help message and exit
-  --port PORT           监听端口
-  --hardware HARDWARE   小爱音箱型号
-  --account ACCOUNT     xiaomi account
-  --password PASSWORD   xiaomi password
-  --cookie COOKIE       xiaomi cookie
-  --verbose             show info
-  --config CONFIG       config file path
-  --ffmpeg_location FFMPEG_LOCATION
-                        ffmpeg bin path
-> xiaomusic --config config.json

其中 config.json 文件可以参考 config-example.json 文件配置。见 https://github.com/hanxi/xiaomusic/issues/94

不修改默认端口 8090 的情况下,只需要执行 xiaomusic 即可启动。

🔩 开发环境运行

  • 使用 install_dependencies.sh 下载依赖
  • 使用 pdm 安装环境
  • 默认监听了端口 8090 , 使用其他端口自行修改。
shell
pdm run xiaomusic.py

如果是开发前端界面,可以通过 http://localhost:8090/docs 查看有什么接口。目前的 web 控制台非常简陋,欢迎有兴趣的朋友帮忙实现一个漂亮的前端,需要什么接口可以随时提需求。

🚦 代码提交规范

提交前请执行

pdm lintfmt

用于检查代码和格式化代码。

本地编译 Docker Image

shell
docker build -t xiaomusic .

技术栈

  • 后端代码使用 Python 语言编写。
  • HTTP 服务使用的是 FastAPI 框架,早期版本使用的是 Flask
  • 使用了 Docker ,在 NAS 上安装更方便。
  • 默认的前端主题使用了 jQuery 。

已测试支持的设备

型号名称
L06A小爱音箱
L07ARedmi小爱音箱 Play
S12/S12A/MDZ-25-DA小米AI音箱
LX5A小爱音箱 万能遥控版
LX05小爱音箱Play(2019款)
L15A小米AI音箱(第二代)
L16AXiaomi Sound
L17AXiaomi Sound Pro
LX06小爱音箱Pro
LX01小爱音箱mini
L05B小爱音箱Play
L05C小米小爱音箱Play 增强版
L09A小米音箱Art
LX04 X10A X08A已经支持的触屏版
X08C X08E X8F需要设置【型号兼容模式】选项为 true
M01/XMYX01JY小米小爱音箱HD 需要设置【特殊型号获取对话记录】选项为 true 才能语音播放

型号与产品名称对照可以在这里查询 https://home.miot-spec.com/s/xiaomi.wifispeaker

NOTE

如果你的设备支持播放,请反馈给我添加到支持列表里,谢谢。 目前应该所有设备类型都已经支持播放,有问题随时反馈。 其他触屏版不能播放可以设置【型号兼容模式】选项为 true 试试。见 https://github.com/hanxi/xiaomusic/issues/30

🎵 支持音乐格式

  • mp3
  • flac
  • wav
  • ape
  • ogg
  • m4a

NOTE

本地音乐会搜索目录下上面格式的文件,下载的歌曲是 mp3 格式的。 已知 L05B L05C LX06 L16A 不支持 flac 格式。 如果格式不能播放可以打开【转换为MP3】和【型号兼容模式】选项。具体见 https://github.com/hanxi/xiaomusic/issues/153#issuecomment-2328168689

💡 简易的控制面板

浏览器进入 http://192.168.2.5:8090

  • ip 是 XIAOMUSIC_HOSTNAME 设置的
  • 8090 是默认端口
  • 支持功能
    • 显示正在播放的歌曲
    • 模糊搜索本地歌曲
    • 播放列表
    • 删除歌曲
    • 设置页面
    • 配置网络歌单
    • 日志文件下载

采用新的设置页面之后,没有必须在启动前配置的环境变量了,除非是改默认的 8090 端口才需要配置环境变量。

🌏 网络歌单功能

可以配置一个 json 格式的歌单,支持电台和歌曲,也可以直接用别人分享的链接,同时配备了 m3u 文件格式转换工具,可以很方便的把 m3u 电台文件转换成网络歌单格式的 json 文件,具体用法见 https://github.com/hanxi/xiaomusic/issues/78

NOTE

欢迎有想法的朋友们制作更多的歌单转换工具。

🍺 更多其他可选配置

  • XIAOMUSIC_ACTIVE_CMD 环境变量,对应后台的 【允许唤醒的命令】,用于唤醒口令,配置成'play,random_play',在非播放状态下,只有这两个指令(播放歌曲和随机播放)可以触发,触发后,xiaomusic进入playing状态,其他指令则可以正常触发。具体见 https://github.com/hanxi/xiaomusic/pull/43
  • XIAOMUSIC_EXCLUDE_DIRS 配置歌曲目录里需要忽略的目录,对应后台的 【忽略目录】
  • XIAOMUSIC_MUSIC_PATH_DEPTH 配置歌曲目录搜索深度,对应后台的 【目录深度】,具体见 https://github.com/hanxi/xiaomusic/issues/76
  • XIAOMUSIC_DISABLE_HTTPAUTH 配置成 false 表示开启密码访问web控制台,对应后台的 【关闭密码验证】,具体见 https://github.com/hanxi/xiaomusic/issues/47
  • XIAOMUSIC_HTTPAUTH_USERNAME 配置 web 控制台用户,对应后台的 【控制台账户】
  • XIAOMUSIC_HTTPAUTH_PASSWORD 配置 web 控制台密码,对应后台的 【控制台密码】
  • XIAOMUSIC_CONF_PATH 用来存放配置文件的目录,对应后台的 【配置文件目录】,记得把目录映射到主机,默认为 /app/config ,具体见 https://github.com/hanxi/xiaomusic/issues/74
  • XIAOMUSIC_CACHE_DIR 用来音乐 tag 缓存,默认为 /app/cache,对应后台的 【缓存文件目录】。
  • XIAOMUSIC_DISABLE_DOWNLOAD 设为 true 时关闭下载功能,对应后台的 【关闭下载功能】,见 https://github.com/hanxi/xiaomusic/issues/82
  • XIAOMUSIC_USE_MUSIC_API 设为 true 时使用 player_play_music 接口播放音乐,对应后台的 【型号兼容模式】,用于兼容不能播放的型号,如果发现需要设置这个选项的时候请告知我加一下设备型号,方便以后不用设置。 见 https://github.com/hanxi/xiaomusic/issues/30
  • XIAOMUSIC_KEYWORDS_PLAY 用来播放歌曲的口令前缀,对应后台的 【播放歌曲口令】,默认是 "播放歌曲,放歌曲" ,可以用英文逗号分割配置多个
  • XIAOMUSIC_KEYWORDS_STOP 用来关机的口令,对应后台的 【停止口令】,默认是 "关机,暂停,停止" ,可以用英文逗号分割配置多个。
  • XIAOMUSIC_KEYWORDS_PLAYLOCAL 用来播放本地歌曲的口令前缀,对应后台的 【播放本地歌曲口令】,本地找不到时不会下载歌曲,默认是 "播放本地歌曲,本地播放歌曲" ,可以用英文逗号分割配置多个。
  • XIAOMUSIC_ENABLE_FUZZY_MATCH 设为 true 时开启模糊匹配(默认),设为 false 时关闭模糊匹配,对应后台的 【开启模糊搜索】,支持模糊匹配歌名和歌单名。 具体见 https://github.com/hanxi/xiaomusic/issues/52
  • XIAOMUSIC_FUZZY_MATCH_CUTOFF 设置模糊搜索匹配的最低相似度阈值(默认0.6,可以配0到1直接的小数),越小越模糊,越大越精准,对应后台的 【模糊匹配阈值】。具体见 https://github.com/hanxi/xiaomusic/issues/52
  • XIAOMUSIC_PUBLIC_PORT 用于设置外网端口,对应后台的 【外网访问端口】,当使用反向代理时可以设置为外网端口,XIAOMUSIC_HOSTNAME 设为外网IP或者域名即可。
  • XIAOMUSIC_DOWNLOAD_PATH 变量可以配置下载目录,默认为空,表示使用 music 目录为下载目录,对应后台的 【音乐下载目录】。设置这个目录必须是 music 的子目录,否则刷新列表后会找不到歌曲。具体见 https://github.com/hanxi/xiaomusic/issues/98
  • XIAOMUSIC_PROXY 用于配置国内使用 youtube 源下载歌曲时使用的代理,参数格式参考 yt-dlp 文档说明。 见 https://github.com/hanxi/xiaomusic/issues/2https://github.com/hanxi/xiaomusic/issues/11
  • MIIO_TTS_CMD 用于部分机型(如:L05C)使用 MiIO 支持 tts 能力,默认为空,命令选择见 MiService-fork 文档

⚠️ 安全提醒

IMPORTANT

  1. 如果配置了公网访问 xiaomusic ,请一定要开启密码登陆,并设置复杂的密码。且不要在公共场所的 WiFi 环境下使用,否则可能造成小米账号密码泄露。
  2. 强烈不建议将小爱音箱的小米账号绑定摄像头,代码难免会有 bug ,一旦小米账号密码泄露,可能监控录像也会泄露。

🤔 高级篇

📢 讨论区

❤️ 感谢

👉 其他教程

更多功能见 📝 文档汇总

🚨 免责声明

本项目仅供学习和研究目的,不得用于任何商业活动。用户在使用本项目时应遵守所在地区的法律法规,对于违法使用所导致的后果,本项目及作者不承担任何责任。 本项目可能存在未知的缺陷和风险(包括但不限于设备损坏和账号封禁等),使用者应自行承担使用本项目所产生的所有风险及责任。 作者不保证本项目的准确性、完整性、及时性、可靠性,也不承担任何因使用本项目而产生的任何损失或损害责任。 使用本项目即表示您已阅读并同意本免责声明的全部内容。

Star History

Star History Chart

赞赏

License

MIT License © 2024 涵曦

- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/sitemap.xml b/docs/.vitepress/dist/sitemap.xml deleted file mode 100644 index 2ff70d5..0000000 --- a/docs/.vitepress/dist/sitemap.xml +++ /dev/null @@ -1 +0,0 @@ -https://docs.x.hanxi.cc/https://docs.x.hanxi.cc/issues/101.htmlhttps://docs.x.hanxi.cc/issues/105.htmlhttps://docs.x.hanxi.cc/issues/182.htmlhttps://docs.x.hanxi.cc/issues/19.htmlhttps://docs.x.hanxi.cc/issues/210.htmlhttps://docs.x.hanxi.cc/issues/211.htmlhttps://docs.x.hanxi.cc/issues/212.htmlhttps://docs.x.hanxi.cc/issues/269.htmlhttps://docs.x.hanxi.cc/issues/285.htmlhttps://docs.x.hanxi.cc/issues/294.htmlhttps://docs.x.hanxi.cc/issues/297.htmlhttps://docs.x.hanxi.cc/issues/78.htmlhttps://docs.x.hanxi.cc/issues/88.htmlhttps://docs.x.hanxi.cc/issues/94.htmlhttps://docs.x.hanxi.cc/issues/96.htmlhttps://docs.x.hanxi.cc/issues/99.htmlhttps://docs.x.hanxi.cc/issues/https://docs.x.hanxi.cc/test1/api-examples.htmlhttps://docs.x.hanxi.cc/test1/https://docs.x.hanxi.cc/test1/markdown-examples.html \ No newline at end of file diff --git a/docs/.vitepress/dist/test1/api-examples.html b/docs/.vitepress/dist/test1/api-examples.html deleted file mode 100644 index 7d4c1e8..0000000 --- a/docs/.vitepress/dist/test1/api-examples.html +++ /dev/null @@ -1,3095 +0,0 @@ - - - - - - Runtime API Examples | XiaoMusic - - - - - - - - - - - - - - -
Skip to content

Runtime API Examples

This page demonstrates usage of some of the runtime APIs provided by VitePress.

The main useData() API can be used to access site, theme, and page data for the current page. It works in both .md and .vue files:

md
<script setup>
-import { useData } from 'vitepress'
-
-const { theme, page, frontmatter } = useData()
-</script>
-
-## Results
-
-### Theme Data
-<pre>{{ theme }}</pre>
-
-### Page Data
-<pre>{{ page }}</pre>
-
-### Page Frontmatter
-<pre>{{ frontmatter }}</pre>

Results

Theme Data

{
-  "nav": [
-    {
-      "text": "Home",
-      "link": "/"
-    },
-    {
-      "text": "Examples",
-      "link": "/markdown-examples"
-    }
-  ],
-  "socialLinks": [
-    {
-      "icon": "github",
-      "link": "https://github.com/hanxi/xiaomusic"
-    }
-  ],
-  "sidebar": {
-    "/issues/": [
-      {
-        "items": [
-          {
-            "text": "群晖docker安装 xiaomusic",
-            "link": "/issues/101.html"
-          },
-          {
-            "text": "【插件】自定义口令功能",
-            "link": "/issues/105.html"
-          },
-          {
-            "text": "定时任务配置格式",
-            "link": "/issues/182.html"
-          },
-          {
-            "text": "如何修改默认的8090端口",
-            "link": "/issues/19.html"
-          },
-          {
-            "text": "yt-dlp cookies 文件上传功能",
-            "link": "/issues/210.html"
-          },
-          {
-            "text": "📝 文档汇总",
-            "link": "/issues/211.html"
-          },
-          {
-            "text": "如何批量下载歌曲",
-            "link": "/issues/212.html"
-          },
-          {
-            "text": "如何添加 网易云音乐playlist",
-            "link": "/issues/269.html"
-          },
-          {
-            "text": "相关工具推荐",
-            "link": "/issues/285.html"
-          },
-          {
-            "text": "关于M01型号的注意事项",
-            "link": "/issues/294.html"
-          },
-          {
-            "text": "xiaomusic极空间安装教程(2024-12-4更新)",
-            "link": "/issues/297.html"
-          },
-          {
-            "text": "已支持配置自定义网络歌单,在这里分享你的歌单",
-            "link": "/issues/78.html"
-          },
-          {
-            "text": "如何添加m3u格式文件的电台",
-            "link": "/issues/88.html"
-          },
-          {
-            "text": "采用config.json配置方式",
-            "link": "/issues/94.html"
-          },
-          {
-            "text": "ios系统上的捷径配置",
-            "link": "/issues/96.html"
-          },
-          {
-            "text": "💬 FAQ问题集合",
-            "link": "/issues/99.html"
-          },
-          {
-            "text": "XiaoMusic: 无限听歌,解放小爱音箱",
-            "link": "/issues/index.html"
-          }
-        ]
-      }
-    ],
-    "/node_modules/": [
-      {
-        "items": [
-          {
-            "text": "@algolia",
-            "items": [
-              {
-                "text": "autocomplete-core",
-                "items": [
-                  {
-                    "text": "@algolia/autocomplete-core",
-                    "link": "/node_modules/@algolia/autocomplete-core/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "autocomplete-plugin-algolia-insights",
-                "items": [
-                  {
-                    "text": "@algolia/autocomplete-plugin-algolia-insights",
-                    "link": "/node_modules/@algolia/autocomplete-plugin-algolia-insights/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "autocomplete-preset-algolia",
-                "items": [
-                  {
-                    "text": "@algolia/autocomplete-preset-algolia",
-                    "link": "/node_modules/@algolia/autocomplete-preset-algolia/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "client-abtesting",
-                "items": [
-                  {
-                    "text": "or",
-                    "link": "/node_modules/@algolia/client-abtesting/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "client-analytics",
-                "items": [
-                  {
-                    "text": "or",
-                    "link": "/node_modules/@algolia/client-analytics/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "client-insights",
-                "items": [
-                  {
-                    "text": "or",
-                    "link": "/node_modules/@algolia/client-insights/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "client-personalization",
-                "items": [
-                  {
-                    "text": "or",
-                    "link": "/node_modules/@algolia/client-personalization/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "client-query-suggestions",
-                "items": [
-                  {
-                    "text": "or",
-                    "link": "/node_modules/@algolia/client-query-suggestions/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "client-search",
-                "items": [
-                  {
-                    "text": "or",
-                    "link": "/node_modules/@algolia/client-search/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "ingestion",
-                "items": [
-                  {
-                    "text": "or",
-                    "link": "/node_modules/@algolia/ingestion/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "monitoring",
-                "items": [
-                  {
-                    "text": "or",
-                    "link": "/node_modules/@algolia/monitoring/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "recommend",
-                "items": [
-                  {
-                    "text": "or",
-                    "link": "/node_modules/@algolia/recommend/README.html"
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "@babel",
-            "items": [
-              {
-                "text": "helper-string-parser",
-                "items": [
-                  {
-                    "text": "@babel/helper-string-parser",
-                    "link": "/node_modules/@babel/helper-string-parser/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "helper-validator-identifier",
-                "items": [
-                  {
-                    "text": "@babel/helper-validator-identifier",
-                    "link": "/node_modules/@babel/helper-validator-identifier/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "parser",
-                "items": [
-                  {
-                    "text": "Changelog",
-                    "link": "/node_modules/@babel/parser/CHANGELOG.html"
-                  },
-                  {
-                    "text": "@babel/parser",
-                    "link": "/node_modules/@babel/parser/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "types",
-                "items": [
-                  {
-                    "text": "@babel/types",
-                    "link": "/node_modules/@babel/types/README.html"
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "@docsearch",
-            "items": [
-              {
-                "text": "css",
-                "items": [
-                  {
-                    "text": "@docsearch/css",
-                    "link": "/node_modules/@docsearch/css/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "js",
-                "items": [
-                  {
-                    "text": "@docsearch/js",
-                    "link": "/node_modules/@docsearch/js/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "react",
-                "items": [
-                  {
-                    "text": "@docsearch/react",
-                    "link": "/node_modules/@docsearch/react/README.html"
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "@esbuild",
-            "items": [
-              {
-                "text": "linux-x64",
-                "items": [
-                  {
-                    "text": "esbuild",
-                    "link": "/node_modules/@esbuild/linux-x64/README.html"
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "@eslint",
-            "items": [
-              {
-                "text": "eslintrc",
-                "items": [
-                  {
-                    "text": "ESLintRC Library",
-                    "link": "/node_modules/@eslint/eslintrc/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "js",
-                "items": [
-                  {
-                    "text": "ESLint JavaScript Plugin",
-                    "link": "/node_modules/@eslint/js/README.html"
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "@eslint-community",
-            "items": [
-              {
-                "text": "eslint-utils",
-                "items": [
-                  {
-                    "text": "@eslint-community/eslint-utils",
-                    "link": "/node_modules/@eslint-community/eslint-utils/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "regexpp",
-                "items": [
-                  {
-                    "text": "@eslint-community/regexpp",
-                    "link": "/node_modules/@eslint-community/regexpp/README.html"
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "@humanwhocodes",
-            "items": [
-              {
-                "text": "config-array",
-                "items": [
-                  {
-                    "text": "Config Array",
-                    "link": "/node_modules/@humanwhocodes/config-array/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "module-importer",
-                "items": [
-                  {
-                    "text": "Changelog",
-                    "link": "/node_modules/@humanwhocodes/module-importer/CHANGELOG.html"
-                  },
-                  {
-                    "text": "ModuleImporter",
-                    "link": "/node_modules/@humanwhocodes/module-importer/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "object-schema",
-                "items": [
-                  {
-                    "text": "Changelog",
-                    "link": "/node_modules/@humanwhocodes/object-schema/CHANGELOG.html"
-                  },
-                  {
-                    "text": "JavaScript ObjectSchema Package",
-                    "link": "/node_modules/@humanwhocodes/object-schema/README.html"
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "@iconify",
-            "items": [
-              {
-                "text": "types",
-                "items": [
-                  {
-                    "text": "Iconify Types",
-                    "link": "/node_modules/@iconify/types/README.html"
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "@iconify-json",
-            "items": [
-              {
-                "text": "simple-icons",
-                "items": [
-                  {
-                    "text": "Simple Icons",
-                    "link": "/node_modules/@iconify-json/simple-icons/README.html"
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "@jridgewell",
-            "items": [
-              {
-                "text": "sourcemap-codec",
-                "items": [
-                  {
-                    "text": "@jridgewell/sourcemap-codec",
-                    "link": "/node_modules/@jridgewell/sourcemap-codec/README.html"
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "@nodelib",
-            "items": [
-              {
-                "text": "fs.scandir",
-                "items": [
-                  {
-                    "text": "@nodelib/fs.scandir",
-                    "link": "/node_modules/@nodelib/fs.scandir/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "fs.stat",
-                "items": [
-                  {
-                    "text": "@nodelib/fs.stat",
-                    "link": "/node_modules/@nodelib/fs.stat/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "fs.walk",
-                "items": [
-                  {
-                    "text": "@nodelib/fs.walk",
-                    "link": "/node_modules/@nodelib/fs.walk/README.html"
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "@rollup",
-            "items": [
-              {
-                "text": "rollup-linux-x64-gnu",
-                "items": [
-                  {
-                    "text": "`@rollup/rollup-linux-x64-gnu`",
-                    "link": "/node_modules/@rollup/rollup-linux-x64-gnu/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "rollup-linux-x64-musl",
-                "items": [
-                  {
-                    "text": "`@rollup/rollup-linux-x64-musl`",
-                    "link": "/node_modules/@rollup/rollup-linux-x64-musl/README.html"
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "@shikijs",
-            "items": [
-              {
-                "text": "core",
-                "items": [
-                  {
-                    "text": "@shikijs/core",
-                    "link": "/node_modules/@shikijs/core/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "engine-javascript",
-                "items": [
-                  {
-                    "text": "@shikijs/engine-javascript",
-                    "link": "/node_modules/@shikijs/engine-javascript/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "engine-oniguruma",
-                "items": [
-                  {
-                    "text": "@shikijs/engine-oniguruma",
-                    "link": "/node_modules/@shikijs/engine-oniguruma/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "transformers",
-                "items": [
-                  {
-                    "text": "@shikijs/transformers",
-                    "link": "/node_modules/@shikijs/transformers/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "types",
-                "items": [
-                  {
-                    "text": "@shikijs/types",
-                    "link": "/node_modules/@shikijs/types/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "vscode-textmate",
-                "items": [
-                  {
-                    "text": "LICENSE",
-                    "link": "/node_modules/@shikijs/vscode-textmate/LICENSE.html"
-                  },
-                  {
-                    "text": "README",
-                    "link": "/node_modules/@shikijs/vscode-textmate/README.html"
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "@types",
-            "items": [
-              {
-                "text": "estree",
-                "items": [
-                  {
-                    "text": "Installation",
-                    "link": "/node_modules/@types/estree/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "hast",
-                "items": [
-                  {
-                    "text": "Installation",
-                    "link": "/node_modules/@types/hast/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "linkify-it",
-                "items": [
-                  {
-                    "text": "Installation",
-                    "link": "/node_modules/@types/linkify-it/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "markdown-it",
-                "items": [
-                  {
-                    "text": "Installation",
-                    "link": "/node_modules/@types/markdown-it/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "mdast",
-                "items": [
-                  {
-                    "text": "Installation",
-                    "link": "/node_modules/@types/mdast/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "mdurl",
-                "items": [
-                  {
-                    "text": "Installation",
-                    "link": "/node_modules/@types/mdurl/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "unist",
-                "items": [
-                  {
-                    "text": "Installation",
-                    "link": "/node_modules/@types/unist/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "web-bluetooth",
-                "items": [
-                  {
-                    "text": "Installation",
-                    "link": "/node_modules/@types/web-bluetooth/README.html"
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "@typescript-eslint",
-            "items": [
-              {
-                "text": "parser",
-                "items": [
-                  {
-                    "text": "`@typescript-eslint/parser`",
-                    "link": "/node_modules/@typescript-eslint/parser/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "scope-manager",
-                "items": [
-                  {
-                    "text": "`@typescript-eslint/scope-manager`",
-                    "link": "/node_modules/@typescript-eslint/scope-manager/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "types",
-                "items": [
-                  {
-                    "text": "`@typescript-eslint/types`",
-                    "link": "/node_modules/@typescript-eslint/types/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "typescript-estree",
-                "items": [
-                  {
-                    "text": "`@typescript-eslint/typescript-estree`",
-                    "link": "/node_modules/@typescript-eslint/typescript-estree/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "visitor-keys",
-                "items": [
-                  {
-                    "text": "`@typescript-eslint/visitor-keys`",
-                    "link": "/node_modules/@typescript-eslint/visitor-keys/README.html"
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "@ungap",
-            "items": [
-              {
-                "text": "structured-clone",
-                "items": [
-                  {
-                    "text": "structuredClone polyfill",
-                    "link": "/node_modules/@ungap/structured-clone/README.html"
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "@vitejs",
-            "items": [
-              {
-                "text": "plugin-vue",
-                "items": [
-                  {
-                    "text": "@vitejs/plugin-vue [![npm](https://img.shields.io/npm/v/@vitejs/plugin-vue.svg)](https://npmjs.com/package/@vitejs/plugin-vue)",
-                    "link": "/node_modules/@vitejs/plugin-vue/README.html"
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "@vue",
-            "items": [
-              {
-                "text": "compiler-core",
-                "items": [
-                  {
-                    "text": "@vue/compiler-core",
-                    "link": "/node_modules/@vue/compiler-core/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "compiler-dom",
-                "items": [
-                  {
-                    "text": "@vue/compiler-dom",
-                    "link": "/node_modules/@vue/compiler-dom/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "compiler-sfc",
-                "items": [
-                  {
-                    "text": "@vue/compiler-sfc",
-                    "link": "/node_modules/@vue/compiler-sfc/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "compiler-ssr",
-                "items": [
-                  {
-                    "text": "@vue/compiler-ssr",
-                    "link": "/node_modules/@vue/compiler-ssr/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "devtools-api",
-                "items": [
-                  {
-                    "text": "@vue/devtools-api",
-                    "link": "/node_modules/@vue/devtools-api/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "devtools-kit",
-                "items": [
-                  {
-                    "text": "@vue/devtools-kit",
-                    "link": "/node_modules/@vue/devtools-kit/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "devtools-shared",
-                "items": [
-                  {
-                    "text": "@vue/devtools-shared",
-                    "link": "/node_modules/@vue/devtools-shared/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "reactivity",
-                "items": [
-                  {
-                    "text": "@vue/reactivity",
-                    "link": "/node_modules/@vue/reactivity/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "runtime-core",
-                "items": [
-                  {
-                    "text": "@vue/runtime-core",
-                    "link": "/node_modules/@vue/runtime-core/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "runtime-dom",
-                "items": [
-                  {
-                    "text": "@vue/runtime-dom",
-                    "link": "/node_modules/@vue/runtime-dom/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "server-renderer",
-                "items": [
-                  {
-                    "text": "@vue/server-renderer",
-                    "link": "/node_modules/@vue/server-renderer/README.html"
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "shared",
-                "items": [
-                  {
-                    "text": "@vue/shared",
-                    "link": "/node_modules/@vue/shared/README.html"
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "@vueuse",
-            "items": [
-              {
-                "text": "core",
-                "items": [
-                  {
-                    "text": "README",
-                    "link": "/node_modules/@vueuse/core/README.html"
-                  },
-                  {
-                    "text": "node_modules",
-                    "items": [
-                      {
-                        "text": "vue-demi",
-                        "items": [
-                          {
-                            "text": "or",
-                            "link": "/node_modules/@vueuse/core/node_modules/vue-demi/README.html"
-                          }
-                        ],
-                        "collapsed": true
-                      }
-                    ],
-                    "collapsed": true
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "integrations",
-                "items": [
-                  {
-                    "text": "@vueuse/integrations",
-                    "link": "/node_modules/@vueuse/integrations/README.html"
-                  },
-                  {
-                    "text": "node_modules",
-                    "items": [
-                      {
-                        "text": "vue-demi",
-                        "items": [
-                          {
-                            "text": "or",
-                            "link": "/node_modules/@vueuse/integrations/node_modules/vue-demi/README.html"
-                          }
-                        ],
-                        "collapsed": true
-                      }
-                    ],
-                    "collapsed": true
-                  }
-                ],
-                "collapsed": true
-              },
-              {
-                "text": "shared",
-                "items": [
-                  {
-                    "text": "node_modules",
-                    "items": [
-                      {
-                        "text": "vue-demi",
-                        "items": [
-                          {
-                            "text": "or",
-                            "link": "/node_modules/@vueuse/shared/node_modules/vue-demi/README.html"
-                          }
-                        ],
-                        "collapsed": true
-                      }
-                    ],
-                    "collapsed": true
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "acorn",
-            "items": [
-              {
-                "text": "CHANGELOG",
-                "link": "/node_modules/acorn/CHANGELOG.html"
-              },
-              {
-                "text": "Acorn",
-                "link": "/node_modules/acorn/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "acorn-jsx",
-            "items": [
-              {
-                "text": "Acorn-JSX",
-                "link": "/node_modules/acorn-jsx/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "ajv",
-            "items": [
-              {
-                "text": "Ajv: Another JSON Schema Validator",
-                "link": "/node_modules/ajv/README.html"
-              },
-              {
-                "text": "lib",
-                "items": [
-                  {
-                    "text": "dotjs",
-                    "items": [
-                      {
-                        "text": "README",
-                        "link": "/node_modules/ajv/lib/dotjs/README.html"
-                      }
-                    ],
-                    "collapsed": true
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "algoliasearch",
-            "items": [
-              {
-                "text": "or",
-                "link": "/node_modules/algoliasearch/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "ansi-regex",
-            "items": [
-              {
-                "text": "ansi-regex",
-                "link": "/node_modules/ansi-regex/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "ansi-styles",
-            "items": [
-              {
-                "text": "ansi-styles [![Build Status](https://travis-ci.org/chalk/ansi-styles.svg?branch=master)](https://travis-ci.org/chalk/ansi-styles)",
-                "link": "/node_modules/ansi-styles/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "argparse",
-            "items": [
-              {
-                "text": "Changelog",
-                "link": "/node_modules/argparse/CHANGELOG.html"
-              },
-              {
-                "text": "README",
-                "link": "/node_modules/argparse/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "array-union",
-            "items": [
-              {
-                "text": "array-union [![Build Status](https://travis-ci.org/sindresorhus/array-union.svg?branch=master)](https://travis-ci.org/sindresorhus/array-union)",
-                "link": "/node_modules/array-union/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "asynckit",
-            "items": [
-              {
-                "text": "asynckit [![NPM Module](https://img.shields.io/npm/v/asynckit.svg?style=flat)](https://www.npmjs.com/package/asynckit)",
-                "link": "/node_modules/asynckit/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "axios",
-            "items": [
-              {
-                "text": "Changelog",
-                "link": "/node_modules/axios/CHANGELOG.html"
-              },
-              {
-                "text": "Migration Guide",
-                "link": "/node_modules/axios/MIGRATION_GUIDE.html"
-              },
-              {
-                "text": "README",
-                "link": "/node_modules/axios/README.html"
-              },
-              {
-                "text": "Reporting a Vulnerability",
-                "link": "/node_modules/axios/SECURITY.html"
-              },
-              {
-                "text": "lib",
-                "items": [
-                  {
-                    "text": "adapters",
-                    "items": [
-                      {
-                        "text": "axios // adapters",
-                        "link": "/node_modules/axios/lib/adapters/README.html"
-                      }
-                    ],
-                    "collapsed": true
-                  },
-                  {
-                    "text": "core",
-                    "items": [
-                      {
-                        "text": "axios // core",
-                        "link": "/node_modules/axios/lib/core/README.html"
-                      }
-                    ],
-                    "collapsed": true
-                  },
-                  {
-                    "text": "env",
-                    "items": [
-                      {
-                        "text": "axios // env",
-                        "link": "/node_modules/axios/lib/env/README.html"
-                      }
-                    ],
-                    "collapsed": true
-                  },
-                  {
-                    "text": "helpers",
-                    "items": [
-                      {
-                        "text": "axios // helpers",
-                        "link": "/node_modules/axios/lib/helpers/README.html"
-                      }
-                    ],
-                    "collapsed": true
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "balanced-match",
-            "items": [
-              {
-                "text": "LICENSE",
-                "link": "/node_modules/balanced-match/LICENSE.html"
-              },
-              {
-                "text": "balanced-match",
-                "link": "/node_modules/balanced-match/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "birpc",
-            "items": [
-              {
-                "text": "birpc",
-                "link": "/node_modules/birpc/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "brace-expansion",
-            "items": [
-              {
-                "text": "brace-expansion",
-                "link": "/node_modules/brace-expansion/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "braces",
-            "items": [
-              {
-                "text": "braces [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=W8YFZ425KND68) [![NPM version](https://img.shields.io/npm/v/braces.svg?style=flat)](https://www.npmjs.com/package/braces) [![NPM monthly downloads](https://img.shields.io/npm/dm/braces.svg?style=flat)](https://npmjs.org/package/braces) [![NPM total downloads](https://img.shields.io/npm/dt/braces.svg?style=flat)](https://npmjs.org/package/braces) [![Linux Build Status](https://img.shields.io/travis/micromatch/braces.svg?style=flat&label=Travis)](https://travis-ci.org/micromatch/braces)",
-                "link": "/node_modules/braces/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "callsites",
-            "items": [
-              {
-                "text": "callsites [![Build Status](https://travis-ci.org/sindresorhus/callsites.svg?branch=master)](https://travis-ci.org/sindresorhus/callsites)",
-                "link": "/node_modules/callsites/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "ccount",
-            "items": [
-              {
-                "text": "ccount",
-                "link": "/node_modules/ccount/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "chalk",
-            "items": [
-              {
-                "text": "readme",
-                "link": "/node_modules/chalk/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "character-entities-html4",
-            "items": [
-              {
-                "text": "character-entities-html4",
-                "link": "/node_modules/character-entities-html4/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "character-entities-legacy",
-            "items": [
-              {
-                "text": "character-entities-legacy",
-                "link": "/node_modules/character-entities-legacy/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "color-convert",
-            "items": [
-              {
-                "text": "1.0.0 - 2016-01-07",
-                "link": "/node_modules/color-convert/CHANGELOG.html"
-              },
-              {
-                "text": "color-convert",
-                "link": "/node_modules/color-convert/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "color-name",
-            "items": [
-              {
-                "text": "README",
-                "link": "/node_modules/color-name/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "combined-stream",
-            "items": [
-              {
-                "text": "combined-stream",
-                "link": "/node_modules/combined-stream/Readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "comma-separated-tokens",
-            "items": [
-              {
-                "text": "comma-separated-tokens",
-                "link": "/node_modules/comma-separated-tokens/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "copy-anything",
-            "items": [
-              {
-                "text": "Copy anything 🎭",
-                "link": "/node_modules/copy-anything/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "cross-spawn",
-            "items": [
-              {
-                "text": "cross-spawn",
-                "link": "/node_modules/cross-spawn/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "csstype",
-            "items": [
-              {
-                "text": "CSSType",
-                "link": "/node_modules/csstype/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "debug",
-            "items": [
-              {
-                "text": "debug",
-                "link": "/node_modules/debug/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "delayed-stream",
-            "items": [
-              {
-                "text": "delayed-stream",
-                "link": "/node_modules/delayed-stream/Readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "dequal",
-            "items": [
-              {
-                "text": "dequal [![CI](https://github.com/lukeed/dequal/workflows/CI/badge.svg)](https://github.com/lukeed/dequal/actions)",
-                "link": "/node_modules/dequal/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "devlop",
-            "items": [
-              {
-                "text": "devlop",
-                "link": "/node_modules/devlop/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "dir-glob",
-            "items": [
-              {
-                "text": "dir-glob [![Build Status](https://travis-ci.org/kevva/dir-glob.svg?branch=master)](https://travis-ci.org/kevva/dir-glob)",
-                "link": "/node_modules/dir-glob/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "doctrine",
-            "items": [
-              {
-                "text": "CHANGELOG",
-                "link": "/node_modules/doctrine/CHANGELOG.html"
-              },
-              {
-                "text": "Doctrine",
-                "link": "/node_modules/doctrine/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "emoji-regex-xs",
-            "items": [
-              {
-                "text": "emoji-regex-xs",
-                "link": "/node_modules/emoji-regex-xs/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "entities",
-            "items": [
-              {
-                "text": "entities [![NPM version](https://img.shields.io/npm/v/entities.svg)](https://npmjs.org/package/entities) [![Downloads](https://img.shields.io/npm/dm/entities.svg)](https://npmjs.org/package/entities) [![Node.js CI](https://github.com/fb55/entities/actions/workflows/nodejs-test.yml/badge.svg)](https://github.com/fb55/entities/actions/workflows/nodejs-test.yml)",
-                "link": "/node_modules/entities/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "esbuild",
-            "items": [
-              {
-                "text": "LICENSE",
-                "link": "/node_modules/esbuild/LICENSE.html"
-              },
-              {
-                "text": "esbuild",
-                "link": "/node_modules/esbuild/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "escape-string-regexp",
-            "items": [
-              {
-                "text": "escape-string-regexp [![Build Status](https://travis-ci.org/sindresorhus/escape-string-regexp.svg?branch=master)](https://travis-ci.org/sindresorhus/escape-string-regexp)",
-                "link": "/node_modules/escape-string-regexp/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "eslint",
-            "items": [
-              {
-                "text": "ESLint",
-                "link": "/node_modules/eslint/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "eslint-scope",
-            "items": [
-              {
-                "text": "ESLint Scope",
-                "link": "/node_modules/eslint-scope/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "eslint-visitor-keys",
-            "items": [
-              {
-                "text": "eslint-visitor-keys",
-                "link": "/node_modules/eslint-visitor-keys/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "espree",
-            "items": [
-              {
-                "text": "Espree",
-                "link": "/node_modules/espree/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "esprima",
-            "items": [
-              {
-                "text": "README",
-                "link": "/node_modules/esprima/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "esquery",
-            "items": [
-              {
-                "text": "README",
-                "link": "/node_modules/esquery/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "esrecurse",
-            "items": [
-              {
-                "text": "README",
-                "link": "/node_modules/esrecurse/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "estraverse",
-            "items": [
-              {
-                "text": "README",
-                "link": "/node_modules/estraverse/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "estree-walker",
-            "items": [
-              {
-                "text": "changelog",
-                "link": "/node_modules/estree-walker/CHANGELOG.html"
-              },
-              {
-                "text": "estree-walker",
-                "link": "/node_modules/estree-walker/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "esutils",
-            "items": [
-              {
-                "text": "README",
-                "link": "/node_modules/esutils/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "fast-deep-equal",
-            "items": [
-              {
-                "text": "fast-deep-equal",
-                "link": "/node_modules/fast-deep-equal/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "fast-glob",
-            "items": [
-              {
-                "text": "fast-glob",
-                "link": "/node_modules/fast-glob/README.html"
-              },
-              {
-                "text": "node_modules",
-                "items": [
-                  {
-                    "text": "glob-parent",
-                    "items": [
-                      {
-                        "text": "CHANGELOG",
-                        "link": "/node_modules/fast-glob/node_modules/glob-parent/CHANGELOG.html"
-                      },
-                      {
-                        "text": "glob-parent",
-                        "link": "/node_modules/fast-glob/node_modules/glob-parent/README.html"
-                      }
-                    ],
-                    "collapsed": true
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "fast-json-stable-stringify",
-            "items": [
-              {
-                "text": "fast-json-stable-stringify",
-                "link": "/node_modules/fast-json-stable-stringify/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "fast-levenshtein",
-            "items": [
-              {
-                "text": "LICENSE",
-                "link": "/node_modules/fast-levenshtein/LICENSE.html"
-              },
-              {
-                "text": "fast-levenshtein - Levenshtein algorithm in Javascript",
-                "link": "/node_modules/fast-levenshtein/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "fastq",
-            "items": [
-              {
-                "text": "fastq",
-                "link": "/node_modules/fastq/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "file-entry-cache",
-            "items": [
-              {
-                "text": "file-entry-cache",
-                "link": "/node_modules/file-entry-cache/README.html"
-              },
-              {
-                "text": "file-entry-cache - Changelog",
-                "link": "/node_modules/file-entry-cache/changelog.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "fill-range",
-            "items": [
-              {
-                "text": "fill-range [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=W8YFZ425KND68) [![NPM version](https://img.shields.io/npm/v/fill-range.svg?style=flat)](https://www.npmjs.com/package/fill-range) [![NPM monthly downloads](https://img.shields.io/npm/dm/fill-range.svg?style=flat)](https://npmjs.org/package/fill-range) [![NPM total downloads](https://img.shields.io/npm/dt/fill-range.svg?style=flat)](https://npmjs.org/package/fill-range) [![Linux Build Status](https://img.shields.io/travis/jonschlinkert/fill-range.svg?style=flat&label=Travis)](https://travis-ci.org/jonschlinkert/fill-range)",
-                "link": "/node_modules/fill-range/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "find-up",
-            "items": [
-              {
-                "text": "find-up [![Build Status](https://travis-ci.com/sindresorhus/find-up.svg?branch=master)](https://travis-ci.com/github/sindresorhus/find-up)",
-                "link": "/node_modules/find-up/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "flat-cache",
-            "items": [
-              {
-                "text": "flat-cache",
-                "link": "/node_modules/flat-cache/README.html"
-              },
-              {
-                "text": "flat-cache - Changelog",
-                "link": "/node_modules/flat-cache/changelog.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "flatted",
-            "items": [
-              {
-                "text": "flatted",
-                "link": "/node_modules/flatted/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "focus-trap",
-            "items": [
-              {
-                "text": "Changelog",
-                "link": "/node_modules/focus-trap/CHANGELOG.html"
-              },
-              {
-                "text": "focus-trap [![CI](https://github.com/focus-trap/focus-trap/workflows/CI/badge.svg?branch=master&event=push)](https://github.com/focus-trap/focus-trap/actions?query=workflow:CI+branch:master) [![license](https://badgen.now.sh/badge/license/MIT)](./LICENSE)",
-                "link": "/node_modules/focus-trap/README.html"
-              },
-              {
-                "text": "Security Policy",
-                "link": "/node_modules/focus-trap/SECURITY.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "follow-redirects",
-            "items": [
-              {
-                "text": "README",
-                "link": "/node_modules/follow-redirects/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "form-data",
-            "items": [
-              {
-                "text": "Form-Data [![NPM Module](https://img.shields.io/npm/v/form-data.svg)](https://www.npmjs.com/package/form-data) [![Join the chat at https://gitter.im/form-data/form-data](http://form-data.github.io/images/gitterbadge.svg)](https://gitter.im/form-data/form-data)",
-                "link": "/node_modules/form-data/Readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "front-matter",
-            "items": [
-              {
-                "text": "front-matter",
-                "link": "/node_modules/front-matter/README.html"
-              },
-              {
-                "text": "node_modules",
-                "items": [
-                  {
-                    "text": "argparse",
-                    "items": [
-                      {
-                        "text": "CHANGELOG",
-                        "link": "/node_modules/front-matter/node_modules/argparse/CHANGELOG.html"
-                      },
-                      {
-                        "text": "README",
-                        "link": "/node_modules/front-matter/node_modules/argparse/README.html"
-                      }
-                    ],
-                    "collapsed": true
-                  },
-                  {
-                    "text": "js-yaml",
-                    "items": [
-                      {
-                        "text": "Changelog",
-                        "link": "/node_modules/front-matter/node_modules/js-yaml/CHANGELOG.html"
-                      },
-                      {
-                        "text": "README",
-                        "link": "/node_modules/front-matter/node_modules/js-yaml/README.html"
-                      }
-                    ],
-                    "collapsed": true
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "fs.realpath",
-            "items": [
-              {
-                "text": "fs.realpath",
-                "link": "/node_modules/fs.realpath/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "glob",
-            "items": [
-              {
-                "text": "Glob",
-                "link": "/node_modules/glob/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "glob-parent",
-            "items": [
-              {
-                "text": "glob-parent",
-                "link": "/node_modules/glob-parent/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "globals",
-            "items": [
-              {
-                "text": "globals",
-                "link": "/node_modules/globals/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "globby",
-            "items": [
-              {
-                "text": "globby",
-                "link": "/node_modules/globby/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "graphemer",
-            "items": [
-              {
-                "text": "Changelog",
-                "link": "/node_modules/graphemer/CHANGELOG.html"
-              },
-              {
-                "text": "Graphemer: Unicode Character Splitter 🪓",
-                "link": "/node_modules/graphemer/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "has-flag",
-            "items": [
-              {
-                "text": "has-flag [![Build Status](https://travis-ci.org/sindresorhus/has-flag.svg?branch=master)](https://travis-ci.org/sindresorhus/has-flag)",
-                "link": "/node_modules/has-flag/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "hast-util-to-html",
-            "items": [
-              {
-                "text": "hast-util-to-html",
-                "link": "/node_modules/hast-util-to-html/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "hast-util-whitespace",
-            "items": [
-              {
-                "text": "hast-util-whitespace",
-                "link": "/node_modules/hast-util-whitespace/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "hookable",
-            "items": [
-              {
-                "text": "LICENSE",
-                "link": "/node_modules/hookable/LICENSE.html"
-              },
-              {
-                "text": "Hookable",
-                "link": "/node_modules/hookable/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "html-void-elements",
-            "items": [
-              {
-                "text": "html-void-elements",
-                "link": "/node_modules/html-void-elements/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "ignore",
-            "items": [
-              {
-                "text": "ignore",
-                "link": "/node_modules/ignore/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "import-fresh",
-            "items": [
-              {
-                "text": "import-fresh",
-                "link": "/node_modules/import-fresh/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "imurmurhash",
-            "items": [
-              {
-                "text": "README",
-                "link": "/node_modules/imurmurhash/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "inflight",
-            "items": [
-              {
-                "text": "inflight",
-                "link": "/node_modules/inflight/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "inherits",
-            "items": [
-              {
-                "text": "README",
-                "link": "/node_modules/inherits/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "is-extglob",
-            "items": [
-              {
-                "text": "is-extglob [![NPM version](https://img.shields.io/npm/v/is-extglob.svg?style=flat)](https://www.npmjs.com/package/is-extglob) [![NPM downloads](https://img.shields.io/npm/dm/is-extglob.svg?style=flat)](https://npmjs.org/package/is-extglob) [![Build Status](https://img.shields.io/travis/jonschlinkert/is-extglob.svg?style=flat)](https://travis-ci.org/jonschlinkert/is-extglob)",
-                "link": "/node_modules/is-extglob/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "is-glob",
-            "items": [
-              {
-                "text": "is-glob [![NPM version](https://img.shields.io/npm/v/is-glob.svg?style=flat)](https://www.npmjs.com/package/is-glob) [![NPM monthly downloads](https://img.shields.io/npm/dm/is-glob.svg?style=flat)](https://npmjs.org/package/is-glob) [![NPM total downloads](https://img.shields.io/npm/dt/is-glob.svg?style=flat)](https://npmjs.org/package/is-glob) [![Build Status](https://img.shields.io/github/workflow/status/micromatch/is-glob/dev)](https://github.com/micromatch/is-glob/actions)",
-                "link": "/node_modules/is-glob/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "is-number",
-            "items": [
-              {
-                "text": "is-number [![NPM version](https://img.shields.io/npm/v/is-number.svg?style=flat)](https://www.npmjs.com/package/is-number) [![NPM monthly downloads](https://img.shields.io/npm/dm/is-number.svg?style=flat)](https://npmjs.org/package/is-number) [![NPM total downloads](https://img.shields.io/npm/dt/is-number.svg?style=flat)](https://npmjs.org/package/is-number) [![Linux Build Status](https://img.shields.io/travis/jonschlinkert/is-number.svg?style=flat&label=Travis)](https://travis-ci.org/jonschlinkert/is-number)",
-                "link": "/node_modules/is-number/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "is-path-inside",
-            "items": [
-              {
-                "text": "is-path-inside",
-                "link": "/node_modules/is-path-inside/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "is-what",
-            "items": [
-              {
-                "text": "is What? 🙉",
-                "link": "/node_modules/is-what/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "isexe",
-            "items": [
-              {
-                "text": "isexe",
-                "link": "/node_modules/isexe/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "js-yaml",
-            "items": [
-              {
-                "text": "Changelog",
-                "link": "/node_modules/js-yaml/CHANGELOG.html"
-              },
-              {
-                "text": "README",
-                "link": "/node_modules/js-yaml/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "json-buffer",
-            "items": [
-              {
-                "text": "json-buffer",
-                "link": "/node_modules/json-buffer/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "json-schema-traverse",
-            "items": [
-              {
-                "text": "json-schema-traverse",
-                "link": "/node_modules/json-schema-traverse/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "keyv",
-            "items": [
-              {
-                "text": "How to Contribute",
-                "link": "/node_modules/keyv/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "levn",
-            "items": [
-              {
-                "text": "levn [![Build Status](https://travis-ci.org/gkz/levn.png)](https://travis-ci.org/gkz/levn) <a name=\"levn\" />",
-                "link": "/node_modules/levn/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "locate-path",
-            "items": [
-              {
-                "text": "locate-path [![Build Status](https://travis-ci.com/sindresorhus/locate-path.svg?branch=master)](https://travis-ci.com/github/sindresorhus/locate-path)",
-                "link": "/node_modules/locate-path/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "lodash.merge",
-            "items": [
-              {
-                "text": "lodash.merge v4.6.2",
-                "link": "/node_modules/lodash.merge/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "magic-string",
-            "items": [
-              {
-                "text": "magic-string",
-                "link": "/node_modules/magic-string/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "mark.js",
-            "items": [
-              {
-                "text": "Contributing to mark.js",
-                "link": "/node_modules/mark.js/CONTRIBUTING.html"
-              },
-              {
-                "text": "ISSUE_TEMPLATE",
-                "link": "/node_modules/mark.js/ISSUE_TEMPLATE.html"
-              },
-              {
-                "text": "mark.js",
-                "link": "/node_modules/mark.js/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "mdast-util-to-hast",
-            "items": [
-              {
-                "text": "mdast-util-to-hast",
-                "link": "/node_modules/mdast-util-to-hast/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "merge2",
-            "items": [
-              {
-                "text": "merge2",
-                "link": "/node_modules/merge2/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "micromark-util-character",
-            "items": [
-              {
-                "text": "micromark-util-character",
-                "link": "/node_modules/micromark-util-character/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "micromark-util-encode",
-            "items": [
-              {
-                "text": "micromark-util-encode",
-                "link": "/node_modules/micromark-util-encode/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "micromark-util-sanitize-uri",
-            "items": [
-              {
-                "text": "micromark-util-sanitize-uri",
-                "link": "/node_modules/micromark-util-sanitize-uri/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "micromark-util-symbol",
-            "items": [
-              {
-                "text": "micromark-util-symbol",
-                "link": "/node_modules/micromark-util-symbol/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "micromark-util-types",
-            "items": [
-              {
-                "text": "micromark-util-types",
-                "link": "/node_modules/micromark-util-types/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "micromatch",
-            "items": [
-              {
-                "text": "micromatch [![NPM version](https://img.shields.io/npm/v/micromatch.svg?style=flat)](https://www.npmjs.com/package/micromatch) [![NPM monthly downloads](https://img.shields.io/npm/dm/micromatch.svg?style=flat)](https://npmjs.org/package/micromatch) [![NPM total downloads](https://img.shields.io/npm/dt/micromatch.svg?style=flat)](https://npmjs.org/package/micromatch)  [![Tests](https://github.com/micromatch/micromatch/actions/workflows/test.yml/badge.svg)](https://github.com/micromatch/micromatch/actions/workflows/test.yml)",
-                "link": "/node_modules/micromatch/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "mime-db",
-            "items": [
-              {
-                "text": "HISTORY",
-                "link": "/node_modules/mime-db/HISTORY.html"
-              },
-              {
-                "text": "mime-db",
-                "link": "/node_modules/mime-db/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "mime-types",
-            "items": [
-              {
-                "text": "HISTORY",
-                "link": "/node_modules/mime-types/HISTORY.html"
-              },
-              {
-                "text": "mime-types",
-                "link": "/node_modules/mime-types/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "minimatch",
-            "items": [
-              {
-                "text": "minimatch",
-                "link": "/node_modules/minimatch/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "minisearch",
-            "items": [
-              {
-                "text": "Changelog",
-                "link": "/node_modules/minisearch/CHANGELOG.html"
-              },
-              {
-                "text": "MiniSearch",
-                "link": "/node_modules/minisearch/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "mitt",
-            "items": [
-              {
-                "text": "Mitt",
-                "link": "/node_modules/mitt/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "ms",
-            "items": [
-              {
-                "text": "license",
-                "link": "/node_modules/ms/license.html"
-              },
-              {
-                "text": "ms",
-                "link": "/node_modules/ms/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "nanoid",
-            "items": [
-              {
-                "text": "Nano ID",
-                "link": "/node_modules/nanoid/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "natural-compare",
-            "items": [
-              {
-                "text": "README",
-                "link": "/node_modules/natural-compare/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "once",
-            "items": [
-              {
-                "text": "once",
-                "link": "/node_modules/once/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "oniguruma-to-es",
-            "items": [
-              {
-                "text": "Oniguruma-To-ES",
-                "link": "/node_modules/oniguruma-to-es/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "optionator",
-            "items": [
-              {
-                "text": "0.9.0",
-                "link": "/node_modules/optionator/CHANGELOG.html"
-              },
-              {
-                "text": "Optionator",
-                "link": "/node_modules/optionator/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "p-limit",
-            "items": [
-              {
-                "text": "p-limit",
-                "link": "/node_modules/p-limit/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "p-locate",
-            "items": [
-              {
-                "text": "p-locate [![Build Status](https://travis-ci.com/sindresorhus/p-locate.svg?branch=master)](https://travis-ci.com/github/sindresorhus/p-locate)",
-                "link": "/node_modules/p-locate/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "parent-module",
-            "items": [
-              {
-                "text": "parent-module [![Build Status](https://travis-ci.org/sindresorhus/parent-module.svg?branch=master)](https://travis-ci.org/sindresorhus/parent-module)",
-                "link": "/node_modules/parent-module/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "path-exists",
-            "items": [
-              {
-                "text": "path-exists [![Build Status](https://travis-ci.org/sindresorhus/path-exists.svg?branch=master)](https://travis-ci.org/sindresorhus/path-exists)",
-                "link": "/node_modules/path-exists/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "path-is-absolute",
-            "items": [
-              {
-                "text": "path-is-absolute [![Build Status](https://travis-ci.org/sindresorhus/path-is-absolute.svg?branch=master)](https://travis-ci.org/sindresorhus/path-is-absolute)",
-                "link": "/node_modules/path-is-absolute/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "path-key",
-            "items": [
-              {
-                "text": "path-key [![Build Status](https://travis-ci.org/sindresorhus/path-key.svg?branch=master)](https://travis-ci.org/sindresorhus/path-key)",
-                "link": "/node_modules/path-key/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "path-type",
-            "items": [
-              {
-                "text": "path-type [![Build Status](https://travis-ci.org/sindresorhus/path-type.svg?branch=master)](https://travis-ci.org/sindresorhus/path-type)",
-                "link": "/node_modules/path-type/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "perfect-debounce",
-            "items": [
-              {
-                "text": "perfect-debounce",
-                "link": "/node_modules/perfect-debounce/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "picocolors",
-            "items": [
-              {
-                "text": "picocolors",
-                "link": "/node_modules/picocolors/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "picomatch",
-            "items": [
-              {
-                "text": "Release history",
-                "link": "/node_modules/picomatch/CHANGELOG.html"
-              },
-              {
-                "text": ".makeRe star",
-                "link": "/node_modules/picomatch/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "postcss",
-            "items": [
-              {
-                "text": "PostCSS",
-                "link": "/node_modules/postcss/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "preact",
-            "items": [
-              {
-                "text": "README",
-                "link": "/node_modules/preact/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "prelude-ls",
-            "items": [
-              {
-                "text": "1.2.1",
-                "link": "/node_modules/prelude-ls/CHANGELOG.html"
-              },
-              {
-                "text": "prelude.ls [![Build Status](https://travis-ci.org/gkz/prelude-ls.png?branch=master)](https://travis-ci.org/gkz/prelude-ls)",
-                "link": "/node_modules/prelude-ls/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "property-information",
-            "items": [
-              {
-                "text": "property-information",
-                "link": "/node_modules/property-information/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "proxy-from-env",
-            "items": [
-              {
-                "text": "proxy-from-env",
-                "link": "/node_modules/proxy-from-env/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "punycode",
-            "items": [
-              {
-                "text": "Punycode.js [![punycode on npm](https://img.shields.io/npm/v/punycode)](https://www.npmjs.com/package/punycode) [![](https://data.jsdelivr.com/v1/package/npm/punycode/badge)](https://www.jsdelivr.com/package/npm/punycode)",
-                "link": "/node_modules/punycode/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "queue-microtask",
-            "items": [
-              {
-                "text": "queue-microtask [![ci][ci-image]][ci-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url]",
-                "link": "/node_modules/queue-microtask/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "regex",
-            "items": [
-              {
-                "text": "README",
-                "link": "/node_modules/regex/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "regex-recursion",
-            "items": [
-              {
-                "text": "regex-recursion",
-                "link": "/node_modules/regex-recursion/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "regex-utilities",
-            "items": [
-              {
-                "text": "regex-utilities",
-                "link": "/node_modules/regex-utilities/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "resolve-from",
-            "items": [
-              {
-                "text": "resolve-from [![Build Status](https://travis-ci.org/sindresorhus/resolve-from.svg?branch=master)](https://travis-ci.org/sindresorhus/resolve-from)",
-                "link": "/node_modules/resolve-from/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "reusify",
-            "items": [
-              {
-                "text": "reusify",
-                "link": "/node_modules/reusify/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "rfdc",
-            "items": [
-              {
-                "text": "rfdc",
-                "link": "/node_modules/rfdc/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "rimraf",
-            "items": [
-              {
-                "text": "v3.0",
-                "link": "/node_modules/rimraf/CHANGELOG.html"
-              },
-              {
-                "text": "README",
-                "link": "/node_modules/rimraf/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "rollup",
-            "items": [
-              {
-                "text": "Rollup core license",
-                "link": "/node_modules/rollup/LICENSE.html"
-              },
-              {
-                "text": "compile to a <script> containing a self-executing function",
-                "link": "/node_modules/rollup/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "run-parallel",
-            "items": [
-              {
-                "text": "run-parallel [![travis][travis-image]][travis-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url]",
-                "link": "/node_modules/run-parallel/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "search-insights",
-            "items": [
-              {
-                "text": "LICENSE",
-                "link": "/node_modules/search-insights/LICENSE.html"
-              },
-              {
-                "text": "Search Insights",
-                "link": "/node_modules/search-insights/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "semver",
-            "items": [
-              {
-                "text": "README",
-                "link": "/node_modules/semver/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "shebang-command",
-            "items": [
-              {
-                "text": "shebang-command [![Build Status](https://travis-ci.org/kevva/shebang-command.svg?branch=master)](https://travis-ci.org/kevva/shebang-command)",
-                "link": "/node_modules/shebang-command/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "shebang-regex",
-            "items": [
-              {
-                "text": "shebang-regex [![Build Status](https://travis-ci.org/sindresorhus/shebang-regex.svg?branch=master)](https://travis-ci.org/sindresorhus/shebang-regex)",
-                "link": "/node_modules/shebang-regex/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "shiki",
-            "items": [
-              {
-                "text": "Shiki 式",
-                "link": "/node_modules/shiki/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "slash",
-            "items": [
-              {
-                "text": "slash [![Build Status](https://travis-ci.org/sindresorhus/slash.svg?branch=master)](https://travis-ci.org/sindresorhus/slash)",
-                "link": "/node_modules/slash/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "source-map-js",
-            "items": [
-              {
-                "text": "Source Map JS",
-                "link": "/node_modules/source-map-js/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "space-separated-tokens",
-            "items": [
-              {
-                "text": "space-separated-tokens",
-                "link": "/node_modules/space-separated-tokens/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "speakingurl",
-            "items": [
-              {
-                "text": "CHANGELOG",
-                "link": "/node_modules/speakingurl/CHANGELOG.html"
-              },
-              {
-                "text": "Add to Gemfile",
-                "link": "/node_modules/speakingurl/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "sprintf-js",
-            "items": [
-              {
-                "text": "sprintf.js",
-                "link": "/node_modules/sprintf-js/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "stringify-entities",
-            "items": [
-              {
-                "text": "stringify-entities",
-                "link": "/node_modules/stringify-entities/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "strip-ansi",
-            "items": [
-              {
-                "text": "strip-ansi [![Build Status](https://travis-ci.org/chalk/strip-ansi.svg?branch=master)](https://travis-ci.org/chalk/strip-ansi)",
-                "link": "/node_modules/strip-ansi/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "strip-json-comments",
-            "items": [
-              {
-                "text": "strip-json-comments [![Build Status](https://travis-ci.com/sindresorhus/strip-json-comments.svg?branch=master)](https://travis-ci.com/github/sindresorhus/strip-json-comments)",
-                "link": "/node_modules/strip-json-comments/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "superjson",
-            "items": [
-              {
-                "text": "README",
-                "link": "/node_modules/superjson/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "supports-color",
-            "items": [
-              {
-                "text": "supports-color [![Build Status](https://travis-ci.org/chalk/supports-color.svg?branch=master)](https://travis-ci.org/chalk/supports-color)",
-                "link": "/node_modules/supports-color/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "tabbable",
-            "items": [
-              {
-                "text": "Changelog",
-                "link": "/node_modules/tabbable/CHANGELOG.html"
-              },
-              {
-                "text": "tabbable [![CI](https://github.com/focus-trap/tabbable/workflows/CI/badge.svg?branch=master&event=push)](https://github.com/focus-trap/tabbable/actions?query=workflow:CI+branch:master) [![Codecov](https://img.shields.io/codecov/c/github/focus-trap/tabbable)](https://codecov.io/gh/focus-trap/tabbable) [![license](https://badgen.now.sh/badge/license/MIT)](./LICENSE)",
-                "link": "/node_modules/tabbable/README.html"
-              },
-              {
-                "text": "Security Policy",
-                "link": "/node_modules/tabbable/SECURITY.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "to-regex-range",
-            "items": [
-              {
-                "text": "to-regex-range [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=W8YFZ425KND68) [![NPM version](https://img.shields.io/npm/v/to-regex-range.svg?style=flat)](https://www.npmjs.com/package/to-regex-range) [![NPM monthly downloads](https://img.shields.io/npm/dm/to-regex-range.svg?style=flat)](https://npmjs.org/package/to-regex-range) [![NPM total downloads](https://img.shields.io/npm/dt/to-regex-range.svg?style=flat)](https://npmjs.org/package/to-regex-range) [![Linux Build Status](https://img.shields.io/travis/micromatch/to-regex-range.svg?style=flat&label=Travis)](https://travis-ci.org/micromatch/to-regex-range)",
-                "link": "/node_modules/to-regex-range/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "trim-lines",
-            "items": [
-              {
-                "text": "trim-lines",
-                "link": "/node_modules/trim-lines/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "tslib",
-            "items": [
-              {
-                "text": "tslib",
-                "link": "/node_modules/tslib/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "tsutils",
-            "items": [
-              {
-                "text": "3.21.0",
-                "link": "/node_modules/tsutils/CHANGELOG.html"
-              },
-              {
-                "text": "Utility functions for working with typescript's AST",
-                "link": "/node_modules/tsutils/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "type-check",
-            "items": [
-              {
-                "text": "type-check [![Build Status](https://travis-ci.org/gkz/type-check.png?branch=master)](https://travis-ci.org/gkz/type-check)",
-                "link": "/node_modules/type-check/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "type-fest",
-            "items": [
-              {
-                "text": "readme",
-                "link": "/node_modules/type-fest/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "typescript",
-            "items": [
-              {
-                "text": "TypeScript",
-                "link": "/node_modules/typescript/README.html"
-              },
-              {
-                "text": "SECURITY",
-                "link": "/node_modules/typescript/SECURITY.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "unist-util-is",
-            "items": [
-              {
-                "text": "unist-util-is",
-                "link": "/node_modules/unist-util-is/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "unist-util-position",
-            "items": [
-              {
-                "text": "unist-util-position",
-                "link": "/node_modules/unist-util-position/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "unist-util-stringify-position",
-            "items": [
-              {
-                "text": "unist-util-stringify-position",
-                "link": "/node_modules/unist-util-stringify-position/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "unist-util-visit",
-            "items": [
-              {
-                "text": "unist-util-visit",
-                "link": "/node_modules/unist-util-visit/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "unist-util-visit-parents",
-            "items": [
-              {
-                "text": "unist-util-visit-parents",
-                "link": "/node_modules/unist-util-visit-parents/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "uri-js",
-            "items": [
-              {
-                "text": "URI.js",
-                "link": "/node_modules/uri-js/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "vfile",
-            "items": [
-              {
-                "text": "readme",
-                "link": "/node_modules/vfile/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "vfile-message",
-            "items": [
-              {
-                "text": "vfile-message",
-                "link": "/node_modules/vfile-message/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "vite",
-            "items": [
-              {
-                "text": "Vite core license",
-                "link": "/node_modules/vite/LICENSE.html"
-              },
-              {
-                "text": "vite ⚡",
-                "link": "/node_modules/vite/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "vite-plugin-vitepress-auto-sidebar",
-            "items": [
-              {
-                "text": "vite-plugin-vitepress-auto-sidebar",
-                "link": "/node_modules/vite-plugin-vitepress-auto-sidebar/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "vitepress",
-            "items": [
-              {
-                "text": "VitePress 📝💨",
-                "link": "/node_modules/vitepress/README.html"
-              },
-              {
-                "text": "https://vitepress.dev/reference/default-theme-home-page",
-                "items": [
-                  {
-                    "text": "Runtime API Examples",
-                    "link": "/node_modules/vitepress/template/api-examples.html"
-                  },
-                  {
-                    "text": "https://vitepress.dev/reference/default-theme-home-page",
-                    "link": "/node_modules/vitepress/template/index.html"
-                  },
-                  {
-                    "text": "Markdown Extension Examples",
-                    "link": "/node_modules/vitepress/template/markdown-examples.html"
-                  }
-                ],
-                "collapsed": true
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "vue",
-            "items": [
-              {
-                "text": "vue",
-                "link": "/node_modules/vue/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "which",
-            "items": [
-              {
-                "text": "Changes",
-                "link": "/node_modules/which/CHANGELOG.html"
-              },
-              {
-                "text": "which",
-                "link": "/node_modules/which/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "word-wrap",
-            "items": [
-              {
-                "text": "word-wrap [![NPM version](https://img.shields.io/npm/v/word-wrap.svg?style=flat)](https://www.npmjs.com/package/word-wrap) [![NPM monthly downloads](https://img.shields.io/npm/dm/word-wrap.svg?style=flat)](https://npmjs.org/package/word-wrap) [![NPM total downloads](https://img.shields.io/npm/dt/word-wrap.svg?style=flat)](https://npmjs.org/package/word-wrap) [![Linux Build Status](https://img.shields.io/travis/jonschlinkert/word-wrap.svg?style=flat&label=Travis)](https://travis-ci.org/jonschlinkert/word-wrap)",
-                "link": "/node_modules/word-wrap/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "wrappy",
-            "items": [
-              {
-                "text": "wrappy",
-                "link": "/node_modules/wrappy/README.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "yocto-queue",
-            "items": [
-              {
-                "text": "yocto-queue [![](https://badgen.net/bundlephobia/minzip/yocto-queue)](https://bundlephobia.com/result?p=yocto-queue)",
-                "link": "/node_modules/yocto-queue/readme.html"
-              }
-            ],
-            "collapsed": true
-          },
-          {
-            "text": "zwitch",
-            "items": [
-              {
-                "text": "zwitch",
-                "link": "/node_modules/zwitch/readme.html"
-              }
-            ],
-            "collapsed": true
-          }
-        ]
-      }
-    ],
-    "/test1/": [
-      {
-        "items": [
-          {
-            "text": "Runtime API Examples",
-            "link": "/test1/api-examples.html"
-          },
-          {
-            "text": "note",
-            "link": "/test1/index.html"
-          },
-          {
-            "text": "Markdown Extension Examples",
-            "link": "/test1/markdown-examples.html"
-          }
-        ]
-      }
-    ]
-  }
-}

Page Data

{
-  "title": "Runtime API Examples",
-  "description": "",
-  "frontmatter": {
-    "outline": "deep"
-  },
-  "headers": [],
-  "relativePath": "test1/api-examples.md",
-  "filePath": "test1/api-examples.md",
-  "lastUpdated": null
-}

Page Frontmatter

{
-  "outline": "deep"
-}

More

Check out the documentation for the full list of runtime APIs.

- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/test1/index.html b/docs/.vitepress/dist/test1/index.html deleted file mode 100644 index a2991b0..0000000 --- a/docs/.vitepress/dist/test1/index.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - note | XiaoMusic - - - - - - - - - - - - - - -
Skip to content

note

note文件夹中创建/删除md文件,侧边栏数据会被自动更新。

Create / delete md file in the note folder, and the sidebar data will be updated automatically.

- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/test1/markdown-examples.html b/docs/.vitepress/dist/test1/markdown-examples.html deleted file mode 100644 index dcf8b1c..0000000 --- a/docs/.vitepress/dist/test1/markdown-examples.html +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - Markdown Extension Examples | XiaoMusic - - - - - - - - - - - - - - -
Skip to content

Markdown Extension Examples

This page demonstrates some of the built-in markdown extensions provided by VitePress.

Syntax Highlighting

VitePress provides Syntax Highlighting powered by Shiki, with additional features like line-highlighting:

Input

md
```js{4}
-export default {
-  data () {
-    return {
-      msg: 'Highlighted!'
-    }
-  }
-}
-```

Output

js
export default {
-  data () {
-    return {
-      msg: 'Highlighted!'
-    }
-  }
-}

Custom Containers

Input

md
::: info
-This is an info box.
-:::
-
-::: tip
-This is a tip.
-:::
-
-::: warning
-This is a warning.
-:::
-
-::: danger
-This is a dangerous warning.
-:::
-
-::: details
-This is a details block.
-:::

Output

INFO

This is an info box.

TIP

This is a tip.

WARNING

This is a warning.

DANGER

This is a dangerous warning.

Details

This is a details block.

More

Check out the documentation for the full list of markdown extensions.

- - - - \ No newline at end of file diff --git a/docs/.vitepress/dist/vp-icons.css b/docs/.vitepress/dist/vp-icons.css deleted file mode 100644 index ddc5bd8..0000000 --- a/docs/.vitepress/dist/vp-icons.css +++ /dev/null @@ -1 +0,0 @@ -.vpi-social-github{--icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='black' d='M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E")} \ No newline at end of file diff --git a/docs/.vitepress/vitepress-plugin-github-issues.mts b/docs/.vitepress/vitepress-plugin-github-issues.mts index fb8ad22..1a8743a 100644 --- a/docs/.vitepress/vitepress-plugin-github-issues.mts +++ b/docs/.vitepress/vitepress-plugin-github-issues.mts @@ -46,7 +46,7 @@ async function fetchAllIssues(repo: string, token: string): Promise { break; // 退出尝试循环 } catch (error) { if (error.response && error.response.status === 503) { - console.error('服务不可用,正在重试...'); + console.error(`服务不可用, 正在重试...`); attempt++; const waitTime = Math.pow(2, attempt) * 1000; // 指数等待时间 await new Promise(resolve => setTimeout(resolve, waitTime)); diff --git a/docs/index.md b/docs/index.md index 4a8100e..0a17c80 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,7 +4,7 @@ layout: home hero: name: "XiaoMusic" - text: "无限听歌,解放小爱音箱" + text: "无限听歌
解放小爱音箱" tagline: 使用小爱音箱播放音乐,音乐使用 yt-dlp 下载 actions: - theme: brand @@ -13,6 +13,9 @@ hero: - theme: alt text: 文档汇总 link: /issues/211 + - theme: alt + text: GitHub + link: https://github.com/hanxi/xiaomusic features: - title: MIT 开源 diff --git a/docs/issues/99.md b/docs/issues/99.md index 9b98b41..cf715bc 100644 --- a/docs/issues/99.md +++ b/docs/issues/99.md @@ -959,11 +959,5 @@ docker compose pull docker compose up -d ``` ---- - -### 评论 60 - zhiquanchi - -我在阿里云的服务器上运行的docker,我登录了小米账号,但是 操控面板 里面 不显示我的设备。音箱是pro LX06 - --- [链接到 GitHub Issue](https://github.com/hanxi/xiaomusic/issues/99)