1
0
forked from bot/app

轻雪天气更新

This commit is contained in:
远野千束 2024-04-27 02:20:44 +08:00
parent 9cfdd375ca
commit 86c7b70e63
107 changed files with 616 additions and 273 deletions

View File

@ -5,23 +5,25 @@ order: 2
category: 使用手册 category: 使用手册
--- ---
## 功能插件命令 ## 功能插件命令
### **轻雪天气`liteyuki_weather`** ### **轻雪天气`liteyuki_weather`**
配置项 配置项
```yaml ```yaml
weather_key: "" # 和风天气的天气key weather_key: "" # 和风天气的天气key会自动判断key版本
weather_dev: false # 是否为开发版
``` ```
命令 命令
```shell ```shell
weather <keywords...> # 查询目标地实时天气,例如:"天气 北京 海淀", "weather Tokyo Shinjuku" weather <keywords...> # 查询目标地实时天气,例如:"天气 北京 海淀", "weather Tokyo Shinjuku"
bind-city <keywords...> # 绑定查询城市,个人全局生效 bind-city <keywords...> # 绑定查询城市,个人全局生效
``` ```
命令别名 命令别名
```shell ```shell
weather 天气, bind-city 绑定城市 weather 天气, bind-city 绑定城市
``` ```

View File

@ -83,7 +83,7 @@ async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent):
} }
) )
common_db.upsert(temp_data) common_db.save(temp_data)
Reloader.reload(0) Reloader.reload(0)
@ -120,7 +120,7 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot, matcher: Matcher
except: except:
pass pass
stored_config.config[key] = value stored_config.config[key] = value
common_db.upsert(stored_config) common_db.save(stored_config)
await matcher.finish(f"{ulang.get('liteyuki.config_set_success', KEY=key, VAL=value)}") await matcher.finish(f"{ulang.get('liteyuki.config_set_success', KEY=key, VAL=value)}")
elif result.subcommands.get("get"): elif result.subcommands.get("get"):
key = result.subcommands.get("get").args.get("key") key = result.subcommands.get("get").args.get("key")
@ -144,7 +144,7 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot, matcher: Matcher
key = result.subcommands.get("remove").args.get("key") key = result.subcommands.get("remove").args.get("key")
if key in stored_config.config: if key in stored_config.config:
stored_config.config.pop(key) stored_config.config.pop(key)
common_db.upsert(stored_config) common_db.save(stored_config)
await matcher.finish(f"{ulang.get('liteyuki.config_remove_success', KEY=key)}") await matcher.finish(f"{ulang.get('liteyuki.config_remove_success', KEY=key)}")
else: else:
await matcher.finish(f"{ulang.get('liteyuki.invalid_command', TEXT=key)}") await matcher.finish(f"{ulang.get('liteyuki.invalid_command', TEXT=key)}")
@ -164,7 +164,7 @@ async def _(event: T_MessageEvent, matcher: Matcher):
stored_config: StoredConfig = common_db.first(StoredConfig(), default=StoredConfig()) stored_config: StoredConfig = common_db.first(StoredConfig(), default=StoredConfig())
stored_config.config["markdown_image"] = not stored_config.config.get("markdown_image", False) stored_config.config["markdown_image"] = not stored_config.config.get("markdown_image", False)
markdown_image = stored_config.config["markdown_image"] markdown_image = stored_config.config["markdown_image"]
common_db.upsert(stored_config) common_db.save(stored_config)
await matcher.finish(ulang.get("liteyuki.image_mode_on" if stored_config.config["markdown_image"] else "liteyuki.image_mode_off")) await matcher.finish(ulang.get("liteyuki.image_mode_on" if stored_config.config["markdown_image"] else "liteyuki.image_mode_off"))
@ -258,7 +258,7 @@ async def on_startup():
if temp_data.data.get("reload", False): if temp_data.data.get("reload", False):
delta_time = time.time() - temp_data.data.get("reload_time", 0) delta_time = time.time() - temp_data.data.get("reload_time", 0)
temp_data.data["delta_time"] = delta_time temp_data.data["delta_time"] = delta_time
common_db.upsert(temp_data) # 更新数据 common_db.save(temp_data) # 更新数据
@driver.on_shutdown @driver.on_shutdown
@ -278,7 +278,7 @@ async def _(bot: T_Bot):
reload_session_type = temp_data.data.get("reload_session_type", "private") reload_session_type = temp_data.data.get("reload_session_type", "private")
reload_session_id = temp_data.data.get("reload_session_id", 0) reload_session_id = temp_data.data.get("reload_session_id", 0)
delta_time = temp_data.data.get("delta_time", 0) delta_time = temp_data.data.get("delta_time", 0)
common_db.upsert(temp_data) # 更新数据 common_db.save(temp_data) # 更新数据
await bot.call_api( await bot.call_api(
"send_msg", "send_msg",
message_type=reload_session_type, message_type=reload_session_type,

View File

@ -66,7 +66,7 @@ async def _(result: Arparma):
target=Node(bot_id=target[0], session_type=target[1], session_id=target[2]), target=Node(bot_id=target[0], session_type=target[1], session_id=target[2]),
inde=len(pushes_db.all(Push(), default=[])) inde=len(pushes_db.all(Push(), default=[]))
) )
pushes_db.upsert(push1) pushes_db.save(push1)
if result.subcommands["add"].args.get("bidirectional"): if result.subcommands["add"].args.get("bidirectional"):
push2 = Push( push2 = Push(
@ -74,7 +74,7 @@ async def _(result: Arparma):
target=Node(bot_id=source[0], session_type=source[1], session_id=source[2]), target=Node(bot_id=source[0], session_type=source[1], session_id=source[2]),
inde=len(pushes_db.all(Push(), default=[])) inde=len(pushes_db.all(Push(), default=[]))
) )
pushes_db.upsert(push2) pushes_db.save(push2)
await add_push.finish("添加成功") await add_push.finish("添加成功")
else: else:
await add_push.finish("参数缺失") await add_push.finish("参数缺失")

