Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd1dc2e3e6 | ||
|
|
bac4565289 | ||
|
|
25f755fd27 | ||
|
|
09ebfc1a76 | ||
|
|
ee9e4a4c0f | ||
|
|
c2bc49b0d4 | ||
|
|
fe8d850422 | ||
|
|
51023efc76 | ||
|
|
0c6a267967 | ||
|
|
28e1319ee3 | ||
|
|
c8b2662d02 | ||
|
|
c69dedba45 | ||
|
|
346b8c6289 | ||
|
|
35ff3d6e81 | ||
|
|
214ec5bf10 | ||
|
|
89c5ff2597 | ||
|
|
3b14691aea | ||
|
|
ae2cfce166 | ||
|
|
59f6561159 | ||
|
|
95254ab80c | ||
|
|
575de9463d | ||
|
|
5db51f09fc | ||
|
|
b6577f1926 | ||
|
|
4ca0b9afe9 | ||
|
|
3dc41d32c9 | ||
|
|
c8a6f4fbfe | ||
|
|
33bf556ed2 | ||
|
|
76e448ffa5 | ||
|
|
1899c24d62 | ||
|
|
2a6a91f2e6 | ||
|
|
03ab659d05 | ||
|
|
029da172c3 | ||
|
|
b1bd4ab1db | ||
|
|
ce5dc74071 | ||
|
|
fd376123b5 | ||
|
|
66fc365ca5 | ||
|
|
0b4d36cbea | ||
|
|
9d2f1e192c | ||
|
|
afaf92df35 | ||
|
|
b97e52f7a1 | ||
|
|
a820b4ae33 | ||
|
|
c31e0c7248 | ||
|
|
8f41fb5220 | ||
|
|
d28dde9ff3 | ||
|
|
0d6e6401e5 | ||
|
|
fba2694df3 | ||
|
|
f7973fa3ee | ||
|
|
56e3ea9afa | ||
|
|
a16d3a89d4 | ||
|
|
5ad45b5f1c | ||
|
|
9767bc9ef1 | ||
|
|
905709808b | ||
|
|
c076b521c6 | ||
|
|
07b8ee96f2 | ||
|
|
a83f6cb4ae | ||
|
|
312789e4cc | ||
|
|
1db1d0f545 | ||
|
|
ce30d6ef75 | ||
|
|
cfc75d5c13 | ||
|
|
e0dcf8af56 | ||
|
|
3c019ad98c | ||
|
|
99bc9481da | ||
|
|
bb50346e60 | ||
|
|
7b68df55b2 | ||
|
|
8fa7b295b3 | ||
|
|
5375225840 | ||
|
|
49d01f5ec6 | ||
|
|
6b2acd344e |
2
.gitignore
vendored
@@ -1,7 +1,5 @@
|
||||
*.yml
|
||||
!config.yml
|
||||
*.token
|
||||
*.zip
|
||||
*.log
|
||||
*.log.*
|
||||
**/__pycache__
|
||||
|
||||
735
QWeather.py
@@ -2,742 +2,13 @@
|
||||
# -- coding:utf-8 --
|
||||
# @Author: markushammered@gmail.com
|
||||
# @Development Tool: PyCharm
|
||||
# @Create Time: 2021/10/23
|
||||
# @Create Time: 2021/12/16
|
||||
# @File Name: QWeather.py
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import pandas
|
||||
import smtplib
|
||||
import getpass
|
||||
import requests
|
||||
import argparse
|
||||
import multiprocessing
|
||||
import logging.handlers
|
||||
|
||||
from ruamel.yaml import YAML
|
||||
from email.header import Header
|
||||
from email.mime.text import MIMEText
|
||||
from colorlog import ColoredFormatter
|
||||
from email.mime.image import MIMEImage
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
|
||||
|
||||
class SendWeatherMail:
|
||||
def __init__(self):
|
||||
with open(CONFIG_NAME, 'r', encoding='utf-8') as config_f:
|
||||
self.config = YAML().load(config_f)
|
||||
|
||||
self.location = self.config['request-settings']['location'] # 城市ID
|
||||
self.key = self.config['request-settings']['key'] # API密钥
|
||||
self.unit = self.config['request-settings']['unit'] # 度量单位
|
||||
self.lang = self.config['request-settings']['lang'] # 语言
|
||||
self.mode = self.config['request-settings']['mode'] # 发送模式 --仅作判断标识
|
||||
self.receiver = self.config['request-settings']['receiver'] # 接收者邮箱 --> 列表 [无论有几个人都必须是列表]
|
||||
self.sender = self.config['mail-settings']['sender'] # 发送者邮箱
|
||||
self.password = self.config['mail-settings']['password'] # 服务器登录密码
|
||||
self.server = self.config['mail-settings']['server'] # 邮箱服务器
|
||||
self.port = self.config['mail-settings']['port'] # 邮箱端口号
|
||||
self.enableSSL = self.config['client-settings'] # 是否使用SSL连接到邮箱服务器
|
||||
self.city_name = self.config['only-view-settings']['city-name'] # 城市名称仅作邮件内容
|
||||
|
||||
self.dev_url = 'https://devapi.qweather.com/v7/weather/7d' # 开发版本天气信息url
|
||||
self.free_url = 'https://devapi.qweather.com/v7/weather/3d' # 免费版本天气信息url
|
||||
self.warning_url = 'https://devapi.qweather.com/v7/warning/now' # 自然灾害预警信息url
|
||||
|
||||
self.headers = {'Accept-Encoding': 'gzip, deflate'} # 请求头
|
||||
|
||||
self.params = {'location': self.location,
|
||||
'key': self.key,
|
||||
'unit': self.unit,
|
||||
'lang': self.lang} # GET方式请求参数
|
||||
|
||||
self.message = MIMEMultipart('related')
|
||||
self.message['From'] = Header('QWeather') # 发件人名称
|
||||
self.message['To'] = Header('All allowed User') # 收件人显示名称
|
||||
self.msg_content = MIMEMultipart('alternative') # 文字目录
|
||||
|
||||
if self.enableSSL:
|
||||
self.smtp = smtplib.SMTP_SSL(self.server, self.port) # 登录服务器 使用SSL连接
|
||||
else:
|
||||
self.smtp = smtplib.SMTP(self.server, self.port) # 登录邮箱服务器 不使用SSL连接
|
||||
|
||||
def dev_mode(self):
|
||||
"""
|
||||
开发版本发送邮件
|
||||
[全七天] | date: 日期 (1-7)
|
||||
[全七天] | day_weather: 白天天气 (1-7)
|
||||
[全七天] | night_weather: 晚上天气 (1-7)
|
||||
[全七天] | temperature_max: 最高气温 (1-7)
|
||||
[全七天] | temperature_min: 最低气温 (1-7)
|
||||
[全七天] | icon_list: 白天和晚上的天气图标 (1-14)
|
||||
[第一天] | sunrise: 日出时间 (1)
|
||||
[第一天] | sunset: 日落时间 (1)
|
||||
[第一天] | humidity: 空气湿度(相对) (1)
|
||||
[第一天] | wind_speed: 风速 (1)
|
||||
[第一天] | wind_scale: 风级 (1)
|
||||
[第一天] | wind_dir: 风向 (1)
|
||||
[第一天] | ultraviolet_rays: 紫外线强度指数 (1)
|
||||
[第一天] | cloud: 云量(相对) (1)
|
||||
[第一天] | pressure: 气压 (1)
|
||||
[第一天] | vis: 能见度 (1)
|
||||
:return: None
|
||||
"""
|
||||
r_day = session.get(self.dev_url, headers=self.headers, params=self.params)
|
||||
|
||||
weather_day_text = r_day.json() # 使用json加载数据
|
||||
logger.info(f'{language["request_result_weather"]}:{r_day}')
|
||||
|
||||
# 1-7天数据
|
||||
day_1 = weather_day_text['daily'][0]
|
||||
day_2 = weather_day_text['daily'][1]
|
||||
day_3 = weather_day_text['daily'][2]
|
||||
day_4 = weather_day_text['daily'][3]
|
||||
day_5 = weather_day_text['daily'][4]
|
||||
day_6 = weather_day_text['daily'][5]
|
||||
day_7 = weather_day_text['daily'][6]
|
||||
|
||||
date = [day_1['fxDate'][5:], day_2['fxDate'][5:], day_3['fxDate'][5:], day_4['fxDate'][5:], day_5['fxDate'][5:],
|
||||
day_6['fxDate'][5:],
|
||||
day_7['fxDate'][5:]]
|
||||
day_weather = [day_1['textDay'], day_2['textDay'], day_3['textDay'], day_4['textDay'], day_5['textDay'],
|
||||
day_6['textDay'], day_7['textDay']]
|
||||
night_weather = [day_1['textNight'], day_2['textNight'], day_3['textNight'], day_4['textNight'],
|
||||
day_5['textNight'], day_6['textNight'], day_7['textNight']]
|
||||
temperature_max = [day_1['tempMax'], day_2['tempMax'], day_3['tempMax'], day_4['tempMax'], day_5['tempMax'],
|
||||
day_6['tempMax'], day_7['tempMax']]
|
||||
temperature_min = [day_1['tempMin'], day_2['tempMin'], day_3['tempMin'], day_4['tempMin'], day_5['tempMin'],
|
||||
day_6['tempMin'], day_7['tempMin']]
|
||||
icon_list = [day_1['iconDay'], day_2['iconDay'], day_3['iconDay'], day_4['iconDay'], day_5['iconDay'],
|
||||
day_6['iconDay'], day_7['iconDay'], day_1['iconNight'], day_2['iconNight'], day_3['iconNight'],
|
||||
day_4['iconNight'], day_5['iconNight'], day_6['iconNight'], day_7['iconNight']]
|
||||
|
||||
sunset = day_1['sunset']
|
||||
sunrise = day_1['sunrise']
|
||||
humidity = day_1['humidity']
|
||||
wind_speed = day_1['windSpeedDay']
|
||||
wind_scale = day_1['windScaleDay']
|
||||
wind_dir = day_1['windDirDay']
|
||||
ultraviolet_rays = day_1['uvIndex']
|
||||
cloud = day_1['cloud']
|
||||
pressure = day_1['pressure']
|
||||
vis = day_1['vis']
|
||||
|
||||
# 邮件内容主体
|
||||
mail_html = f"""
|
||||
<p style="text-align: center">
|
||||
<i>
|
||||
<b>
|
||||
{language['area']}:{self.city_name}
|
||||
<br>
|
||||
{language['sender']}:{self.sender}
|
||||
</b>
|
||||
</i>
|
||||
</p>
|
||||
<br />
|
||||
<table style="border: 0; text-align: center; margin:0 auto">
|
||||
<tr>
|
||||
<th>| {language['date']} </th>
|
||||
<th>| {language['weather']} </th>
|
||||
<th>| {language['lowestTemp']} </th>
|
||||
<th>| {language['highestTemp']} </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<!--日期 天气 最低 最高/Date Weather LowestTemp HighestTemp-->
|
||||
<td>{language['today']}</td>
|
||||
<td>{day_weather[0]}<img src="cid:img1" width="20" alt="">/{night_weather[0]}<img src="cid:img2"
|
||||
width="20" alt=""></td> <td>{temperature_min[0]}℃</td>
|
||||
<td>{temperature_max[0]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{date[1]}</td>
|
||||
<td>{day_weather[1]}<img src="cid:img3" width="20" alt="">/{night_weather[1]}<img src="cid:img4"
|
||||
width="20" alt=""></td> <td>{temperature_min[1]}℃</td>
|
||||
<td>{temperature_max[1]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{date[2]}</td>
|
||||
<td>{day_weather[2]}<img src="cid:img5" width="20" alt="">/{night_weather[2]}<img src="cid:img6"
|
||||
width="20" alt=""></td> <td>{temperature_min[2]}℃</td>
|
||||
<td>{temperature_max[2]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{date[3]}</td>
|
||||
<td>{day_weather[3]}<img src="cid:img7" width="20" alt="">/{night_weather[3]}<img src="cid:img8"
|
||||
width="20" alt=""></td> <td>{temperature_min[3]}℃</td>
|
||||
<td>{temperature_max[3]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{date[4]}</td>
|
||||
<td>{day_weather[4]}<img src="cid:img9" width="20" alt="">/{night_weather[4]}<img src="cid:img10"
|
||||
width="20" alt=""></td> <td>{temperature_min[4]}℃</td>
|
||||
<td>{temperature_max[4]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{date[5]}</td>
|
||||
<td>{day_weather[5]}<img src="cid:img11" width="20" alt="">/{night_weather[5]}<img src="cid:img12"
|
||||
width="20" alt=""></td> <td>{temperature_min[5]}℃</td>
|
||||
<td>{temperature_max[5]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{date[6]}</td>
|
||||
<td>{day_weather[6]}<img src="cid:img13" width="20" alt="">/{night_weather[6]}<img src="cid:img14"
|
||||
width="20" alt=""></td> <td>{temperature_min[6]}℃</td>
|
||||
<td>{temperature_max[6]}℃</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table style="border: 0;text-align: center; margin:0 auto">
|
||||
<tr>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{language['wind_info']}</th>
|
||||
<th>{language['humidity']}</th>
|
||||
<th>{language['uv_info']}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{wind_speed}m/s {wind_scale} {wind_dir} </td>
|
||||
<td>{humidity}% </td>
|
||||
<td>{ultraviolet_rays} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{language['vis']}</th>
|
||||
<th>{language['pressure']}</th>
|
||||
<th>{language['cloud']}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{vis}km </td>
|
||||
<td>{pressure}hPa </td>
|
||||
<td>{cloud}% </td>
|
||||
</tr>
|
||||
</table>
|
||||
<table style="border: 0;text-align: center; margin:0 auto">
|
||||
<tr>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="cid:sunrise" alt="Sunrise" width="30"></td>
|
||||
<td> </td>
|
||||
<td><img src="cid:sunset" alt="Sunset" width="30"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> {sunrise} </td>
|
||||
<td>---------------</td>
|
||||
<td> {sunset} </td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="text-align: center;" id="About">
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<i>
|
||||
<b>
|
||||
<a href="https://dev.qweather.com/" style="color: black" target="_blank">QWeather</a>
|
||||
<a style="color: black"> · </a>
|
||||
<a href="https://github.com/MarkusJoe/QWeather" style="color: black" target="_blank">Github Repo</a>
|
||||
</b>
|
||||
</i>
|
||||
</div>"""
|
||||
self.message['Subject'] = language['subject_7'] # 邮件标题
|
||||
self.message.attach(MIMEText(mail_html, 'html', 'utf-8'))
|
||||
|
||||
# 循环将图片attach到html里
|
||||
image_count = 1
|
||||
for image_resource in icon_list:
|
||||
with open(f'./resource/icons/{image_resource}.png', 'rb') as fp:
|
||||
MyImage = MIMEImage(fp.read())
|
||||
MyImage.add_header('Content-ID', f'img{image_count}')
|
||||
self.message.attach(MyImage)
|
||||
image_count += 1
|
||||
|
||||
with open('./resource/basic-resources/sunrise.png', 'rb') as sr_f:
|
||||
sunrise_img = MIMEImage(sr_f.read())
|
||||
sunrise_img.add_header('Content-ID', 'sunrise')
|
||||
self.message.attach(sunrise_img)
|
||||
with open('./resource/basic-resources/sunset.png', 'rb') as ss_f:
|
||||
sunset_img = MIMEImage(ss_f.read())
|
||||
sunset_img.add_header('Content-ID', 'sunset')
|
||||
self.message.attach(sunset_img)
|
||||
|
||||
try:
|
||||
self.smtp.login(self.sender, self.password) # 登录
|
||||
self.smtp.sendmail(self.sender, self.receiver, self.message.as_string()) # 发送
|
||||
except smtplib.SMTPException as e:
|
||||
logger.critical(f'{language["mail_error"]}:', e)
|
||||
sys.exit(1)
|
||||
|
||||
def free_mode(self):
|
||||
"""
|
||||
免费版本发送邮件
|
||||
:return:
|
||||
"""
|
||||
f_request = session.get(self.free_url, headers=self.headers, params=self.params)
|
||||
f_weather = f_request.json()
|
||||
|
||||
logger.info(f'{language["request_result_weather"]}:{f_request}')
|
||||
|
||||
day_1 = f_weather['daily'][0]
|
||||
day_2 = f_weather['daily'][1]
|
||||
day_3 = f_weather['daily'][2]
|
||||
|
||||
date = [day_1['fxDate'][5:], day_2['fxDate'][5:], day_2['fxDate'][5:]]
|
||||
day_weather = [day_1['textDay'], day_2['textDay'], day_2['textDay']]
|
||||
night_weather = [day_1['textNight'], day_2['textNight'], day_2['textNight']]
|
||||
temperature_max = [day_1['tempMax'], day_2['tempMax'], day_3['tempMax']]
|
||||
temperature_min = [day_1['tempMin'], day_2['tempMin'], day_2['tempMin']]
|
||||
icon_list = [day_1['iconDay'], day_1['iconDay'], day_3['iconDay'], day_1['iconNight'], day_2['iconNight'],
|
||||
day_3['iconNight']]
|
||||
|
||||
sunset = day_1['sunset']
|
||||
sunrise = day_1['sunrise']
|
||||
humidity = day_1['humidity']
|
||||
wind_speed = day_1['windSpeedDay']
|
||||
wind_scale = day_1['windScaleDay']
|
||||
wind_dir = day_1['windDirDay']
|
||||
ultraviolet_rays = day_1['uvIndex']
|
||||
cloud = day_1['cloud']
|
||||
pressure = day_1['pressure']
|
||||
vis = day_1['vis']
|
||||
|
||||
mail_html = f"""
|
||||
<p style="text-align: center">
|
||||
<i>
|
||||
<b>
|
||||
{language['area']}:{self.city_name}
|
||||
<br>
|
||||
{language['sender']}:{self.sender}
|
||||
</b>
|
||||
</i>
|
||||
</p>
|
||||
<br />
|
||||
<table style="border: 0; text-align: center; margin:0 auto">
|
||||
<tr>
|
||||
<th>| {language['date']} </th>
|
||||
<th>| {language['weather']} </th>
|
||||
<th>| {language['lowestTemp']} </th>
|
||||
<th>| {language['highestTemp']} </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<!--日期 天气 最低 最高/Date Weather LowestTemp HighestTemp-->
|
||||
<td>{language['today']}</td>
|
||||
<td>{day_weather[0]}<img src="cid:img1" width="20" alt="">/{night_weather[0]}<img src="cid:img2"
|
||||
width="20" alt=""></td> <td>{temperature_min[0]}℃</td>
|
||||
<td>{temperature_max[0]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{date[1]}</td>
|
||||
<td>{day_weather[1]}<img src="cid:img3" width="20" alt="">/{night_weather[1]}<img src="cid:img4"
|
||||
width="20" alt=""></td> <td>{temperature_min[1]}℃</td>
|
||||
<td>{temperature_max[1]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{date[2]}</td>
|
||||
<td>{day_weather[2]}<img src="cid:img5" width="20" alt="">/{night_weather[2]}<img src="cid:img6"
|
||||
width="20" alt=""></td> <td>{temperature_min[2]}℃</td>
|
||||
<td>{temperature_max[2]}℃</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table style="border: 0;text-align: center; margin:0 auto">
|
||||
<tr>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{language['wind_info']}</th>
|
||||
<th>{language['humidity']}</th>
|
||||
<th>{language['uv_info']}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{wind_speed}m/s {wind_scale} {wind_dir} </td>
|
||||
<td>{humidity}% </td>
|
||||
<td>{ultraviolet_rays} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{language['vis']}</th>
|
||||
<th>{language['pressure']}</th>
|
||||
<th>{language['cloud']}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{vis}km </td>
|
||||
<td>{pressure}hPa </td>
|
||||
<td>{cloud}% </td>
|
||||
</tr>
|
||||
</table>
|
||||
<table style="border: 0;text-align: center; margin:0 auto">
|
||||
<tr>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="cid:sunrise" alt="Sunrise" width="30"></td>
|
||||
<td> </td>
|
||||
<td><img src="cid:sunset" alt="Sunset" width="30"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> {sunrise} </td>
|
||||
<td>---------------</td>
|
||||
<td> {sunset} </td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="text-align: center;" id="About">
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<i>
|
||||
<b>
|
||||
<a href="https://dev.qweather.com/" style="color: black" target="_blank">QWeather</a>
|
||||
<a style="color: black"> · </a>
|
||||
<a href="https://github.com/MarkusJoe/QWeather" style="color: black" target="_blank">Github Repo</a>
|
||||
</b>
|
||||
</i>
|
||||
</div>"""
|
||||
self.message['Subject'] = language['subject_3']
|
||||
self.message.attach(MIMEText(mail_html, 'html', 'utf-8'))
|
||||
|
||||
image_count = 1
|
||||
for image_source_free in icon_list:
|
||||
with open(f'./resource/icons/{image_source_free}.png', 'rb') as file:
|
||||
MyImage = MIMEImage(file.read())
|
||||
MyImage.add_header('Content-ID', f'img{image_count}')
|
||||
self.message.attach(MyImage)
|
||||
image_count += 1
|
||||
|
||||
with open('resource/basic-resources/sunrise.png', 'rb') as sr_f:
|
||||
sunrise_img = MIMEImage(sr_f.read())
|
||||
sunrise_img.add_header('Content-ID', 'sunrise')
|
||||
self.message.attach(sunrise_img)
|
||||
with open('resource/basic-resources/sunset.png', 'rb') as ss_f:
|
||||
sunset_img = MIMEImage(ss_f.read())
|
||||
sunset_img.add_header('Content-ID', 'sunset')
|
||||
self.message.attach(sunset_img)
|
||||
|
||||
try:
|
||||
self.smtp.login(self.sender, self.password)
|
||||
self.smtp.sendmail(self.sender, self.receiver, self.message.as_string())
|
||||
except smtplib.SMTPException as e:
|
||||
logger.critical(f'{language["mail_error"]}:', e)
|
||||
sys.exit(1)
|
||||
|
||||
def warning_send_mail(self):
|
||||
"""
|
||||
发送自然灾害预警信息
|
||||
releaseTime: 更新时间(并不是获取数据的时间 而是API更新数据的时间)
|
||||
title: 标题
|
||||
startTime: 开始时间
|
||||
endTime: 结束时间
|
||||
status: 状态
|
||||
level: 等级
|
||||
type: 类型
|
||||
text: 详细描述
|
||||
获取自然灾害并判断灾害信息是否为空
|
||||
如果空则跳过
|
||||
如果不为空则单独发送一封邮件
|
||||
:return:
|
||||
"""
|
||||
r = session.get(self.warning_url, headers=self.headers, params=self.params).json()
|
||||
|
||||
logger.info(f'{language["request_result_warning"]}:{r["code"]}')
|
||||
if r['warning']:
|
||||
public_time = r['warning'][0]['pubTime']
|
||||
title = r['warning'][0]['title']
|
||||
start_time = r['warning'][0]['startTime']
|
||||
end_time = r['warning'][0]['endTime']
|
||||
if not start_time:
|
||||
start_time = None
|
||||
elif not end_time:
|
||||
end_time = None
|
||||
warning_status = r['warning'][0]['status']
|
||||
if warning_status == 'update':
|
||||
warning_status = {language["new_warning"]}
|
||||
logger.info(f'{language["new_warning"]}')
|
||||
elif warning_status == 'active':
|
||||
warning_status = language["warning_updated"]
|
||||
logger.info(f'{language["warning_updated"]}')
|
||||
elif warning_status == 'cancel':
|
||||
logger.info(f'{language["warning_canceled"]}')
|
||||
|
||||
level = r['warning'][0]['level']
|
||||
type_ = r['warning'][0]['typeName']
|
||||
text = r['warning'][0]['text']
|
||||
self.message['Subject'] = language['subject_war']
|
||||
self.message['Subject'] = f'{title}'
|
||||
mail_html = f"""
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Warning</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="text-align: center;color: black;">
|
||||
<h2>{title}</h2>
|
||||
<h3>{language['release_time']}:{public_time[:10]} {level}{language['early_warning']}</h3>
|
||||
<p>
|
||||
{language['warning_status']}:{warning_status} {language['warning_type']}:{type_} {language['warning_duration']}:{start_time[:10]}~{end_time[:10]}
|
||||
<br />
|
||||
{text}
|
||||
</p>
|
||||
</div>
|
||||
<div style="text-align: center;" id="About">
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<i>
|
||||
<b>
|
||||
<a href="https://dev.qweather.com/" style="color: black" target="_blank">QWeather</a>
|
||||
<a style="color: black"> · </a>
|
||||
<a href="https://github.com/MarkusJoe/QWeather" style="color: black" target="_blank">Github Repo</a>
|
||||
</b>
|
||||
</i>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
self.msg_content.attach(MIMEText(mail_html, 'html', 'utf-8'))
|
||||
self.message.attach(self.msg_content)
|
||||
try:
|
||||
if warning_status != 'cancel':
|
||||
self.smtp.login(self.sender, self.password)
|
||||
self.smtp.sendmail(self.sender, self.receiver, self.message.as_string())
|
||||
except smtplib.SMTPException as e:
|
||||
logger.error(f'{language["mail_error"]}:', e)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def loop_check(mode: str, time_list: list):
|
||||
"""
|
||||
循环检测时间如果本地时间等于配置文件内填写的时间则发送一封天气信息的邮件
|
||||
:return:
|
||||
"""
|
||||
match mode:
|
||||
case 'dev':
|
||||
while True:
|
||||
local_time = time.strftime("%H:%M", time.localtime())
|
||||
time.sleep(1)
|
||||
if local_time in time_list:
|
||||
SendWeatherMail().dev_mode()
|
||||
logger.info(f'{language["mail_succeed"]}')
|
||||
logger.info(f'{language["wait_seconds"]}')
|
||||
time.sleep(61)
|
||||
case 'free':
|
||||
while True:
|
||||
local_time = time.strftime("%H:%M", time.localtime())
|
||||
time.sleep(1)
|
||||
if local_time in time_list:
|
||||
SendWeatherMail().free_mode()
|
||||
logger.info(f'{language["mail_succeed"]}')
|
||||
logger.info(f'{language["wait_seconds"]}')
|
||||
time.sleep(61)
|
||||
|
||||
|
||||
def check_config():
|
||||
"""
|
||||
检查各项配置是否填写完成
|
||||
:return:
|
||||
"""
|
||||
for mail in config['mail-settings'].values():
|
||||
if mail is None:
|
||||
logger.critical(f'"mail-settings"{language["config_not_filled"]}')
|
||||
sys.exit(1)
|
||||
for request in config['request-settings'].values():
|
||||
if request is None:
|
||||
logger.critical(f'"request-settings"{language["config_not_filled"]}')
|
||||
sys.exit(1)
|
||||
for other in config['client-settings'].values():
|
||||
if other is None:
|
||||
logger.critical(f'"client-settings"{language["config_not_filled"]}')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def read_excel(kw: str):
|
||||
"""
|
||||
读取xlsx城市ID文件并返回搜索到的结果
|
||||
:param kw: 用于搜索的关键字
|
||||
:return: city_list
|
||||
"""
|
||||
index_count = 0
|
||||
city_list = []
|
||||
logger.info(f'[Search]{language["reading_the_file"]}')
|
||||
df = pandas.read_excel(f'./resource/{LOCATION_ID_FILE_NAME}')
|
||||
pandas.set_option('max_rows', None) # 读取xlsx文件不折叠
|
||||
data_records = df.to_dict(orient='split')
|
||||
for i in data_records['data']:
|
||||
if kw in str(i):
|
||||
logger.info(f'{index_count}|{i[0]}-{i[2]}-{i[4]}-{i[6]}')
|
||||
city = [index_count, i[0], i[1], i[2], i[3], i[4], i[5], i[6], i[7], i[8], i[9]]
|
||||
index_count += 1
|
||||
city_list.append(city)
|
||||
return city_list
|
||||
|
||||
|
||||
def modify_config(mode: bool = False):
|
||||
"""
|
||||
修改配置文件 -> 检测配置文件内的location项是否填写
|
||||
填写 -> 跳过
|
||||
未填写 -> 触发
|
||||
(如果不小心在location项内随便输入了什么会导致误判)
|
||||
:param mode: 触发修改模式的条件
|
||||
:return: Nothing
|
||||
"""
|
||||
|
||||
if not _LOCATION or not mode: # 如果配置中location未填写或status未False则触发条件
|
||||
logger.info(f'[Modify]{language["fill_the_config"]}')
|
||||
logger.info(f'[Modify]{language["input_a_city_name"]}')
|
||||
while True:
|
||||
time.sleep(0.3)
|
||||
city_name = input('-->')
|
||||
match city_name:
|
||||
case 'q':
|
||||
logger.info(f'[Modify]User quit.')
|
||||
sys.exit(0)
|
||||
case '':
|
||||
logger.critical(f'[Modify]{language["null_value"]}')
|
||||
continue
|
||||
case _:
|
||||
break
|
||||
searched_city = read_excel(city_name)
|
||||
logger.info(f'[Modify]{language["user_input"]}:[{city_name}]')
|
||||
logger.info(f'[Modify]{language["select_a_index"]}')
|
||||
if not searched_city:
|
||||
logger.error(f'[Modify]{language["no_result"]}')
|
||||
sys.exit(1)
|
||||
time.sleep(0.3)
|
||||
while True:
|
||||
try:
|
||||
time.sleep(0.3)
|
||||
user_input = input('-->')
|
||||
if user_input == 'q':
|
||||
logger.info('[Quit]User quit')
|
||||
sys.exit(1)
|
||||
index = searched_city[int(user_input)]
|
||||
with open(CONFIG_NAME, 'r', encoding='utf-8') as of:
|
||||
data = YAML().load(of)
|
||||
data['request-settings']['location'] = index[1]
|
||||
data['only-view-settings']['city-name'] = f'{index[3]}-{index[7]}-{index[7]}'
|
||||
data['only-view-settings']['time'] = time.strftime("%a %b %d %Y %H:%M:%S", time.localtime())
|
||||
data['only-view-settings']['user'] = getpass.getuser()
|
||||
with open(CONFIG_NAME, 'w', encoding='utf-8') as wf:
|
||||
YAML().dump(data, wf)
|
||||
logger.info(f'[Write]{language["write_successfully"]}:{CONFIG_NAME}')
|
||||
break
|
||||
except (IndexError, ValueError) as e:
|
||||
logger.info(e)
|
||||
logger.error(f'[Write]{language["input_type_error"]}')
|
||||
continue
|
||||
finally:
|
||||
logger.info('[Done]Program has done.')
|
||||
sys.exit(0)
|
||||
from core import qweather
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
CONFIG_NAME = 'config.yml' # 配置文件名称
|
||||
LOCATION_ID_FILE_NAME = 'china_city_list.xlsx' # 城市id文件必须为xlsx文件
|
||||
|
||||
# logs为空文件夹git上传时会自动忽略此文件夹, 故添加自动创建文件夹
|
||||
if not os.path.isdir('./logs'):
|
||||
os.mkdir('./logs')
|
||||
|
||||
date_format = '%H:%M:%S'
|
||||
info_format_console = '%(log_color)s[%(asctime)s] |%(levelname)-8s |%(lineno)-3s |%(message)s'
|
||||
info_format_file = '[%(asctime)s] |%(levelname)-8s |%(lineno)-3s |%(funcName)s |%(pathname)s |%(message)s'
|
||||
formatter = ColoredFormatter(fmt=info_format_console,
|
||||
datefmt=date_format,
|
||||
reset=True,
|
||||
log_colors={
|
||||
'DEBUG': 'light_purple',
|
||||
'INFO': 'light_cyan',
|
||||
'WARNING': 'yellow',
|
||||
'ERROR': 'red',
|
||||
'CRITICAL': 'red,bold_red'})
|
||||
formatter_file = logging.Formatter(fmt=info_format_file,
|
||||
datefmt=date_format)
|
||||
|
||||
logger = logging.getLogger('MainLogger')
|
||||
logger.setLevel(logging.DEBUG)
|
||||
ConsoleLogger = logging.StreamHandler() # 输出到终端
|
||||
ConsoleLogger.setFormatter(formatter)
|
||||
log_name = time.strftime('%Y-%m-%d#%H') # 一小时内使用的日志文件都是同一个
|
||||
FileLogger = logging.handlers.RotatingFileHandler(filename=f'./logs/{log_name}.log',
|
||||
maxBytes=102400,
|
||||
backupCount=5) # 每个日志文件最大102400字节(100Kb)
|
||||
FileLogger.setFormatter(formatter_file)
|
||||
logger.addHandler(ConsoleLogger)
|
||||
logger.addHandler(FileLogger)
|
||||
|
||||
# 使用本地网络进行请求
|
||||
session = requests.Session()
|
||||
session.trust_env = False
|
||||
|
||||
# 获取语言配置
|
||||
with open(CONFIG_NAME, 'r', encoding='utf-8') as lang:
|
||||
config = YAML().load(lang.read())
|
||||
language_sel = config['client-settings']['language']
|
||||
if language_sel not in ['zh_cn', 'en_us']:
|
||||
language_sel = 'zh_cn'
|
||||
|
||||
# 打开语言json文件
|
||||
with open(f'./resource/lang/{language_sel}.json', 'r', encoding='utf-8') as lang_f:
|
||||
language = json.loads(lang_f.read())
|
||||
|
||||
_TIMES = config['client-settings']['send-times']
|
||||
_MODE = config['request-settings']['mode']
|
||||
_LOCATION = config['request-settings']['location']
|
||||
|
||||
modify_config(True)
|
||||
|
||||
# 获取参数
|
||||
parser = argparse.ArgumentParser()
|
||||
arg_keywords = ['free', 'dev', 'warning', 'modify']
|
||||
parser.add_argument('-t',
|
||||
'--test',
|
||||
help='Some operations for test.',
|
||||
choices=arg_keywords)
|
||||
startup_arg = parser.parse_args().test
|
||||
# Python3.10 更新的match-case 语法
|
||||
match startup_arg:
|
||||
case 'free':
|
||||
SendWeatherMail().free_mode()
|
||||
logger.debug(f'{language["debug_done"]}')
|
||||
sys.exit(0)
|
||||
case 'dev':
|
||||
SendWeatherMail().dev_mode()
|
||||
logger.debug(f'{language["debug_done"]}')
|
||||
sys.exit(0)
|
||||
case 'warning':
|
||||
SendWeatherMail().warning_send_mail()
|
||||
logger.debug(f'{language["debug_done"]}')
|
||||
sys.exit(0)
|
||||
case 'modify':
|
||||
modify_config()
|
||||
logger.info(f'{language["debug_done"]}')
|
||||
case _:
|
||||
pass
|
||||
|
||||
# 检查配置文件是否填写完成
|
||||
check_config()
|
||||
|
||||
# 输出声明
|
||||
logger.info(f'{language["statement_1"]}')
|
||||
logger.info(f'{language["statement_2"]}')
|
||||
logger.info(f'{language["statement_3"]}')
|
||||
logger.info(f'{language["statement_4"]}')
|
||||
logger.info(f'{language["current_profile"]}: {CONFIG_NAME}')
|
||||
|
||||
# 另开一个进程与主进程同时运行 --> 运行loopCheck --> 循环检查本地时间是否与配置内时间相符
|
||||
multiprocessing.Process(target=loop_check, args=(_MODE, _TIMES,)).run()
|
||||
|
||||
# 循环检测时间 --> 每10分钟检查一次, 如果有则发送如果无则直接跳过
|
||||
loop_timer = 0
|
||||
while True:
|
||||
time.sleep(1)
|
||||
loop_timer += 1
|
||||
if loop_timer == 600:
|
||||
SendWeatherMail().warning_send_mail()
|
||||
loop_timer = 0
|
||||
sys.exit(qweather.main())
|
||||
|
||||
34
README.md
@@ -1,7 +1,7 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/MarkusJoe/QWeather">
|
||||
<img src="https://img.shields.io/badge/Python-V3.10-blue.svg" alt="PythonVersion">
|
||||
<img src="https://img.shields.io/badge/release-V2.8.1-green.svg" alt="QWeatherVersion">
|
||||
<img src="https://img.shields.io/badge/Python-3.10.x-blue.svg" alt="PythonVersion">
|
||||
<img src="https://img.shields.io/badge/release-3.1.1b-green.svg" alt="QWeatherVersion">
|
||||
<img src="https://img.shields.io/badge/LINCESE-Apache2.0-orange.svg" alt="LICENSE">
|
||||
</a>
|
||||
</p>
|
||||
@@ -15,7 +15,23 @@
|
||||
|
||||
</div>
|
||||
|
||||
## 问题汇总
|
||||
<div align="center">
|
||||
<b><i>当前分支版本为重构后的版本重构前版本请在before分支中查看<br>(重构前版本不再更新,最新版本为v2.9.0)</i></b><br>
|
||||
<b><i><a href="https://markusjoe.github.io/" target="_blank">点击跳转到帮助文档</a></i></b>
|
||||
</div>
|
||||
|
||||
### 开源
|
||||
- 本项目以[Apache-2.0](./LICENSE)许可开源, 即:
|
||||
- 你可以直接使用该项目提供的功能, 无需任何授权
|
||||
- 你可以在**注明来源版权信息**的情况下对源代码进行任意分发和修改以及衍生
|
||||
|
||||
### 已实现功能
|
||||
- [x] 发送免费版&开发板右键
|
||||
- [x] 间隔10分钟请求一次自然灾害预警信息
|
||||
- [x] 在网页上快速查看天气
|
||||
- [ ] ~~推送到QQ(咕咕咕)~~
|
||||
|
||||
### 问题汇总
|
||||
#### Python 版本:
|
||||
> 程序使用了Python3.10中的match-case语句
|
||||
> 请使用Python3.10版本运行
|
||||
@@ -34,4 +50,14 @@
|
||||
- 程序基于python3.10开发 务必使用python3.10版本运行
|
||||
- 将config.yml正确填写完成
|
||||
- 使用`pip/pip3 install -r requirements.txt` 安装需要的库
|
||||
- 运行`QWeather.py`
|
||||
- 运行`QWeather.py`
|
||||
|
||||
### 网页上查看天气
|
||||
- 将所有准备工作完成(能正常运行QWeather.py)
|
||||
- 运行QWeather.py
|
||||
- 打开浏览器输入***127.0.0.1***:7898 (127.0.0.1可以更改为部署本项目的服务器ip)
|
||||
|
||||
|
||||
## 联系方式
|
||||
- 邮箱: <markushammered@gmail.com>
|
||||
|
||||
|
||||
15
config.yml
@@ -39,7 +39,7 @@ request-settings:
|
||||
|
||||
|
||||
# [必填/只能一个] 地区ID代码
|
||||
# 请勿擅自修改 更改地区ID请使用命令: "python -m QWeather.py -t modify" 回车后根据向导完成修改
|
||||
# 请勿擅自修改 更改地区ID请使用命令: "python -m QWeather.py -t modify" 回车后跟随引导修改
|
||||
location:
|
||||
|
||||
|
||||
@@ -95,12 +95,23 @@ client-settings:
|
||||
# 填错默认 "zh_cn"
|
||||
language: zh_cn
|
||||
|
||||
# [必填/只能一个] 日志等级
|
||||
# 默认: DEBUG
|
||||
# 可选: "DEBUG" "INFO" "WARNING" "ERROR" "CRITICAL"
|
||||
level: DEBUG
|
||||
|
||||
# [选填/只能一个] 开启本地网页快速查看天气
|
||||
# 默认: False
|
||||
# 可选: "True" "False"
|
||||
webserver: false
|
||||
|
||||
|
||||
# [标识/不填] 仅作用户读取标识
|
||||
only-view-settings:
|
||||
# 城市名
|
||||
city-name: 黄岩-台州-台州
|
||||
# 修改时间
|
||||
time: Sat Dec 04 2021 15:27:51
|
||||
time: Mon Dec 20 2021 21:08:22
|
||||
# 修改用户
|
||||
user: rtakland
|
||||
|
||||
|
||||
12
core/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
# -- coding:utf-8 --
|
||||
# @Author: markushammered@gmail.com
|
||||
# @Development Tool: PyCharm
|
||||
# @Create Time: 2021/12/15
|
||||
# @File Name: __init__.py
|
||||
|
||||
|
||||
import os
|
||||
|
||||
if not os.path.exists('./logs'):
|
||||
os.mkdir('./logs')
|
||||
132
core/information.py
Normal file
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
# -- coding:utf-8 --
|
||||
# @Author: markushammered@gmail.com
|
||||
# @Development Tool: PyCharm
|
||||
# @Create Time: 2021/12/15
|
||||
# @File Name: information.py
|
||||
|
||||
import requests
|
||||
from core import read_config
|
||||
|
||||
|
||||
class WeatherInfo:
|
||||
def __init__(self):
|
||||
self.config = read_config()
|
||||
self.dev = 'https://devapi.qweather.com/v7/weather/7d'
|
||||
self.free = 'https://devapi.qweather.com/v7/weather/3d'
|
||||
self.warning = 'https://devapi.qweather.com/v7/warning/now'
|
||||
self.key = self.config[1]['key']
|
||||
self.location = self.config[1]['location']
|
||||
self.unit = self.config[1]['unit']
|
||||
self.language = self.config[1]['lang']
|
||||
self.params = {'location': self.location,
|
||||
'key': self.key,
|
||||
'unit': self.unit,
|
||||
'lang': self.language}
|
||||
self.session = requests.Session()
|
||||
self.session.trust_env = False
|
||||
|
||||
def dev_version(self):
|
||||
"""
|
||||
Developer's Qweather version
|
||||
:return: dates, day_weathers, night_weathers, highest_temps, lowest_temps,
|
||||
icon_list, sunset, sunrise, humidity, wind_speed, wind_scale, wind_dir, ultraviolet_rays, cloud, pressure, vis
|
||||
"""
|
||||
dev_res = self.session.get(self.dev, params=self.params).json()
|
||||
|
||||
day_1 = dev_res['daily'][0]
|
||||
day_2 = dev_res['daily'][1]
|
||||
day_3 = dev_res['daily'][2]
|
||||
day_4 = dev_res['daily'][3]
|
||||
day_5 = dev_res['daily'][4]
|
||||
day_6 = dev_res['daily'][5]
|
||||
day_7 = dev_res['daily'][6]
|
||||
|
||||
dates = [day_1['fxDate'][5:], day_2['fxDate'][5:], day_3['fxDate'][5:], day_4['fxDate'][5:],
|
||||
day_5['fxDate'][5:],
|
||||
day_6['fxDate'][5:],
|
||||
day_7['fxDate'][5:]]
|
||||
day_weathers = [day_1['textDay'], day_2['textDay'], day_3['textDay'], day_4['textDay'], day_5['textDay'],
|
||||
day_6['textDay'], day_7['textDay']]
|
||||
night_weathers = [day_1['textNight'], day_2['textNight'], day_3['textNight'], day_4['textNight'],
|
||||
day_5['textNight'], day_6['textNight'], day_7['textNight']]
|
||||
highest_temps = [day_1['tempMax'], day_2['tempMax'], day_3['tempMax'], day_4['tempMax'], day_5['tempMax'],
|
||||
day_6['tempMax'], day_7['tempMax']]
|
||||
lowest_temps = [day_1['tempMin'], day_2['tempMin'], day_3['tempMin'], day_4['tempMin'], day_5['tempMin'],
|
||||
day_6['tempMin'], day_7['tempMin']]
|
||||
icon_list = [day_1['iconDay'], day_2['iconDay'], day_3['iconDay'], day_4['iconDay'], day_5['iconDay'],
|
||||
day_6['iconDay'], day_7['iconDay'], day_1['iconNight'], day_2['iconNight'], day_3['iconNight'],
|
||||
day_4['iconNight'], day_5['iconNight'], day_6['iconNight'], day_7['iconNight']]
|
||||
|
||||
sunset = day_1['sunset']
|
||||
sunrise = day_1['sunrise']
|
||||
humidity = day_1['humidity']
|
||||
wind_speed = day_1['windSpeedDay']
|
||||
wind_scale = day_1['windScaleDay']
|
||||
wind_dir = day_1['windDirDay']
|
||||
ultraviolet_rays = day_1['uvIndex']
|
||||
cloud = day_1['cloud']
|
||||
pressure = day_1['pressure']
|
||||
vis = day_1['vis']
|
||||
|
||||
return dates, day_weathers, night_weathers, highest_temps, lowest_temps, icon_list, sunset, sunrise, humidity, \
|
||||
wind_speed, wind_scale, wind_dir, ultraviolet_rays, cloud, pressure, vis
|
||||
|
||||
def free_version(self):
|
||||
"""
|
||||
|
||||
:return: return dates, day_weathers, night_weathers, highest_temps, lowest_temps, icon_list, sunset, sunrise,
|
||||
humidity, wind_speed, wind_scale, wind_dir, ultraviolet_rays, cloud, pressure, vis
|
||||
"""
|
||||
|
||||
free_res = self.session.get(self.free, params=self.params).json()
|
||||
|
||||
day_1 = free_res['daily'][0]
|
||||
day_2 = free_res['daily'][1]
|
||||
day_3 = free_res['daily'][2]
|
||||
|
||||
dates = [day_1['fxDate'][5:], day_2['fxDate'][5:], day_2['fxDate'][5:]]
|
||||
day_weathers = [day_1['textDay'], day_2['textDay'], day_2['textDay']]
|
||||
night_weathers = [day_1['textNight'], day_2['textNight'], day_2['textNight']]
|
||||
highest_temps = [day_1['tempMax'], day_2['tempMax'], day_3['tempMax']]
|
||||
lowest_temps = [day_1['tempMin'], day_2['tempMin'], day_2['tempMin']]
|
||||
icon_list = [day_1['iconDay'], day_1['iconDay'], day_3['iconDay'], day_1['iconNight'], day_2['iconNight'],
|
||||
day_3['iconNight']]
|
||||
|
||||
sunset = day_1['sunset']
|
||||
sunrise = day_1['sunrise']
|
||||
humidity = day_1['humidity']
|
||||
wind_speed = day_1['windSpeedDay']
|
||||
wind_scale = day_1['windScaleDay']
|
||||
wind_dir = day_1['windDirDay']
|
||||
ultraviolet_rays = day_1['uvIndex']
|
||||
cloud = day_1['cloud']
|
||||
pressure = day_1['pressure']
|
||||
vis = day_1['vis']
|
||||
|
||||
return dates, day_weathers, night_weathers, highest_temps, lowest_temps, icon_list, sunset, sunrise, humidity, \
|
||||
wind_speed, wind_scale, wind_dir, ultraviolet_rays, cloud, pressure, vis
|
||||
|
||||
def warning_(self):
|
||||
"""
|
||||
|
||||
:return: release_time, title, status, level, type_, text, start_time, end_time
|
||||
"""
|
||||
warning_res = self.session.get(self.warning, params=self.params).json()
|
||||
|
||||
release_time = warning_res['warning'][0]['pubTime'][:10]
|
||||
title = warning_res['warning'][0]['title']
|
||||
status = warning_res['warning'][0]['status']
|
||||
level = warning_res['warning'][0]['level']
|
||||
type_ = warning_res['warning'][0]['typeName']
|
||||
text = warning_res['warning'][0]['text']
|
||||
start_time = warning_res['warning'][0]['startTime']
|
||||
end_time = warning_res['warning'][0]['endTime']
|
||||
match start_time, end_time:
|
||||
case None, None:
|
||||
start_time, end_time = None, None
|
||||
case _:
|
||||
pass
|
||||
|
||||
return release_time, title, status, level, type_, text, start_time, end_time
|
||||
|
||||
27
core/language.py
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
# -- coding:utf-8 --
|
||||
# @Author: markushammered@gmail.com
|
||||
# @Development Tool: PyCharm
|
||||
# @Create Time: 2021/12/15
|
||||
# @File Name: language.py
|
||||
|
||||
from ruamel.yaml import YAML
|
||||
import json
|
||||
|
||||
|
||||
def Language():
|
||||
"""
|
||||
读取配置文件中的语言选项并返回相对因的语言文件的读取结果
|
||||
:return:
|
||||
"""
|
||||
with open('./config.yml', 'r', encoding='utf-8') as lang:
|
||||
config = YAML().load(lang.read())
|
||||
language_sel = config['client-settings']['language']
|
||||
if language_sel not in ['zh_cn', 'en_us']:
|
||||
language_sel = 'zh_cn'
|
||||
|
||||
# 打开语言json文件
|
||||
with open(f'./res/lang/{language_sel}.json', 'r', encoding='utf-8') as lang_f:
|
||||
language = json.loads(lang_f.read())
|
||||
|
||||
return language
|
||||
55
core/logger.py
Normal file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
# -- coding:utf-8 --
|
||||
# @Author: markushammered@gmail.com
|
||||
# @Development Tool: PyCharm
|
||||
# @Create Time: 2021/12/15
|
||||
# @File Name: logger.py
|
||||
|
||||
from colorlog import ColoredFormatter
|
||||
from core.read_config import read_config
|
||||
import logging.handlers
|
||||
import time
|
||||
|
||||
level = read_config()[2]['level']
|
||||
|
||||
date_format = '%H:%M:%S'
|
||||
info_format_console = '%(log_color)s[%(asctime)s] |%(filename)s[ %(lineno)-3s] |%(levelname)-8s |%(message)s'
|
||||
info_format_file = '[%(asctime)s] |%(filename)s[%(funcName)sline:%(lineno)d] |%(levelname)-8s |%(message)s'
|
||||
formatter = ColoredFormatter(fmt=info_format_console,
|
||||
datefmt=date_format,
|
||||
reset=True,
|
||||
log_colors={
|
||||
'DEBUG': 'light_purple',
|
||||
'INFO': 'light_cyan',
|
||||
'WARNING': 'yellow',
|
||||
'ERROR': 'red',
|
||||
'CRITICAL': 'red,bold_red'})
|
||||
formatter_file = logging.Formatter(fmt=info_format_file,
|
||||
datefmt=date_format)
|
||||
|
||||
Logger = logging.getLogger('MainLogger')
|
||||
|
||||
match level:
|
||||
case 'DEBUG':
|
||||
Logger.setLevel(logging.DEBUG)
|
||||
case 'INFO':
|
||||
Logger.setLevel(logging.INFO)
|
||||
case 'WARNING':
|
||||
Logger.setLevel(logging.WARNING)
|
||||
case 'ERROR':
|
||||
Logger.setLevel(logging.ERROR)
|
||||
case 'CRITICAL':
|
||||
Logger.setLevel(logging.CRITICAL)
|
||||
case _:
|
||||
Logger.setLevel(logging.DEBUG)
|
||||
|
||||
ConsoleLogger = logging.StreamHandler() # 输出到终端
|
||||
ConsoleLogger.setFormatter(formatter)
|
||||
log_name = time.strftime('%Y-%m-%d#%H') # 一小时内使用的日志文件都是同一个
|
||||
FileLogger = logging.handlers.RotatingFileHandler(filename=f'./logs/{log_name}.log',
|
||||
maxBytes=102400,
|
||||
backupCount=5) # 每个日志文件最大102400字节(100Kb)
|
||||
FileLogger.setFormatter(formatter_file)
|
||||
Logger.addHandler(ConsoleLogger)
|
||||
Logger.addHandler(FileLogger)
|
||||
|
||||
136
core/qweather.py
Normal file
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env python3
|
||||
# -- coding:utf-8 --
|
||||
# @Author: markushammered@gmail.com
|
||||
# @Development Tool: PyCharm
|
||||
# @Create Time: 2021/12/15
|
||||
# @File Name: qweather.py
|
||||
|
||||
|
||||
import sys
|
||||
import time
|
||||
import argparse
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from core.logger import Logger
|
||||
from core.language import Language
|
||||
from core.settings import change_settings
|
||||
from lib import webserver
|
||||
from core.read_config import read_config
|
||||
from core.sendmail import Mail
|
||||
|
||||
|
||||
def check_time():
|
||||
"""
|
||||
通过多进程让函数和主程序并行,
|
||||
并持续检测本地计算机时间是否和配置文件内填写的发送时间一致
|
||||
:return:
|
||||
"""
|
||||
mode = settings[1]['mode']
|
||||
time_list = settings[2]['send-times']
|
||||
match mode:
|
||||
case 'dev':
|
||||
while True:
|
||||
local_time = time.strftime("%H:%M", time.localtime())
|
||||
time.sleep(1)
|
||||
if local_time in time_list:
|
||||
Mail().dev_version()
|
||||
Logger.info(f'{language["mail_succeed"]}')
|
||||
Logger.info(f'{language["wait_seconds"]}')
|
||||
time.sleep(60)
|
||||
case 'free':
|
||||
while True:
|
||||
local_time = time.strftime("%H:%M", time.localtime())
|
||||
time.sleep(1)
|
||||
if local_time in time_list:
|
||||
Mail().free_version()
|
||||
Logger.info(f'{language["mail_succeed"]}')
|
||||
Logger.info(f'{language["wait_seconds"]}')
|
||||
time.sleep(60)
|
||||
|
||||
|
||||
def check_config():
|
||||
"""
|
||||
返回配置文件中的location项是否填写
|
||||
:return: True or False
|
||||
"""
|
||||
location = settings[1]['location']
|
||||
if location:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def setting():
|
||||
"""
|
||||
检查配置文件是否填写完成
|
||||
:return:
|
||||
"""
|
||||
for mail in settings[0].values():
|
||||
if not mail:
|
||||
Logger.critical('mail-settings 有未填写项目')
|
||||
sys.exit(1)
|
||||
for request in settings[1].values():
|
||||
if not request:
|
||||
Logger.critical('request-settings 有未填写项目')
|
||||
sys.exit(1)
|
||||
for other in settings[1].values():
|
||||
if not other:
|
||||
Logger.critical('client-settings 有未填写项目')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
主程序
|
||||
:return:
|
||||
"""
|
||||
parser = argparse.ArgumentParser()
|
||||
arg_keywords = ['free', 'dev', 'warning', 'setting']
|
||||
parser.add_argument('-t',
|
||||
'--test',
|
||||
help='Some operations for test.',
|
||||
choices=arg_keywords)
|
||||
startup_arg = parser.parse_args().test
|
||||
|
||||
match startup_arg:
|
||||
case 'free':
|
||||
Mail().free_version()
|
||||
Logger.debug(f'{language["debug_done"]}')
|
||||
sys.exit(0)
|
||||
case 'dev':
|
||||
Mail().dev_version()
|
||||
Logger.debug(f'{language["debug_done"]}')
|
||||
sys.exit(0)
|
||||
case 'warning':
|
||||
Mail().warning_()
|
||||
Logger.debug(f'{language["debug_done"]}')
|
||||
sys.exit(0)
|
||||
case 'setting':
|
||||
if check_config():
|
||||
setting()
|
||||
else:
|
||||
change_settings()
|
||||
Logger.debug(f'{language["debug_done"]}')
|
||||
case _:
|
||||
pass
|
||||
processes.submit(check_time)
|
||||
if settings[2]['webserver']:
|
||||
processes.submit(webserver.process_request())
|
||||
|
||||
time_count = 0
|
||||
while True:
|
||||
time_count += 1
|
||||
time.sleep(1)
|
||||
if time_count == 600:
|
||||
Mail().warning_()
|
||||
time_count = 0
|
||||
|
||||
|
||||
if __name__ != '__main__':
|
||||
language = Language()
|
||||
settings = read_config()
|
||||
processes = ProcessPoolExecutor(max_workers=3)
|
||||
|
||||
Logger.info(f'{language["statement_1"]}')
|
||||
Logger.info(f'{language["statement_2"]}')
|
||||
Logger.info(f'{language["statement_3"]}')
|
||||
Logger.info(f'{language["statement_4"]}')
|
||||
24
core/read_config.py
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
# -- coding:utf-8 --
|
||||
# @Author: markushammered@gmail.com
|
||||
# @Development Tool: PyCharm
|
||||
# @Create Time: 2021/12/15
|
||||
# @File Name: read_config.py
|
||||
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
|
||||
def read_config():
|
||||
"""
|
||||
读取配置文件并返回读取到的内容同
|
||||
:return: mail-settings, request-settings, client-settings, only-view-settings --> 0, 1, 2, 3
|
||||
"""
|
||||
config_file = 'config.yml'
|
||||
with open(f'./{config_file}', 'r') as conf:
|
||||
config = YAML().load(conf.read())
|
||||
mail_settings = config['mail-settings']
|
||||
request_settings = config['request-settings']
|
||||
client_settings = config['client-settings']
|
||||
only_view = config['only-view-settings']
|
||||
return mail_settings, request_settings, client_settings, only_view
|
||||
|
||||
33
core/read_excel.py
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
# -- coding:utf-8 --
|
||||
# @Author: markushammered@gmail.com
|
||||
# @Development Tool: PyCharm
|
||||
# @Create Time: 2021/12/15
|
||||
# @File Name: read_excel.py
|
||||
|
||||
import pandas
|
||||
from core.logger import Logger
|
||||
from core.language import Language
|
||||
|
||||
|
||||
def read_excel(kw: str):
|
||||
"""
|
||||
读取china_city_list.xlsx并搜索匹配关键字的结果并输出到终端
|
||||
:param kw: keyword
|
||||
:return: city_list
|
||||
"""
|
||||
|
||||
language = Language()
|
||||
index_count = 0
|
||||
city_list = []
|
||||
Logger.info(f'[Search]{language["reading_the_file"]}')
|
||||
df = pandas.read_excel(f'./res/china_city_list.xlsx')
|
||||
pandas.set_option('max_rows', None) # 读取xlsx文件不折叠
|
||||
data_records = df.to_dict(orient='split')
|
||||
for i in data_records['data']:
|
||||
if kw in str(i):
|
||||
Logger.info(f' {index_count} | {i[2]}-{i[4]}-{i[6]}')
|
||||
city = [index_count, i[0], i[1], i[2], i[3], i[4], i[5], i[6], i[7], i[8], i[9]]
|
||||
index_count += 1
|
||||
city_list.append(city)
|
||||
return city_list
|
||||
428
core/sendmail.py
Normal file
@@ -0,0 +1,428 @@
|
||||
#!/usr/bin/env python3
|
||||
# -- coding:utf-8 --
|
||||
# @Author: markushammered@gmail.com
|
||||
# @Development Tool: PyCharm
|
||||
# @Create Time: 2021/12/16
|
||||
# @File Name: sendmail.py
|
||||
|
||||
|
||||
import sys
|
||||
import smtplib
|
||||
from email.mime.image import MIMEImage
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.header import Header
|
||||
from email.mime.text import MIMEText
|
||||
from core.logger import Logger
|
||||
from core.language import Language
|
||||
from core.information import WeatherInfo
|
||||
from core.read_config import read_config
|
||||
|
||||
|
||||
class Mail:
|
||||
def __init__(self):
|
||||
self.settings = read_config()
|
||||
self.language = Language()
|
||||
self.enableSSL = self.settings[2]['SSL']
|
||||
self.server = self.settings[0]['server']
|
||||
self.port = self.settings[0]['port']
|
||||
self.password = self.settings[0]['password']
|
||||
self.sender = self.settings[0]['sender']
|
||||
self.receiver = self.settings[1]['receiver']
|
||||
self.city_name = self.settings[3]['city-name']
|
||||
self.message = MIMEMultipart('related')
|
||||
self.message['From'] = Header('QWeather') # 发件人名称
|
||||
self.message['To'] = Header('All allowed User') # 收件人显示名称
|
||||
|
||||
if self.enableSSL:
|
||||
self.smtp = smtplib.SMTP_SSL(self.server, self.port) # 登录服务器 使用SSL连接
|
||||
else:
|
||||
self.smtp = smtplib.SMTP(self.server, self.port) # 登录邮箱服务器 不使用SSL连接
|
||||
|
||||
def dev_version(self):
|
||||
"""
|
||||
开发者版本
|
||||
:return:
|
||||
"""
|
||||
dev_weather = WeatherInfo().dev_version()
|
||||
dates = dev_weather[0]
|
||||
day_weathers = dev_weather[1]
|
||||
night_weathers = dev_weather[2]
|
||||
highest_temps = dev_weather[3]
|
||||
lowest_temps = dev_weather[4]
|
||||
icons = dev_weather[5]
|
||||
sunset = dev_weather[6]
|
||||
sunrise = dev_weather[7]
|
||||
humidity = dev_weather[8]
|
||||
wind_speed = dev_weather[9]
|
||||
wind_scale = dev_weather[10]
|
||||
wind_dir = dev_weather[11]
|
||||
uv_index = dev_weather[12]
|
||||
cloud = dev_weather[13]
|
||||
pressure = dev_weather[14]
|
||||
vis = dev_weather[15]
|
||||
|
||||
mail_html = f"""
|
||||
<p style="text-align: center">
|
||||
<i>
|
||||
<b>
|
||||
地区:{self.city_name}
|
||||
<br>
|
||||
发送者:{self.sender}
|
||||
</b>
|
||||
</i>
|
||||
</p>
|
||||
<br />
|
||||
<table style="border: 0; text-align: center; margin:0 auto">
|
||||
<tr>
|
||||
<th>| 日期 </th>
|
||||
<th>| 天气 </th>
|
||||
<th>| 最低温度 </th>
|
||||
<th>| 最高温度 </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<!--日期 天气 最低 最高/Date Weather LowestTemp HighestTemp-->
|
||||
<td>今天</td>
|
||||
<td>{day_weathers[0]}<img src="cid:img1" width="20" alt="">/{night_weathers[0]}<img src="cid:img2"
|
||||
width="20" alt=""></td> <td>{lowest_temps[0]}℃</td>
|
||||
<td>{highest_temps[0]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{dates[1]}</td>
|
||||
<td>{day_weathers[1]}<img src="cid:img3" width="20" alt="">/{night_weathers[1]}<img src="cid:img4"
|
||||
width="20" alt=""></td> <td>{lowest_temps[1]}℃</td>
|
||||
<td>{highest_temps[1]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{dates[2]}</td>
|
||||
<td>{day_weathers[2]}<img src="cid:img5" width="20" alt="">/{night_weathers[2]}<img src="cid:img6"
|
||||
width="20" alt=""></td> <td>{lowest_temps[2]}℃</td>
|
||||
<td>{highest_temps[2]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{dates[3]}</td>
|
||||
<td>{day_weathers[3]}<img src="cid:img7" width="20" alt="">/{night_weathers[3]}<img src="cid:img8"
|
||||
width="20" alt=""></td> <td>{lowest_temps[3]}℃</td>
|
||||
<td>{highest_temps[3]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{dates[4]}</td>
|
||||
<td>{day_weathers[4]}<img src="cid:img9" width="20" alt="">/{night_weathers[4]}<img src="cid:img10"
|
||||
width="20" alt=""></td> <td>{lowest_temps[4]}℃</td>
|
||||
<td>{highest_temps[4]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{dates[5]}</td>
|
||||
<td>{day_weathers[5]}<img src="cid:img11" width="20" alt="">/{night_weathers[5]}<img src="cid:img12"
|
||||
width="20" alt=""></td> <td>{lowest_temps[5]}℃</td>
|
||||
<td>{highest_temps[5]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{dates[6]}</td>
|
||||
<td>{day_weathers[6]}<img src="cid:img13" width="20" alt="">/{night_weathers[6]}<img src="cid:img14"
|
||||
width="20" alt=""></td> <td>{lowest_temps[6]}℃</td>
|
||||
<td>{highest_temps[6]}℃</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table style="border: 0;text-align: center; margin:0 auto">
|
||||
<tr>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>风速/风级/风向</th>
|
||||
<th>相对湿度</th>
|
||||
<th>紫外线</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{wind_speed}m/s {wind_scale} {wind_dir} </td>
|
||||
<td>{humidity}% </td>
|
||||
<td>{uv_index} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>能见度</th>
|
||||
<th>大气压强</th>
|
||||
<th>相对云量</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{vis}km </td>
|
||||
<td>{pressure}hPa </td>
|
||||
<td>{cloud}% </td>
|
||||
</tr>
|
||||
</table>
|
||||
<table style="border: 0;text-align: center; margin:0 auto">
|
||||
<tr>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="cid:sunrise" alt="Sunrise" width="30"></td>
|
||||
<td> </td>
|
||||
<td><img src="cid:sunset" alt="Sunset" width="30"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> {sunrise} </td>
|
||||
<td>---------------</td>
|
||||
<td> {sunset} </td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="text-align: center;" id="About">
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<i>
|
||||
<b>
|
||||
<a href="https://dev.qweather.com/" style="color: black" target="_blank">QWeather</a>
|
||||
<a style="color: black"> · </a>
|
||||
<a href="https://github.com/MarkusJoe/QWeather" style="color: black" target="_blank">Github Repo</a>
|
||||
</b>
|
||||
</i>
|
||||
</div>"""
|
||||
self.message['Subject'] = '七天天气预报' # 邮件标题
|
||||
self.message.attach(MIMEText(mail_html, 'html', 'utf-8'))
|
||||
|
||||
image_count = 1
|
||||
for image_resource in icons:
|
||||
with open(f'./res/icons/{image_resource}.png', 'rb') as fp:
|
||||
MyImage = MIMEImage(fp.read())
|
||||
MyImage.add_header('Content-ID', f'img{image_count}')
|
||||
self.message.attach(MyImage)
|
||||
image_count += 1
|
||||
|
||||
with open('./res/basic-resources/sunrise.png', 'rb') as sr_f:
|
||||
sunrise_img = MIMEImage(sr_f.read())
|
||||
sunrise_img.add_header('Content-ID', 'sunrise')
|
||||
self.message.attach(sunrise_img)
|
||||
with open('./res/basic-resources/sunset.png', 'rb') as ss_f:
|
||||
sunset_img = MIMEImage(ss_f.read())
|
||||
sunset_img.add_header('Content-ID', 'sunset')
|
||||
self.message.attach(sunset_img)
|
||||
|
||||
try:
|
||||
self.smtp.login(self.sender, self.password) # 登录
|
||||
self.smtp.sendmail(self.sender, self.receiver, self.message.as_string()) # 发送
|
||||
Logger.info(f'{self.language["mail_succeed"]}')
|
||||
except smtplib.SMTPException as e: # 处理错误
|
||||
Logger.critical(f'{self.language["mail_error"]}: {e}')
|
||||
sys.exit(1)
|
||||
|
||||
def free_version(self):
|
||||
"""
|
||||
免费版本
|
||||
:return:
|
||||
"""
|
||||
free_weather = WeatherInfo().free_version()
|
||||
dates = free_weather[0]
|
||||
day_weathers = free_weather[1]
|
||||
night_weathers = free_weather[2]
|
||||
highest_temps = free_weather[3]
|
||||
lowest_temps = free_weather[4]
|
||||
icons = free_weather[5]
|
||||
sunset = free_weather[6]
|
||||
sunrise = free_weather[7]
|
||||
humidity = free_weather[8]
|
||||
wind_speed = free_weather[9]
|
||||
wind_scale = free_weather[10]
|
||||
wind_dir = free_weather[11]
|
||||
uv_index = free_weather[12]
|
||||
cloud = free_weather[13]
|
||||
pressure = free_weather[14]
|
||||
vis = free_weather[15]
|
||||
|
||||
mail_html = f"""
|
||||
<p style="text-align: center">
|
||||
<i>
|
||||
<b>
|
||||
地区:{self.city_name}
|
||||
<br>
|
||||
发送者:{self.sender}
|
||||
</b>
|
||||
</i>
|
||||
</p>
|
||||
<br />
|
||||
<table style="border: 0; text-align: center; margin:0 auto">
|
||||
<tr>
|
||||
<th>| 日期 </th>
|
||||
<th>| 天气 </th>
|
||||
<th>| 最低温度 </th>
|
||||
<th>| 最高温度 </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<!--日期 天气 最低 最高/Date Weather LowestTemp HighestTemp-->
|
||||
<td>今天</td>
|
||||
<td>{day_weathers[0]}<img src="cid:img1" width="20" alt="">/{night_weathers[0]}<img src="cid:img2"
|
||||
width="20" alt=""></td> <td>{lowest_temps[0]}℃</td>
|
||||
<td>{highest_temps[0]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{dates[1]}</td>
|
||||
<td>{day_weathers[1]}<img src="cid:img3" width="20" alt="">/{night_weathers[1]}<img src="cid:img4"
|
||||
width="20" alt=""></td> <td>{lowest_temps[1]}℃</td>
|
||||
<td>{highest_temps[1]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{dates[2]}</td>
|
||||
<td>{day_weathers[2]}<img src="cid:img5" width="20" alt="">/{night_weathers[2]}<img src="cid:img6"
|
||||
width="20" alt=""></td> <td>{lowest_temps[2]}℃</td>
|
||||
<td>{highest_temps[2]}℃</td>
|
||||
</table>
|
||||
<table style="border: 0;text-align: center; margin:0 auto">
|
||||
<tr>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>风速/风级/风向</th>
|
||||
<th>相对湿度</th>
|
||||
<th>紫外线</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{wind_speed}m/s {wind_scale} {wind_dir} </td>
|
||||
<td>{humidity}% </td>
|
||||
<td>{uv_index} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>能见度</th>
|
||||
<th>大气压强</th>
|
||||
<th>相对云量</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{vis}km </td>
|
||||
<td>{pressure}hPa </td>
|
||||
<td>{cloud}% </td>
|
||||
</tr>
|
||||
</table>
|
||||
<table style="border: 0;text-align: center; margin:0 auto">
|
||||
<tr>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="cid:sunrise" alt="Sunrise" width="30"></td>
|
||||
<td> </td>
|
||||
<td><img src="cid:sunset" alt="Sunset" width="30"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> {sunrise} </td>
|
||||
<td>---------------</td>
|
||||
<td> {sunset} </td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="text-align: center;" id="About">
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<i>
|
||||
<b>
|
||||
<a href="https://dev.qweather.com/" style="color: black" target="_blank">QWeather</a>
|
||||
<a style="color: black"> · </a>
|
||||
<a href="https://github.com/MarkusJoe/QWeather" style="color: black" target="_blank">Github Repo</a>
|
||||
</b>
|
||||
</i>
|
||||
</div>"""
|
||||
self.message['Subject'] = '三天天气预报' # 邮件标题
|
||||
self.message.attach(MIMEText(mail_html, 'html', 'utf-8'))
|
||||
|
||||
image_count = 1
|
||||
for image_resource in icons:
|
||||
with open(f'./res/icons/{image_resource}.png', 'rb') as fp:
|
||||
MyImage = MIMEImage(fp.read())
|
||||
MyImage.add_header('Content-ID', f'img{image_count}')
|
||||
self.message.attach(MyImage)
|
||||
image_count += 1
|
||||
|
||||
with open('./res/basic-resources/sunrise.png', 'rb') as sr_f:
|
||||
sunrise_img = MIMEImage(sr_f.read())
|
||||
sunrise_img.add_header('Content-ID', 'sunrise')
|
||||
self.message.attach(sunrise_img)
|
||||
with open('./res/basic-resources/sunset.png', 'rb') as ss_f:
|
||||
sunset_img = MIMEImage(ss_f.read())
|
||||
sunset_img.add_header('Content-ID', 'sunset')
|
||||
self.message.attach(sunset_img)
|
||||
|
||||
try:
|
||||
self.smtp.login(self.sender, self.password) # 登录
|
||||
self.smtp.sendmail(self.sender, self.receiver, self.message.as_string()) # 发送
|
||||
Logger.info(f'{self.language["mail_succeed"]}')
|
||||
except smtplib.SMTPException as e: # 处理错误
|
||||
Logger.critical(f'{self.language["mail_error"]}: {e}')
|
||||
sys.exit(1)
|
||||
|
||||
def warning_(self):
|
||||
"""
|
||||
自然灾害预警
|
||||
:return:
|
||||
"""
|
||||
info = WeatherInfo().warning_()
|
||||
release_time = info[0]
|
||||
title = info[1]
|
||||
status = info[2]
|
||||
level = info[3]
|
||||
type_ = info[4]
|
||||
text = info[5]
|
||||
start_time = info[6]
|
||||
end_time = info[7]
|
||||
|
||||
match status:
|
||||
case 'update':
|
||||
status = '预警更新'
|
||||
Logger.info(f'{self.language["new_warning"]}')
|
||||
case 'active':
|
||||
status = '已有灾害'
|
||||
Logger.info(f'{self.language["warning_updated"]}')
|
||||
case 'cancel':
|
||||
Logger.info(f'{self.language["warning_canceled"]}')
|
||||
|
||||
mail_html = f"""
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Warning</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="text-align: center;color: black;">
|
||||
<h2>{title}</h2>
|
||||
<h3>释放时间:{release_time} {level}级</h3>
|
||||
<p>
|
||||
预警状态:{status} 预警类型:{type_} 灾害持续时间:{start_time[:10]}~{end_time[:10]}
|
||||
<br />
|
||||
{text}
|
||||
</p>
|
||||
</div>
|
||||
<div style="text-align: center;" id="About">
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<i>
|
||||
<b>
|
||||
<a href="https://dev.qweather.com/" style="color: black" target="_blank">QWeather</a>
|
||||
<a style="color: black"> · </a>
|
||||
<a href="https://github.com/MarkusJoe/QWeather" style="color: black" target="_blank">Github Repo</a>
|
||||
</b>
|
||||
</i>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
self.message['Subject'] = '自然灾害预警' # 邮件标题
|
||||
self.message.attach(MIMEText(mail_html, 'html', 'utf-8'))
|
||||
|
||||
with open('./res/basic-resources/sunrise.png', 'rb') as sr_f:
|
||||
sunrise_img = MIMEImage(sr_f.read())
|
||||
sunrise_img.add_header('Content-ID', 'sunrise')
|
||||
self.message.attach(sunrise_img)
|
||||
with open('./res/basic-resources/sunset.png', 'rb') as ss_f:
|
||||
sunset_img = MIMEImage(ss_f.read())
|
||||
sunset_img.add_header('Content-ID', 'sunset')
|
||||
self.message.attach(sunset_img)
|
||||
|
||||
try:
|
||||
if status != 'cancel':
|
||||
self.smtp.login(self.sender, self.password) # 登录
|
||||
self.smtp.sendmail(self.sender, self.receiver, self.message.as_string()) # 发送
|
||||
Logger.info(f'{self.language["mail_succeed"]}')
|
||||
except smtplib.SMTPException as e: # 处理错误
|
||||
Logger.critical(f'{self.language["mail_error"]}: {e}')
|
||||
sys.exit(1)
|
||||
70
core/settings.py
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
# -- coding:utf-8 --
|
||||
# @Author: markushammered@gmail.com
|
||||
# @Development Tool: PyCharm
|
||||
# @Create Time: 2021/12/15
|
||||
# @File Name: settings.py
|
||||
|
||||
import sys
|
||||
import time
|
||||
import getpass
|
||||
from core.logger import Logger
|
||||
from core.language import Language
|
||||
from core.read_excel import read_excel
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
|
||||
def change_settings():
|
||||
"""
|
||||
使用read_excel.py搜索到的结果再次进行二次选择, 并写入文件
|
||||
:return:
|
||||
"""
|
||||
language = Language()
|
||||
|
||||
Logger.info(f'[Modify]{language["change_setting"]}')
|
||||
Logger.info(f'[Modify]{language["fill_the_config"]}')
|
||||
Logger.info(f'[Modify]{language["input_a_city_name"]}')
|
||||
while True:
|
||||
time.sleep(0.3)
|
||||
city_name = input('-->')
|
||||
match city_name:
|
||||
case 'q':
|
||||
Logger.info(f'[Exit]{language["exit"]}')
|
||||
sys.exit(0)
|
||||
case '':
|
||||
Logger.error(f'[Modify]{language["null_value"]}')
|
||||
continue
|
||||
case _:
|
||||
break
|
||||
searched_city = read_excel(city_name)
|
||||
Logger.info(f'[Modify]{language["user_input"]}:[{city_name}]')
|
||||
Logger.info(f'[Modify]{language["select_a_index"]}')
|
||||
if not searched_city:
|
||||
Logger.error(f'[Modify]{language["no_result"]}')
|
||||
sys.exit(1)
|
||||
time.sleep(0.3)
|
||||
while True:
|
||||
try:
|
||||
time.sleep(0.3)
|
||||
user_input = input('-->')
|
||||
if user_input == 'q':
|
||||
Logger.info(f'[Exit]{language["exit"]}')
|
||||
sys.exit(1)
|
||||
index = searched_city[int(user_input)]
|
||||
with open('./config.yml', 'r', encoding='utf-8') as of:
|
||||
data = YAML().load(of)
|
||||
data['request-settings']['location'] = index[1]
|
||||
data['only-view-settings']['city-name'] = f'{index[3]}-{index[7]}-{index[7]}'
|
||||
data['only-view-settings']['time'] = time.strftime("%a %b %d %Y %H:%M:%S", time.localtime())
|
||||
data['only-view-settings']['user'] = getpass.getuser()
|
||||
with open('./config.yml', 'w', encoding='utf-8') as wf:
|
||||
YAML().dump(data, wf)
|
||||
Logger.info(f'[Write]{language["write_successfully"]}:config.yml')
|
||||
break
|
||||
except (IndexError, ValueError) as e:
|
||||
Logger.info(e)
|
||||
Logger.error(f'[Write]{language["input_type_error"]}')
|
||||
continue
|
||||
finally:
|
||||
Logger.info(f'[Exit]{language["exit"]}')
|
||||
sys.exit(0)
|
||||
@@ -10,17 +10,27 @@
|
||||
启用等级: DEV
|
||||
"""
|
||||
|
||||
from core.read_config import read_config
|
||||
import requests
|
||||
import json
|
||||
|
||||
|
||||
def hourly_weather(location: int, key: str, lang: str = 'zh', unit: str = 'm'):
|
||||
def hourly_weather():
|
||||
"""
|
||||
获取24小时的天气
|
||||
:return:
|
||||
"""
|
||||
settings = read_config()
|
||||
location = settings[1]['location']
|
||||
key = settings[1]['key']
|
||||
lang = settings[1]['lang']
|
||||
unit = settings[1]['unit']
|
||||
r = requests.get(
|
||||
f'https://devapi.qweather.com/v7/weather/24h?location={location}&key={key}&lang={lang}&unit={unit}')
|
||||
data = json.loads(r.text)
|
||||
|
||||
status_code = data['code']
|
||||
updateTime = str(data['updateTime'][:-6]).replace('T', '')
|
||||
updateTime = str(data['updateTime'][:-6])[10:].replace('T', '')
|
||||
main_data = data['hourly'] # 24
|
||||
return status_code, updateTime, main_data
|
||||
|
||||
|
||||
@@ -11,11 +11,21 @@
|
||||
启用等级: DEV
|
||||
"""
|
||||
|
||||
from core.read_config import read_config
|
||||
import requests
|
||||
import json
|
||||
|
||||
|
||||
def indices(location: int, key: str, lang: str = 'zh', unit: str = 'm'):
|
||||
def indices():
|
||||
"""
|
||||
获取生活建议
|
||||
:return:
|
||||
"""
|
||||
settings = read_config()
|
||||
location = settings[1]['location']
|
||||
key = settings[1]['key']
|
||||
lang = settings[1]['lang']
|
||||
unit = settings[1]['unit']
|
||||
r = requests.get(
|
||||
f'https://devapi.qweather.com/v7/indices/1d?type=0&location={location}&key={key}&lang={lang}&unit={unit}')
|
||||
data = json.loads(r.text)
|
||||
|
||||
@@ -10,29 +10,24 @@
|
||||
启用等级: DEV
|
||||
"""
|
||||
|
||||
from core.read_config import read_config
|
||||
import requests
|
||||
import json
|
||||
import sys
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
|
||||
def real_time_air_quality():
|
||||
yaml = YAML()
|
||||
with open(sys.path[1] + '/config.yml', 'r', encoding='utf-8') as f:
|
||||
config = yaml.load(f.read())
|
||||
"""
|
||||
获取实时的空气质量数据
|
||||
:return:
|
||||
"""
|
||||
settings = read_config()
|
||||
location = settings[1]['location']
|
||||
key = settings[1]['key']
|
||||
lang = settings[1]['lang']
|
||||
unit = settings[1]['unit']
|
||||
|
||||
mode = config['request-settings']['mode']
|
||||
key = config['request-settings']['key']
|
||||
location = config['request-settings']['location']
|
||||
unit = config['request-settings']['unit']
|
||||
lang = config['request-settings']['lang']
|
||||
|
||||
if mode != 'dev':
|
||||
return False, print('Only Dev-mode')
|
||||
session = requests.Session()
|
||||
session.trust_env = False
|
||||
r = session.get(f'https://devapi.qweather.com/v7/air/now?'
|
||||
f'location={location}config.yml&key={key}&lang={lang}&unit={unit}&gzip=y')
|
||||
r = requests.get(f'https://devapi.qweather.com/v7/air/now?'
|
||||
f'location={location}&key={key}&lang={lang}&unit={unit}&gzip=y')
|
||||
_data = json.loads(r.text)
|
||||
|
||||
return _data['code'], _data['now']
|
||||
return _data['now']
|
||||
|
||||
@@ -10,18 +10,19 @@
|
||||
启用等级: DEV
|
||||
"""
|
||||
|
||||
from core.read_config import read_config
|
||||
import requests
|
||||
import json
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
|
||||
def get_warning_list(_range='cn'):
|
||||
yaml = YAML()
|
||||
with open('./config.yml', 'r', encoding='utf-8') as f:
|
||||
config = yaml.load(f.read())
|
||||
key = config['request-settings']['key']
|
||||
session = requests.Session()
|
||||
session.trust_env = False
|
||||
r = session.get(f'https://devapi.qweather.com/v7/warning/list?range={_range}&key={key}')
|
||||
"""
|
||||
获取当前正在发送自然灾害的城市id列表
|
||||
:param _range: Range
|
||||
:return:
|
||||
"""
|
||||
settings = read_config()
|
||||
key = settings[1]['key']
|
||||
r = requests.get(f'https://devapi.qweather.com/v7/warning/list?range={_range}&key={key}')
|
||||
_data = json.loads(r.text)
|
||||
return _data['code'], _data['warningLocList'][0]['locationId']
|
||||
|
||||
315
lib/webserver.py
Normal file
@@ -0,0 +1,315 @@
|
||||
#!/usr/bin/env python3
|
||||
# -- coding:utf-8 --
|
||||
# @Author: markushammered@gmail.com
|
||||
# @Development Tool: PyCharm
|
||||
# @Create Time: 2021/12/18
|
||||
# @File Name: webserver.py
|
||||
|
||||
|
||||
import socket
|
||||
import sys
|
||||
from core.logger import Logger
|
||||
from core.read_config import read_config
|
||||
from core.information import WeatherInfo
|
||||
|
||||
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server.bind(('127.0.0.1', 7898))
|
||||
server.listen(5)
|
||||
|
||||
|
||||
def build_html():
|
||||
settings = read_config()
|
||||
city = settings[3]['city-name']
|
||||
mode = settings[1]['mode']
|
||||
match mode:
|
||||
case 'dev':
|
||||
dev_weather = WeatherInfo().dev_version()
|
||||
dates = dev_weather[0]
|
||||
day_weathers = dev_weather[1]
|
||||
night_weathers = dev_weather[2]
|
||||
highest_temps = dev_weather[3]
|
||||
lowest_temps = dev_weather[4]
|
||||
icons = dev_weather[5]
|
||||
sunset = dev_weather[6]
|
||||
sunrise = dev_weather[7]
|
||||
humidity = dev_weather[8]
|
||||
wind_speed = dev_weather[9]
|
||||
wind_scale = dev_weather[10]
|
||||
wind_dir = dev_weather[11]
|
||||
uv_index = dev_weather[12]
|
||||
cloud = dev_weather[13]
|
||||
pressure = dev_weather[14]
|
||||
vis = dev_weather[15]
|
||||
|
||||
html = f"""
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<p style="text-align: center"><i><b>地区:{city}</b></i></p>
|
||||
<br />
|
||||
<table style="border: 0; text-align: center; margin:0 auto">
|
||||
<tr>
|
||||
<th>| 日期 </th>
|
||||
<th>| 天气 </th>
|
||||
<th>| 最低温度 </th>
|
||||
<th>| 最高温度 </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<!--日期 天气 最低 最高/Date Weather LowestTemp HighestTemp-->
|
||||
<td>今天</td>
|
||||
<td>{day_weathers[0]}<img src="./res/icons/{icons[0]}.png" width="20" alt="">/{night_weathers[0]}
|
||||
<img src="./res/icons/{icons[1]}.png"
|
||||
width="20" alt=""></td> <td>{lowest_temps[0]}℃</td>
|
||||
<td>{highest_temps[0]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{dates[1]}</td>
|
||||
<td>{day_weathers[1]}<img src="./res/icons/{icons[2]}.png" width="20" alt="">/{night_weathers[1]}
|
||||
<img src="./res/icons/{icons[3]}.png"
|
||||
width="20" alt=""></td> <td>{lowest_temps[1]}℃</td>
|
||||
<td>{highest_temps[1]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{dates[2]}</td>
|
||||
<td>{day_weathers[2]}<img src="./res/icons/{icons[4]}.png" width="20" alt="">/{night_weathers[2]}
|
||||
<img src="./res/icons/{icons[5]}.png"
|
||||
width="20" alt=""></td> <td>{lowest_temps[2]}℃</td>
|
||||
<td>{highest_temps[2]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{dates[3]}</td>
|
||||
<td>{day_weathers[3]}<img src="./res/icons/{icons[6]}.png" width="20" alt="">/{night_weathers[3]}
|
||||
<img src="./res/icons/{icons[7]}.png"
|
||||
width="20" alt=""></td> <td>{lowest_temps[3]}℃</td>
|
||||
<td>{highest_temps[3]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{dates[4]}</td>
|
||||
<td>{day_weathers[4]}<img src="./res/icons/{icons[8]}.png" width="20" alt="">/{night_weathers[4]}
|
||||
<img src="./res/icons/{icons[9]}.png"
|
||||
width="20" alt=""></td> <td>{lowest_temps[4]}℃</td>
|
||||
<td>{highest_temps[4]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{dates[5]}</td>
|
||||
<td>{day_weathers[5]}<img src="./res/icons/{icons[10]}.png" width="20" alt="">/{night_weathers[5]}
|
||||
<img src="./res/icons/{icons[11]}.png"
|
||||
width="20" alt=""></td> <td>{lowest_temps[5]}℃</td>
|
||||
<td>{highest_temps[5]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{dates[6]}</td>
|
||||
<td>{day_weathers[6]}<img src="./res/icons/{icons[12]}.png" width="20" alt="">/{night_weathers[6]}
|
||||
<img src="./res/icons/{icons[13]}.png"
|
||||
width="20" alt=""></td> <td>{lowest_temps[6]}℃</td>
|
||||
<td>{highest_temps[6]}℃</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table style="border: 0;text-align: center; margin:0 auto">
|
||||
<tr>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>风速/风级/风向</th>
|
||||
<th>相对湿度</th>
|
||||
<th>紫外线</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{wind_speed}m/s {wind_scale} {wind_dir} </td>
|
||||
<td>{humidity}% </td>
|
||||
<td>{uv_index} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>能见度</th>
|
||||
<th>大气压强</th>
|
||||
<th>相对云量</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{vis}km </td>
|
||||
<td>{pressure}hPa </td>
|
||||
<td>{cloud}% </td>
|
||||
</tr>
|
||||
</table>
|
||||
<table style="border: 0;text-align: center; margin:0 auto">
|
||||
<tr>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="./res/basic-resources/sunrise.png" alt="Sunrise" width="30"></td>
|
||||
<td> </td>
|
||||
<td><img src="./res/basic-resources/sunset.png" alt="Sunset" width="30"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> {sunrise} </td>
|
||||
<td>---------------</td>
|
||||
<td> {sunset} </td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="text-align: center;" id="About">
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<i>
|
||||
<b>
|
||||
<a href="https://dev.qweather.com/" style="color: black" target="_blank">QWeather</a>
|
||||
<a style="color: black"> · </a>
|
||||
<a href="https://github.com/MarkusJoe/QWeather" style="color: black" target="_blank">Github Repo</a>
|
||||
</b>
|
||||
</i>
|
||||
</div>
|
||||
</body>
|
||||
</html>"""
|
||||
return html
|
||||
case 'free':
|
||||
dev_weather = WeatherInfo().free_version()
|
||||
dates = dev_weather[0]
|
||||
day_weathers = dev_weather[1]
|
||||
night_weathers = dev_weather[2]
|
||||
highest_temps = dev_weather[3]
|
||||
lowest_temps = dev_weather[4]
|
||||
icons = dev_weather[5]
|
||||
sunset = dev_weather[6]
|
||||
sunrise = dev_weather[7]
|
||||
humidity = dev_weather[8]
|
||||
wind_speed = dev_weather[9]
|
||||
wind_scale = dev_weather[10]
|
||||
wind_dir = dev_weather[11]
|
||||
uv_index = dev_weather[12]
|
||||
cloud = dev_weather[13]
|
||||
pressure = dev_weather[14]
|
||||
vis = dev_weather[15]
|
||||
|
||||
html = f"""
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<p style="text-align: center"><i><b>地区:{city}</b></i></p>
|
||||
<br />
|
||||
<table style="border: 0; text-align: center; margin:0 auto">
|
||||
<tr>
|
||||
<th>| 日期 </th>
|
||||
<th>| 天气 </th>
|
||||
<th>| 最低温度 </th>
|
||||
<th>| 最高温度 </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<!--日期 天气 最低 最高/Date Weather LowestTemp HighestTemp-->
|
||||
<td>今天</td>
|
||||
<td>{day_weathers[0]}<img src="./res/icons/{icons[0]}.png" width="20" alt="">/{night_weathers[0]}
|
||||
<img src="./res/icons/{icons[1]}.png"
|
||||
width="20" alt=""></td> <td>{lowest_temps[0]}℃</td>
|
||||
<td>{highest_temps[0]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{dates[1]}</td>
|
||||
<td>{day_weathers[1]}<img src="./res/icons/{icons[2]}.png" width="20" alt="">/{night_weathers[1]}
|
||||
<img src="./res/icons/{icons[3]}.png"
|
||||
width="20" alt=""></td> <td>{lowest_temps[1]}℃</td>
|
||||
<td>{highest_temps[1]}℃</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{dates[2]}</td>
|
||||
<td>{day_weathers[2]}<img src="./res/icons/{icons[4]}.png" width="20" alt="">/{night_weathers[2]}
|
||||
<img src="./res/icons/{icons[5]}.png"
|
||||
width="20" alt=""></td> <td>{lowest_temps[2]}℃</td>
|
||||
<td>{highest_temps[2]}℃</td>
|
||||
</table>
|
||||
<table style="border: 0;text-align: center; margin:0 auto">
|
||||
<tr>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>风速/风级/风向</th>
|
||||
<th>相对湿度</th>
|
||||
<th>紫外线</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{wind_speed}m/s {wind_scale} {wind_dir} </td>
|
||||
<td>{humidity}% </td>
|
||||
<td>{uv_index} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>能见度</th>
|
||||
<th>大气压强</th>
|
||||
<th>相对云量</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{vis}km </td>
|
||||
<td>{pressure}hPa </td>
|
||||
<td>{cloud}% </td>
|
||||
</tr>
|
||||
</table>
|
||||
<table style="border: 0;text-align: center; margin:0 auto">
|
||||
<tr>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="./res/basic-resources/sunrise.png" alt="Sunrise" width="30"></td>
|
||||
<td> </td>
|
||||
<td><img src="./res/basic-resources/sunset.png" alt="Sunset" width="30"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> {sunrise} </td>
|
||||
<td>---------------</td>
|
||||
<td> {sunset} </td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="text-align: center;" id="About">
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<i>
|
||||
<b>
|
||||
<a href="https://dev.qweather.com/" style="color: black" target="_blank">QWeather</a>
|
||||
<a style="color: black"> · </a>
|
||||
<a href="https://github.com/MarkusJoe/QWeather" style="color: black" target="_blank">Github Repo</a>
|
||||
</b>
|
||||
</i>
|
||||
</div>
|
||||
</body>
|
||||
</html>"""
|
||||
return html
|
||||
case _:
|
||||
return "You hadn't selected a mode"
|
||||
|
||||
|
||||
def process_request():
|
||||
try:
|
||||
while True:
|
||||
c, a = server.accept()
|
||||
data = str(c.recv(1024)).split(':')[0][6:][:-17]
|
||||
html = build_html()
|
||||
if data == '/':
|
||||
c.send('HTTP1.1/ 200 OK\r\n\r\n'.encode('utf-8'))
|
||||
c.send(html.encode('utf-8'))
|
||||
Logger.info(f'{a}: Get {data} --by browser')
|
||||
else:
|
||||
try:
|
||||
with open(f'.{data}', 'rb') as f:
|
||||
c.send('HTTP1.1/ 200 OK\r\n\r\n'.encode('utf-8'))
|
||||
c.send(f.read())
|
||||
Logger.info(f'{a}: Get {data} --by browser')
|
||||
except FileNotFoundError:
|
||||
c.send(f'HTTP1.1/ 404 Not Found\r\n\r\n{html}'.encode('utf-8'))
|
||||
c.close()
|
||||
except BrokenPipeError:
|
||||
Logger.critical('Link speed was too fast! Subprocess:webserver exited')
|
||||
sys.exit(1)
|
||||
except IOError:
|
||||
Logger.critical('An IO Error')
|
||||
sys.exit(1)
|
||||
@@ -1,7 +1,5 @@
|
||||
requests==2.26.0
|
||||
ruamel.yaml==0.17.17
|
||||
pandas==1.3.4
|
||||
pyppeteer==0.2.6
|
||||
bs4==0.0.1
|
||||
openpyxl==3.0.9
|
||||
colorlog==6.5.0
|
||||
requests>=2.26.0
|
||||
ruamel.yaml>=0.17.17
|
||||
pandas>=1.3.4
|
||||
colorlog>=6.5.0
|
||||
openpyxl>=3.0.9
|
||||
|
||||
BIN
res/basic-resources/sunrise.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
res/basic-resources/sunset.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
res/icons/100.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
res/icons/1001.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
res/icons/1002.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
res/icons/1003.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
res/icons/1004.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
res/icons/1005.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
res/icons/1006.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
res/icons/1007.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
res/icons/1008.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
res/icons/1009.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
res/icons/101.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
res/icons/1010.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
res/icons/1011.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
res/icons/1012.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
res/icons/1013.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
res/icons/1014.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
res/icons/1015.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
res/icons/1016.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
res/icons/1017.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
res/icons/1018.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
res/icons/1019.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
res/icons/102.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
res/icons/1020.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
res/icons/1021.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
res/icons/1022.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
res/icons/1023.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
res/icons/1024.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
res/icons/1025.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
res/icons/1026.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
res/icons/1027.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
res/icons/1028.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
res/icons/1029.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
res/icons/103.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
res/icons/1030.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
res/icons/1031.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
res/icons/1032.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
res/icons/1033.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
res/icons/1034.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
res/icons/1035.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
res/icons/1036.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
res/icons/1037.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
res/icons/1038.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
res/icons/1039.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
res/icons/104.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
res/icons/1040.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
res/icons/1041.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
res/icons/1042.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
res/icons/1043.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
res/icons/1044.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
res/icons/1045.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
res/icons/1046.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
res/icons/1047.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
res/icons/1048.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
res/icons/1049.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
res/icons/1050.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
res/icons/1051.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
res/icons/1052.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
res/icons/1053.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
res/icons/1054.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
res/icons/1055.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
res/icons/1056.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
res/icons/1057.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
res/icons/1058.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
res/icons/1059.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
res/icons/1061.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
res/icons/1064.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
res/icons/1101.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
res/icons/1302.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
res/icons/1402.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
res/icons/150.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
res/icons/151.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
res/icons/152.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
res/icons/153.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
res/icons/1601.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
res/icons/1602.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
res/icons/1603.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
res/icons/1604.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
res/icons/1605.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |