Support show gift code

This commit is contained in:
xtaodada 2023-06-10 22:20:58 +08:00
parent 01fb1f9e16
commit d034fe0a72
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
10 changed files with 611 additions and 1 deletions

2
.gitignore vendored
View File

@ -157,4 +157,4 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ .idea/

55
add.py Normal file
View File

@ -0,0 +1,55 @@
from pathlib import Path
from sys import argv
from datetime import datetime
from typing import List
from models.code import CodeList, Code, Reward
data_path = Path("data")
custom_path = data_path / "custom.json"
def add(code: str, expire_str: str, rewards_str: List[str]) -> Code:
expire = datetime.strptime(expire_str, "%Y-%m-%d")
expire = expire.replace(hour=23, minute=59, second=59, microsecond=999999)
rewards = []
for reward_str in rewards_str:
reward_list = reward_str.split(":")
rewards.append(
Reward(
name=reward_list[0],
cnt=int(reward_list[1])
)
)
return Code(
code=code,
expire=int(expire.timestamp() * 1000),
reward=rewards,
)
if __name__ == '__main__':
try:
add_type = argv[1]
if add_type not in ["main", "over"]:
raise IndexError
code = add(argv[2], argv[3], argv[4:])
with open(custom_path, "r", encoding="utf-8") as f:
custom: CodeList = CodeList.parse_raw(f.read())
if add_type == "main":
main_codes = [i.code for i in custom.main]
if code.code in main_codes:
raise ValueError("Duplicate code")
custom.main.append(code)
else:
over_codes = [i.code for i in custom.over]
if code.code in over_codes:
raise ValueError("Duplicate code")
custom.over.append(code)
custom.main.sort(key=lambda x: x.expire, reverse=True)
custom.over.sort(key=lambda x: x.expire, reverse=True)
with open(custom_path, "w", encoding="utf-8") as f:
f.write(custom.json(indent=4, ensure_ascii=False))
except IndexError:
print("Usage: python add.py [main/over] [code] [expire] [rewards...]")
print("Example: python add.py main code 2023-11-1 星琼:1 信用点:1000")

189
data/code.html Normal file
View File

@ -0,0 +1,189 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Dark Responsive Page with Tabs</title>
<!-- 新 Bootstrap5 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/css/bootstrap.min.css">
<!-- 最新的 Bootstrap5 核心 JavaScript 文件 -->
<script src="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/js/bootstrap.bundle.min.js"></script>
<!-- custom styles -->
<style>
body {
background-color: #222;
color: #222;
}
h1 {
color: #eee;
margin-top: 50px;
text-align: center;
}
.table-title {
text-align: center;
font-size: 24px;
font-weight: bold;
margin-bottom: 20px;
}
.navbar {
background-color: #333;
position: fixed;
bottom: 0;
width: 100%;
}
.navbar-brand {
color: #eee;
}
.tab-content {
margin-top: 20px;
}
.table {
margin-top: 20px;
}
.table-title {
color: #eee;
}
</style>
</head>
<body>
<header>
<h1>《崩坏:星穹铁道》兑换码</h1>
</header>
<div style="height: 30px;"></div>
<main class="container">
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#tab1">国服(官服)</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#tab2">国际服</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tab1">
<div class="row">
<div class="col-12 col-md-8 offset-md-2">
<h2 class="table-title">未到期</h2>
<table class="table table-dark table-striped">
<thead>
<tr>
<th>兑换码</th>
<th>奖励</th>
<th>到期时间</th>
</tr>
</thead>
<tbody id="main-true">
</tbody>
</table>
<h2 class="table-title">已到期</h2>
<table class="table table-dark table-striped">
<thead>
<tr>
<th>兑换码</th>
<th>奖励</th>
<th>到期时间</th>
</tr>
</thead>
<tbody id="main-false">
</tbody>
</table>
</div>
</div>
</div>
<div class="tab-pane" id="tab2">
<div class="row">
<div class="col-12 col-md-8 offset-md-2">
<h2 class="table-title">未到期</h2>
<table class="table table-dark table-striped">
<thead>
<tr>
<th>兑换码</th>
<th>奖励</th>
<th>到期时间</th>
</tr>
</thead>
<tbody id="over-true">
</tbody>
</table>
<h2 class="table-title">已到期</h2>
<table class="table table-dark table-striped">
<thead>
<tr>
<th>兑换码</th>
<th>奖励</th>
<th>到期时间</th>
</tr>
</thead>
<tbody id="over-false">
</tbody>
</table>
</div>
</div>
</div>
</div>
</main>
<div style="height: 100px;"></div>
<footer>
<nav class="navbar">
<div class="container-fluid">
<a class="navbar-brand" href="#">PaiGramTeam</a>
</div>
</nav>
</footer>
<script>
function addRow(tableId, data) {
let now = new Date();
let expire_time = new Date(data.expire);
if (now > expire_time) {
tableId += "-false";
} else {
tableId += "-true";
}
let table = document.getElementById(tableId);
let new_tr = document.createElement("tr");
let code = document.createElement("td");
code.innerHTML = data.code;
let reward = document.createElement("td");
data.reward.forEach(function (item) {
let p = document.createElement("p");
p.innerHTML = item.name + " x" + item.cnt;
reward.appendChild(p);
});
let expire = document.createElement("td");
expire.innerHTML = expire_time.toLocaleString();
new_tr.appendChild(code);
new_tr.appendChild(reward);
new_tr.appendChild(expire);
table.appendChild(new_tr);
}
fetch('code.json')
.then(response => response.json())
.then(data => {
for (let key in data) {
for (let value in data[key]) {
addRow(key, data[key][value]);
}
}
})
.catch(error => console.error(error));
</script>
</body>
</html>