View File

@ -150,10 +150,10 @@ def set_plugin_session_enable(event: T_MessageEvent, plugin_name: str, enable: b
if event.message_type == "group": if event.message_type == "group":
__group_data[str(event.group_id)] = session __group_data[str(event.group_id)] = session
print(session) print(session)
group_db.upsert(session) group_db.save(session)
else: else:
__user_data[str(event.user_id)] = session __user_data[str(event.user_id)] = session
user_db.upsert(session) user_db.save(session)
def get_plugin_global_enable(plugin_name: str) -> bool: def get_plugin_global_enable(plugin_name: str) -> bool:
@ -193,7 +193,7 @@ def set_plugin_global_enable(plugin_name: str, enable: bool):
default=GlobalPlugin(module_name=plugin_name, enabled=True)) default=GlobalPlugin(module_name=plugin_name, enabled=True))
plugin.enabled = enable plugin.enabled = enable
plugin_db.upsert(plugin) plugin_db.save(plugin)
__global_enable[plugin_name] = enable __global_enable[plugin_name] = enable
@ -242,4 +242,4 @@ def set_group_enable(group_id: str, enable: bool):
group.enable = enable group.enable = enable
__group_data[group_id] = group __group_data[group_id] = group
group_db.upsert(group) group_db.save(group)

View File

@ -225,7 +225,7 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot, npm: Matcher):
found_in_db_plugin = plugin_db.first(InstalledPlugin(), "module_name = ?", plugin_name) # 查询数据库中是否已经安装 found_in_db_plugin = plugin_db.first(InstalledPlugin(), "module_name = ?", plugin_name) # 查询数据库中是否已经安装
if r_load: if r_load:
if found_in_db_plugin is None: if found_in_db_plugin is None:
plugin_db.upsert(installed_plugin) plugin_db.save(installed_plugin)
info = md.escape(ulang.get("npm.install_success", NAME=store_plugin.name)) # markdown转义 info = md.escape(ulang.get("npm.install_success", NAME=store_plugin.name)) # markdown转义
await md.send_md( await md.send_md(
f"{info}\n\n" f"{info}\n\n"

View File

@ -55,18 +55,22 @@ data
""" """
async def generate_status_card(bot: dict, hardware: dict, liteyuki: dict, lang="zh-CN", bot_id="0") -> bytes: async def generate_status_card(bot: dict, hardware: dict, liteyuki: dict, lang="zh-CN", bot_id="0", use_cache=False) -> bytes:
return await template2image( if not use_cache:
get_path("templates/status.html", abs_path=True), return await template2image(
{ get_path("templates/status.html", abs_path=True),
"data": { {
"bot" : bot, "data": {
"hardware" : hardware, "bot" : bot,
"liteyuki" : liteyuki, "hardware" : hardware,
"localization": get_local_data(lang) "liteyuki" : liteyuki,
} "localization": get_local_data(lang)
} }
) },
debug=True
)
else:
pass
def get_local_data(lang_code) -> dict: def get_local_data(lang_code) -> dict:
@ -97,7 +101,7 @@ def get_local_data(lang_code) -> dict:
"cores" : lang.get("status.cores"), "cores" : lang.get("status.cores"),
"process" : lang.get("status.process"), "process" : lang.get("status.process"),
"resources" : lang.get("status.resources"), "resources" : lang.get("status.resources"),
"description" : lang.get("status.description"),
} }

View File

