commit 822fb7113a63b2257db6b79360447d08a49c4b4c Author: Zichao Lin Date: Sat Aug 2 19:49:52 2025 +0800 init diff --git a/main.py b/main.py new file mode 100644 index 0000000..749d258 --- /dev/null +++ b/main.py @@ -0,0 +1,312 @@ +import requests +import csv +from datetime import datetime +import subprocess +import time +import logging +from pathlib import Path + +# 配置常量 +GIT_INTERVAL = 3600 # Git提交间隔(秒) +FETCH_INTERVAL = 3600 # 数据获取间隔(秒) +LOG_FILE = "btc_plus.log" + + +def timestamp_to_datetime(timestamp): + """将时间戳转换为可读时间字符串""" + return ( + datetime.fromtimestamp(timestamp).isoformat() + if timestamp + else datetime.now().isoformat() + ) + + +def get_current_time(): + """获取当前时间字符串""" + return datetime.now().isoformat() + + +def fetch_btc_plus_reward(address, stage_no=1): + """从API获取BTC Plus奖励数据""" + url = "https://graphql.sft-api.com/graphql" + + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0", + "Accept": "*/*", + "Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3", + "Accept-Encoding": "gzip, deflate, br, zstd", + "Referer": "https://app.solv.finance/", + "content-type": "application/json", + "authorization": "dW5kZWZpbmVkfHx1bmRlZmluZWR8fHVuZGVmaW5lZHx8.undefined", + "x-amz-user-agent": "aws-amplify/3.0.7", + "Origin": "https://app.solv.finance", + "DNT": "1", + "Connection": "keep-alive", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "cross-site", + "Sec-GPC": "1", + "Priority": "u=4", + } + + query = """ + query BtcPlusRewardByAddress($address: String!, $stageNo: Int!) { + btcPlusRewardByAddress(address: $address, stageNo: $stageNo) { + balance + balanceUSD + rewardPower + currentTotalRewardPower + estimatedReward + __typename + } + } + """ + + variables = {"stageNo": stage_no, "address": address} + + payload = { + "operationName": "BtcPlusRewardByAddress", + "query": query, + "variables": variables, + } + + response = requests.post(url, headers=headers, json=payload) + response.raise_for_status() + data = response.json() + data["timestamp"] = get_current_time() # 添加当前时间 + return data + + +def fetch_btc_plus_allocations(): + """从API获取BTC Plus分配数据""" + url = "https://graphql.sft-api.com/graphql" + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0", + "Accept": "*/*", + "Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3", + "Accept-Encoding": "gzip, deflate, br, zstd", + "Referer": "https://app.solv.finance/", + "content-type": "application/json", + "authorization": "dW5kZWZpbmVkfHx1bmRlZmluZWR8fHVuZGVmaW5lZHx8.undefined", + "x-amz-user-agent": "aws-amplify/3.0.7", + "Origin": "https://app.solv.finance", + "DNT": "1", + "Connection": "keep-alive", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "cross-site", + "Sec-GPC": "1", + "Priority": "u=4", + "Pragma": "no-cache", + "Cache-Control": "no-cache", + "TE": "trailers", + } + + query = """ + query BtcPlusAllocations { + btcPlusAllocations { + tvl + allocations { + assetName + percentage + color + __typename + } + __typename + } + } + """ + + payload = { + "operationName": "BtcPlusAllocations", + "query": query, + "variables": {}, + } + + response = requests.post(url, headers=headers, json=payload) + response.raise_for_status() + data = response.json() + data["timestamp"] = get_current_time() # 添加当前时间 + return data + + +def fetch_btc_plus_stats(stage_no=1): + """从API获取BTC Plus统计数据""" + url = "https://graphql.sft-api.com/graphql" + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0", + "Accept": "*/*", + "Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3", + "Accept-Encoding": "gzip, deflate, br, zstd", + "Referer": "https://app.solv.finance/", + "content-type": "application/json", + "authorization": "dW5kZWZpbmVkfHx1bmRlZmluZWR8fHVuZGVmaW5lZHx8.undefined", + "x-amz-user-agent": "aws-amplify/3.0.7", + "Origin": "https://app.solv.finance", + "DNT": "1", + "Connection": "keep-alive", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "cross-site", + "Sec-GPC": "1", + "Priority": "u=4", + } + + query = """ + query BtcPlusStats($stageNo: Int) { + btcPlusStats(stageNo: $stageNo) { + baseApy + rewardApy + tvl + cap + lastUpdatedTime + startDate + endDate + currentTotalRewardPower + totalRewards + __typename + } + } + """ + + variables = {"stageNo": stage_no} + payload = { + "operationName": "BtcPlusStats", + "query": query, + "variables": variables, + } + + response = requests.post(url, headers=headers, json=payload) + response.raise_for_status() + data = response.json() + data["timestamp"] = get_current_time() # 添加当前时间 + return data + + +def save_to_csv(data, filename="btc_plus_rewards.csv"): + """将数据保存到CSV文件""" + timestamp = data.get("timestamp", get_current_time()) + + if "btcPlusRewardByAddress" in data.get("data", {}): + reward_data = data["data"]["btcPlusRewardByAddress"] + row = { + "timestamp": timestamp, + "address": data.get("variables", {}).get("address", "unknown"), + "stageNo": data.get("variables", {}).get("stageNo", "unknown"), + "balance": reward_data["balance"], + "balanceUSD": reward_data["balanceUSD"], + "rewardPower": reward_data["rewardPower"], + "currentTotalRewardPower": reward_data["currentTotalRewardPower"], + "estimatedReward": reward_data["estimatedReward"], + } + filename = "btc_plus_rewards.csv" + + elif "btcPlusAllocations" in data.get("data", {}): + allocation_data = data["data"]["btcPlusAllocations"] + row = { + "timestamp": timestamp, + "tvl": allocation_data["tvl"], + "allocations": ";".join( + f"{a['assetName']}:{a['percentage']}" + for a in allocation_data["allocations"] + ), + } + filename = "btc_plus_allocations.csv" + + else: + stats_data = data["data"]["btcPlusStats"] + row = { + "timestamp": timestamp, + "stageNo": data.get("variables", {}).get("stageNo", "unknown"), + "baseApy": stats_data["baseApy"], + "rewardApy": stats_data["rewardApy"], + "tvl": stats_data["tvl"], + "cap": stats_data["cap"], + "lastUpdatedTime": timestamp_to_datetime(stats_data["lastUpdatedTime"]), + "startDate": timestamp_to_datetime(stats_data["startDate"]), + "endDate": timestamp_to_datetime(stats_data["endDate"]), + "currentTotalRewardPower": stats_data["currentTotalRewardPower"], + "totalRewards": stats_data["totalRewards"], + } + filename = "btc_plus_stats.csv" + + # 写入CSV文件 + try: + with open(filename, "a", newline="") as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=row.keys()) + + # 如果是新文件,写入表头 + if csvfile.tell() == 0: + writer.writeheader() + + writer.writerow(row) + print(f"数据已成功保存到 {filename}") + except Exception as e: + print(f"保存到CSV时出错: {e}") + + +# 初始化日志 +def setup_logging(): + logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(levelname)s - %(message)s", + handlers=[ + logging.FileHandler(LOG_FILE, encoding="utf-8"), + logging.StreamHandler(), + ], + ) + return logging.getLogger(__name__) + + +def git_commit_and_push(): + """自动提交并推送数据更新到Git仓库""" + logger = logging.getLogger(__name__) + try: + subprocess.run(["git", "add", "."], check=True) + subprocess.run( + ["git", "commit", "-m", f"Auto update BTC Plus data at {datetime.now()}"], + check=True, + ) + subprocess.run(["git", "push"], check=True) + logger.info("Git提交成功") + except subprocess.CalledProcessError as e: + logger.error("Git操作失败: %s", e) + + +def main_loop(): + """主循环,定期获取数据并更新""" + logger = setup_logging() + logger.info("BTC Plus数据收集程序启动") + last_git_time = 0 + + while True: + try: + # 获取并保存所有数据 + address = "0x1bFD4C5bBD6d3eabDeec1Ece32Fb2dFC8f28C6b0" + stage_no = 1 + + reward_data = fetch_btc_plus_reward(address, stage_no) + reward_data["variables"] = {"address": address, "stageNo": stage_no} + save_to_csv(reward_data) + + allocation_data = fetch_btc_plus_allocations() + save_to_csv(allocation_data) + + stats_data = fetch_btc_plus_stats(stage_no) + stats_data["variables"] = {"stageNo": stage_no} + save_to_csv(stats_data) + + # 定时Git提交 + current_time = time.time() + if current_time - last_git_time >= GIT_INTERVAL: + git_commit_and_push() + last_git_time = current_time + + time.sleep(FETCH_INTERVAL) + except Exception as e: + logger.error("发生错误: %s,10秒后重试...", e) + time.sleep(10) + + +if __name__ == "__main__": + main_loop()