mirror of
https://github.com/woodchen-ink/Q58Bot.git
synced 2025-07-18 05:42:06 +08:00
first commit
This commit is contained in:
commit
e2936b8243
65
.github/workflows/build.yml
vendored
Normal file
65
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
name: Build and Push Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
tags:
|
||||||
|
- v*
|
||||||
|
|
||||||
|
env:
|
||||||
|
IMAGE_NAME: telegrambot-binance
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
- name: Run tests (if any)
|
||||||
|
run: |
|
||||||
|
# Add your test commands here
|
||||||
|
# For example: python -m unittest discover tests
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: woodchen
|
||||||
|
password: ${{ secrets.ACCESS_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
woodchen/${{ env.IMAGE_NAME }}:latest
|
||||||
|
woodchen/${{ env.IMAGE_NAME }}:${{ github.sha }}
|
||||||
|
|
||||||
|
- name: Update Docker Hub description
|
||||||
|
uses: peter-evans/dockerhub-description@v3
|
||||||
|
with:
|
||||||
|
username: woodchen
|
||||||
|
password: ${{ secrets.ACCESS_TOKEN }}
|
||||||
|
repository: woodchen/${{ env.IMAGE_NAME }}
|
||||||
|
short-description: ${{ github.event.repository.description }}
|
15
Dockerfile
Normal file
15
Dockerfile
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
FROM python:3.9-slim
|
||||||
|
|
||||||
|
# 设置时区
|
||||||
|
ENV TZ=Asia/Singapore
|
||||||
|
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY src /app/src
|
||||||
|
COPY data /app/data
|
||||||
|
|
||||||
|
CMD ["python", "src/main.py"]
|
1
data/keywords.json
Normal file
1
data/keywords.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
["推广", "广告", "ad", "promotion"]
|
13
docker-compose.yml
Normal file
13
docker-compose.yml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
services:
|
||||||
|
q58bot:
|
||||||
|
container_name: q58bot
|
||||||
|
image: woodchen/q58bot:latest
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- BOT_TOKEN=719XXX42:AAEydXXXX8rg #换成自己的机器人ID
|
||||||
|
- ADMIN_ID=5912366993 #换成自己的ID
|
||||||
|
- CHAT_ID=-100xxx781 #换成自己的群ID
|
||||||
|
- SYMBOLS=DOGS/USDT,TON/USDT
|
||||||
|
- TZ=Asia/Singapore
|
||||||
|
volumes:
|
||||||
|
- ./data:/app/data
|
5
requirement.txt
Normal file
5
requirement.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
telethon
|
||||||
|
ccxt
|
||||||
|
pyTelegramBotAPI
|
||||||
|
schedule
|
||||||
|
pytz
|
87
src/binance.py
Normal file
87
src/binance.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import os
|
||||||
|
import ccxt
|
||||||
|
import telebot
|
||||||
|
import schedule
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
singapore_tz = pytz.timezone('Asia/Singapore')
|
||||||
|
exchange = ccxt.binance()
|
||||||
|
BOT_TOKEN = os.environ['BOT_TOKEN']
|
||||||
|
CHAT_ID = os.environ['CHAT_ID']
|
||||||
|
bot = telebot.TeleBot(BOT_TOKEN)
|
||||||
|
SYMBOLS = os.environ['SYMBOLS'].split(',')
|
||||||
|
|
||||||
|
def get_ticker_info(symbol):
|
||||||
|
ticker = exchange.fetch_ticker(symbol)
|
||||||
|
return {
|
||||||
|
'symbol': symbol,
|
||||||
|
'last': ticker['last'],
|
||||||
|
'change_percent': ticker['percentage'],
|
||||||
|
'high': ticker['high'],
|
||||||
|
'low': ticker['low'],
|
||||||
|
'volume': ticker['baseVolume'],
|
||||||
|
'quote_volume': ticker['quoteVolume'],
|
||||||
|
'bid': ticker['bid'],
|
||||||
|
'ask': ticker['ask']
|
||||||
|
}
|
||||||
|
def format_change(change_percent):
|
||||||
|
if change_percent > 0:
|
||||||
|
return f"🔼 +{change_percent:.2f}%"
|
||||||
|
elif change_percent < 0:
|
||||||
|
return f"🔽 {change_percent:.2f}%"
|
||||||
|
else:
|
||||||
|
return f"◀▶ {change_percent:.2f}%"
|
||||||
|
def send_price_update():
|
||||||
|
now = datetime.now(singapore_tz)
|
||||||
|
message = f"市场更新 - {now.strftime('%Y-%m-%d %H:%M:%S')} (SGT)\n\n"
|
||||||
|
|
||||||
|
for symbol in SYMBOLS:
|
||||||
|
info = get_ticker_info(symbol)
|
||||||
|
change_str = format_change(info['change_percent'])
|
||||||
|
|
||||||
|
message += f"*{info['symbol']}*\n"
|
||||||
|
message += f"价格: ${info['last']:.7f}\n"
|
||||||
|
message += f"24h 涨跌: {change_str}\n"
|
||||||
|
message += f"24h 高/低: ${info['high']:.7f} / ${info['low']:.7f}\n"
|
||||||
|
message += f"24h 成交量: {info['volume']:.2f}\n"
|
||||||
|
message += f"24h 成交额: ${info['quote_volume']:.2f}\n"
|
||||||
|
message += f"买一/卖一: ${info['bid']:.7f} / ${info['ask']:.7f}\n\n"
|
||||||
|
|
||||||
|
bot.send_message(CHAT_ID, message, parse_mode='Markdown')
|
||||||
|
|
||||||
|
def run():
|
||||||
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
logger = logging.getLogger('BinanceUpdater')
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# 立即执行一次价格更新
|
||||||
|
logger.info("Sending initial price update...")
|
||||||
|
send_price_update()
|
||||||
|
|
||||||
|
# 设置定时任务,每小时整点执行
|
||||||
|
for hour in range(24):
|
||||||
|
schedule.every().day.at(f"{hour:02d}:00").do(send_price_update)
|
||||||
|
|
||||||
|
logger.info("Scheduled tasks set. Waiting for next hour...")
|
||||||
|
|
||||||
|
# 等待下一个整点
|
||||||
|
now = datetime.now(singapore_tz)
|
||||||
|
next_hour = (now + timedelta(hours=1)).replace(minute=0, second=0, microsecond=0)
|
||||||
|
time.sleep((next_hour - now).total_seconds())
|
||||||
|
|
||||||
|
logger.info("Starting main loop...")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
schedule.run_pending()
|
||||||
|
time.sleep(30) # 每30秒检查一次,可以根据需要调整
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"An error occurred in BinanceUpdater: {str(e)}")
|
||||||
|
logger.info("Attempting to restart BinanceUpdater in 60 seconds...")
|
||||||
|
time.sleep(60) # 等待60秒后重试
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run()
|
70
src/guard.py
Normal file
70
src/guard.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from telethon import TelegramClient, events
|
||||||
|
|
||||||
|
BOT_TOKEN = os.environ.get('BOT_TOKEN')
|
||||||
|
ADMIN_ID = int(os.environ.get('ADMIN_ID')) # 从环境变量获取 ADMIN_ID 并转换为整数
|
||||||
|
KEYWORDS_FILE = '/app/data/keywords.json'
|
||||||
|
|
||||||
|
def load_keywords():
|
||||||
|
if os.path.exists(KEYWORDS_FILE):
|
||||||
|
with open(KEYWORDS_FILE, 'r') as f:
|
||||||
|
return json.load(f)
|
||||||
|
return ['推广', '广告', 'ad', 'promotion']
|
||||||
|
|
||||||
|
def save_keywords(keywords):
|
||||||
|
with open(KEYWORDS_FILE, 'w') as f:
|
||||||
|
json.dump(keywords, f)
|
||||||
|
|
||||||
|
KEYWORDS = load_keywords()
|
||||||
|
|
||||||
|
client = TelegramClient('bot', api_id=6, api_hash='eb06d4abfb49dc3eeb1aeb98ae0f581e')
|
||||||
|
client.start(bot_token=BOT_TOKEN)
|
||||||
|
|
||||||
|
@client.on(events.NewMessage(pattern=''))
|
||||||
|
async def handler(event):
|
||||||
|
global KEYWORDS
|
||||||
|
if event.is_private and event.sender_id == ADMIN_ID:
|
||||||
|
command = event.message.text.split()
|
||||||
|
if command[0].lower() == '/add' and len(command) > 1:
|
||||||
|
new_keyword = command[1].lower()
|
||||||
|
if new_keyword not in KEYWORDS:
|
||||||
|
KEYWORDS.append(new_keyword)
|
||||||
|
save_keywords(KEYWORDS)
|
||||||
|
await event.respond(f"关键词 '{new_keyword}' 已添加到列表中。")
|
||||||
|
else:
|
||||||
|
await event.respond(f"关键词 '{new_keyword}' 已经在列表中。")
|
||||||
|
elif command[0].lower() == '/delete' and len(command) > 1:
|
||||||
|
keyword_to_delete = command[1].lower()
|
||||||
|
if keyword_to_delete in KEYWORDS:
|
||||||
|
KEYWORDS.remove(keyword_to_delete)
|
||||||
|
save_keywords(KEYWORDS)
|
||||||
|
await event.respond(f"关键词 '{keyword_to_delete}' 已从列表中删除。")
|
||||||
|
else:
|
||||||
|
await event.respond(f"关键词 '{keyword_to_delete}' 不在列表中。")
|
||||||
|
elif command[0].lower() == '/list':
|
||||||
|
await event.respond(f"当前关键词列表:{', '.join(KEYWORDS)}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not event.is_private and any(keyword in event.message.text.lower() for keyword in KEYWORDS):
|
||||||
|
if event.sender_id != ADMIN_ID:
|
||||||
|
await event.delete()
|
||||||
|
await event.respond("已撤回该消息。注:已发送的推广链接不要多次发送,置顶已有项目的推广链接也会自动撤回。")
|
||||||
|
|
||||||
|
def run():
|
||||||
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
logger = logging.getLogger('TeleGuard')
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
logger.info("TeleGuard is starting...")
|
||||||
|
client.run_until_disconnected()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"An error occurred in TeleGuard: {str(e)}")
|
||||||
|
logger.info("Attempting to restart TeleGuard in 60 seconds...")
|
||||||
|
time.sleep(60) # 等待60秒后重试
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run()
|
35
src/main.py
Normal file
35
src/main.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import multiprocessing
|
||||||
|
import guard
|
||||||
|
import binance
|
||||||
|
import logging
|
||||||
|
|
||||||
|
def run_guard():
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
guard.run()
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Guard process crashed: {str(e)}")
|
||||||
|
logging.info("Restarting Guard process...")
|
||||||
|
|
||||||
|
def run_binance():
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
binance.run()
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Binance process crashed: {str(e)}")
|
||||||
|
logging.info("Restarting Binance process...")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
|
# 创建两个进程分别运行 guard 和 binance 服务
|
||||||
|
guard_process = multiprocessing.Process(target=run_guard)
|
||||||
|
binance_process = multiprocessing.Process(target=run_binance)
|
||||||
|
|
||||||
|
# 启动进程
|
||||||
|
guard_process.start()
|
||||||
|
binance_process.start()
|
||||||
|
|
||||||
|
# 等待进程结束
|
||||||
|
guard_process.join()
|
||||||
|
binance_process.join()
|
Loading…
x
Reference in New Issue
Block a user