@ -49,7 +49,7 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot):
r = set_profile(result.args["key"], result.args["value"], str(event.user_id)) r = set_profile(result.args["key"], result.args["value"], str(event.user_id))
if r: if r:
user.profile[result.args["key"]] = result.args["value"] user.profile[result.args["key"]] = result.args["value"]
user_db.upsert(user) # 数据库保存 user_db.save(user) # 数据库保存
await profile_alc.finish( await profile_alc.finish(
ulang.get( ulang.get(
"user.profile.set_success", "user.profile.set_success",

View File

@ -1,7 +1,7 @@
from nonebot.plugin import PluginMetadata from nonebot.plugin import PluginMetadata
from nonebot import get_driver
from .qweather import * from .qweather import *
__plugin_meta__ = PluginMetadata( __plugin_meta__ = PluginMetadata(
name="轻雪天气", name="轻雪天气",
description="基于和风天气api的天气插件", description="基于和风天气api的天气插件",
@ -9,9 +9,19 @@ __plugin_meta__ = PluginMetadata(
type="application", type="application",
homepage="https://github.com/snowykami/LiteyukiBot", homepage="https://github.com/snowykami/LiteyukiBot",
extra={ extra={
"liteyuki": True, "liteyuki" : True,
"toggleable" : True, "toggleable" : True,
"default_enable" : True, "default_enable": True,
} }
) )
from ...utils.base.data_manager import set_memory_data
driver = get_driver()
@driver.on_startup
async def _():
# 检查是否为开发者模式
is_dev = await check_key_dev(get_config("weather_key", ""))
set_memory_data("weather.is_dev", is_dev)

View File

@ -1,6 +1,8 @@
from .qw_models import * from .qw_models import *
import httpx import httpx
from ...utils.base.data_manager import get_memory_data
from ...utils.base.language import Language
dev_url = "https://devapi.qweather.com/" # 开发HBa dev_url = "https://devapi.qweather.com/" # 开发HBa
com_url = "https://api.qweather.com/" # 正式环境 com_url = "https://api.qweather.com/" # 正式环境
@ -17,6 +19,42 @@ def get_qw_lang(lang: str) -> str:
return lang return lang
async def check_key_dev(key: str) -> bool:
url = "https://api.qweather.com/v7/weather/now?"
params = {
"location": "101010100",
"key" : key,
}
async with httpx.AsyncClient() as client:
resp = await client.get(url, params=params)
return resp.json().get("code") != "200" # 查询不到付费数据为开发版
def get_local_data(ulang_code: str) -> dict:
"""
获取本地化数据
Args:
ulang_code:
Returns:
"""
ulang = Language(ulang_code)
return {
"monday" : ulang.get("weather.monday"),
"tuesday" : ulang.get("weather.tuesday"),
"wednesday": ulang.get("weather.wednesday"),
"thursday" : ulang.get("weather.thursday"),
"friday" : ulang.get("weather.friday"),
"saturday" : ulang.get("weather.saturday"),
"sunday" : ulang.get("weather.sunday"),
"today" : ulang.get("weather.today"),
"tomorrow" : ulang.get("weather.tomorrow"),
"day" : ulang.get("weather.day"),
"night" : ulang.get("weather.night"),
}
async def city_lookup( async def city_lookup(
location: str, location: str,
key: str, key: str,
@ -54,7 +92,7 @@ async def get_weather_now(
location: str, location: str,
lang: str = "zh", lang: str = "zh",
unit: str = "m", unit: str = "m",
dev: bool = True, dev: bool = get_memory_data("is_dev", True),
) -> dict: ) -> dict:
url_path = "v7/weather/now?" url_path = "v7/weather/now?"
url = dev_url + url_path if dev else com_url + url_path url = dev_url + url_path if dev else com_url + url_path
@ -74,7 +112,7 @@ async def get_weather_daily(
location: str, location: str,
lang: str = "zh", lang: str = "zh",
unit: str = "m", unit: str = "m",
dev: bool = True, dev: bool = get_memory_data("is_dev", True),
) -> dict: ) -> dict:
url_path = "v7/weather/%dd?" % (7 if dev else 30) url_path = "v7/weather/%dd?" % (7 if dev else 30)
url = dev_url + url_path if dev else com_url + url_path url = dev_url + url_path if dev else com_url + url_path
@ -94,7 +132,7 @@ async def get_weather_hourly(
location: str, location: str,
lang: str = "zh", lang: str = "zh",
unit: str = "m", unit: str = "m",
dev: bool = True, dev: bool = get_memory_data("is_dev", True),
) -> dict: ) -> dict:
url_path = "v7/weather/%dh?" % (24 if dev else 168) url_path = "v7/weather/%dh?" % (24 if dev else 168)
url = dev_url + url_path if dev else com_url + url_path url = dev_url + url_path if dev else com_url + url_path
@ -115,7 +153,7 @@ async def get_airquality(
lang: str, lang: str,
pollutant: bool = False, pollutant: bool = False,
station: bool = False, station: bool = False,
dev: bool = True dev: bool = get_memory_data("is_dev", True),
) -> dict: ) -> dict:
url_path = f"airquality/v1/now/{location}?" url_path = f"airquality/v1/now/{location}?"
url = dev_url + url_path if dev else com_url + url_path url = dev_url + url_path if dev else com_url + url_path

View File

@ -1,4 +1,4 @@
from nonebot import require from nonebot import require, on_endswith
from nonebot.adapters.onebot.v11 import MessageSegment from nonebot.adapters.onebot.v11 import MessageSegment
from nonebot.internal.matcher import Matcher from nonebot.internal.matcher import Matcher
@ -7,7 +7,7 @@ from liteyuki.utils.base.ly_typing import T_MessageEvent
from .qw_api import * from .qw_api import *
from liteyuki.utils.base.data_manager import User, user_db from liteyuki.utils.base.data_manager import User, user_db
from liteyuki.utils.base.language import get_user_lang from liteyuki.utils.base.language import Language, get_user_lang
from liteyuki.utils.base.resource import get_path from liteyuki.utils.base.resource import get_path
from liteyuki.utils.message.html_tool import template2image from liteyuki.utils.message.html_tool import template2image
@ -24,40 +24,50 @@ from nonebot_plugin_alconna import on_alconna, Alconna, Args, MultiVar, Arparma
).handle() ).handle()
async def _(result: Arparma, event: T_MessageEvent, matcher: Matcher): async def _(result: Arparma, event: T_MessageEvent, matcher: Matcher):
"""await alconna.send("weather", city)""" """await alconna.send("weather", city)"""
ulang = get_user_lang(str(event.user_id)) kws = result.main_args.get("keywords")
image = await get_weather_now_card(matcher, event, kws)
await matcher.finish(MessageSegment.image(image))
@on_endswith(("天气", "weather")).handle()
async def _(event: T_MessageEvent, matcher: Matcher):
"""await alconna.send("weather", city)"""
kws = event.message.extract_plain_text()
image = await get_weather_now_card(matcher, event, [kws.replace("天气", "").replace("weather", "")], False)
await matcher.finish(MessageSegment.image(image))
async def get_weather_now_card(matcher: Matcher, event: T_MessageEvent, keyword: list[str], tip: bool = True):
ulang = get_user_lang(event.user_id)
qw_lang = get_qw_lang(ulang.lang_code) qw_lang = get_qw_lang(ulang.lang_code)
key = get_config("weather_key") key = get_config("weather_key")
is_dev = get_config("weather_dev") is_dev = get_memory_data("weather.is_dev", True)
user: User = user_db.first(User(), "user_id = ?", event.user_id, default=User())
user: User = user_db.first(User(), "user_id = ?", str(event.user_id), default=User())
# params # params
unit = user.profile.get("unit", "m") unit = user.profile.get("unit", "m")
stored_location = user.profile.get("location", None) stored_location = user.profile.get("location", None)
if not key: if not key:
await matcher.finish(ulang.get("weather.no_key")) await matcher.finish(ulang.get("weather.no_key") if tip else None)
kws = result.main_args.get("keywords") if keyword:
if kws: if len(keyword) >= 2:
if len(kws) >= 2: adm = keyword[0]
adm = kws[0] city = keyword[-1]
city = kws[-1]
else: else:
adm = "" adm = ""
city = kws[0] city = keyword[0]
city_info = await city_lookup(city, key, adm=adm, lang=qw_lang) city_info = await city_lookup(city, key, adm=adm, lang=qw_lang)
city_name = " ".join(kws) city_name = " ".join(keyword)
else: else:
if not stored_location: if not stored_location:
await matcher.finish(ulang.get("liteyuki.invalid_command", TEXT="location")) await matcher.finish(ulang.get("liteyuki.invalid_command", TEXT="location") if tip else None)
city_info = await city_lookup(stored_location, key, lang=qw_lang) city_info = await city_lookup(stored_location, key, lang=qw_lang)
city_name = stored_location city_name = stored_location
if city_info.code == "200": if city_info.code == "200":
location_data = city_info.location[0] location_data = city_info.location[0]
else: else:
await matcher.finish(ulang.get("weather.city_not_found", CITY=city_name)) await matcher.finish(ulang.get("weather.city_not_found", CITY=city_name) if tip else None)
weather_now = await get_weather_now(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev) weather_now = await get_weather_now(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev)
weather_daily = await get_weather_daily(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev) weather_daily = await get_weather_daily(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev)
weather_hourly = await get_weather_hourly(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev) weather_hourly = await get_weather_hourly(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev)
@ -76,9 +86,10 @@ async def _(result: Arparma, event: T_MessageEvent, matcher: Matcher):
"weatherHourly": weather_hourly, "weatherHourly": weather_hourly,
"aqi" : aqi, "aqi" : aqi,
"location" : location_data.dump(), "location" : location_data.dump(),
"localization" : get_local_data(ulang.lang_code)
} }
}, },
debug=True, debug=True,
wait=1 wait=1
) )
await matcher.finish(MessageSegment.image(image)) return image

