mirror of
https://github.com/MeoProject/lx-music-api-server.git
synced 2025-05-23 19:17:41 +08:00
feat: 更新太大了不想总结自己去看提交记录吧(已知把配置文件换成了yaml
This commit is contained in:
parent
45e2e7147d
commit
667d420499
4
.gitignore
vendored
4
.gitignore
vendored
@ -22,6 +22,7 @@ test.*
|
||||
*/test.*
|
||||
logs
|
||||
config.json
|
||||
config.yml
|
||||
/config/config.json
|
||||
/config/data.db
|
||||
*.log
|
||||
@ -41,3 +42,6 @@ config.json
|
||||
|
||||
# temp script
|
||||
lx-music-source-example.js
|
||||
|
||||
# dumprecord
|
||||
dumprecord_*.txt
|
2
build.py
2
build.py
@ -30,7 +30,7 @@ def get_changelog():
|
||||
noticeMsg = []
|
||||
unknownMsg = []
|
||||
for msg in res:
|
||||
if (re.match('[a-f0-9]*.(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert|notice)\(?.*?\)?\:', msg[1:-1])):
|
||||
if (re.match('[a-f0-9]*.(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert|notice).*?\(?.*?\)?\:', msg[1:-1])):
|
||||
msg = msg[1:-1]
|
||||
if msg[8:].startswith('notice'):
|
||||
noticeMsg.append(msg)
|
||||
|
334
common/config.py
334
common/config.py
@ -14,8 +14,10 @@ import traceback
|
||||
import sys
|
||||
import sqlite3
|
||||
import shutil
|
||||
import ruamel.yaml as yaml
|
||||
from . import variable
|
||||
from .log import log
|
||||
from . import default_config
|
||||
import threading
|
||||
|
||||
logger = log('config_manager')
|
||||
@ -45,302 +47,17 @@ class ConfigReadException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
default = {
|
||||
"common": {
|
||||
"host": "0.0.0.0",
|
||||
"_host-desc": "服务器启动时所使用的HOST地址",
|
||||
"ports": [ 9763 ],
|
||||
"_ports-desc": "服务器启动时所使用的端口",
|
||||
"ssl_info": {
|
||||
"desc": "服务器https配置,is_https是这个服务器是否是https服务器,如果你使用了反向代理来转发这个服务器,如果它使用了https,也请将它设置为true",
|
||||
"is_https": False,
|
||||
"enable": False,
|
||||
"ssl_ports": [ 443 ],
|
||||
"path": {
|
||||
"desc": "ssl证书的文件地址",
|
||||
"cert": "/path/to/your/cer",
|
||||
"privkey": "/path/to/your/private/key",
|
||||
},
|
||||
},
|
||||
"reverse_proxy": {
|
||||
"desc": "针对类似于nginx一类的反代的配置",
|
||||
"allow_proxy": True,
|
||||
"_allow_proxy-desc": "是否允许反代",
|
||||
"proxy_whitelist_remote": [
|
||||
"反代时允许的ip来源列表,通常为127.0.0.1",
|
||||
"127.0.0.1"
|
||||
],
|
||||
"real_ip_header": 'X-Real-IP',
|
||||
"_real_ip_header-desc": "反代来源ip的来源头,不懂请保持默认",
|
||||
},
|
||||
"debug_mode": False,
|
||||
"_debug_mode-desc": "是否开启调试模式",
|
||||
"log_length_limit": 500,
|
||||
"_log_length_limit-desc": "单条日志长度限制",
|
||||
"fakeip": "1.0.1.114",
|
||||
"_fakeip-desc": "服务器在海外时的IP伪装值",
|
||||
"proxy": {
|
||||
"enable": False,
|
||||
"http_value": "http://127.0.0.1:7890",
|
||||
"https_value": "http://127.0.0.1:7890",
|
||||
},
|
||||
"_proxy-desc": "代理配置,HTTP与HTTPS协议需分开配置",
|
||||
"log_file": True,
|
||||
"_log_file-desc": "是否开启日志文件",
|
||||
'cookiepool': False,
|
||||
'_cookiepool-desc': '是否开启cookie池,这将允许用户配置多个cookie并在请求时随机使用一个,启用后请在module.cookiepool中配置cookie,在user处配置的cookie会被忽略,cookiepool中格式统一为列表嵌套user处的cookie的字典',
|
||||
"allow_download_script": True,
|
||||
'_allow_download_script-desc': '是否允许直接从服务端下载脚本,开启后可以直接访问 /script?key=你的请求key 下载脚本',
|
||||
"download_config": {
|
||||
"desc": "源脚本的相关配置,dev为是否启用开发模式",
|
||||
"name": "修改为你的源脚本名称",
|
||||
"intro": "修改为你的源脚本描述",
|
||||
"author": "修改为你的源脚本作者",
|
||||
"version": "修改为你的源版本",
|
||||
"filename": "lx-music-source.js",
|
||||
"_filename-desc": "客户端保存脚本时的文件名(可能因浏览器不同出现不一样的情况)",
|
||||
"dev": True,
|
||||
"quality": {
|
||||
"kw": ["128k"],
|
||||
"kg": ["128k"],
|
||||
"tx": ["128k"],
|
||||
"wy": ["128k"],
|
||||
"mg": ["128k"],
|
||||
}
|
||||
},
|
||||
"local_music": {
|
||||
"desc": "服务器侧本地音乐相关配置,请确保你的带宽足够",
|
||||
"audio_path": "./audio",
|
||||
"temp_path": "./temp",
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"rate_limit": {
|
||||
"global": 0,
|
||||
"ip": 0,
|
||||
"desc": "请求速率限制,global为全局,ip为单个ip,填入的值为至少间隔多久才能进行一次请求,单位:秒,不限制请填为0"
|
||||
},
|
||||
"key": {
|
||||
"enable": False,
|
||||
"_enable-desc": "是否开启请求key,开启后只有请求头中包含key,且值一样时可以访问API",
|
||||
"ban": True,
|
||||
"values": ["114514"],
|
||||
},
|
||||
"whitelist_host": [
|
||||
"localhost",
|
||||
"0.0.0.0",
|
||||
"127.0.0.1",
|
||||
],
|
||||
"_whitelist_host-desc": "强制白名单HOST,不需要加端口号(即不受其他安全设置影响的HOST)",
|
||||
"check_lxm": False,
|
||||
"_check_lxm-desc": "是否检查lxm请求头(正常的LX Music请求时都会携带这个请求头)",
|
||||
"lxm_ban": True,
|
||||
"_lxm_ban-desc": "lxm请求头不存在或不匹配时是否将用户IP加入黑名单",
|
||||
"allowed_host": {
|
||||
"desc": "HOST允许列表,启用后只允许列表内的HOST访问服务器,不需要加端口号",
|
||||
"enable": False,
|
||||
"blacklist": {
|
||||
"desc": "当用户访问的HOST并不在允许列表中时是否将请求IP加入黑名单,长度单位:秒",
|
||||
"enable": False,
|
||||
"length": 0,
|
||||
},
|
||||
"list": [
|
||||
"localhost",
|
||||
"0.0.0.0",
|
||||
"127.0.0.1",
|
||||
],
|
||||
},
|
||||
"banlist": {
|
||||
"desc": "是否启用黑名单(全局设置,关闭后已存储的值并不受影响,但不会再检查)",
|
||||
"enable": True,
|
||||
"expire": {
|
||||
"desc": "是否启用黑名单IP过期(关闭后其他地方的配置会失效)",
|
||||
"enable": True,
|
||||
"length": 86400 * 7, # 七天
|
||||
},
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
"kg": {
|
||||
"desc": "酷狗音乐相关配置",
|
||||
"client": {
|
||||
"desc": "客户端请求配置,不懂请保持默认,修改请统一为字符串格式",
|
||||
"appid": "1005",
|
||||
"_appid-desc": "酷狗音乐的appid,官方安卓为1005,官方PC为1001",
|
||||
"signatureKey": "OIlwieks28dk2k092lksi2UIkp",
|
||||
"_signatureKey-desc": "客户端signature采用的key值,需要与appid对应",
|
||||
"clientver": "12029",
|
||||
"_clientver-desc": "客户端versioncode,pidversionsecret可能随此值而变化",
|
||||
"pidversionsecret": "57ae12eb6890223e355ccfcb74edf70d",
|
||||
"_pidversionsecret-desc": "获取URL时所用的key值计算验证值",
|
||||
"pid": "2",
|
||||
},
|
||||
"tracker": {
|
||||
"desc": "trackerapi请求配置,不懂请保持默认,修改请统一为字符串格式",
|
||||
"host": "https://gateway.kugou.com",
|
||||
"path": "/v5/url",
|
||||
"version": "v5",
|
||||
"x-router": {
|
||||
"desc": "当host为gateway.kugou.com时需要追加此头,为tracker类地址时则不需要",
|
||||
"enable": True,
|
||||
"value": "tracker.kugou.com",
|
||||
},
|
||||
"extra_params": {},
|
||||
"_extra_params-desc": "自定义添加的param,优先级大于默认,填写类型为普通的JSON数据,会自动转换为请求param",
|
||||
},
|
||||
"user": {
|
||||
"desc": "此处内容请统一抓包获取,需要vip账号来获取会员歌曲,如果没有请留为空值,mid必填,可以瞎填一段数字",
|
||||
"token": "",
|
||||
"userid": "0",
|
||||
"mid": "114514",
|
||||
"lite_sign_in": {
|
||||
"desc": "是否启用概念版自动签到,仅在appid=3116时运行",
|
||||
"enable": False,
|
||||
"interval": 86400,
|
||||
"mixsongmid": {
|
||||
"desc": "mix_songmid的获取方式, 默认auto, 可以改成一个数字手动",
|
||||
"value": "auto"
|
||||
}
|
||||
},
|
||||
"refresh_token": {
|
||||
"desc": "酷狗token保活相关配置,30天不刷新token会失效,enable是否启动,interval刷新间隔。默认appid=1005时有效,3116需要更换signatureKey",
|
||||
"enable": False,
|
||||
"interval": 86000,
|
||||
"login_url": "http://login.user.kugou.com/v4/login_by_token"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tx": {
|
||||
"desc": "QQ音乐相关配置",
|
||||
"vkeyserver": {
|
||||
"desc": "请求官方api时使用的guid,uin等信息,不需要与cookie中信息一致",
|
||||
"guid": "114514",
|
||||
"uin": "10086",
|
||||
},
|
||||
"user": {
|
||||
"desc": "用户数据,可以通过浏览器获取,需要vip账号来获取会员歌曲,如果没有请留为空值,qqmusic_key可以从Cookie中/客户端的请求体中(comm.authst)获取",
|
||||
"qqmusic_key": "",
|
||||
"uin": "",
|
||||
"_uin-desc": "key对应的QQ号",
|
||||
'refresh_login': {
|
||||
'desc': '刷新登录相关配置,enable是否启动,interval刷新间隔',
|
||||
'enable': False,
|
||||
'interval': 86000
|
||||
}
|
||||
},
|
||||
"cdnaddr": "http://ws.stream.qqmusic.qq.com/",
|
||||
},
|
||||
"wy": {
|
||||
"desc": "网易云音乐相关配置",
|
||||
"user": {
|
||||
"desc": "账号cookie数据,可以通过浏览器获取,需要vip账号来获取会员歌曲,如果没有请留为空值",
|
||||
"cookie": ""
|
||||
},
|
||||
},
|
||||
"mg": {
|
||||
"desc": "咪咕音乐相关配置",
|
||||
"user": {
|
||||
"desc": "研究不深,后两项自行抓包获取,网页端cookie",
|
||||
"by": "",
|
||||
"session": "",
|
||||
"useragent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36",
|
||||
"refresh_login": {
|
||||
"enable": False,
|
||||
"interval": 86400,
|
||||
"desc": "进行cookie保活"
|
||||
}
|
||||
},
|
||||
},
|
||||
"kw": {
|
||||
"desc": "酷我音乐相关配置,proto支持值:['bd-api', 'kuwodes']",
|
||||
"proto": "bd-api",
|
||||
"user": {
|
||||
"uid": "0",
|
||||
"token": "",
|
||||
"device_id": "0",
|
||||
},
|
||||
"des": {
|
||||
"desc": "kuwodes接口(mobi, nmobi)一类的加密相关配置",
|
||||
"f": "kuwo",
|
||||
"need_encrypt": True,
|
||||
"param填写注释": "{songId}为歌曲id, {map_quality}为map后的歌曲音质(酷我规范), {raw_quality}为请求时的歌曲音质(LX规范), {ext}为歌曲文件扩展名",
|
||||
"params": "type=convert_url_with_sign&rid={songId}&quality={map_quality}&ext={ext}",
|
||||
"host": "nmobi.kuwo.cn",
|
||||
"path": "mobi.s",
|
||||
"response_types": ['这里是reponse_type的所有支持值,当设置为json时会使用到下面的两个值来获取url/bitrate,如果为text,则为传统的逐行解析方式', 'json', 'text'],
|
||||
"response_type": "json",
|
||||
"url_json_path": "data.url",
|
||||
"bitrate_json_path": "data.bitrate",
|
||||
"headers": {
|
||||
"User-Agent": 'okhttp/3.10.0'
|
||||
}
|
||||
}
|
||||
},
|
||||
'cookiepool': {
|
||||
'kg': [
|
||||
{
|
||||
'userid': '0',
|
||||
'token': '',
|
||||
'mid': '114514',
|
||||
"lite_sign_in": {
|
||||
"desc": "是否启用概念版自动签到,仅在appid=3116时运行",
|
||||
"enable": False,
|
||||
"interval": 86400,
|
||||
"mixsongmid": {
|
||||
"desc": "mix_songmid的获取方式, 默认auto, 可以改成一个数字手动",
|
||||
"value": "auto"
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
'tx': [
|
||||
{
|
||||
'qqmusic_key': '',
|
||||
'uin': '',
|
||||
'refresh_login': {
|
||||
'desc': 'cookie池中对于此账号刷新登录的配置,账号间互不干扰',
|
||||
'enable': False,
|
||||
'interval': 86000,
|
||||
}
|
||||
}
|
||||
],
|
||||
'wy': [
|
||||
{
|
||||
'cookie': '',
|
||||
}
|
||||
],
|
||||
'mg': [
|
||||
{
|
||||
'by': '',
|
||||
'session': '',
|
||||
'useragent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36',
|
||||
"refresh_login": {
|
||||
"enable": False,
|
||||
"interval": 86400
|
||||
}
|
||||
}
|
||||
],
|
||||
'kw': [
|
||||
{
|
||||
"uid": "0",
|
||||
"token": "",
|
||||
"device_id": "0",
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
}
|
||||
default_str = default_config.default
|
||||
default = yaml.safe_load(default_str)
|
||||
|
||||
|
||||
def handle_default_config():
|
||||
with open("./config/config.json", "w", encoding="utf-8") as f:
|
||||
f.write(json.dumps(default, indent=2, ensure_ascii=False,
|
||||
escape_forward_slashes=False))
|
||||
f.close()
|
||||
with open("./config/config.yml", "w", encoding="utf-8") as f:
|
||||
f.write(default_str)
|
||||
if (not os.getenv('build')):
|
||||
logger.info('首次启动或配置文件被删除,已创建默认配置文件')
|
||||
logger.info(
|
||||
f'\n建议您到{variable.workdir + os.path.sep}config.json修改配置后重新启动服务器')
|
||||
f'\n建议您到{variable.workdir + os.path.sep}config.yml修改配置后重新启动服务器')
|
||||
return default
|
||||
|
||||
|
||||
@ -525,8 +242,8 @@ def push_to_list(key, obj):
|
||||
|
||||
def write_config(key, value):
|
||||
config = None
|
||||
with open('./config/config.json', 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
with open('./config/config.yml', 'r', encoding='utf-8') as f:
|
||||
config = yaml.YAML().load(f)
|
||||
|
||||
keys = key.split('.')
|
||||
current = config
|
||||
@ -536,11 +253,15 @@ def write_config(key, value):
|
||||
current = current[k]
|
||||
|
||||
current[keys[-1]] = value
|
||||
variable.config = config
|
||||
with open('./config/config.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(config, f, indent=2, ensure_ascii=False,
|
||||
escape_forward_slashes=False)
|
||||
f.close()
|
||||
|
||||
# 设置保留注释和空行的参数
|
||||
y = yaml.YAML()
|
||||
y.preserve_quotes = True
|
||||
y.preserve_blank_lines = True
|
||||
|
||||
# 写入配置并保留注释和空行
|
||||
with open('./config/config.yml', 'w', encoding='utf-8') as f:
|
||||
y.dump(config, f)
|
||||
|
||||
|
||||
def read_default_config(key):
|
||||
@ -639,21 +360,26 @@ def initConfig():
|
||||
shutil.move('config.json','./config')
|
||||
if (os.path.exists('./data.db')):
|
||||
shutil.move('./data.db','./config')
|
||||
if (os.path.exists('./config/config.json')):
|
||||
os.rename('./config/config.json', './config/config.json.bak')
|
||||
handle_default_config()
|
||||
logger.warning('json配置文件已不再使用,已将其重命名为config.json.bak')
|
||||
logger.warning('配置文件不会自动更新(因为变化太大),请手动修改配置文件重启服务器')
|
||||
sys.exit(0)
|
||||
|
||||
try:
|
||||
with open("./config/config.json", "r", encoding="utf-8") as f:
|
||||
with open("./config/config.yml", "r", encoding="utf-8") as f:
|
||||
try:
|
||||
variable.config = json.loads(f.read())
|
||||
variable.config = yaml.safe_load(f.read())
|
||||
if (not isinstance(variable.config, dict)):
|
||||
logger.warning('配置文件并不是一个有效的字典,使用默认值')
|
||||
variable.config = default
|
||||
with open("./config/config.json", "w", encoding="utf-8") as f:
|
||||
f.write(json.dumps(variable.config, indent=2,
|
||||
ensure_ascii=False, escape_forward_slashes=False))
|
||||
with open("./config/config.yml", "w", encoding="utf-8") as f:
|
||||
yaml.dump(variable.config, f)
|
||||
f.close()
|
||||
except:
|
||||
if os.path.getsize("./config/config.json") != 0:
|
||||
logger.error("配置文件加载失败,请检查是否遵循JSON语法规范")
|
||||
if os.path.getsize("./config/config.yml") != 0:
|
||||
logger.error("配置文件加载失败,请检查是否遵循YAML语法规范")
|
||||
sys.exit(1)
|
||||
else:
|
||||
variable.config = handle_default_config()
|
||||
|
195
common/default_config.py
Normal file
195
common/default_config.py
Normal file
@ -0,0 +1,195 @@
|
||||
default = '''
|
||||
common:
|
||||
hosts: # 服务器监听地址
|
||||
- 0.0.0.0
|
||||
# - '::' # 取消这一行的注释,启用 ipv6 监听
|
||||
ports: # 服务器启动时所使用的端口
|
||||
- 9763
|
||||
ssl_info: # 服务器https配置
|
||||
# 这个服务器是否是https服务器,如果你使用了反向代理来转发这个服务器,如果它使用了https,也请将它设置为true
|
||||
is_https: false
|
||||
# python原生https监听
|
||||
enable: false
|
||||
ssl_ports:
|
||||
- 443
|
||||
path: # ssl证书的文件地址
|
||||
cert: /path/to/your/cer
|
||||
privkey: /path/to/your/private/key
|
||||
reverse_proxy: # 针对类似于nginx一类的反代的配置
|
||||
allow_public_ip: false # 允许来自公网的转发
|
||||
allow_proxy: true # 是否允许反代
|
||||
real_ip_header: X-Real-IP # 反代来源ip的来源头,不懂请保持默认
|
||||
debug_mode: false # 是否开启调试模式
|
||||
log_length_limit: 500 # 单条日志长度限制
|
||||
fakeip: 1.0.1.114 # 服务器在海外时的IP伪装值
|
||||
proxy: # 代理配置,HTTP与HTTPS协议需分开配置
|
||||
enable: false
|
||||
http_value: http://127.0.0.1:7890
|
||||
https_value: http://127.0.0.1:7890
|
||||
log_file: true # 是否存储日志文件
|
||||
cookiepool: false # 是否开启cookie池,这将允许用户配置多个cookie并在请求时随机使用一个,启用后请在module.cookiepool中配置cookie,在user处配置的cookie会被忽略,cookiepool中格式统一为列表嵌套user处的cookie的字典
|
||||
allow_download_script: true # 是否允许直接从服务端下载脚本,开启后可以直接访问 /script?key=你的请求key 下载脚本
|
||||
download_config:
|
||||
desc: 源脚本的相关配置,dev为是否启用开发模式
|
||||
name: 修改为你的源脚本名称
|
||||
intro: 修改为你的源脚本描述
|
||||
author: 修改为你的源脚本作者
|
||||
version: 修改为你的源版本
|
||||
filename: lx-music-source.js # 客户端保存脚本时的文件名(可能因浏览器不同出现不一样的情况)
|
||||
dev: true
|
||||
quality:
|
||||
kw: [128k]
|
||||
kg: [128k]
|
||||
tx: [128k]
|
||||
wy: [128k]
|
||||
mg: [128k]
|
||||
local_music: # 服务器侧本地音乐相关配置,如果需要使用此功能请确保你的带宽足够
|
||||
audio_path: ./audio
|
||||
temp_path: ./temp
|
||||
|
||||
security:
|
||||
rate_limit: # 请求速率限制 填入的值为至少间隔多久才能进行一次请求,单位:秒,不限制请填为0
|
||||
global: 0 # 全局
|
||||
ip: 0 # 单个IP
|
||||
key:
|
||||
enable: false # 是否开启请求key,开启后只有请求头中包含key,且值一样时可以访问API
|
||||
ban: true
|
||||
values: # 填自己所有的请求key
|
||||
- '114514'
|
||||
whitelist_host: # 强制白名单HOST,不需要加端口号(即不受其他安全设置影响的HOST)
|
||||
- localhost
|
||||
- 0.0.0.0
|
||||
- 127.0.0.1
|
||||
check_lxm: false # 是否检查lxm请求头(正常的LX Music在内置源请求时都会携带这个请求头)
|
||||
lxm_ban: true # lxm请求头不存在或不匹配时是否将用户IP加入黑名单
|
||||
allowed_host: # HOST允许列表,启用后只允许列表内的HOST访问服务器,不需要加端口号
|
||||
enable: false
|
||||
blacklist: # 当用户访问的HOST并不在允许列表中时是否将请求IP加入黑名单,长度单位:秒
|
||||
enable: false
|
||||
length: 0
|
||||
list:
|
||||
- localhost
|
||||
- 0.0.0.0
|
||||
- 127.0.0.1
|
||||
banlist: # 是否启用黑名单(全局设置,关闭后已存储的值并不受影响,但不会再检查)
|
||||
enable: true
|
||||
expire: # 是否启用黑名单IP过期(关闭后其他地方的配置会失效)
|
||||
enable: true
|
||||
length: 604800
|
||||
|
||||
module:
|
||||
kg: # 酷狗音乐相关配置
|
||||
client: # 客户端请求配置,不懂请保持默认,修改请统一为字符串格式
|
||||
appid: '1005' # 酷狗音乐的appid,官方安卓为1005,官方PC为1001
|
||||
signatureKey: OIlwieks28dk2k092lksi2UIkp # 客户端signature采用的key值,需要与appid对应
|
||||
clientver: '12029' # 客户端versioncode,pidversionsecret可能随此值而变化
|
||||
pidversionsecret: 57ae12eb6890223e355ccfcb74edf70d # 获取URL时所用的key值计算验证值
|
||||
pid: '2' # url接口的pid
|
||||
tracker: # trackerapi请求配置,不懂请保持默认,修改请统一为字符串格式
|
||||
host: https://gateway.kugou.com
|
||||
path: /v5/url
|
||||
version: v5
|
||||
x-router: # 当host为gateway.kugou.com时需要追加此头,为tracker类地址时则不需要
|
||||
enable: true
|
||||
value: tracker.kugou.com
|
||||
extra_params: {} # 自定义添加的param,优先级大于默认,填写类型为普通的JSON数据,会自动转换为请求param
|
||||
user: # 此处内容请统一抓包获取(/v5/url),需要vip账号来获取会员歌曲,如果没有请留为空值,mid必填,可以瞎填一段数字
|
||||
token: ''
|
||||
userid: '0'
|
||||
mid: '114514'
|
||||
lite_sign_in: # 是否启用概念版自动签到,仅在appid=3116时运行
|
||||
enable: false
|
||||
interval: 86400
|
||||
mixsongmid: # mix_songmid的获取方式, 默认auto, 可以改成一个数字手动
|
||||
value: auto
|
||||
refresh_token: # 酷狗token保活相关配置,30天不刷新token会失效,enable是否启动,interval刷新间隔。默认appid=1005时有效,3116需要更换signatureKey
|
||||
enable: false
|
||||
interval: 86000
|
||||
login_url: http://login.user.kugou.com/v4/login_by_token
|
||||
|
||||
tx: # QQ音乐相关配置
|
||||
vkeyserver: # 请求官方api时使用的guid,uin等信息,不需要与cookie中信息一致
|
||||
guid: '114514'
|
||||
uin: '10086'
|
||||
user: # 用户数据,可以通过浏览器获取,需要vip账号来获取会员歌曲,如果没有请留为空值,qqmusic_key可以从Cookie中/客户端的请求体中(comm.authst)获取
|
||||
qqmusic_key: ''
|
||||
uin: '' # key对应的QQ号
|
||||
refresh_login: # 刷新登录相关配置,enable是否启动,interval刷新间隔
|
||||
enable: false
|
||||
interval: 86000
|
||||
cdnaddr: http://ws.stream.qqmusic.qq.com/
|
||||
|
||||
wy: # 网易云音乐相关配置
|
||||
user: # 账号cookie数据,可以通过浏览器获取,需要vip账号来获取会员歌曲,如果没有请留为空值
|
||||
cookie: ''
|
||||
|
||||
mg: # 咪咕音乐相关配置
|
||||
user: # 研究不深,后两项自行抓包获取,网页端cookie
|
||||
by: ''
|
||||
session: ''
|
||||
useragent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36
|
||||
refresh_login: # cookie保活配置
|
||||
enable: false
|
||||
interval: 86400
|
||||
|
||||
kw: # 酷我音乐相关配置,proto支持值:['bd-api', 'kuwodes']
|
||||
proto: bd-api
|
||||
user:
|
||||
uid: '0'
|
||||
token: ''
|
||||
device_id: '0'
|
||||
des: # kuwodes接口(mobi, nmobi)一类的加密相关配置
|
||||
f: kuwo
|
||||
need_encrypt: true # 是否开启kuwodes
|
||||
# {songId}为歌曲id
|
||||
# {map_quality}为map后的歌曲音质(酷我规范)
|
||||
# {raw_quality}为请求时的歌曲音质(LX规范)
|
||||
# {ext}为歌曲文件扩展名
|
||||
params: type=convert_url_with_sign&rid={songId}&quality={map_quality}&ext={ext}
|
||||
host: nmobi.kuwo.cn
|
||||
path: mobi.s
|
||||
# 这里是reponse_type的所有支持值,当设置为json时会使用到下面的两个值来获取url/bitrate,如果为text,则为传统的逐行解析方式
|
||||
response_type: json
|
||||
url_json_path: data.url
|
||||
bitrate_json_path: data.bitrate
|
||||
headers:
|
||||
User-Agent: okhttp/3.10.0
|
||||
|
||||
cookiepool:
|
||||
kg:
|
||||
- userid: '0'
|
||||
token: ''
|
||||
mid: '114514'
|
||||
lite_sign_in: # 是否启用概念版自动签到,仅在appid=3116时运行
|
||||
enable: false
|
||||
interval: 86400
|
||||
mixsongmid: # mix_songmid的获取方式, 默认auto, 可以改成一个数字手动
|
||||
value: auto
|
||||
refresh_login: # cookie池中对于此账号刷新登录的配置,账号间互不干扰
|
||||
enable: false
|
||||
interval: 604800
|
||||
login_url: http://login.user.kugou.com/v4/login_by_token
|
||||
|
||||
tx:
|
||||
- qqmusic_key: ''
|
||||
uin: ''
|
||||
refresh_login: # cookie池中对于此账号刷新登录的配置,账号间互不干扰
|
||||
enable: false
|
||||
interval: 86000
|
||||
|
||||
wy:
|
||||
- cookie: ''
|
||||
|
||||
mg:
|
||||
- by: ''
|
||||
session: ''
|
||||
useragent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36
|
||||
refresh_login:
|
||||
enable: false
|
||||
interval: 86400
|
||||
|
||||
kw:
|
||||
- uid: '0'
|
||||
token: ''
|
||||
device_id: '0'
|
||||
'''
|
@ -12,12 +12,14 @@ import colorlog
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import io
|
||||
import traceback
|
||||
import time
|
||||
from pygments import highlight
|
||||
from pygments.lexers import PythonLexer
|
||||
from pygments.formatters import TerminalFormatter
|
||||
from .utils import filterFileName, addToGlobalNamespace
|
||||
from .variable import debug_mode, log_length_limit, log_file
|
||||
from .utils import filterFileName, setGlobal
|
||||
from .variable import debug_mode, log_length_limit, log_file, log_files
|
||||
from colorama import Fore, Back, Style
|
||||
from colorama import init as clinit
|
||||
|
||||
@ -159,10 +161,19 @@ class LogHelper(logging.Handler):
|
||||
log_message = self.format(record)
|
||||
self.custom_logger.info(log_message)
|
||||
|
||||
class fileWriter(logging.Handler):
|
||||
def __init__(self, f: io.TextIOWrapper, f2: logging.Formatter):
|
||||
self.file = f
|
||||
self.formatter = f2
|
||||
|
||||
def emit(self, record: logging.LogRecord):
|
||||
self.file.write(self.format(record) + '\n')
|
||||
self.file.flush()
|
||||
|
||||
class log:
|
||||
# 主类
|
||||
def __init__(self, module_name='Not named logger', output_level='INFO', filename=''):
|
||||
self.name = module_name
|
||||
self._logger = logging.getLogger(module_name)
|
||||
if not output_level.upper() in dir(logging):
|
||||
raise NameError('Unknown loglevel: '+output_level)
|
||||
@ -181,21 +192,12 @@ class log:
|
||||
'CRITICAL': 'red,bg_white',
|
||||
})
|
||||
if log_file:
|
||||
file_formatter = logging.Formatter(
|
||||
'%(asctime)s|[%(name)s/%(levelname)s]|%(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
if filename:
|
||||
filename = filterFileName(filename)
|
||||
else:
|
||||
filename = './logs/' + module_name + '.log'
|
||||
file_handler = logging.FileHandler(filename, encoding="utf-8")
|
||||
file_handler.setFormatter(file_formatter)
|
||||
file_handler_ = logging.FileHandler(
|
||||
"./logs/console_full.log", encoding="utf-8")
|
||||
file_handler_.setFormatter(file_formatter)
|
||||
self._logger.addHandler(file_handler_)
|
||||
self._logger.addHandler(file_handler)
|
||||
self.file = open(filename, 'a+', encoding='utf-8')
|
||||
log_files.append(self.file)
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setFormatter(formatter)
|
||||
self.module_name = module_name
|
||||
@ -204,60 +206,54 @@ class log:
|
||||
debug_handler.setFormatter(formatter)
|
||||
|
||||
def debug(self, message, allow_hidden=True):
|
||||
if self.module_name == "flask" and "\n" in message:
|
||||
if message.startswith("Error"):
|
||||
return self._logger.error(message)
|
||||
for m in message.split("\n"):
|
||||
if "WARNING" in m:
|
||||
self._logger.warning(m)
|
||||
else:
|
||||
self._logger.info(m)
|
||||
return
|
||||
if (log_file):
|
||||
self.file.write('{time}|[{name}/DEBUG]{msg}'.format(time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), name = self.module_name, msg = message) + '\n')
|
||||
|
||||
if len(str(message)) > log_length_limit and allow_hidden:
|
||||
message = str(message)[:log_length_limit] + " ..."
|
||||
self._logger.debug(message)
|
||||
|
||||
def log(self, message, allow_hidden=True):
|
||||
if self.module_name == "flask" and "\n" in message:
|
||||
if message.startswith("Error"):
|
||||
return self._logger.error(message)
|
||||
for m in message.split("\n"):
|
||||
if "WARNING" in m:
|
||||
self._logger.warning(m)
|
||||
else:
|
||||
self._logger.info(m)
|
||||
return
|
||||
if (log_file):
|
||||
self.file.write('{time}|[{name}/INFO]{msg}'.format(time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), name = self.module_name, msg = message) + '\n')
|
||||
|
||||
if len(str(message)) > log_length_limit and allow_hidden:
|
||||
message = str(message)[:log_length_limit] + " ..."
|
||||
self._logger.info(message)
|
||||
|
||||
def info(self, message, allow_hidden=True):
|
||||
if self.module_name == "flask" and "\n" in message:
|
||||
if message.startswith("Error"):
|
||||
return self._logger.error(message)
|
||||
for m in message.split("\n"):
|
||||
if "WARNING" in m:
|
||||
self._logger.warning(m)
|
||||
else:
|
||||
self._logger.info(m)
|
||||
return
|
||||
if (log_file):
|
||||
self.file.write('{time}|[{name}/INFO]{msg}'.format(time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), name = self.module_name, msg = message) + '\n')
|
||||
|
||||
if len(str(message)) > log_length_limit and allow_hidden:
|
||||
message = str(message)[:log_length_limit] + "..."
|
||||
self._logger.info(message)
|
||||
|
||||
def warning(self, message):
|
||||
if (log_file):
|
||||
self.file.write('{time}|[{name}/WARNING]{msg}'.format(time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), name = self.module_name, msg = message) + '\n')
|
||||
|
||||
if (message.strip().startswith('Traceback')):
|
||||
self._logger.warning('\n' + highlight_error(message))
|
||||
else:
|
||||
self._logger.warning(message)
|
||||
|
||||
def error(self, message):
|
||||
if (log_file):
|
||||
self.file.write('{time}|[{name}/ERROR]{msg}'.format(time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), name = self.module_name, msg = message) + '\n')
|
||||
|
||||
if (message.startswith('Traceback')):
|
||||
self._logger.error('\n' + highlight_error(message))
|
||||
else:
|
||||
self._logger.error(message)
|
||||
|
||||
def critical(self, message):
|
||||
if (log_file):
|
||||
self.file.write('{time}|[{name}/CRITICAL]{msg}'.format(time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), name = self.module_name, msg = message) + '\n')
|
||||
|
||||
if (message.startswith('Traceback')):
|
||||
self._logger.critical('\n' + highlight_error(message))
|
||||
else:
|
||||
self._logger.critical(message)
|
||||
|
||||
def set_level(self, loglevel):
|
||||
@ -280,4 +276,4 @@ def logprint(*args, sep=' ', end='', file=None, flush=None):
|
||||
printlogger.info(sep.join(str(arg) for arg in args), allow_hidden=False)
|
||||
|
||||
|
||||
addToGlobalNamespace('print', logprint)
|
||||
setGlobal(logprint, 'print')
|
||||
|
@ -37,8 +37,8 @@ async def get_response(retry = 0):
|
||||
if (retry > 21):
|
||||
logger.warning('请求源脚本内容失败')
|
||||
return
|
||||
baseurl = '/lxmusics/lx-music-api-server/main/lx-music-source-example.js'
|
||||
jsdbaseurl = '/gh/lxmusics/lx-music-api-server@main/lx-music-source-example.js'
|
||||
baseurl = '/MeoProject/lx-music-api-server/main/lx-music-source-example.js'
|
||||
jsdbaseurl = '/gh/MeoProject/lx-music-api-server@main/lx-music-source-example.js'
|
||||
try:
|
||||
i = retry
|
||||
if (i > 10):
|
||||
|
@ -40,6 +40,9 @@ class taskWrapper:
|
||||
logger.error(f"task {self.name} run failed, waiting for next execute...")
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
def __str__(self):
|
||||
return f'SchedulerTaskWrapper(name="{self.name}", interval={self.interval}, function={self.function}, args={self.args}, latest_execute={self.latest_execute})'
|
||||
|
||||
def append(name, task, interval = 86400, args = {}):
|
||||
global tasks
|
||||
wrapper = taskWrapper(name, task, interval, args)
|
||||
|
@ -16,6 +16,7 @@ import zlib
|
||||
import time
|
||||
import re
|
||||
import xmltodict
|
||||
import ipaddress
|
||||
from urllib.parse import quote, unquote, urlparse
|
||||
|
||||
def createBase64Encode(data_bytes):
|
||||
@ -51,8 +52,8 @@ def require(module):
|
||||
index += 1
|
||||
return _module
|
||||
|
||||
def addToGlobalNamespace(key, data):
|
||||
setattr(builtins, key, data)
|
||||
def setGlobal(obj, key = ''):
|
||||
setattr(builtins, obj.__name__ if (not key) else key, obj)
|
||||
|
||||
def filterFileName(filename):
|
||||
if platform.system() == 'Windows' or platform.system() == 'Cygwin':
|
||||
@ -198,5 +199,11 @@ def timestamp_format(t):
|
||||
t = int(t)
|
||||
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(t))
|
||||
|
||||
addToGlobalNamespace('require', require)
|
||||
def is_local_ip(ip):
|
||||
try:
|
||||
i = ipaddress.ip_address(ip)
|
||||
return i.is_private
|
||||
except:
|
||||
return False
|
||||
|
||||
setGlobal(require)
|
@ -10,6 +10,7 @@
|
||||
import os as _os
|
||||
import ujson as _json
|
||||
|
||||
|
||||
def _read_config_file():
|
||||
try:
|
||||
with open("./config/config.json", "r", encoding="utf-8") as f:
|
||||
@ -17,6 +18,7 @@ def _read_config_file():
|
||||
except:
|
||||
return {}
|
||||
|
||||
|
||||
def _read_config(key):
|
||||
try:
|
||||
config = _read_config_file()
|
||||
@ -36,10 +38,13 @@ def _read_config(key):
|
||||
return value
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
_dm = _read_config("common.debug_mode")
|
||||
_lm = _read_config("common.log_file")
|
||||
_ll = _read_config("common.log_length_limit")
|
||||
debug_mode = _dm if (_dm) else False
|
||||
debug_mode = True if (_os.getenv('CURRENT_ENV') ==
|
||||
'development') else (_dm if (_dm) else False)
|
||||
log_length_limit = _ll if (_ll) else 500
|
||||
log_file = _lm if (isinstance(_lm, bool)) else True
|
||||
running = True
|
||||
@ -55,3 +60,4 @@ running_ports = []
|
||||
use_proxy = False
|
||||
http_proxy = ''
|
||||
https_proxy = ''
|
||||
log_files = []
|
||||
|
123
main.py
123
main.py
@ -9,14 +9,25 @@
|
||||
# ----------------------------------------
|
||||
# This file is part of the "lx-music-api-server" project.
|
||||
|
||||
import time
|
||||
import aiohttp
|
||||
import asyncio
|
||||
import traceback
|
||||
import threading
|
||||
import ujson as json
|
||||
from aiohttp.web import Response, FileResponse, StreamResponse
|
||||
from io import TextIOWrapper
|
||||
import sys
|
||||
|
||||
from common.utils import createBase64Decode
|
||||
import os
|
||||
|
||||
if ((sys.version_info.major == 3 and sys.version_info.minor < 6) or sys.version_info.major == 2):
|
||||
print('Python版本过低,请使用Python 3.6+ ')
|
||||
sys.exit(1)
|
||||
|
||||
# fix: module not found: common/modules
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from common import utils
|
||||
from common import config, localMusic
|
||||
from common import lxsecurity
|
||||
from common import log
|
||||
@ -24,15 +35,7 @@ from common import Httpx
|
||||
from common import variable
|
||||
from common import scheduler
|
||||
from common import lx_script
|
||||
from aiohttp.web import Response, FileResponse, StreamResponse
|
||||
import ujson as json
|
||||
import threading
|
||||
import traceback
|
||||
import modules
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import time
|
||||
import os
|
||||
|
||||
def handleResult(dic, status=200) -> Response:
|
||||
if (not isinstance(dic, dict)):
|
||||
@ -43,6 +46,7 @@ def handleResult(dic, status = 200) -> Response:
|
||||
}
|
||||
return Response(body=json.dumps(dic, indent=2, ensure_ascii=False), content_type='application/json', status=status)
|
||||
|
||||
|
||||
logger = log.log("main")
|
||||
aiologger = log.log('aiohttp_web')
|
||||
|
||||
@ -54,20 +58,24 @@ if (sys.version_info.minor < 8 and sys.version_info.major == 3):
|
||||
else:
|
||||
stopEvent = asyncio.exceptions.CancelledError
|
||||
|
||||
|
||||
def start_checkcn_thread() -> None:
|
||||
threading.Thread(target=Httpx.checkcn).start()
|
||||
|
||||
# check request info before start
|
||||
|
||||
|
||||
async def handle_before_request(app, handler):
|
||||
async def handle_request(request):
|
||||
try:
|
||||
if (config.read_config('common.reverse_proxy.allow_proxy')):
|
||||
if (request.headers.get(config.read_config('common.reverse_proxy.real_ip_header'))):
|
||||
# proxy header
|
||||
if (request.remote in config.read_config('common.reverse_proxy.proxy_whitelist_remote')):
|
||||
request.remote_addr = request.headers.get(config.read_config('common.reverse_proxy.real_ip_header'))
|
||||
if (config.read_config('common.reverse_proxy.allow_public_ip') and (not utils.is_private_ip(request.remote))):
|
||||
request.remote_addr = request.headers.get(
|
||||
config.read_config('common.reverse_proxy.real_ip_header'))
|
||||
else:
|
||||
return handleResult({"code": 1, "msg": "反代客户端远程地址不在反代ip白名单中", "data": None}, 403)
|
||||
return handleResult({"code": 1, "msg": "不允许的公网ip转发", "data": None}, 403)
|
||||
else:
|
||||
request.remote_addr = request.remote
|
||||
else:
|
||||
@ -95,7 +103,8 @@ async def handle_before_request(app, handler):
|
||||
if (config.read_config("security.allowed_host.enable")):
|
||||
if request.host.split(":")[0] not in config.read_config("security.allowed_host.list"):
|
||||
if config.read_config("security.allowed_host.blacklist.enable"):
|
||||
config.ban_ip(request.remote_addr, int(config.read_config("security.allowed_host.blacklist.length")))
|
||||
config.ban_ip(request.remote_addr, int(
|
||||
config.read_config("security.allowed_host.blacklist.length")))
|
||||
return handleResult({'code': 6, 'msg': '未找到您所请求的资源', 'data': None}, 404)
|
||||
|
||||
resp = await handler(request)
|
||||
@ -106,16 +115,20 @@ async def handle_before_request(app, handler):
|
||||
if (isinstance(body, (str, list, dict))):
|
||||
resp = handleResult(body, status)
|
||||
else:
|
||||
resp = Response(body = str(body), content_type='text/plain', status = status)
|
||||
resp = Response(
|
||||
body=str(body), content_type='text/plain', status=status)
|
||||
elif (not isinstance(resp, (Response, FileResponse, StreamResponse))):
|
||||
resp = Response(body = str(resp), content_type='text/plain', status = 200)
|
||||
aiologger.info(f'{request.remote_addr + ("" if (request.remote == request.remote_addr) else f"|proxy@{request.remote}")} - {request.method} "{request.path}", {resp.status}')
|
||||
resp = Response(
|
||||
body=str(resp), content_type='text/plain', status=200)
|
||||
aiologger.info(
|
||||
f'{request.remote_addr + ("" if (request.remote == request.remote_addr) else f"|proxy@{request.remote}")} - {request.method} "{request.path}", {resp.status}')
|
||||
return resp
|
||||
except:
|
||||
logger.error(traceback.format_exc())
|
||||
return {"code": 4, "msg": "内部服务器错误", "data": None}
|
||||
return handle_request
|
||||
|
||||
|
||||
async def main(request):
|
||||
return handleResult({"code": 0, "msg": "success", "data": None})
|
||||
|
||||
@ -147,14 +160,17 @@ async def handle(request):
|
||||
logger.error(traceback.format_exc())
|
||||
return handleResult({'code': 4, 'msg': '内部服务器错误', 'data': None}, 500)
|
||||
|
||||
|
||||
async def handle_404(request):
|
||||
return handleResult({'code': 6, 'msg': '未找到您所请求的资源', 'data': None}, 404)
|
||||
|
||||
|
||||
async def handle_local(request):
|
||||
try:
|
||||
query = dict(request.query)
|
||||
data = query.get('q')
|
||||
data = createBase64Decode(data.replace('-', '+').replace('_', '/'))
|
||||
data = utils.createBase64Decode(
|
||||
data.replace('-', '+').replace('_', '/'))
|
||||
data = json.loads(data)
|
||||
t = request.match_info.get('type')
|
||||
data['t'] = t
|
||||
@ -208,28 +224,30 @@ if (config.read_config('common.allow_download_script')):
|
||||
# 404
|
||||
app.router.add_route('*', '/{tail:.*}', handle_404)
|
||||
|
||||
async def run_app():
|
||||
|
||||
async def run_app_host(host):
|
||||
retries = 0
|
||||
while True:
|
||||
if (retries > 4):
|
||||
logger.warning("重试次数已达上限,但仍有部分端口未能完成监听,已自动进行忽略")
|
||||
return
|
||||
break
|
||||
try:
|
||||
host = config.read_config('common.host')
|
||||
ports = [int(port) for port in config.read_config('common.ports')]
|
||||
ssl_ports = [int(port) for port in config.read_config('common.ssl_info.ssl_ports')]
|
||||
|
||||
ports = [int(port)
|
||||
for port in config.read_config('common.ports')]
|
||||
ssl_ports = [int(port) for port in config.read_config(
|
||||
'common.ssl_info.ssl_ports')]
|
||||
final_ssl_ports = []
|
||||
final_ports = []
|
||||
for p in ports:
|
||||
if (p not in ssl_ports and p not in variable.running_ports):
|
||||
if (p not in ssl_ports and f'{host}_{p}' not in variable.running_ports):
|
||||
final_ports.append(p)
|
||||
else:
|
||||
if (p not in variable.running_ports):
|
||||
final_ssl_ports.append(p)
|
||||
# 读取证书和私钥路径
|
||||
cert_path = config.read_config('common.ssl_info.path.cert')
|
||||
privkey_path = config.read_config('common.ssl_info.path.privkey')
|
||||
privkey_path = config.read_config(
|
||||
'common.ssl_info.path.privkey')
|
||||
|
||||
# 创建 HTTP AppRunner
|
||||
http_runner = aiohttp.web.AppRunner(app)
|
||||
@ -238,16 +256,21 @@ async def run_app():
|
||||
# 启动 HTTP 端口监听
|
||||
for port in final_ports:
|
||||
if (port not in variable.running_ports):
|
||||
http_site = aiohttp.web.TCPSite(http_runner, host, port)
|
||||
http_site = aiohttp.web.TCPSite(
|
||||
http_runner, host, port)
|
||||
await http_site.start()
|
||||
variable.running_ports.append(port)
|
||||
logger.info(f"监听 -> http://{host}:{port}")
|
||||
variable.running_ports.append(f'{host}_{port}')
|
||||
logger.info(f"""监听 -> http://{
|
||||
host if (':' not in host)
|
||||
else '[' + host + ']'
|
||||
}:{port}""")
|
||||
|
||||
if (config.read_config("common.ssl_info.enable") and final_ssl_ports != []):
|
||||
if (os.path.exists(cert_path) and os.path.exists(privkey_path)):
|
||||
import ssl
|
||||
# 创建 SSL 上下文,加载配置文件中指定的证书和私钥
|
||||
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||
ssl_context = ssl.create_default_context(
|
||||
ssl.Purpose.CLIENT_AUTH)
|
||||
ssl_context.load_cert_chain(cert_path, privkey_path)
|
||||
|
||||
# 创建 HTTPS AppRunner
|
||||
@ -257,12 +280,16 @@ async def run_app():
|
||||
# 启动 HTTPS 端口监听
|
||||
for port in ssl_ports:
|
||||
if (port not in variable.running_ports):
|
||||
https_site = aiohttp.web.TCPSite(https_runner, host, port, ssl_context=ssl_context)
|
||||
https_site = aiohttp.web.TCPSite(
|
||||
https_runner, host, port, ssl_context=ssl_context)
|
||||
await https_site.start()
|
||||
variable.running_ports.append(port)
|
||||
logger.info(f"监听 -> https://{host}:{port}")
|
||||
|
||||
return
|
||||
variable.running_ports.append(f'{host}_{port}')
|
||||
logger.info(f"""监听 -> http://{
|
||||
host if (':' not in host)
|
||||
else '[' + host + ']'
|
||||
}:{port}""")
|
||||
logger.debug(f"HOST({host}) 已完成监听")
|
||||
break
|
||||
except OSError as e:
|
||||
if (str(e).startswith("[Errno 98]") or str(e).startswith('[Errno 10048]')):
|
||||
logger.error("端口已被占用,请检查\n" + str(e))
|
||||
@ -271,7 +298,12 @@ async def run_app():
|
||||
logger.info('重新尝试启动...')
|
||||
retries += 1
|
||||
else:
|
||||
raise
|
||||
logger.error("未知错误,请检查\n" + traceback.format_exc())
|
||||
|
||||
|
||||
async def run_app():
|
||||
for host in config.read_config('common.hosts'):
|
||||
await run_app_host(host)
|
||||
|
||||
|
||||
async def initMain():
|
||||
@ -305,5 +337,20 @@ if __name__ == "__main__":
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
except:
|
||||
logger.error('初始化出错,请检查日志')
|
||||
logger.error(traceback.format_exc())
|
||||
logger.critical('初始化出错,请检查日志')
|
||||
logger.critical(traceback.format_exc())
|
||||
with open('dumprecord_{}.txt'.format(int(time.time())), 'w', encoding='utf-8') as f:
|
||||
f.write(traceback.format_exc())
|
||||
e = '\n\nGlobal variable object:\n\n'
|
||||
for k in dir(variable):
|
||||
e += (k + ' = ' + str(getattr(variable, k)) + '\n') if (not k.startswith('_')) else ''
|
||||
f.write(e)
|
||||
e = '\n\nsys.modules:\n\n'
|
||||
for k in sys.modules:
|
||||
e += (k + ' = ' + str(sys.modules[k]) + '\n') if (not k.startswith('_')) else ''
|
||||
f.write(e)
|
||||
logger.critical('dumprecord_{}.txt 已保存至当前目录'.format(int(time.time())))
|
||||
finally:
|
||||
for f in variable.log_files:
|
||||
if (f and isinstance(f, TextIOWrapper)):
|
||||
f.close()
|
||||
|
@ -22,6 +22,7 @@ async def refresh():
|
||||
return
|
||||
if (not config.read_config('module.tx.user.refresh_login.enable')):
|
||||
return
|
||||
print(config.read_config('module.tx.user.qqmusic_key'))
|
||||
if (config.read_config('module.tx.user.qqmusic_key').startswith('W_X')):
|
||||
options = {
|
||||
'method': 'POST',
|
||||
|
11
script.py
Normal file
11
script.py
Normal file
@ -0,0 +1,11 @@
|
||||
# https://github.com/python-poetry/poetry/issues/241#issuecomment-445434646
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
|
||||
def __getattr__(name): # python 3.7+, otherwise define each script manually
|
||||
name = name.replace('_', '-')
|
||||
subprocess.run(
|
||||
['python', '-u', '-m', name] + sys.argv[1:]
|
||||
) # run whatever you like based on 'name'
|
24
setup.py
Normal file
24
setup.py
Normal file
@ -0,0 +1,24 @@
|
||||
from setuptools import setup
|
||||
import toml
|
||||
|
||||
try:
|
||||
version = toml.load("./pyproject.toml")["tool"]["poetry"]["version"]
|
||||
description = toml.load("./pyproject.toml")["tool"]["poetry"]["description"]
|
||||
except Exception:
|
||||
version = "1.0.0"
|
||||
description = "Description not available"
|
||||
|
||||
setup(
|
||||
name='lx_music_api_server_setup',
|
||||
version=version,
|
||||
scripts=['poetry_run.py'],
|
||||
author='helloplhm-qwq',
|
||||
author_email='helloplhm-qwq@outlook.com',
|
||||
description=description,
|
||||
url='https://github.com/helloplhm-qwq/lx-music-api-server',
|
||||
classifiers=[
|
||||
'Programming Language :: Python :: 3',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: OS Independent',
|
||||
],
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user