From e2936b8243e27cb5a9d06732406855fa07d90455 Mon Sep 17 00:00:00 2001 From: wood Date: Tue, 3 Sep 2024 17:25:50 +0800 Subject: [PATCH] first commit --- .github/workflows/build.yml | 65 +++++++++++++++++++++++++++ Dockerfile | 15 +++++++ data/keywords.json | 1 + docker-compose.yml | 13 ++++++ requirement.txt | 5 +++ src/binance.py | 87 +++++++++++++++++++++++++++++++++++++ src/guard.py | 70 +++++++++++++++++++++++++++++ src/main.py | 35 +++++++++++++++ 8 files changed, 291 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 Dockerfile create mode 100644 data/keywords.json create mode 100644 docker-compose.yml create mode 100644 requirement.txt create mode 100644 src/binance.py create mode 100644 src/guard.py create mode 100644 src/main.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..de51a2c --- /dev/null +++ b/.github/workflows/build.yml @@ -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 }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..805f3cb --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/data/keywords.json b/data/keywords.json new file mode 100644 index 0000000..794e433 --- /dev/null +++ b/data/keywords.json @@ -0,0 +1 @@ +["推广", "广告", "ad", "promotion"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e4720fb --- /dev/null +++ b/docker-compose.yml @@ -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 \ No newline at end of file diff --git a/requirement.txt b/requirement.txt new file mode 100644 index 0000000..924e2c4 --- /dev/null +++ b/requirement.txt @@ -0,0 +1,5 @@ +telethon +ccxt +pyTelegramBotAPI +schedule +pytz diff --git a/src/binance.py b/src/binance.py new file mode 100644 index 0000000..8f688da --- /dev/null +++ b/src/binance.py @@ -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() diff --git a/src/guard.py b/src/guard.py new file mode 100644 index 0000000..7395bdd --- /dev/null +++ b/src/guard.py @@ -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() diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..62c34c8 --- /dev/null +++ b/src/main.py @@ -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()