View File

@ -0,0 +1,11 @@
weather.monday=Mon
weather.tuesday=Tue
weather.wednesday=Wed
weather.thursday=Thu
weather.friday=Fri
weather.saturday=Sat
weather.sunday=Sun
weather.day=Day
weather.night=Night
weather.today=Today
weather.tomorrow=Tomorrow

View File

@ -0,0 +1,11 @@
weather.monday=月
weather.tuesday=火
weather.wednesday=水
weather.thursday=木
weather.friday=金
weather.saturday=土
weather.sunday=日
weather.day=昼
weather.night=夜
weather.today=今日
weather.tomorrow=明日

View File

@ -0,0 +1,11 @@
weather.monday=周一
weather.tuesday=周二
weather.wednesday=周三
weather.thursday=周四
weather.friday=周五
weather.saturday=周六
weather.sunday=周日
weather.day=白天
weather.night=夜晚
weather.today=今天
weather.tomorrow=明天

View File

@ -0,0 +1,3 @@
name: 轻雪天气资源包
description: For Liteyuki Weather
version: 2024.4.26

View File

@ -0,0 +1,184 @@
:root {
--main-text-color: #fff;
--sub-text-color: #ccc;
--tip-text-color: #999;
--device-info-width: 240px;
--sub-border-radius: 60px;
}
#weather-info {
color: white;
/*justify-content: center;*/
/*align-items: center;*/
/*align-content: center;*/
}
.icon {
/* icon 类img阴影*/
filter: drop-shadow(1px 1px 10px #00000044);
}
#main-info {
display: flex;
justify-content: center;
align-items: center;
}
#main-left {
display: flex;
justify-content: flex-end;
width: 50%;
}
#main-right {
width: 50%;
}
#time {
font-size: 25px;
font-weight: bold;
font-style: italic;
text-align: right;
color: var(--sub-text-color);
}
#adm {
font-size: 32px;
font-weight: bold;
text-align: center;
color: var(--sub-text-color);
}
#city {
margin-top: 20px;
font-size: 70px;
font-weight: bold;
text-align: center;
}
#temperature {
display: flex;
align-items: baseline;
}
#temperature-now {
font-size: 70px;
font-weight: bold;
}
#temperature-range {
font-size: 40px;
font-weight: bold;
color: var(--sub-text-color);
}
#description {
font-size: 50px;
font-weight: bold;
}
#aqi {
height: 50px;
display: flex;
border-radius: 60px;
padding: 5px;
font-size: 40px;
text-align: center;
align-content: center;
align-items: center;
justify-content: center;
}
#aqi-dot {
height: 80%;
aspect-ratio: 1 / 1;
border-radius: 50%;
background-color: var(--sub-text-color);
margin-right: 20px;
}
.main-icon {
width: 240px;
height: 240px;
}
#hours-info {
display: flex;
justify-content: space-between;
}
.hourly-item {
text-align: center;
background-color: #ffffff44;
border-radius: var(--sub-border-radius);
align-items: center;
padding: 20px 10px;
}
.hourly-icon{
width: 80%;
margin-bottom: 20px;
}
.hourly-temperature {
text-align: center;
color: var(--main-text-color);
font-size: 30px;
margin-bottom: 20px;
}
.hourly-time {
text-align: center;
color: var(--main-text-color);
font-size: 25px;
margin-bottom: 10px;
}
/**/
.daily-item {
display: flex;
position: relative;
justify-content: space-between;
align-items: center;
background-color: #ffffff44;
height: 90px;
border-radius: var(--sub-border-radius);
margin-bottom: 20px;
padding: 0 30px;
}
/*最后一个没有margin_button*/
.daily-item:last-child {
margin-bottom: 0;
}
.icon-day {
position: absolute;
left: 60%;
height: 80px;
}
.icon-night {
position: absolute;
left: 70%;
height: 80px;
}
.daily-weather{
position: absolute;
left: 30%;
}
.daily-temperature{
position: absolute;
left: 83%;
}
.daily-day, .daily-weather, .daily-temperature {
text-align: center;
color: var(--main-text-color);
font-size: 30px;
}

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -0,0 +1,114 @@
/**
* @typedef {Object} Location
* @property {string} city - The city name.
* @property {string} country - The country name.
*
* @typedef {Object} Weather
* @property {number} temperature - The current temperature.
* @property {string} description - The weather description.
*
* @typedef {Object} Data
* @property {Location} location - The location data.
* @property {Weather} weather - The weather data.
*/
/** @type {Data} */
let data = JSON.parse(document.getElementById("data").innerText)
let localData = data["localization"] // 本地化数据
let weatherNow = data["weatherNow"]
let weatherDaily = data["weatherDaily"]
let weatherHourly = data["weatherHourly"]
let aqi = data["aqi"]
let locationData = data["location"]
// 处理aqi
let aqiValue = 0
aqi["aqi"].forEach(
(item) => {
if (item["defaultLocalAqi"]) {
document.getElementById("aqi-data").innerText = "AQI " + item["valueDisplay"] + " " + item["category"]
// 将(255,255,255)这种格式的颜色设置给css
document.getElementById("aqi-dot").style.backgroundColor = "rgb(" + item["color"] + ")"
}
}
)
templates = {
"time": weatherNow["now"]["obsTime"],
"city": locationData["name"],
"adm": locationData["country"] + " " + locationData["adm1"] + " " + locationData["adm2"],
"temperature-now": weatherNow["now"]["temp"] + "°",
"temperature-range": weatherDaily["daily"][0]["tempMin"] + "°/" + weatherDaily["daily"][0]["tempMax"] + "°",
"description": weatherNow["now"]["text"]
}
// 遍历每一个id给其赋值
for (let id in templates) {
document.getElementById(id).innerText = templates[id]
}
let maxHourlyItem = 8
let percentWidth = 1 / (maxHourlyItem * 1.5) * 100
let hourlyStep = 2 // n小时一个数据
let hourlyCount = 0
weatherHourly['hourly'].forEach(
(item, index) => {
if (index % hourlyStep !== 0) {
return
}
if (hourlyCount >= maxHourlyItem) {
return
}
let hourlyItemDiv = document.importNode(document.getElementById("hourly-item-template").content, true)
hourlyItemDiv.className = "hourly-item"
hourlyItemDiv.querySelector('.hourly-icon').setAttribute("src", `./img/qw_icon/${item["icon"]}.png`)
hourlyItemDiv.querySelector('.hourly-time').innerText = get_time_hour(item["fxTime"])
hourlyItemDiv.querySelector('.hourly-temperature').innerText = " " + item["temp"] + "°"
// 设置最大宽度
hourlyItemDiv.querySelector('.hourly-item').style.maxWidth = percentWidth + "%"
hourlyItemDiv.querySelector('.hourly-icon').style.maxWidth = "100%"
document.getElementById("hours-info").appendChild(hourlyItemDiv)
hourlyCount++
}
)
let maxDailyItem = 7
// 第一和第二天用today和tomorrow后面用星期X英文小写
let daysStandard = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']
let todayDay = new Date().getDay()
let days = [localData['today'], localData['tomorrow']]
for (let i = 0; i < 5; i++) {
days.push(localData[daysStandard[(todayDay + i) % 7]])
}
weatherDaily['daily'].forEach(
(item, index) => {
if (index >= maxDailyItem) {
return
}
let today = days[index]
if (index >= 2){
today += `(${item["fxDate"].split("-")[1]}.${item["fxDate"].split("-")[2]})`
}
let dailyItemDiv = document.importNode(document.getElementById("daily-item-template").content, true)
dailyItemDiv.querySelector('.icon-day').setAttribute("src", `./img/qw_icon/${item["iconDay"]}.png`)
dailyItemDiv.querySelector('.icon-night').setAttribute("src", `./img/qw_icon/${item["iconNight"]}.png`)
dailyItemDiv.querySelector('.daily-day').innerText = today
dailyItemDiv.querySelector('.daily-weather').innerText = item["textDay"]
dailyItemDiv.querySelector('.daily-temperature').innerText = item["tempMin"] + "°~" + item["tempMax"] + "°"
document.getElementById('days-info').appendChild(dailyItemDiv)
}
)
function get_time_hour(fxTime) {
// fxTime 2024-05-03T02:00+08:00'
return fxTime.split("T")[1].split("+")[0]
}

