├── README.md ├── data └── config.py ├── main.py ├── requirements.txt └── utils ├── cats_gang.py ├── core ├── __init__.py ├── file_manager.py ├── logger.py └── telegram.py └── starter.py /README.md: -------------------------------------------------------------------------------- 1 | # CatsGang-bot 2 | Soft for https://t.me/catsgang_bot 3 | 4 | More crypto themes and softs in telegram: [ApeCryptor](https://t.me/+_xCNXumUNWJkYjAy "ApeCryptor") 🦧 5 | 6 | ## Functionality 7 | | Functional | Supported | 8 | |----------------------------------------------------------------|:---------:| 9 | | Multithreading | ✅ | 10 | | Binding a proxy to a session | ✅ | 11 | | r; auto-reger; complete tasks, | ✅ | 12 | | Random sleep time between accounts | ✅ | 13 | | Support pyrogram .session | ✅ | 14 | | Get statistics for all accounts | ✅ | 15 | 16 | ## Settings data/config.py 17 | | Setting | Description | 18 | |------------------------------|------------------------------------------------------------------------------------------------| 19 | | **API_ID / API_HASH** | Platform data from which to launch a Telegram session _(stock - Android)_ | 20 | | **DELAYS-ACCOUNT** | Delay between connections to accounts (the more accounts, the longer the delay) _(eg [5, 15])_ | 21 | | **PROXY_TYPES-TG** | Proxy type for telegram session _(eg 'socks5')_ | 22 | | **PROXY_TYPES-REQUESTS** | Proxy type for requests _(eg 'socks5')_ | 23 | | **WORKDIR** | directory with session _(eg "sessions/")_ | 24 | | **TIMEOUT** | timeout in seconds for checking accounts on valid _(eg 30)_ | 25 | 26 | ## Requirements 27 | - Python 3.9 (you can install it [here](https://www.python.org/downloads/release/python-390/)) 28 | - Telegram API_ID and API_HASH (you can get them [here](https://my.telegram.org/auth)) 29 | 30 | 1. Install the required dependencies: 31 | ```bash 32 | pip install -r requirements.txt 33 | ``` 34 | 35 | ## Usage 36 | 1. Run the bot: 37 | ```bash 38 | python main.py 39 | ``` 40 | -------------------------------------------------------------------------------- /data/config.py: -------------------------------------------------------------------------------- 1 | # api id, hash 2 | API_ID = 1488 3 | API_HASH = 'abcde1488' 4 | 5 | 6 | DELAYS = { 7 | "RELOGIN": [5, 7], # delay after a login attempt 8 | 'ACCOUNT': [5, 15], # delay between connections to accounts (the more accounts, the longer the delay) 9 | 'TASK': [5, 10], # delay after complete task 10 | } 11 | 12 | BLACKLIST_TASKS = ['Join MemeFi Community', 'Join Major Community', 'Subscribe to Channel', 'Join MemeFi Community', 'Follow BAKS 🐈‍⬛ channel', 'Follow Activity 🚀 channel', 'Join Tomarket Announcement', 'Join OKX news Channel'] 13 | 14 | PROXY = { 15 | "USE_PROXY_FROM_FILE": False, # True - if use proxy from file, False - if use proxy from accounts.json 16 | "PROXY_PATH": "data/proxy.txt", # path to file proxy 17 | "TYPE": { 18 | "TG": "http", # proxy type for tg client. "socks4", "socks5" and "http" are supported 19 | "REQUESTS": "http" # proxy type for requests. "http" for https and http proxys, "socks5" for socks5 proxy. 20 | } 21 | } 22 | 23 | # session folder (do not change) 24 | WORKDIR = "sessions/" 25 | 26 | # timeout in seconds for checking accounts on valid 27 | TIMEOUT = 30 28 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from utils.core.telegram import Accounts 2 | from utils.starter import start, stats 3 | import asyncio 4 | from data import config 5 | import os 6 | 7 | 8 | async def main(): 9 | print("Soft's author: https://t.me/ApeCryptor\n") 10 | action = int(input("Select action:\n0. About soft\n1. Start soft\n2. Get statistics\n3. Create sessions\n\n> ")) 11 | 12 | if action == 0: 13 | print(config.SOFT_INFO) 14 | return 15 | 16 | if not os.path.exists('sessions'): os.mkdir('sessions') 17 | 18 | if config.PROXY['USE_PROXY_FROM_FILE']: 19 | if not os.path.exists(config.PROXY['PROXY_PATH']): 20 | with open(config.PROXY['PROXY_PATH'], 'w') as f: 21 | f.write("") 22 | else: 23 | if not os.path.exists('sessions/accounts.json'): 24 | with open("sessions/accounts.json", 'w') as f: 25 | f.write("[]") 26 | 27 | if action == 3: 28 | await Accounts().create_sessions() 29 | 30 | if action == 2: 31 | await stats() 32 | 33 | if action == 1: 34 | accounts = await Accounts().get_accounts() 35 | 36 | tasks = [] 37 | 38 | for thread, account in enumerate(accounts): 39 | session_name, phone_number, proxy = account.values() 40 | tasks.append(asyncio.create_task(start(session_name=session_name, phone_number=phone_number, thread=thread, proxy=proxy))) 41 | 42 | await asyncio.gather(*tasks) 43 | 44 | 45 | if __name__ == '__main__': 46 | asyncio.get_event_loop().run_until_complete(main()) 47 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyrogram==2.0.106 2 | tgcrypto==1.2.5 3 | loguru==0.7.2 4 | aiohttp==3.9.5 5 | fake-useragent==1.5.1 6 | pandas 7 | aiohttp_socks==0.8.4 -------------------------------------------------------------------------------- /utils/cats_gang.py: -------------------------------------------------------------------------------- 1 | import random 2 | import time 3 | from utils.core import logger 4 | from pyrogram import Client 5 | from pyrogram.raw.functions.messages import RequestAppWebView 6 | from pyrogram.raw.types import InputBotAppShortName 7 | import asyncio 8 | from urllib.parse import unquote, quote 9 | from data import config 10 | import aiohttp 11 | from fake_useragent import UserAgent 12 | from aiohttp_socks import ProxyConnector 13 | 14 | 15 | class CatsGang: 16 | def __init__(self, thread: int, session_name: str, phone_number: str, proxy: [str, None]): 17 | self.account = session_name + '.session' 18 | self.thread = thread 19 | self.ref = 'Uy6cF65jLxUbFFDWXewDx' 20 | self.proxy = f"{config.PROXY['TYPE']['REQUESTS']}://{proxy}" if proxy is not None else None 21 | connector = ProxyConnector.from_url(self.proxy) if proxy else aiohttp.TCPConnector(verify_ssl=False) 22 | 23 | if proxy: 24 | proxy = { 25 | "scheme": config.PROXY['TYPE']['TG'], 26 | "hostname": proxy.split(":")[1].split("@")[1], 27 | "port": int(proxy.split(":")[2]), 28 | "username": proxy.split(":")[0], 29 | "password": proxy.split(":")[1].split("@")[0] 30 | } 31 | 32 | self.client = Client( 33 | name=session_name, 34 | api_id=config.API_ID, 35 | api_hash=config.API_HASH, 36 | workdir=config.WORKDIR, 37 | proxy=proxy, 38 | lang_code='ru' 39 | ) 40 | 41 | headers = { 42 | 'User-Agent': UserAgent(os='android', browsers='chrome').random, 43 | } 44 | self.session = aiohttp.ClientSession(headers=headers, trust_env=True, connector=connector) 45 | 46 | async def stats(self): 47 | await self.login() 48 | 49 | user = await self.user() 50 | balance = str(user.get('totalRewards')) 51 | referral_link = f"https://t.me/catsgang_bot/join?startapp={user.get('referrerCode')}" 52 | 53 | r = await (await self.session.get('https://api.catshouse.club/leaderboard')).json() 54 | leaderboard = r.get('userPlace') 55 | 56 | await self.logout() 57 | 58 | await self.client.connect() 59 | me = await self.client.get_me() 60 | phone_number, name = "'" + me.phone_number, f"{me.first_name} {me.last_name if me.last_name is not None else ''}" 61 | await self.client.disconnect() 62 | 63 | proxy = self.proxy.replace('http://', "") if self.proxy is not None else '-' 64 | 65 | return [phone_number, name, balance, leaderboard, referral_link, proxy] 66 | 67 | async def user(self): 68 | resp = await self.session.get('https://api.catshouse.club/user') 69 | return await resp.json() 70 | 71 | async def logout(self): 72 | await self.session.close() 73 | 74 | async def check_task(self, task_id: int): 75 | try: 76 | resp = await self.session.post(f'https://api.catshouse.club/tasks/{task_id}/check') 77 | return (await resp.json()).get('completed') 78 | except: 79 | return False 80 | 81 | async def complete_task(self, task_id: int): 82 | try: 83 | resp = await self.session.post(f'https://api.catshouse.club/tasks/{task_id}/complete') 84 | return (await resp.json()).get('success') 85 | except: 86 | return False 87 | 88 | async def get_tasks(self): 89 | resp = await self.session.get('https://api.catshouse.club/tasks/user?group=cats') 90 | return (await resp.json()).get('tasks') 91 | 92 | async def register(self): 93 | resp = await self.session.post(f'https://api.catshouse.club/user/create?referral_code=9uGLmLKtMc2ut8Kl8F-YH') 94 | return resp.status == 200 95 | 96 | async def login(self): 97 | await asyncio.sleep(random.uniform(*config.DELAYS['ACCOUNT'])) 98 | self.ref = '9uGLmLKtMc2ut8Kl8F-YH' 99 | query = await self.get_tg_web_data() 100 | 101 | if query is None: 102 | logger.error(f"Thread {self.thread} | {self.account} | Session {self.account} invalid") 103 | await self.logout() 104 | return None, None 105 | 106 | self.session.headers['Authorization'] = 'tma ' + query 107 | 108 | r = await (await self.session.get('https://api.catshouse.club/user')).text() 109 | if r == '{"name":"Error","message":"User was not found"}': 110 | if await self.register(): 111 | logger.success(f"Thread {self.thread} | {self.account} | Register") 112 | 113 | async def get_tg_web_data(self): 114 | try: 115 | await self.client.connect() 116 | 117 | web_view = await self.client.invoke(RequestAppWebView( 118 | peer=await self.client.resolve_peer('catsgang_bot'), 119 | app=InputBotAppShortName(bot_id=await self.client.resolve_peer('catsgang_bot'), short_name="join"), 120 | platform='android', 121 | write_allowed=True, 122 | start_param=self.ref 123 | )) 124 | await self.client.disconnect() 125 | 126 | auth_url = web_view.url 127 | query = unquote(string=auth_url.split('tgWebAppData=')[1].split('&tgWebAppVersion')[0]) 128 | return query 129 | 130 | except: 131 | return None 132 | -------------------------------------------------------------------------------- /utils/core/__init__.py: -------------------------------------------------------------------------------- 1 | from .logger import logger 2 | from .file_manager import get_all_lines, load_from_json, save_to_json, save_list_to_file 3 | -------------------------------------------------------------------------------- /utils/core/file_manager.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | def get_all_lines(filepath: str): 5 | with open(filepath, 'r') as file: 6 | lines = file.readlines() 7 | 8 | if not lines: 9 | return [] 10 | 11 | return [line.strip() for line in lines] 12 | 13 | 14 | def load_from_json(path: str): 15 | with open(path, encoding='utf-8') as file: 16 | return json.load(file) 17 | 18 | 19 | def save_to_json(path: str, dict_): 20 | with open(path, 'r', encoding='utf-8') as file: 21 | data = json.load(file) 22 | 23 | data.append(dict_) 24 | with open(path, 'w', encoding='utf-8') as file: 25 | json.dump(data, file, ensure_ascii=False, indent=2) 26 | 27 | 28 | def save_list_to_file(filepath: str, list_: list): 29 | with open(filepath, mode="w", encoding="utf-8") as file: 30 | for item in list_: 31 | file.write(f"{item['session_name']}.session\n") 32 | -------------------------------------------------------------------------------- /utils/core/logger.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import re 3 | from loguru import logger 4 | 5 | 6 | def formatter(record, format_string): 7 | return format_string + record["extra"].get("end", "\n") + "{exception}" 8 | 9 | 10 | def clean_brackets(raw_str): 11 | return re.sub(r'<.*?>', '', raw_str) 12 | 13 | 14 | def logging_setup(): 15 | format_info = "{time:HH:mm:ss.SS} | {level} | {message}" 16 | format_error = "{time:HH:mm:ss.SS} | {level} | {name}:{function}:{line} | {message}" 17 | logger_path = r"logs/out.log" 18 | 19 | logger.remove() 20 | 21 | logger.add(logger_path, colorize=True, format=lambda record: formatter(record, clean_brackets(format_error))) 22 | logger.add(sys.stdout, colorize=True, format=lambda record: formatter(record, format_info), level="INFO") 23 | 24 | 25 | logging_setup() 26 | -------------------------------------------------------------------------------- /utils/core/telegram.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | import random 4 | 5 | from data import config 6 | from pyrogram import Client 7 | from utils.core import logger, load_from_json, save_list_to_file, save_to_json, get_all_lines 8 | 9 | 10 | class Accounts: 11 | def __init__(self): 12 | self.workdir = config.WORKDIR 13 | self.api_id = config.API_ID 14 | self.api_hash = config.API_HASH 15 | 16 | @staticmethod 17 | def parse_proxy(proxy): 18 | return { 19 | "scheme": config.PROXY['TYPE']['TG'], 20 | "hostname": proxy.split(":")[1].split("@")[1], 21 | "port": int(proxy.split(":")[2]), 22 | "username": proxy.split(":")[0], 23 | "password": proxy.split(":")[1].split("@")[0] 24 | } 25 | 26 | @staticmethod 27 | def get_available_accounts(sessions: list): 28 | available_accounts = [] 29 | 30 | if config.PROXY['USE_PROXY_FROM_FILE']: 31 | proxys = get_all_lines(config.PROXY['PROXY_PATH']) 32 | for session in sessions: 33 | available_accounts.append({ 34 | 'session_name': session, 35 | 'phone_number': '+0', 36 | 'proxy': proxys.pop(proxys.index(random.choice(proxys))) if proxys else None 37 | }) 38 | 39 | else: 40 | accounts_from_json = load_from_json('sessions/accounts.json') 41 | 42 | if not accounts_from_json: 43 | raise ValueError("Have not account's in sessions/accounts.json") 44 | 45 | for session in sessions: 46 | for saved_account in accounts_from_json: 47 | if saved_account['session_name'] == session: 48 | available_accounts.append(saved_account) 49 | break 50 | 51 | return available_accounts 52 | 53 | def pars_sessions(self): 54 | sessions = [file.replace(".session", "") for file in os.listdir(self.workdir) if file.endswith(".session")] 55 | 56 | logger.info(f"Searched sessions: {len(sessions)}.") 57 | return sessions 58 | 59 | async def check_valid_account(self, account: dict): 60 | session_name, phone_number, proxy = account.values() 61 | 62 | try: 63 | proxy_dict = { 64 | "scheme": config.PROXY['TYPE']['TG'], 65 | "hostname": proxy.split(":")[1].split("@")[1], 66 | "port": int(proxy.split(":")[2]), 67 | "username": proxy.split(":")[0], 68 | "password": proxy.split(":")[1].split("@")[0] 69 | } if proxy else None 70 | 71 | client = Client(name=session_name, api_id=self.api_id, api_hash=self.api_hash, workdir=self.workdir, 72 | proxy=proxy_dict) 73 | 74 | connect = await asyncio.wait_for(client.connect(), timeout=config.TIMEOUT) 75 | if connect: 76 | await client.get_me() 77 | await client.disconnect() 78 | return account 79 | else: 80 | await client.disconnect() 81 | except: 82 | pass 83 | 84 | async def check_valid_accounts(self, accounts: list): 85 | logger.info(f"Checking accounts for valid...") 86 | 87 | tasks = [] 88 | for account in accounts: 89 | tasks.append(asyncio.create_task(self.check_valid_account(account))) 90 | 91 | v_accounts = await asyncio.gather(*tasks) 92 | 93 | valid_accounts = [account for account, is_valid in zip(accounts, v_accounts) if is_valid] 94 | invalid_accounts = [account for account, is_valid in zip(accounts, v_accounts) if not is_valid] 95 | logger.success(f"Valid accounts: {len(valid_accounts)}; Invalid: {len(invalid_accounts)}") 96 | 97 | return valid_accounts, invalid_accounts 98 | 99 | async def get_accounts(self): 100 | sessions = self.pars_sessions() 101 | available_accounts = self.get_available_accounts(sessions) 102 | 103 | if not available_accounts: 104 | raise ValueError("Have not available accounts!") 105 | else: 106 | logger.success(f"Search available accounts: {len(available_accounts)}.") 107 | 108 | valid_accounts, invalid_accounts = await self.check_valid_accounts(available_accounts) 109 | 110 | if invalid_accounts: 111 | save_list_to_file(f"{config.WORKDIR}invalid_accounts.txt", invalid_accounts) 112 | logger.info(f"Saved {len(invalid_accounts)} invalid account(s) in {config.WORKDIR}invalid_accounts.txt") 113 | 114 | if not valid_accounts: 115 | raise ValueError("Have not valid sessions") 116 | else: 117 | return valid_accounts 118 | 119 | async def create_sessions(self): 120 | while True: 121 | session_name = input('\nInput the name of the session (press Enter to exit): ') 122 | if not session_name: return 123 | 124 | if config.PROXY['USE_PROXY_FROM_FILE']: 125 | proxys = get_all_lines(config.PROXY['PROXY_PATH']) 126 | proxy = random.choice(proxys) if proxys else None 127 | else: 128 | proxy = input("Input the proxy in the format login:password@ip:port (press Enter to use without proxy): ") 129 | 130 | dict_proxy = self.parse_proxy(proxy) if proxy else None 131 | 132 | phone_number = (input("Input the phone number of the account: ")).replace(' ', '') 133 | phone_number = '+' + phone_number if not phone_number.startswith('+') else phone_number 134 | 135 | client = Client( 136 | api_id=self.api_id, 137 | api_hash=self.api_hash, 138 | name=session_name, 139 | workdir=self.workdir, 140 | phone_number=phone_number, 141 | proxy=dict_proxy, 142 | lang_code='ru' 143 | ) 144 | 145 | async with client: 146 | me = await client.get_me() 147 | 148 | save_to_json(f'{config.WORKDIR}accounts.json', dict_={ 149 | "session_name": session_name, 150 | "phone_number": phone_number, 151 | "proxy": proxy 152 | }) 153 | logger.success(f'Added a account {me.username} ({me.first_name}) | {me.phone_number}') -------------------------------------------------------------------------------- /utils/starter.py: -------------------------------------------------------------------------------- 1 | import random 2 | from utils.cats_gang import CatsGang 3 | from data import config 4 | from utils.core import logger 5 | import datetime 6 | import pandas as pd 7 | from utils.core.telegram import Accounts 8 | import asyncio 9 | import os 10 | 11 | 12 | async def start(thread: int, session_name: str, phone_number: str, proxy: [str, None]): 13 | cats = CatsGang(session_name=session_name, phone_number=phone_number, thread=thread, proxy=proxy) 14 | account = session_name + '.session' 15 | 16 | attempts = 3 17 | while attempts: 18 | try: 19 | await cats.login() 20 | logger.success(f"Thread {thread} | {account} | Login") 21 | break 22 | except Exception as e: 23 | logger.error(f"Thread {thread} | {account} | Left login attempts: {attempts}, error: {e}") 24 | await asyncio.sleep(random.uniform(*config.DELAYS['RELOGIN'])) 25 | attempts -= 1 26 | else: 27 | logger.error(f"Thread {thread} | {account} | Couldn't login") 28 | await cats.logout() 29 | return 30 | 31 | for task in await cats.get_tasks(): 32 | if task['completed'] or task['title'] in config.BLACKLIST_TASKS: continue 33 | 34 | if task['type'] == 'OPEN_LINK': 35 | if await cats.complete_task(task_id=task['id']): 36 | logger.success(f"Thread {thread} | {account} | Completed task «{task['title']}» and got {task['rewardPoints']} CATS") 37 | else: 38 | logger.warning(f"Thread {thread} | {account} | Couldn't complete task «{task['title']}»") 39 | 40 | elif task['type'] == 'SUBSCRIBE_TO_CHANNEL' and task['allowCheck']: 41 | if await cats.check_task(task_id=task['id']): 42 | logger.success(f"Thread {thread} | {account} | Completed task «{task['title']}» and got {task['rewardPoints']} CATS") 43 | else: 44 | logger.warning(f"Thread {thread} | {account} | Couldn't complete task «{task['title']}»") 45 | 46 | else: 47 | continue 48 | await asyncio.sleep(random.uniform(*config.DELAYS['TASK'])) 49 | 50 | await cats.logout() 51 | 52 | 53 | async def stats(): 54 | accounts = await Accounts().get_accounts() 55 | 56 | tasks = [] 57 | for thread, account in enumerate(accounts): 58 | session_name, phone_number, proxy = account.values() 59 | tasks.append(asyncio.create_task(CatsGang(session_name=session_name, phone_number=phone_number, thread=thread, proxy=proxy).stats())) 60 | 61 | data = await asyncio.gather(*tasks) 62 | path = f"statistics/statistics_{datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S')}.csv" 63 | columns = ['Phone number', 'Name', 'balance', 'leaderboard', 'referral_link', 'Proxy (login:password@ip:port)'] 64 | 65 | if not os.path.exists('statistics'): os.mkdir('statistics') 66 | df = pd.DataFrame(data, columns=columns) 67 | df.to_csv(path, index=False, encoding='utf-8-sig') 68 | 69 | logger.success(f"Saved statistics to {path}") 70 | --------------------------------------------------------------------------------