214
data/code.json Normal file
View File

@ -0,0 +1,214 @@
{
"main": [
{
"code": "MIYOUSHE2023",
"reward": [
{
"name": "星琼",
"cnt": 60
}
],
"expire": 1686402166000
}
],
"over": [
{
"code": "BSN2EWMHA4RP",
"reward": [
{
"name": "星琼",
"cnt": 100
},
{
"name": "信用点",
"cnt": 10000
}
],
"expire": 4102415999999
},
{
"code": "HSRVER10JYTGHC",
"reward": [
{
"name": "星琼",
"cnt": 100
},
{
"name": "信用点",
"cnt": 10000
}
],
"expire": 4102415999999
},
{
"code": "STARRAILGIFT",
"reward": [
{
"name": "星琼",
"cnt": 50
},
{
"name": "信用点",
"cnt": 10000
}
],
"expire": 4102415999999
},
{
"code": "SURPRISE1024",
"reward": [
{
"name": "星琼",
"cnt": 100
},
{
"name": "冒险记录",
"cnt": 3
},
{
"name": "疾速粉尘",
"cnt": 2
},
{
"name": "信用点",
"cnt": 5000
}
],
"expire": 1686070799999
},
{
"code": "ZTPTNMTX8LUF",
"reward": [
{
"name": "星琼",
"cnt": 100
},
{
"name": "信用点",
"cnt": 50000
}
],
"expire": 1685206799999
},
{
"code": "8A6T6LBFQ4D3",
"reward": [
{
"name": "星琼",
"cnt": 100
},
{
"name": "漫游指南",
"cnt": 5
}
],
"expire": 1685206799999
},
{
"code": "DB7A64BW8LC7",
"reward": [
{
"name": "星琼",
"cnt": 100
},
{
"name": "提纯以太",
"cnt": 4
}
],
"expire": 1685206799999
},
{
"code": "CS75WMP976AK",
"reward": [
{
"name": "星琼",
"cnt": 100
}
],
"expire": 1685206799999
},
{
"code": "HSRVER10XEDLFE",
"reward": [
{
"name": "星琼",
"cnt": 50
},
{
"name": "信用点",
"cnt": 10000
},
{
"name": "漫游指南",
"cnt": 2
}
],
"expire": 1683565199999
},
{
"code": "2T7BP4JVEBT7",
"reward": [
{
"name": "信用点",
"cnt": 5000
},
{
"name": "冒险记录",
"cnt": 3
},
{
"name": "凝缩以太",
"cnt": 2
},
{
"name": "大宇宙炒饭",
"cnt": 3
}
],
"expire": 1683392399999
},
{
"code": "HSRGRANDOPEN1",
"reward": [
{
"name": "星琼",
"cnt": 100
},
{
"name": "信用点",
"cnt": 5000
}
],
"expire": 1682787599999
},
{
"code": "HSRGRANDOPEN2",
"reward": [
{
"name": "星琼",
"cnt": 100
},
{
"name": "漫游指南",
"cnt": 5
}
],
"expire": 1682787599999
},
{
"code": "HSRGRANDOPEN3",
"reward": [
{
"name": "星琼",
"cnt": 100
},
{
"name": "提纯以太",
"cnt": 4
}
],
"expire": 1682787599999
}
]
}