View File

@ -0,0 +1,73 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Liteyuki Status</title>
<link rel="stylesheet" href="./css/card.css">
<link rel="stylesheet" href="./css/fonts.css">
<link rel="stylesheet" href="css/weather_now.css">
</head>
<!-- qw_icon: https://a.hecdn.net/img/common/icon/202106d/%d.png-->
<body>
<template id="hourly-item-template">
<div class="hourly-item">
<img class="hourly-icon icon" src="./img/qw_icon/101.png" alt="WeatherIcon">
<div class="hourly-temperature">90°</div>
<!-- <div class="hourly-windDir">None</div>-->
<div class="hourly-time">02:00</div>
</div>
</template>
<template id="daily-item-template">
<div class="daily-item">
<div class="daily-day">
周八
</div>
<div class="daily-weather">
小水
</div>
<img class="daily-icon icon-day icon" src="./img/qw_icon/101.png" alt="WeatherIcon">
<img class="daily-icon icon-night icon" src="./img/qw_icon/101.png" alt="WeatherIcon">
<div class="daily-temperature">
12°~23°
</div>
</div>
</template>
<div class="data-storage" id="data">{{ data | tojson }}</div>
<div class="info-box" id="weather-info">
<div id="detail-info">
<div id="time">2045-01-12 22:22:22</div>
<div id="adm">枫丹 白露 白露区</div>
<div id="city">白露区</div>
</div>
<div id="main-info">
<div id="main-left">
<img class="main-icon icon" src="./img/qw_icon/101.png" alt="WeatherIcon">
</div>
<div id="main-right">
<div id="temperature">
<div id="temperature-now">
90°
</div>
<div id="temperature-range">
10°~90°
</div>
</div>
<div id="description">
示例天气
</div>
</div>
</div>
<div id="aqi">
<div id="aqi-dot"></div>
<div id="aqi-data"> AQI 114 优</div>
</div>
</div>
<div class="info-box" id="sub-info"></div>
<div class="info-box" id="hours-info"></div>
<div class="info-box" id="days-info"></div>
<script src="./js/card.js"></script>
<script src="js/weather_now.js"></script>
</body>

