🚂🔨FN_extend
This commit is contained in:
parent
596130b3a8
commit
5795e02548
53
Scripts/py/FreeNom/FN_extend.py
Normal file
53
Scripts/py/FreeNom/FN_extend.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# -*- coding: utf8 -*-
|
||||||
|
'''
|
||||||
|
Author: shuai93
|
||||||
|
Modifier: Oreo
|
||||||
|
Date: Wed Aug 11 10:15:41 UTC 2021
|
||||||
|
建议cron: 25 7 */10 * * python3 FN_extend.py
|
||||||
|
------------
|
||||||
|
环境变量说明 示例
|
||||||
|
FN_ID: Freenom 用户名 1234567890@gmail.com
|
||||||
|
FN_PW: Freenom 密码 12345678
|
||||||
|
MAIL_USER: 发件人邮箱用户名 address@vip.qq.com 或 123456@qq.com
|
||||||
|
MAIL_ADDRESS: 发件人邮箱地址 address@vip.qq.com 或 123456@qq.com
|
||||||
|
* MAIL_HOST: 发件人邮箱服务器 smt.qq.com 不填默认为这个
|
||||||
|
* MAIL_PORT: 邮箱服务器端口 465 不填默认为这个
|
||||||
|
MAIL_TO: 收件人邮箱可与发件人相同 address@vip.qq.com 或 123456@qq.com
|
||||||
|
|
||||||
|
填写总参考:https://service.mail.qq.com/cgi-bin/help?subtype=1&&id=28&&no=369
|
||||||
|
------------
|
||||||
|
依赖模块说明
|
||||||
|
pip install -r requirements.txt / pip3 install -r requirements.txt
|
||||||
|
'''
|
||||||
|
from utils.settings import *
|
||||||
|
from utils.exception import CustomException
|
||||||
|
from utils.freenom import FreeNom
|
||||||
|
from utils.mail import EmailPoster
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("配置信息")
|
||||||
|
print([MAIL_TO, MAIL_PORT, MAIL_HOST, MAIL_ADDRESS, MAIL_PW, MAIL_USER, FN_ID, FN_PW])
|
||||||
|
if not all([MAIL_TO, MAIL_PORT, MAIL_HOST, MAIL_ADDRESS, MAIL_PW, MAIL_USER, FN_ID, FN_PW]):
|
||||||
|
raise CustomException("参数缺失")
|
||||||
|
|
||||||
|
to = [MAIL_TO]
|
||||||
|
|
||||||
|
body = {
|
||||||
|
'subject': "FreeNom 自动续期",
|
||||||
|
'to': to,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
results = FreeNom().run()
|
||||||
|
body['payload'] = {
|
||||||
|
"results": results,
|
||||||
|
"user": FN_ID
|
||||||
|
}
|
||||||
|
except CustomException as e:
|
||||||
|
body['body'] = e.message
|
||||||
|
|
||||||
|
EmailPoster().send(data=body)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
7
Scripts/py/FreeNom/requirements.txt
Normal file
7
Scripts/py/FreeNom/requirements.txt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
certifi==2020.12.5
|
||||||
|
chardet==4.0.0
|
||||||
|
idna==2.10
|
||||||
|
Jinja2==3.0.0
|
||||||
|
MarkupSafe==2.0.0
|
||||||
|
requests==2.25.1
|
||||||
|
urllib3==1.26.4
|
||||||
344
Scripts/py/FreeNom/templates/default.html
Normal file
344
Scripts/py/FreeNom/templates/default.html
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
<title>Simple Transactional Email</title>
|
||||||
|
<style>
|
||||||
|
/* -------------------------------------
|
||||||
|
GLOBAL RESETS
|
||||||
|
------------------------------------- */
|
||||||
|
img {
|
||||||
|
border: none;
|
||||||
|
-ms-interpolation-mode: bicubic;
|
||||||
|
max-width: 100%; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
font-family: sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
-webkit-text-size-adjust: 100%; }
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: separate;
|
||||||
|
mso-table-lspace: 0pt;
|
||||||
|
mso-table-rspace: 0pt;
|
||||||
|
width: 100%; }
|
||||||
|
table td {
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
vertical-align: top; }
|
||||||
|
|
||||||
|
/* -------------------------------------
|
||||||
|
BODY & CONTAINER
|
||||||
|
------------------------------------- */
|
||||||
|
|
||||||
|
.body {
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
width: 100%; }
|
||||||
|
|
||||||
|
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
|
||||||
|
.container {
|
||||||
|
display: block;
|
||||||
|
Margin: 0 auto !important;
|
||||||
|
/* makes it centered */
|
||||||
|
max-width: 580px;
|
||||||
|
padding: 10px;
|
||||||
|
width: 580px; }
|
||||||
|
|
||||||
|
/* This should also be a block element, so that it will fill 100% of the .container */
|
||||||
|
.content {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
Margin: 0 auto;
|
||||||
|
max-width: 580px;
|
||||||
|
padding: 10px; }
|
||||||
|
|
||||||
|
/* -------------------------------------
|
||||||
|
HEADER, FOOTER, MAIN
|
||||||
|
------------------------------------- */
|
||||||
|
.main {
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 3px;
|
||||||
|
width: 100%; }
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 20px; }
|
||||||
|
|
||||||
|
.content-block {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
clear: both;
|
||||||
|
Margin-top: 10px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%; }
|
||||||
|
.footer td,
|
||||||
|
.footer p,
|
||||||
|
.footer span,
|
||||||
|
.footer a {
|
||||||
|
color: #999999;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center; }
|
||||||
|
|
||||||
|
/* -------------------------------------
|
||||||
|
TYPOGRAPHY
|
||||||
|
------------------------------------- */
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4 {
|
||||||
|
color: #000000;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin: 0;
|
||||||
|
Margin-bottom: 30px; }
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 35px;
|
||||||
|
font-weight: 300;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: capitalize; }
|
||||||
|
|
||||||
|
p,
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: normal;
|
||||||
|
margin: 0;
|
||||||
|
Margin-bottom: 15px; }
|
||||||
|
p li,
|
||||||
|
ul li,
|
||||||
|
ol li {
|
||||||
|
list-style-position: inside;
|
||||||
|
margin-left: 5px; }
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #3498db;
|
||||||
|
text-decoration: underline; }
|
||||||
|
|
||||||
|
/* -------------------------------------
|
||||||
|
BUTTONS
|
||||||
|
------------------------------------- */
|
||||||
|
.btn {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%; }
|
||||||
|
.btn > tbody > tr > td {
|
||||||
|
padding-bottom: 15px; }
|
||||||
|
.btn table {
|
||||||
|
width: auto; }
|
||||||
|
.btn table td {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-align: center; }
|
||||||
|
.btn a {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: solid 1px #3498db;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #3498db;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px 25px;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: capitalize; }
|
||||||
|
|
||||||
|
.btn-primary table td {
|
||||||
|
background-color: #3498db; }
|
||||||
|
|
||||||
|
.btn-primary a {
|
||||||
|
background-color: #3498db;
|
||||||
|
border-color: #3498db;
|
||||||
|
color: #ffffff; }
|
||||||
|
|
||||||
|
/* -------------------------------------
|
||||||
|
OTHER STYLES THAT MIGHT BE USEFUL
|
||||||
|
------------------------------------- */
|
||||||
|
.last {
|
||||||
|
margin-bottom: 0; }
|
||||||
|
|
||||||
|
.first {
|
||||||
|
margin-top: 0; }
|
||||||
|
|
||||||
|
.align-center {
|
||||||
|
text-align: center; }
|
||||||
|
|
||||||
|
.align-right {
|
||||||
|
text-align: right; }
|
||||||
|
|
||||||
|
.align-left {
|
||||||
|
text-align: left; }
|
||||||
|
|
||||||
|
.clear {
|
||||||
|
clear: both; }
|
||||||
|
|
||||||
|
.mt0 {
|
||||||
|
margin-top: 0; }
|
||||||
|
|
||||||
|
.mb0 {
|
||||||
|
margin-bottom: 0; }
|
||||||
|
|
||||||
|
.preheader {
|
||||||
|
color: transparent;
|
||||||
|
display: none;
|
||||||
|
height: 0;
|
||||||
|
max-height: 0;
|
||||||
|
max-width: 0;
|
||||||
|
opacity: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
mso-hide: all;
|
||||||
|
visibility: hidden;
|
||||||
|
width: 0; }
|
||||||
|
|
||||||
|
.powered-by a {
|
||||||
|
text-decoration: none; }
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 1px solid #f6f6f6;
|
||||||
|
Margin: 20px 0; }
|
||||||
|
|
||||||
|
/* -------------------------------------
|
||||||
|
RESPONSIVE AND MOBILE FRIENDLY STYLES
|
||||||
|
------------------------------------- */
|
||||||
|
@media only screen and (max-width: 620px) {
|
||||||
|
table[class=body] h1 {
|
||||||
|
font-size: 28px !important;
|
||||||
|
margin-bottom: 10px !important; }
|
||||||
|
table[class=body] p,
|
||||||
|
table[class=body] ul,
|
||||||
|
table[class=body] ol,
|
||||||
|
table[class=body] td,
|
||||||
|
table[class=body] span,
|
||||||
|
table[class=body] a {
|
||||||
|
font-size: 16px !important; }
|
||||||
|
table[class=body] .wrapper,
|
||||||
|
table[class=body] .article {
|
||||||
|
padding: 10px !important; }
|
||||||
|
table[class=body] .content {
|
||||||
|
padding: 0 !important; }
|
||||||
|
table[class=body] .container {
|
||||||
|
padding: 0 !important;
|
||||||
|
width: 100% !important; }
|
||||||
|
table[class=body] .main {
|
||||||
|
border-left-width: 0 !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
border-right-width: 0 !important; }
|
||||||
|
table[class=body] .btn table {
|
||||||
|
width: 100% !important; }
|
||||||
|
table[class=body] .btn a {
|
||||||
|
width: 100% !important; }
|
||||||
|
table[class=body] .img-responsive {
|
||||||
|
height: auto !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
width: auto !important; }}
|
||||||
|
|
||||||
|
/* -------------------------------------
|
||||||
|
PRESERVE THESE STYLES IN THE HEAD
|
||||||
|
------------------------------------- */
|
||||||
|
@media all {
|
||||||
|
.ExternalClass {
|
||||||
|
width: 100%; }
|
||||||
|
.ExternalClass,
|
||||||
|
.ExternalClass p,
|
||||||
|
.ExternalClass span,
|
||||||
|
.ExternalClass font,
|
||||||
|
.ExternalClass td,
|
||||||
|
.ExternalClass div {
|
||||||
|
line-height: 100%; }
|
||||||
|
.apple-link a {
|
||||||
|
color: inherit !important;
|
||||||
|
font-family: inherit !important;
|
||||||
|
font-size: inherit !important;
|
||||||
|
font-weight: inherit !important;
|
||||||
|
line-height: inherit !important;
|
||||||
|
text-decoration: none !important; }
|
||||||
|
.btn-primary table td:hover {
|
||||||
|
background-color: #34495e !important; }
|
||||||
|
.btn-primary a:hover {
|
||||||
|
background-color: #34495e !important;
|
||||||
|
border-color: #34495e !important; } }
|
||||||
|
td {
|
||||||
|
|
||||||
|
text-align:center;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" class="body">
|
||||||
|
<tr>
|
||||||
|
<td> </td>
|
||||||
|
<td class="container">
|
||||||
|
<div class="content" style="text-align:center">
|
||||||
|
<h4 style="text-align:left" >账户 <span style="color: #34495e"> {{ payload.get('user') }} </span> 今天所有域名续期情况如下:</h4>
|
||||||
|
|
||||||
|
<!-- START CENTERED WHITE CONTAINER -->
|
||||||
|
<table class="main">
|
||||||
|
<!-- START MAIN CONTENT AREA -->
|
||||||
|
<tr>
|
||||||
|
<td class="wrapper">
|
||||||
|
<table style="width: 100%;margin:auto" >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>域名</th>
|
||||||
|
<th>剩余天数</th>
|
||||||
|
<th>结果</th>
|
||||||
|
<th>详情</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
{% for data in payload.get('results') %}
|
||||||
|
<tr>
|
||||||
|
<td>{{data[0]}}</td>
|
||||||
|
<td>{{data[1]}}</td>
|
||||||
|
<td>{{data[3]}}</td>
|
||||||
|
<td><a href="https://my.freenom.com/domains.php?a=renewdomain&domain={{data[2]}}" >查看详情</a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- END MAIN CONTENT AREA -->
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- START FOOTER -->
|
||||||
|
<div class="footer">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0">
|
||||||
|
<tr>
|
||||||
|
<td class="content-block">
|
||||||
|
<span class="apple-link"></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="content-block powered-by">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- END FOOTER -->
|
||||||
|
|
||||||
|
<!-- END CENTERED WHITE CONTAINER -->
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td> </td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
0
Scripts/py/FreeNom/utils/__init__.py
Normal file
0
Scripts/py/FreeNom/utils/__init__.py
Normal file
9
Scripts/py/FreeNom/utils/exception.py
Normal file
9
Scripts/py/FreeNom/utils/exception.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
class CustomException(Exception):
|
||||||
|
|
||||||
|
def __init__(self, message):
|
||||||
|
super().__init__(self)
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.message
|
||||||
126
Scripts/py/FreeNom/utils/freenom.py
Normal file
126
Scripts/py/FreeNom/utils/freenom.py
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from utils import settings
|
||||||
|
from utils.exception import CustomException
|
||||||
|
|
||||||
|
|
||||||
|
class FreeNom(object):
|
||||||
|
"""
|
||||||
|
FreeNom api请求
|
||||||
|
"""
|
||||||
|
# 登录
|
||||||
|
LOGIN_URL = 'https://my.freenom.com/dologin.php'
|
||||||
|
# 查看域名状态
|
||||||
|
DOMAIN_STATUS_URL = 'https://my.freenom.com/domains.php?a=renewals'
|
||||||
|
# 域名续期
|
||||||
|
RENEW_DOMAIN_URL = 'https://my.freenom.com/domains.php?submitrenewals=true'
|
||||||
|
|
||||||
|
TOKEN_REGEX = 'name="token"\svalue="(?P<token>[a-z||A-Z||0-9]+)"'
|
||||||
|
DOMAIN_INFO_REGEX = '<tr><td>(?P<domain>[^<]+)<\/td><td>[^<]+<\/td><td>[^<]+<span class="[^"]+">(?P<days>\d+)[' \
|
||||||
|
'^&]+&domain=(?P<id>\d+)"'
|
||||||
|
LOGIN_STATUS_REGEX = '<li.*?Logout.*?<\/li>'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.headers = {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
}
|
||||||
|
self.session = requests.session()
|
||||||
|
self.token_pattern = re.compile(self.TOKEN_REGEX)
|
||||||
|
self.domain_info_pattern = re.compile(self.DOMAIN_INFO_REGEX)
|
||||||
|
self.login_pattern = re.compile(self.LOGIN_STATUS_REGEX)
|
||||||
|
|
||||||
|
def run(self) -> list:
|
||||||
|
self.login()
|
||||||
|
html = self.get_domains()
|
||||||
|
token_match = self.token_pattern.findall(html)
|
||||||
|
domain_info_match = self.domain_info_pattern.findall(html)
|
||||||
|
login_match = self.login_pattern.findall(html)
|
||||||
|
|
||||||
|
if not login_match:
|
||||||
|
print("FreeNom login parse failed")
|
||||||
|
raise CustomException("登录检查失败")
|
||||||
|
|
||||||
|
if not token_match:
|
||||||
|
print("FreeNom token parse failed")
|
||||||
|
raise CustomException("页面token检查失败")
|
||||||
|
|
||||||
|
if not domain_info_match:
|
||||||
|
print("FreeNom domain info parse failed")
|
||||||
|
raise CustomException("页面没有获取到域名信息")
|
||||||
|
|
||||||
|
token = token_match[0]
|
||||||
|
print(f"waiting for renew domain info is {domain_info_match}")
|
||||||
|
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for info in domain_info_match:
|
||||||
|
time.sleep(1)
|
||||||
|
domain, days, domain_id = info
|
||||||
|
msg = "失败"
|
||||||
|
|
||||||
|
if int(days) > 14:
|
||||||
|
print(f"FreeNom domain {domain} can not renew, days until expiry is {days}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
response = self.renew_domain(token, domain_id)
|
||||||
|
|
||||||
|
if response.find("Order Confirmation") != -1:
|
||||||
|
msg = "成功"
|
||||||
|
print(f"FreeNom renew domain {domain} is success")
|
||||||
|
|
||||||
|
result.append((domain, days, domain_id, msg))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
data = {
|
||||||
|
'username': settings.FN_ID,
|
||||||
|
'password': settings.FN_PW
|
||||||
|
}
|
||||||
|
headers = {
|
||||||
|
**self.headers,
|
||||||
|
'Referer': 'https://my.freenom.com/clientarea.php'
|
||||||
|
}
|
||||||
|
response = self.session.post(self.LOGIN_URL, data=data, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print("FreeNom login failed")
|
||||||
|
raise CustomException("调用登录接口失败")
|
||||||
|
|
||||||
|
def get_domains(self) -> str:
|
||||||
|
headers = {
|
||||||
|
'Referer': 'https://my.freenom.com/clientarea.php'
|
||||||
|
}
|
||||||
|
response = self.session.get(self.DOMAIN_STATUS_URL, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.text
|
||||||
|
else:
|
||||||
|
print("FreeNom check domain status failed")
|
||||||
|
raise CustomException("调用获取域名信息接口失败")
|
||||||
|
|
||||||
|
def renew_domain(self, token, renewalid) -> str:
|
||||||
|
headers = {
|
||||||
|
**self.headers,
|
||||||
|
"Referer": "https://my.freenom.com/domains.php?a=renewdomain&domain=" + "renewalid"
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"token": token,
|
||||||
|
"renewalid": renewalid,
|
||||||
|
f"renewalperiod[{renewalid}]": "12M",
|
||||||
|
'paymentmethod': 'credit'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.post(self.RENEW_DOMAIN_URL, data=data, headers=headers)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.text
|
||||||
|
else:
|
||||||
|
print("FreeNom renew domain failed")
|
||||||
|
raise CustomException("调用续期接口失败接口失败")
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.session.close()
|
||||||
52
Scripts/py/FreeNom/utils/mail.py
Normal file
52
Scripts/py/FreeNom/utils/mail.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import smtplib
|
||||||
|
import traceback
|
||||||
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from jinja2 import FileSystemLoader, Environment, Template
|
||||||
|
|
||||||
|
from . import settings
|
||||||
|
|
||||||
|
|
||||||
|
class EmailPoster(object):
|
||||||
|
"""
|
||||||
|
邮件发送基础类
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_template():
|
||||||
|
loader = FileSystemLoader('templates')
|
||||||
|
env = Environment(loader=loader)
|
||||||
|
template = env.get_template("default.html")
|
||||||
|
return template
|
||||||
|
|
||||||
|
def send(self, data: dict):
|
||||||
|
payload = data.get("payload", {})
|
||||||
|
if payload:
|
||||||
|
template = self.get_template()
|
||||||
|
content = template.render(payload=payload)
|
||||||
|
else:
|
||||||
|
content = data.get('body', '')
|
||||||
|
subject = data.get('subject', '')
|
||||||
|
mail_to = data.get('to', [])
|
||||||
|
mail_from = data.get('from', settings.MAIL_ADDRESS)
|
||||||
|
self._send(content, subject, mail_from, mail_to)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _send(content: str, subject: str, mail_from: str, mail_to: list):
|
||||||
|
msg_root = MIMEMultipart('related')
|
||||||
|
msg_text = MIMEText(content, 'html', 'utf-8')
|
||||||
|
msg_root.attach(msg_text)
|
||||||
|
msg_root['Subject'] = subject
|
||||||
|
msg_root['From'] = mail_from
|
||||||
|
msg_root['To'] = ";".join(mail_to)
|
||||||
|
|
||||||
|
try:
|
||||||
|
smtp = smtplib.SMTP_SSL(settings.MAIL_HOST, settings.MAIL_PORT)
|
||||||
|
# smtp.set_debuglevel(1)
|
||||||
|
smtp.ehlo()
|
||||||
|
smtp.login(settings.MAIL_USER, settings.MAIL_PW)
|
||||||
|
smtp.sendmail(settings.MAIL_ADDRESS, mail_to, msg_root.as_string())
|
||||||
|
smtp.quit()
|
||||||
|
except Exception as e:
|
||||||
|
print(traceback.format_exc(e))
|
||||||
15
Scripts/py/FreeNom/utils/settings.py
Normal file
15
Scripts/py/FreeNom/utils/settings.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
# qq mail
|
||||||
|
MAIL_ADDRESS = os.getenv("MAIL_ADDRESS", "")
|
||||||
|
MAIL_HOST = os.getenv("SMTP_HOST", "smtp.qq.com")
|
||||||
|
MAIL_PW= os.getenv("MAIL_PW", "")
|
||||||
|
MAIL_PORT = int(os.getenv("SMTP_PORT", 465))
|
||||||
|
MAIL_TO = os.getenv("MAIL_TO", "")
|
||||||
|
MAIL_USER = os.getenv("MAIL_USER", "")
|
||||||
|
|
||||||
|
|
||||||
|
# free nom
|
||||||
|
FN_ID = os.getenv("FN_ID", "")
|
||||||
|
FN_PW = os.getenv("FN_PW", "")
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ Date: Tue Aug 10 08:24:30 UTC 2021
|
|||||||
MI_USER: 账号 仅支持手机号,多账号用 # 分隔
|
MI_USER: 账号 仅支持手机号,多账号用 # 分隔
|
||||||
MI_PWD: 密码 多账号用 # 分隔,且与账号一一对应
|
MI_PWD: 密码 多账号用 # 分隔,且与账号一一对应
|
||||||
STEP: 步数 空或不填则为 18000-25000 之间随机,自定义示例: 18763 或 19000-24000
|
STEP: 步数 空或不填则为 18000-25000 之间随机,自定义示例: 18763 或 19000-24000
|
||||||
PMODE: 推送模式 || PKEY: 具体推送格式填写(不带 [ ],请用具体的值代替)
|
PMODE: 推送模式 || PKEY: 具体推送格式填写(不带 [TG: ],请用具体的值代替)
|
||||||
wx [Server 酱: skey]
|
wx [Server 酱: skey]
|
||||||
nwx [新 Server 酱: skey]
|
nwx [新 Server 酱: skey]
|
||||||
tg [TG: tg_bot_token@user_id]
|
tg [TG: tg_bot_token@user_id]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user