15
data/custom.json Normal file
View File

@ -0,0 +1,15 @@
{
"main": [
{
"code": "MIYOUSHE2023",
"reward": [
{
"name": "星琼",
"cnt": 60
}
],
"expire": 1689695999999
}
],
"over": []
}

33
main.py Normal file
View File

@ -0,0 +1,33 @@
from typing import List
from models.code import CodeList, Code
from models.honkai import get_code
from pathlib import Path
data_path = Path("data")
custom_path = data_path / "custom.json"
code_path = data_path / "code.json"
def merge_code(over: List[Code], custom: CodeList) -> CodeList:
over_codes = [i for i in over]
custom_over_codes = [i.code for i in custom.over]
for code in over_codes:
if code.code in custom_over_codes:
continue
custom.over.append(code)
return custom
def main():
over = get_code()
with open(custom_path, "r", encoding="utf-8") as f:
custom = CodeList.parse_raw(f.read())
custom = merge_code(over, custom)
with open(code_path, "w", encoding="utf-8") as f:
f.write(custom.json(indent=4, ensure_ascii=False))
if __name__ == '__main__':
main()

0
models/__init__.py Normal file
View File

19
models/code.py Normal file
View File

@ -0,0 +1,19 @@
from typing import List
from pydantic import BaseModel
class Reward(BaseModel):
name: str
cnt: int
class Code(BaseModel):
code: str
reward: List[Reward]
expire: int
class CodeList(BaseModel):
main: List[Code]
over: List[Code]

81
models/honkai.py Normal file
View File

@ -0,0 +1,81 @@
from datetime import datetime
from typing import List
from httpx import get
from bs4 import BeautifulSoup, Tag
from .code import Code, Reward
url = "https://honkai.gg/cn/codes"
reward_map = {
"Stellar Jade": "星琼",
"Credit": "信用点",
"Credits": "信用点",
"Traveler's Guide": "漫游指南",
"Refined Aether": "提纯以太",
"Adventure Log": "冒险记录",
"Dust of Alacrity": "疾速粉尘",
"Condensed Aether": "凝缩以太",
"Cosmic Fried Rice": "大宇宙炒饭",
}
def parse_reward(reward: List[str]) -> Reward:
try:
name = reward_map.get(reward[0])
if not name:
print("Unknown reward: ", reward[0])
name = reward[0]
return Reward(
name=name,
cnt=int(reward[1]),
)
except ValueError:
print("Bad reward data: ", reward)
def parse_code(tr: Tag) -> Code:
tds = tr.find_all("td")
code = tds[0].text.strip()
expire = tds[2].text.strip()
if expire.endswith("?"):
expire = datetime(2099, 12, 31, 23, 59, 59, 999999)
else:
expires = expire.split(" - ")
day = expires[1].split(" ")[-1]
month = expires[0].split(" ")[0]
try:
if " " not in expires[1]:
raise ValueError
month = expires[1].split(" ")[0]
except ValueError:
pass
now = datetime.now()
expire = datetime.strptime(f"{day} {month}", "%d %b")
expire = expire.replace(year=now.year, hour=23, minute=59, second=59, microsecond=999999)
expire = int(expire.timestamp() * 1000)
rewards = []
for reward in tds[1].find_all("div", {"class": "flex"}):
reward_div = reward.text.strip().split("\xa0x ")
parsed_reward = parse_reward(reward_div)
if parsed_reward:
rewards.append(parsed_reward)
for reward in tds[1].find_all("a"):
reward_a = reward.text.strip().split(" x ")
parsed_reward = parse_reward(reward_a)
if parsed_reward:
rewards.append(parsed_reward)
return Code(code=code, reward=rewards, expire=expire)
def get_code():
html = get(url).text
soup = BeautifulSoup(html, "lxml")
tables = soup.find_all("table")
codes = []
for table in tables:
trs = table.find_all("tr")[1:]
for tr in trs:
codes.append(parse_code(tr))
codes.sort(key=lambda x: x.expire, reverse=True)
return codes

4
requirements.txt Normal file
View File

@ -0,0 +1,4 @@
pydantic
httpx
beautifulsoup4
lxml