View File

@ -157,3 +157,4 @@ status.seconds=Sekunden
status.cores=Kerne status.cores=Kerne
status.threads=Threads status.threads=Threads
status.process=Prozesse status.process=Prozesse
status.description=Liteyuki Bot-Status

View File

@ -157,3 +157,4 @@ status.seconds=Seconds
status.cores=Cores status.cores=Cores
status.threads=Threads status.threads=Threads
status.process=Processes status.process=Processes
status.description=Liteyuki Dashboard

View File

@ -157,3 +157,4 @@ status.seconds=Segundos
status.cores=Núcleos status.cores=Núcleos
status.threads=Hilos status.threads=Hilos
status.process=Procesos status.process=Procesos
status.description=Liteyuki Dashboard Status

View File

@ -157,3 +157,4 @@ status.seconds=秒
status.cores=コア status.cores=コア
status.threads=スレッド status.threads=スレッド
status.process=プロセス status.process=プロセス
status.description=軽雪監視パネル

View File

@ -157,3 +157,4 @@ status.seconds=秒
status.cores=核心 status.cores=核心
status.threads=线程 status.threads=线程
status.process=进程 status.process=进程
status.description=轻雪机器人状态面板

View File

@ -11,9 +11,15 @@ body {
} }
.info-box { .info-box {
border-radius: 30px; border-radius: 50px;
padding: 30px; padding: 30px;
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.5);
margin-bottom: 20px; margin-bottom: 20px;
} }
#author-description {
text-align: center;
color: var(--sub-text-color);
font-size: 30px;
}

