You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

343 lines
12 KiB

2 months ago
import asyncio
import configparser
import json
import os
import sys
import time
import httpx
from loguru import logger
2 months ago
from cookie import get_cookie
2 months ago
# code by wlt233 | for LoveLive! Series AsiaTour 2024
# 2024.11.13 | v0.3
2 months ago
# ref: https://www.ticketlink.co.kr/global/zh/product/51390
2 months ago
2 months ago
# log
logger.add("./data/log/{time}.log")
# seat data
2 months ago
COOKIE, USERNAME, PASSWORD, HEADLESS, PROXY, SEATS_SPEC = "", "", "", "", "", ""
SEATS_IGNORE = []
2 months ago
COOKIE_TIME = time.time()
2 months ago
with open("./data/able_d1.json", "r") as f:
able_d1 = json.load(f)
with open("./data/able_d2.json", "r") as f:
able_d2 = json.load(f)
coordinates = [
(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7),
(2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (2, 6), (2, 7),
(3, 2), (3, 3), (3, 4), (3, 5), (3, 6), (3, 7),
(4, 2), (4, 3), (4, 4), (4, 5), (4, 6), (4, 7),
(5, 2), (5, 3), (5, 4), (5, 5), (5, 6), (5, 7),
(6, 2), (6, 3), (6, 4), (6, 5), (6, 6), (6, 7),
(7, 1), (7, 2), (7, 3), (7, 4), (7, 5), (7, 6), (7, 7),
(8, 2), (8, 3), (8, 4), (8, 5),
]
json_data = []
for x, y in coordinates:
json_data.append({
'virtualX': str(x),
'virtualY': str(y),
})
def get_seat(attr_or_seatid, day=None):
if not day or day == 1:
for k, l in able_d1.items():
for seat in l:
if seat["mapInfo"] == attr_or_seatid:
return 1, seat
elif str(seat["logicalSeatId"]) == attr_or_seatid:
return 1, seat
if not day or day == 2:
for k, l in able_d2.items():
for seat in l:
if seat["mapInfo"] == attr_or_seatid:
return 2, seat
elif str(seat["logicalSeatId"]) == attr_or_seatid:
return 2, seat
logger.error(f"没有找到座位:{attr_or_seatid}")
return None, None
2 months ago
2 months ago
# web wrapper
2 months ago
async def _async_req(method, url: str, proxy=None, *args, **kwargs):
logger.info(f"[async {method}] {url}")
# logger.info(f"{args} {kwargs}")
async with httpx.AsyncClient(proxies=proxy, verify=False) as client:
resp = await client.request(method, url, timeout=None, *args, **kwargs)
return resp
async def async_post_json(url: str, proxy=None, *args, **kwargs):
resp = await _async_req("post", url, proxy, *args, **kwargs)
try:
return resp.json()
except Exception as e:
logger.error(resp.content)
raise e
2 months ago
# soldout check
async def get_sold():
2 months ago
global COOKIE
headers = {
'Cookie': COOKIE,
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'zh-CN,zh;q=0.9,ja;q=0.8',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Content-Type': 'application/json;charset=UTF-8',
'Origin': 'https://www.ticketlink.co.kr',
'Pragma': 'no-cache',
'Referer': 'https://www.ticketlink.co.kr/global/zh/reserve/plan/schedule/1740993756',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
'sec-ch-ua': '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
try:
resp_d1 = await async_post_json(
'https://www.ticketlink.co.kr/global/zh/api/V2/plan/552446605/seat-soldout/area', #d1
proxy=PROXY, headers=headers, json=json_data, #cookies=cookies
)
resp_d2 = await async_post_json(
'https://www.ticketlink.co.kr/global/zh/api/V2/plan/1740993756/seat-soldout/area', #d2
proxy=PROXY, headers=headers, json=json_data, #cookies=cookies
)
sold_d1 = resp_d1["data"]
sold_d2 = resp_d2["data"]
return sold_d1, sold_d2
except:
2 months ago
logger.error("座位情况检查失败!请尝试清空配置里的 cookie 并重启!")
return None, None
async def search_seats(seats):
sold_d1, sold_d2 = await get_sold()
if sold_d1 and sold_d2:
for k, l in able_d1.items():
for seat in l:
seat_id = str(seat["logicalSeatId"])
2 months ago
if not "FLOOR" in seat['mapInfo'] or sold_d1[seat_id]: continue
if int(seat["mapInfo"].split()[3][:-1]) >= 8: continue
if not seat_id in SEATS_IGNORE:
logger.success(f"发现内场票d1 {seat['mapInfo']} {seat_id}")
seats[1].append(seat)
else:
logger.warning(f"发现内场票d1 {seat['mapInfo']} 在忽略列表中")
for k, l in able_d2.items():
for seat in l:
seat_id = str(seat["logicalSeatId"])
2 months ago
if not "FLOOR" in seat['mapInfo'] or sold_d2[seat_id]: continue
if int(seat["mapInfo"].split()[3][:-1]) >= 8: continue
if not seat_id in SEATS_IGNORE:
logger.success(f"发现内场票d2 {seat['mapInfo']} {seat_id}")
seats[2].append(seat)
else:
logger.warning(f"发现内场票d2 {seat['mapInfo']} 在忽略列表中")
2 months ago
# lock ticket
def parse_seat(seat):
if seat["gradeId"] == 102831: productGradeName = 'VIP석'
if seat["gradeId"] == 102832: productGradeName = 'R석'
if seat["gradeId"] == 102833: productGradeName = 'S석'
return {
'logicalSeatId': seat["logicalSeatId"],
'allotmentCode': 'TKL',
'seatAttribute': seat["mapInfo"],
'sortSeatAttribute': seat["sortMapInfo"],
'productGradeId': seat["gradeId"],
'orderNum': seat["orderNum"],
'productGradeName': productGradeName,
'blockId': seat["blockId"],
'area': {
'virtualX': seat["area"]["virtualX"],
'virtualY': seat["area"]["virtualY"],
},
'st': int(time.time() * 1000),
}
async def lock_ticket(day, seats):
2 months ago
global COOKIE, COOKIE_TIME
2 months ago
if not seats: return
if len(seats) > 4: seats = seats[:4]
day_code = 552446605 if day == 1 else 1740993756
headers = {
'Cookie': COOKIE,
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'zh-CN,zh;q=0.9,ja;q=0.8',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Content-Type': 'application/json;charset=UTF-8',
'Origin': 'https://www.ticketlink.co.kr',
'Pragma': 'no-cache',
'Referer': f'https://www.ticketlink.co.kr/global/en/reserve/plan/schedule/{day_code}',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
'sec-ch-ua': '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
json_data = {
'scheduleId': day_code,
'memberNo': 0,
'code': 'TKL',
'totalCnt': len(seats),
'seats': [
parse_seat(seat) for seat in seats
],
'zones': [],
'auto': None,
'pt': int(time.time() * 1000),
'nbt': int(time.time() * 1000),
}
try:
resp = await async_post_json(
f'https://www.ticketlink.co.kr/global/en/api/V2/plan/occupy/schedules/{day_code}/',
proxy=PROXY, headers=headers, json=json_data
2 months ago
)
if resp["success"]:
print("\x07")
2 months ago
logger.success(f"锁票成功支付链接https://www.ticketlink.co.kr/global/en/reserve/key/{resp['data']}/price")
2 months ago
seats.clear()
2 months ago
else:
msg = f"锁票失败msg:{resp['result']['message']}"
if "이전에 진행된 예매가 있습니다" in msg:
msg += (f" 有正在进行的预售,请访问以下链接初始化。"
f"https://www.ticketlink.co.kr/global/en/reserve/plan/schedule/{day_code}?loadPrevious=true")
logger.critical(msg)
else:
logger.error(msg)
except:
logger.critical(f"锁票失败!可能是 cookie 过期了!正在尝试更新 cookie")
2 months ago
if time.time() - COOKIE_TIME > 60:
update_cookie()
2 months ago
2 months ago
# cookie
2 months ago
def update_cookie():
2 months ago
global COOKIE, USERNAME, PASSWORD, HEADLESS, COOKIE_TIME
2 months ago
for i in range(5):
COOKIE = get_cookie(USERNAME, PASSWORD, HEADLESS)
if COOKIE:
config = configparser.ConfigParser(interpolation=None)
config.read("./conf.ini", encoding="utf8")
config.set("conf", "cookie", COOKIE)
with open("./conf.ini", 'w', encoding="utf8") as f:
config.write(f)
logger.info("cookie 更新成功!")
2 months ago
COOKIE_TIME = time.time()
2 months ago
return
logger.critical("登陆失败,无法获取 cookie")
os.system("pause")
sys.exit(0)
2 months ago
2 months ago
# main loop state machine
async def loop(interval1, interval2, seats):
2 months ago
global SEATS_SPEC
while True:
2 months ago
if not (seats[1] or seats[2]):
t = time.time()
while True:
await search_seats(seats)
if seats[1] or seats[2]:
break
else:
logger.error("没有搜索到内场座位...")
if time.time() - t > 1200:
update_cookie()
t = time.time()
else:
await asyncio.sleep(interval1 / 1000)
else:
2 months ago
event_loop = asyncio.get_event_loop()
2 months ago
t = time.time()
2 months ago
while True:
event_loop.create_task(lock_ticket(1, seats[1]))
event_loop.create_task(lock_ticket(2, seats[2]))
2 months ago
if not SEATS_SPEC and time.time() - t > 30:
seats[1].clear()
seats[2].clear()
break
2 months ago
if not (seats[1] or seats[2]):
break
else:
await asyncio.sleep(interval2 / 1000)
2 months ago
if __name__ == "__main__":
config = configparser.ConfigParser(allow_no_value=True, delimiters=('=', ':'))
config.read("./conf.ini", encoding="utf8")
USERNAME = config.get("conf", "username")
PASSWORD = config.get("conf", "password")
HEADLESS = True if config.get("conf", "headless") == "1" else False
COOKIE = config.get("conf", "cookie", raw=True)
if not COOKIE:
logger.info("正在尝试获取 cookie...")
if not USERNAME or not PASSWORD:
logger.critical("请在配置中填写用户名与密码!")
os.system("pause")
sys.exit(0)
update_cookie()
PROXY = config.get("conf", "proxy") or None
interval1 = int(config.get("conf", "interval1") or 10000)
interval2 = int(config.get("conf", "interval2") or 1000)
2 months ago
SEATS_SPEC = config.get("seat", "seats")
seats_list = SEATS_SPEC.split(",") if SEATS_SPEC else []
ignore_list = config.get("seat", "ignore").split(",")
2 months ago
logger.info(f"cookie = {COOKIE[:50]}...")
logger.info(f"proxy = {PROXY}")
logger.info(f"interval1 = {interval1} ms (search)")
logger.info(f"interval2 = {interval2} ms (lock)")
logger.info(f"seats = {seats_list}")
2 months ago
seats = { 1: [], 2: [] }
for seat_str in seats_list:
if " " in seat_str:
daystr, attr = seat_str.split(maxsplit=1)
day = 1 if "1" in daystr else 2
day, seat = get_seat(attr, day)
else:
day, seat = get_seat(seat_str)
if day and seat:
seats[day].append(seat)
logger.info(f"已找到锁票位置 d{day} {seat['mapInfo']} {seat['logicalSeatId']}")
if not (seats[1] or seats[2]):
logger.error(f"没有找到配置的锁票座位,开始自动搜索内场座位...")
for seat_str in ignore_list:
if " " in seat_str:
daystr, attr = seat_str.split(maxsplit=1)
day = 1 if "1" in daystr else 2
day, seat = get_seat(attr, day)
else:
day, seat = get_seat(seat_str)
if day and seat:
SEATS_IGNORE.append(str(seat['logicalSeatId']))
logger.info(f"已找到忽略位置 d{day} {seat['mapInfo']} {seat['logicalSeatId']}")
2 months ago
asyncio.run(loop(interval1, interval2, seats))