View File

@ -1,50 +1,46 @@
:root { :root {
--main-text-color: #fff; --main-text-color: #fff;
--sub-text-color: #bbb; --sub-text-color: #ccc;
--tip-text-color: #888; --tip-text-color: #999;
--device-info-width: 240px; --device-info-width: 240px;
} }
.bot-info { .bot-info {
display: flex; display: flex;
height: 200px;
} }
.bot-icon { .bot-icon {
display: flex; display: flex;
height: 100%; width: 220px;
aspect-ratio: 1;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
margin-right: 20px; margin-right: 20px;
} }
.bot-icon-img { .bot-icon-img {
height: 220px;
border-radius: 50%; border-radius: 50%;
height: 100%;
width: 100%;
background-color: white; background-color: white;
} }
.bot-name { .bot-name {
color: var(--main-text-color); color: var(--main-text-color);
display: flex; display: flex;
font-size: 40px; font-size: 45px;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
} }
.bot-tag { .bot-tag {
white-space: nowrap; white-space: break-spaces;
color: var(--sub-text-color); color: var(--sub-text-color);
font-size: 27px; font-size: 30px;
font-weight: 700; font-weight: 700;
line-height: 1.6; line-height: 1.6;
} }
.bot-tag[suffix="1"]::after { .bot-tag[suffix="1"]::after {
content: " | "; content: "|";
display: inline-block; display: inline-block;
margin: 0 5px; margin: 0 5px;
height: 30%; height: 30%;
@ -128,3 +124,4 @@
color: var(--sub-text-color); color: var(--sub-text-color);
text-align: right; text-align: right;
} }

View File

@ -1,83 +0,0 @@
#weather-info {
color: white;
/*justify-content: center;*/
/*align-items: center;*/
/*align-content: center;*/
}
#main-info {
display: flex;
justify-content: center;
align-items: center;
}
#time {
font-size: 25px;
font-weight: bold;
font-style: italic;
text-align: right;
color: #aaa;
}
#adm {
font-size: 30px;
font-weight: bold;
text-align: center;
color: #aaa;
}
#city {
margin-top: 20px;
font-size: 70px;
font-weight: bold;
text-align: center;
}
#temperature {
display: flex;
align-items: baseline;
}
#temperature-now {
font-size: 70px;
font-weight: bold;
}
#temperature-range {
font-size: 40px;
font-weight: bold;
color: #aaa;
}
#description {
font-size: 50px;
font-weight: bold;
}
#aqi {
height: 50px;
display: flex;
border-radius: 60px;
padding: 5px;
font-size: 40px;
text-align: center;
align-content: center;
align-items: center;
justify-content: center;
}
#aqi-dot {
height: 80%;
aspect-ratio: 1 / 1;
border-radius: 50%;
background-color: #aaa;
margin-right: 20px;
}
.main-icon {
width: 240px;
height: 240px;
}

View File

@ -9,3 +9,9 @@ const bgs = [
] ]
// 随机选择背景图片 // 随机选择背景图片
document.body.style.backgroundImage = `url(./img/${bgs[Math.floor(Math.random() * bgs.length)]})`; document.body.style.backgroundImage = `url(./img/${bgs[Math.floor(Math.random() * bgs.length)]})`;
// body后插入info-box id=description
let descriptionDiv = document.createElement("div");
descriptionDiv.className = 'info-box'
descriptionDiv.id = 'author-description'
descriptionDiv.innerText = 'Designed by SnowyKami'
document.body.appendChild(descriptionDiv);

View File

@ -290,6 +290,8 @@ function main() {
document.getElementById('motto-text').innerText = mottoText document.getElementById('motto-text').innerText = mottoText
document.getElementById('motto-from').innerText = mottoFrom document.getElementById('motto-from').innerText = mottoFrom
document.getElementById('author-description').innerText = localData['description'] + ' Powered by Liteyuki'
} }

View File

@ -1,59 +0,0 @@
/**
* @typedef {Object} Location
* @property {string} city - The city name.
* @property {string} country - The country name.
*
* @typedef {Object} Weather
* @property {number} temperature - The current temperature.
* @property {string} description - The weather description.
*
* @typedef {Object} Data
* @property {Location} location - The location data.
* @property {Weather} weather - The weather data.
*/
/** @type {Data} */
let data = JSON.parse(document.getElementById("data").innerText)
let weatherNow = data["weatherNow"]
let weatherDaily = data["weatherDaily"]
let weatherHourly = data["weatherHourly"]
let aqi = data["aqi"]
let locationData = data["location"]
// set info
// document.getElementById("time").innerText = weatherNow["now"]["obsTime"]
// document.getElementById("city").innerText = locationData["name"]
// document.getElementById("adm").innerText = locationData["country"] + " " + locationData["adm1"] + " " + locationData["adm2"]
// document.getElementById("temperature-now").innerText = weatherNow["now"]["temp"] + "°"
// document.getElementById("temperature-range").innerText = weatherNow["now"]["feelsLike"] + "°"
// document.getElementById("description").innerText = weatherNow["now"]["text"]
// 处理aqi
let aqiValue = 0
aqi["aqi"].forEach(
(item) => {
if (item["defaultLocalAqi"]) {
document.getElementById("aqi-data").innerText = "AQI " + item["valueDisplay"] + " " + item["category"]
// 将(255,255,255)这种格式的颜色设置给css
document.getElementById("aqi-dot").style.backgroundColor = "rgb(" + item["color"] + ")"
}
}
)
templates = {
"time": weatherNow["now"]["obsTime"],
"city": locationData["name"],
"adm": locationData["country"] + " " + locationData["adm1"] + " " + locationData["adm2"],
"temperature-now": weatherNow["now"]["temp"] + "°",
"temperature-range": weatherDaily["daily"][0]["tempMin"] + "°/" + weatherDaily["daily"][0]["tempMax"] + "°",
"description": weatherNow["now"]["text"]
}
// 遍历每一个id给其赋值
for (let id in templates) {
document.getElementById(id).innerText = templates[id]
}

View File

@ -1,48 +0,0 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Liteyuki Status</title>
<link rel="stylesheet" href="./css/card.css">
<link rel="stylesheet" href="./css/fonts.css">
<link rel="stylesheet" href="./css/weather_now.css">
</head>
<!-- qw_icon: https://a.hecdn.net/img/common/icon/202106d/%d.png-->
<body>
<div class="data-storage" id="data">{{ data | tojson }}</div>
<div class="info-box" id="weather-info">
<div id="detail-info">
<div id="time">1145-01-12 22:22:22</div>
<div id="adm">国家 一级 二级</div>
<div id="city">城市</div>
</div>
<div id="main-info">
<div>
<img class="main-icon" src="./img/qw_icon/101.png" alt="AAA">
</div>
<div>
<div id="temperature">
<div id="temperature-now">
90°
</div>
<div id="temperature-range">
10°~90°
</div>
</div>
<div id="description">
好天气
</div>
</div>
</div>
<div id="aqi">
<div id="aqi-dot"></div>
<div id="aqi-data"> AQI 114 优</div>
</div>
</div>
<div class="info-box" id="sub-info"></div>
<div class="info-box" id="hours-info"></div>
<div class="info-box" id="days-info"></div>
<script src="./js/card.js"></script>
<script src="./js/weather_now.js"></script>
</body>

Some files were not shown because too many files have changed in this diff Show More