├── src ├── config │ ├── __init__.py │ └── config.py ├── __init__.py ├── utils │ ├── logger.py │ ├── helpers.py │ ├── request.py │ └── scripts.py ├── database │ ├── user.py │ ├── config.py │ ├── hamster.py │ ├── models.py │ └── acc.py └── telegram │ ├── client.py │ ├── registrator.py │ ├── multiClients.py │ └── telegramApp.py ├── .gitignore ├── login.py ├── requirements.txt ├── Dockerfile ├── plugins ├── ping.py ├── total.py ├── start.py ├── shutdown.py ├── resource.py ├── list.py ├── restart.py ├── info.py ├── status.py └── __init__.py ├── .env.example ├── compose.yaml ├── main.py ├── .github └── workflows │ └── build.yaml ├── tasks.py ├── README.md └── games └── hamster ├── tapper.py └── fingerprint.py /src/config/__init__.py: -------------------------------------------------------------------------------- 1 | from .config import settings 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | sessions/ 2 | .vscode/ 3 | .env 4 | bad_sessions/ 5 | test.py 6 | __pycache__/ 7 | database.db -------------------------------------------------------------------------------- /login.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from src.telegram.registrator import register_sessions 3 | 4 | asyncio.run(register_sessions()) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp 2 | loguru 3 | pydantic_settings 4 | Telethon 5 | numpy 6 | requests 7 | fake_useragent 8 | peewee 9 | aioclock 10 | psutil -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | WORKDIR /app 4 | 5 | ADD . /app 6 | 7 | RUN pip install --no-cache-dir -r requirements.txt 8 | 9 | CMD ["python", "main.py"] 10 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from src.config import settings 3 | 4 | if not os.path.exists(settings.SESSION_PATH): 5 | os.mkdir(settings.SESSION_PATH) 6 | 7 | if not os.path.exists(settings.BAD_SESSIONS_PATH): 8 | os.mkdir(settings.BAD_SESSIONS_PATH) -------------------------------------------------------------------------------- /src/utils/logger.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from loguru import logger 3 | 4 | logger.remove() 5 | logger.add(sink=sys.stdout, format= 6 | "{time:HH:mm:ss}" 7 | " | {level: <8}" 8 | " | {line}" 9 | " - {message}" 10 | ) 11 | 12 | logger = logger.opt(colors=True) -------------------------------------------------------------------------------- /src/database/user.py: -------------------------------------------------------------------------------- 1 | from .models import users 2 | 3 | class user: 4 | def get(user_id): 5 | return users.get(users.user_id == user_id) 6 | 7 | def insertOrUpdateUser(user_id, step = None): 8 | return ( 9 | users 10 | .insert(user_id=user_id, step=step) 11 | .on_conflict('replace') 12 | .execute() 13 | ) -------------------------------------------------------------------------------- /src/telegram/client.py: -------------------------------------------------------------------------------- 1 | import os 2 | from src.config import settings 3 | from telethon import TelegramClient 4 | 5 | bot = TelegramClient( 6 | os.path.join(settings.SESSION_PATH, settings.MAIN_NAME), 7 | settings.API_ID, 8 | settings.API_HASH 9 | ) 10 | 11 | async def startClient(): 12 | await bot.start(bot_token=settings.TG_TOKEN) 13 | 14 | async def stopClient(): 15 | await bot.disconnect() 16 | 17 | async def getClient(): 18 | return bot -------------------------------------------------------------------------------- /plugins/ping.py: -------------------------------------------------------------------------------- 1 | import time 2 | import asyncio 3 | 4 | from telethon import events 5 | 6 | async def init(bot): 7 | @bot.on(events.NewMessage(pattern='^[\/\#\!\.]ping$', func=lambda e: e.is_private, forwards=False, outgoing=False)) 8 | async def handler(event): 9 | s = time.time() 10 | message = await event.reply('Pong!') 11 | d = time.time() - s 12 | await message.edit(f'Pong! (reply took {d:.2f}s)') 13 | await asyncio.sleep(5) 14 | await asyncio.gather(event.delete(), message.delete()) -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | API_ID= 2 | API_HASH= 3 | 4 | MIN_AVAILABLE_ENERGY=100 5 | SLEEP_BY_MIN_ENERGY=[300, 500] 6 | 7 | ADD_TAPS_ON_TURBO=2500 8 | 9 | AUTO_UPGRADE=True 10 | MAX_LEVEL=15 11 | 12 | BALANCE_TO_SAVE=100000 13 | UPGRADES_COUNT=3 14 | 15 | APPLY_DAILY_ENERGY=True 16 | APPLY_DAILY_TURBO=True 17 | 18 | RANDOM_TAPS_COUNT=[50,200] 19 | SLEEP_BETWEEN_TAP=[50, 60] 20 | USE_PROXY_FROM_FILE=False 21 | 22 | TG_TOKEN= 23 | 24 | MAIN_NAME=bot 25 | SESSION_PATH=sessions 26 | BAD_SESSIONS_PATH=bad_sessions 27 | DB_PATH=database.db 28 | RENEW_AUTH=3600 29 | 30 | OWNERS=[] -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | build: 4 | context: . 5 | dockerfile: Dockerfile 6 | cache_from: 7 | - ghcr.io/rosegoli/hamster:latest 8 | restart: always 9 | env_file: 10 | - .env 11 | environment: 12 | DB_PATH: /app/db/database.db 13 | privileged: true 14 | network_mode: "host" 15 | volumes: 16 | - ./sessions:/app/sessions 17 | - ./db:/app/db 18 | logging: 19 | driver: "json-file" 20 | options: 21 | max-size: "5m" 22 | max-file: "2" 23 | deploy: 24 | resources: 25 | limits: 26 | cpus: "0.5" 27 | memory: "0.5G" 28 | -------------------------------------------------------------------------------- /plugins/total.py: -------------------------------------------------------------------------------- 1 | from telethon import events 2 | from src.config import settings 3 | from src.database.hamster import hamster 4 | from src.utils.helpers import align, format_large_num 5 | 6 | async def init(bot): 7 | @bot.on(events.NewMessage(pattern='Total Coins 🗼', func=lambda e: e.is_private, from_users=settings.OWNERS)) 8 | async def handler(event): 9 | total = hamster.total_info() 10 | thamster = align({ 11 | '💰 Total Balance' : f"{format_large_num(int(total['balance']))}", 12 | '📊 Total PPH' : f"{format_large_num(int(total['profit']))}" 13 | }) 14 | await event.reply(f"🐹 Hamster:\n{thamster}") -------------------------------------------------------------------------------- /src/database/config.py: -------------------------------------------------------------------------------- 1 | from .models import config 2 | from peewee import DoesNotExist 3 | 4 | class conf: 5 | def fetch(index: str = None): 6 | try: 7 | find = config.select().dicts().get() 8 | if index: 9 | find = find[index] 10 | 11 | except DoesNotExist: 12 | conf.insertOrUpdateConfig() 13 | return conf.fetch(index) 14 | 15 | return find 16 | 17 | def insertOrUpdateConfig(**kwargs): 18 | try: 19 | record = config.get() 20 | query = record.update(**kwargs) 21 | query.execute() 22 | except DoesNotExist: 23 | config.create(**kwargs) -------------------------------------------------------------------------------- /plugins/start.py: -------------------------------------------------------------------------------- 1 | from telethon import events 2 | from src.config import settings 3 | from telethon.tl.custom.button import Button 4 | 5 | async def init(bot): 6 | @bot.on(events.NewMessage(pattern='^[\/\#\!\.]start$', func=lambda e: e.is_private, from_users=settings.OWNERS)) 7 | async def handler(event): 8 | user = await event.get_sender() 9 | await event.reply(f'Hey {user.first_name}!', buttons = [ 10 | [Button.text('📊 Accounts List 📊', resize = True)], 11 | [Button.text('🚏 Clickers Status'), Button.text('Total Coins 🗼')], 12 | [Button.text('🔄 Restart Bot'), Button.text('Shutdown🔥')], 13 | [Button.text('🎛 Resources 🎛')] 14 | ]) -------------------------------------------------------------------------------- /plugins/shutdown.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from telethon import events 3 | from src.config import settings 4 | 5 | async def init(bot): 6 | @bot.on(events.NewMessage(pattern='Shutdown🔥', func=lambda e: e.is_private, from_users=settings.OWNERS)) 7 | async def handler(event): 8 | loop = asyncio.get_event_loop() 9 | all_tasks = asyncio.all_tasks(loop=loop) 10 | tasks = [ 11 | task for task in all_tasks 12 | if isinstance(task, asyncio.Task) and task._coro.__name__ == 'run' 13 | ] 14 | 15 | for task in tasks: 16 | try: 17 | task.cancel() 18 | except: 19 | ... 20 | 21 | await event.respond('Shutted Down !') 22 | bot.disconnect() -------------------------------------------------------------------------------- /plugins/resource.py: -------------------------------------------------------------------------------- 1 | from telethon import events 2 | from src.config import settings 3 | from src.utils.helpers import align, get_server_usage 4 | 5 | async def init(bot): 6 | @bot.on(events.NewMessage(pattern='🎛 Resources 🎛', func=lambda e: e.is_private, from_users=settings.OWNERS)) 7 | async def handler(event): 8 | su = get_server_usage() 9 | 10 | mem_usage = su['memory_usage_MB'] 11 | mem_total = su['memory_total_MB'] 12 | mem_percent = su['memory_percent'] 13 | cpu_percent = su['cpu_percent'] 14 | 15 | msg = align({ 16 | '🧠 CPU usage' : f"{cpu_percent:.2f}%", 17 | '💯 Memory Percent' : f"{mem_percent:.2f}%", 18 | '🫀 Memory usage' : f"{mem_usage:.2f}/{mem_total:.2f} MB" 19 | }) 20 | 21 | await event.reply(msg) -------------------------------------------------------------------------------- /plugins/list.py: -------------------------------------------------------------------------------- 1 | from telethon import events 2 | from src.config import settings 3 | from src.utils.helpers import rtl 4 | from src.utils.scripts import chunk 5 | from telethon.tl.custom.button import Button 6 | from src.database.acc import accounts 7 | 8 | async def init(bot): 9 | @bot.on(events.NewMessage(pattern='📊 Accounts List 📊', func=lambda e: e.is_private, from_users=settings.OWNERS)) 10 | @bot.on(events.CallbackQuery(data='list', chats=settings.OWNERS)) 11 | async def handler(event): 12 | list = [Button.inline(x['name'], f"user-{x['user_id']}") for x in accounts.select().dicts()] 13 | method = 'reply' if getattr(event, 'text', False) else 'edit' 14 | 15 | if list: 16 | msg = "🌱 Your account list :\n" 17 | else: 18 | msg = "📦 You dont have any accounts!\n" 19 | 20 | await getattr(event, method)(rtl(msg), buttons = [x for x in chunk(list, 3)] if list else None) -------------------------------------------------------------------------------- /src/telegram/registrator.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from src.config import settings 4 | from src.utils.logger import logger 5 | from telethon import TelegramClient as Client 6 | 7 | async def register_sessions() -> None: 8 | API_ID = settings.API_ID 9 | API_HASH = settings.API_HASH 10 | 11 | if not API_ID or not API_HASH: 12 | raise ValueError("API_ID and API_HASH not found in the .env file.") 13 | 14 | session_name = input('\nEnter the session name (press Enter to exit): ') 15 | 16 | if not session_name: 17 | return None 18 | 19 | if not os.path.exists(settings.SESSION_PATH): 20 | os.mkdir(settings.SESSION_PATH) 21 | 22 | session = Client( 23 | session = os.path.join(settings.SESSION_PATH, session_name), 24 | api_id = API_ID, 25 | api_hash = API_HASH 26 | ) 27 | 28 | async with session: 29 | user_data = await session.get_me() 30 | 31 | logger.success(f'Session added successfully @{user_data.username} | {user_data.first_name} {user_data.last_name}') -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | import traceback 4 | 5 | from tasks import app 6 | from src.telegram.client import getClient, startClient 7 | 8 | try: 9 | import plugins as plugins 10 | except ImportError: 11 | try: 12 | from . import plugins 13 | except ImportError: 14 | print(traceback.format_exc()) 15 | print('could not load the plugins module', file=sys.stderr) 16 | exit(1) 17 | 18 | async def main(): 19 | bot = await getClient() 20 | await startClient() 21 | bot.parse_mode = 'html' 22 | 23 | try: 24 | await plugins.init(bot) 25 | await app.serve() 26 | await bot.run_until_disconnected() 27 | 28 | except asyncio.CancelledError: 29 | pass 30 | 31 | except KeyboardInterrupt: 32 | print("KeyboardInterrupt received, shutting down...") 33 | 34 | except Exception as err: 35 | print(f"An error occurred: {err}") 36 | 37 | finally: 38 | await bot.disconnect() 39 | 40 | if __name__ == '__main__': 41 | asyncio.run(main()) -------------------------------------------------------------------------------- /src/database/hamster.py: -------------------------------------------------------------------------------- 1 | from .models import hamsterKombat 2 | from peewee import DoesNotExist 3 | from peewee import fn 4 | 5 | class hamster: 6 | def fetch(user_id: str|int): 7 | try: 8 | find = hamsterKombat.select().where(hamsterKombat.user_id == user_id).dicts().get() 9 | except DoesNotExist: 10 | find = {} 11 | 12 | return find 13 | 14 | def insertOrUpdate(user_id, **kwargs): 15 | try: 16 | record = hamsterKombat.get(hamsterKombat.user_id == user_id) 17 | query = hamsterKombat.update(**kwargs).where(hamsterKombat.user_id == user_id) 18 | query.execute() 19 | except DoesNotExist: 20 | hamsterKombat.create(user_id=user_id, **kwargs) 21 | 22 | def total_info(): 23 | total_balance = hamsterKombat.select(fn.SUM(hamsterKombat.balance)).scalar() 24 | total_profit = hamsterKombat.select(fn.SUM(hamsterKombat.profit)).scalar() 25 | 26 | return { 27 | 'balance' : total_balance, 28 | 'profit' : total_profit 29 | } 30 | -------------------------------------------------------------------------------- /src/config/config.py: -------------------------------------------------------------------------------- 1 | from pydantic_settings import BaseSettings, SettingsConfigDict 2 | 3 | 4 | class Settings(BaseSettings): 5 | model_config = SettingsConfigDict(env_file=".env", env_ignore_empty=True) 6 | 7 | API_ID : int 8 | API_HASH : str 9 | TG_TOKEN : str 10 | MAIN_NAME : str = 'bot' 11 | MAIN_NAME : str 12 | SESSION_PATH : str = 'sessions' 13 | BAD_SESSIONS_PATH : str = 'bad_sessions' 14 | DB_PATH : str = 'database.db' 15 | 16 | MIN_AVAILABLE_ENERGY: int = 100 17 | SLEEP_BY_MIN_ENERGY : list[int] = [1800, 2400] 18 | ADD_TAPS_ON_TURBO : int = 2500 19 | 20 | AUTO_UPGRADE : bool = True 21 | MAX_LEVEL : int = 20 22 | 23 | BALANCE_TO_SAVE : int = 1000000 24 | UPGRADES_COUNT : int = 10 25 | 26 | APPLY_DAILY_ENERGY : bool = True 27 | APPLY_DAILY_TURBO : bool = True 28 | 29 | RANDOM_TAPS_COUNT : list[int] = [10, 50] 30 | SLEEP_BETWEEN_TAP : list[int] = [10, 25] 31 | OWNERS : list[int] 32 | 33 | USE_PROXY_FROM_FILE : bool = False 34 | 35 | RENEW_AUTH : int = 3600 36 | 37 | settings = Settings() -------------------------------------------------------------------------------- /src/database/models.py: -------------------------------------------------------------------------------- 1 | from peewee import * 2 | from src.config import settings 3 | 4 | sqlite_db = SqliteDatabase(settings.DB_PATH) 5 | 6 | class BaseModel(Model): 7 | class Meta: 8 | database = sqlite_db 9 | 10 | class users(BaseModel): 11 | user_id = BigIntegerField(primary_key=True) 12 | step = TextField(null=True) 13 | 14 | class accounts(BaseModel): 15 | user_id = BigIntegerField(primary_key=True) 16 | name = CharField(max_length=100) 17 | username = CharField(max_length=100, default = None, null = True) 18 | phone_number = CharField(max_length=50) 19 | session_file = CharField(max_length=100) 20 | 21 | class hamsterKombat(BaseModel): 22 | user_id = ForeignKeyField(accounts, backref='hamsterKombat') 23 | url = CharField(max_length=1000) 24 | token = CharField(max_length=500, default = None, null = True) 25 | last_login = IntegerField(default=0) 26 | balance = IntegerField(default=0) 27 | profit = IntegerField(default=0) 28 | last_check = IntegerField(default=0) 29 | 30 | class config(BaseModel): 31 | hamsterKombat = BooleanField(default=False) 32 | 33 | sqlite_db.connect() 34 | sqlite_db.create_tables([users, config, accounts, hamsterKombat]) -------------------------------------------------------------------------------- /plugins/restart.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from telethon import events 5 | from src.config import settings 6 | 7 | MAGIC_FILE = os.path.join(os.path.dirname(__file__), 'self-update.lock') 8 | 9 | async def init(bot): 10 | @bot.on(events.NewMessage(pattern='🔄 Restart Bot', func=lambda e: e.is_private, from_users=settings.OWNERS)) 11 | async def handler(event): 12 | await event.delete() 13 | m = await event.respond('Checking for plugin updates…') 14 | 15 | try: 16 | with open(MAGIC_FILE, 'w') as fd: 17 | fd.write('{}\n{}\n'.format(m.chat_id, m.id)) 18 | 19 | await m.edit('Plugins updated. Restarting…') 20 | except OSError: 21 | await m.edit('Plugins updated. Will not notify after restart. Restarting…') 22 | 23 | try: 24 | python = sys.executable 25 | os.execl(python, python, *sys.argv) 26 | 27 | except Exception: 28 | await m.edit('Error on disconnect, this is a bug') 29 | 30 | try: 31 | with open(MAGIC_FILE) as fd: 32 | chat_id, msg_id = map(int, fd) 33 | await bot.edit_message(chat_id, msg_id, 'Plugins updated.') 34 | 35 | os.unlink(MAGIC_FILE) 36 | except OSError: 37 | pass -------------------------------------------------------------------------------- /plugins/info.py: -------------------------------------------------------------------------------- 1 | from telethon import events 2 | from datetime import datetime 3 | from src.config import settings 4 | from src.database.acc import acc 5 | from src.utils.helpers import align 6 | from telethon.tl.custom.button import Button 7 | 8 | async def init(bot): 9 | @bot.on(events.CallbackQuery(pattern='^user\-(\d+)$', func=lambda e: e.is_private, chats=settings.OWNERS)) 10 | async def handler(event): 11 | id = int(event.pattern_match.group(1)) 12 | info = acc.fetch(id) 13 | 14 | main = align({ 15 | "🌟 Name" : f"{info['name']}", 16 | "🫀 UserId" : f"{info['user_id']}", 17 | "👤 Username" : f"@{info['username']}", 18 | "📞 Phone" : f"@{info['phone_number']}" 19 | }) 20 | 21 | hamster = align({ 22 | "💰 Balance" : f"{info['hamsterKombat']['balance']}", 23 | "📈 PPH" : f"{info['hamsterKombat']['profit']}", 24 | "🕒 Last Login" : datetime.fromtimestamp(info['hamsterKombat']['last_login']) 25 | }) 26 | 27 | keys = [ 28 | [Button.url('🐹 Hamster', info['hamsterKombat']['url'])], 29 | [Button.inline('back', 'list')] 30 | ] 31 | 32 | await event.edit(f"👳🏿‍♂️ Account:\n{main}\n🐹 Hamster:\n{hamster}", buttons = keys) -------------------------------------------------------------------------------- /plugins/status.py: -------------------------------------------------------------------------------- 1 | from telethon import events 2 | from src.config import settings 3 | from src.database.config import conf 4 | from src.utils.helpers import rtl, emoticate 5 | from telethon.tl.custom.button import Button 6 | 7 | async def init(bot): 8 | @bot.on(events.NewMessage(pattern='🚏 Clickers Status', func=lambda e: e.is_private, from_users=settings.OWNERS)) 9 | async def handler(event): 10 | info = conf.fetch() 11 | del info['id'] 12 | 13 | msg = "Change bot clickers status:\n" 14 | list = [ 15 | [Button.inline(key, f"change-{key}"), Button.inline(emoticate(value), f"change-{key}")] 16 | for key, value in info.items() 17 | ] 18 | 19 | await event.reply(rtl(msg), buttons = list) 20 | 21 | @bot.on(events.CallbackQuery(pattern='^change\-(.*)$', chats=settings.OWNERS)) 22 | async def handler(event): 23 | index = event.pattern_match.group(1).decode('utf-8') 24 | info = conf.fetch() 25 | 26 | del info['id'] 27 | conf.insertOrUpdateConfig(**{index: not info[index]}) 28 | 29 | msg = "Change bot clickers status:\n" 30 | list = [ 31 | [Button.inline(key, f"change-{key}"), Button.inline(emoticate(not value), f"change-{key}")] 32 | for key, value in info.items() 33 | ] 34 | 35 | await event.edit(rtl(msg), buttons = list) -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | run-name: "🧪 Build " 3 | 4 | on: 5 | push: 6 | branches: 7 | - '*' 8 | workflow_dispatch: 9 | 10 | concurrency: 11 | group: build-ci 12 | cancel-in-progress: true 13 | 14 | env: 15 | REGISTRY: ghcr.io 16 | IMAGE_NAME: rosegoli/hamster 17 | 18 | jobs: 19 | build-tswap: 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: write 23 | packages: write 24 | id-token: write 25 | 26 | name: "🛖 Build and release docker image" 27 | 28 | steps: 29 | - uses: actions/checkout@master 30 | - uses: actions/setup-node@master 31 | - name: Set up QEMU 32 | uses: docker/setup-qemu-action@v3 33 | 34 | - name: Setup Docker buildX 35 | uses: docker/setup-buildx-action@v3 36 | 37 | - name: Log into registry ${{ env.REGISTRY }} 38 | uses: docker/login-action@v3 39 | with: 40 | registry: ${{ env.REGISTRY }} 41 | username: ${{ github.actor }} 42 | password: ${{ secrets.GITHUB_TOKEN }} 43 | 44 | - name: Build and push Docker image 45 | id: build-and-push 46 | uses: docker/build-push-action@v5 47 | with: 48 | context: . 49 | file: Dockerfile 50 | network: host 51 | push: ${{ github.event_name != 'pull_request' }} 52 | tags: | 53 | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref == 'refs/heads/master' && 'latest' || github.ref_name }} 54 | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} 55 | cache-from: type=gha 56 | cache-to: type=gha,mode=max 57 | -------------------------------------------------------------------------------- /tasks.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from aioclock.group import Group 4 | from aioclock import AioClock, Every, Once 5 | 6 | from src.config import settings 7 | from src.utils.logger import logger 8 | from games.hamster.tapper import Tapper 9 | from src.utils.scripts import getSessions 10 | from src.telegram.multiClients import connectAndCacheClients 11 | 12 | group = Group() 13 | app = AioClock() 14 | app.include_group(group) 15 | 16 | @app.task(trigger=Once()) 17 | async def once(): 18 | logger.info('tasks on startup!') 19 | 20 | await connectAndCacheClients( 21 | 'hamster_kombat_bot', 22 | 'https://hamsterkombatgame.io', 23 | f"kentId{settings.OWNERS[0]}" 24 | ) 25 | 26 | referal_tasks = [asyncio.create_task(Tapper(session).add_referral()) for session in getSessions()] 27 | tasks_tasks = [asyncio.create_task(Tapper(session).run()) for session in getSessions()] 28 | daily_tasks = [asyncio.create_task(Tapper(session).daily_events()) for session in getSessions()] 29 | all_jobs = referal_tasks + tasks_tasks + daily_tasks 30 | 31 | await asyncio.gather(*all_jobs) 32 | 33 | @group.task(trigger=Every(minutes=60)) 34 | async def every(): 35 | logger.info("Cache on Every 60 minutes") 36 | await connectAndCacheClients( 37 | 'hamster_kombat_bot', 38 | 'https://hamsterkombatgame.io', 39 | f"kentId{settings.OWNERS[0]}" 40 | ) 41 | 42 | @group.task(trigger=Every(minutes=120)) 43 | async def every(): 44 | logger.info("Daily on Every 120 minutes") 45 | 46 | sessions = getSessions() 47 | tasks = [asyncio.create_task(Tapper(session).daily_events()) for session in sessions] 48 | await asyncio.gather(*tasks) -------------------------------------------------------------------------------- /plugins/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import inspect 4 | import asyncio 5 | import importlib 6 | 7 | from src.utils.logger import logger 8 | 9 | async def init(bot): 10 | plugins = [ 11 | importlib.import_module(f'.', f'{__name__}.{file[:-3]}') 12 | for file in os.listdir(os.path.dirname(__file__)) 13 | if file[0].isalpha() and file.endswith('.py') 14 | ] 15 | 16 | modules = {m.__name__.split('.')[-1]: m for m in plugins} 17 | to_init = (get_init_coro(plugin, bot=bot, modules=modules) for plugin in plugins) 18 | 19 | await asyncio.gather(*(filter(None, to_init))) 20 | 21 | 22 | def get_init_coro(plugin, **kwargs): 23 | p_init = getattr(plugin, 'init', None) 24 | if not callable(p_init): 25 | return 26 | 27 | result = {} 28 | sig = inspect.signature(p_init) 29 | 30 | for param in sig.parameters: 31 | if param in kwargs: 32 | result[param] = kwargs[param] 33 | else: 34 | logger.error('Plugin %s has unknown init parameter %s', plugin.__name__, param.__name__) 35 | return 36 | 37 | return _init_plugin(plugin, result) 38 | 39 | async def _init_plugin(plugin, kwargs): 40 | try: 41 | logger.warning(f'Loading plugin {plugin.__name__}…') 42 | 43 | start = time.time() 44 | ret = await plugin.init(**kwargs) 45 | took = time.time() - start 46 | 47 | logger.warning(f'Loaded plugin {plugin.__name__} (took {took:.2f}s)') 48 | except Exception: 49 | logger.exception(f'Failed to load plugin {plugin}') 50 | else: 51 | if asyncio.iscoroutinefunction(ret): 52 | await ret 53 | 54 | 55 | async def start_plugins(bot, plugins): 56 | await asyncio.gather(*(_init_plugin(bot, plugin) for plugin in plugins)) -------------------------------------------------------------------------------- /src/utils/helpers.py: -------------------------------------------------------------------------------- 1 | import psutil 2 | 3 | def align(args:dict, prefix:str='', suffix:str='', sep:str=' : ') -> str: 4 | [result, maxLength] = ['', 0] 5 | 6 | for key, value in args.items(): 7 | if (len(key) > maxLength): 8 | maxLength = len(key) 9 | 10 | for key, value in args.items(): 11 | result += prefix + str(key).ljust(maxLength) + sep + suffix + str(value) + "\n" 12 | 13 | return rtl(result) 14 | 15 | def rtl(string): 16 | return f"{string}\u200f" 17 | 18 | def emoticate(value: str, is_number: bool = False) -> str: 19 | if is_number and value.isdigit(): 20 | emoji_digits = ['0️⃣', '1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣'] 21 | return ''.join(emoji_digits[int(digit)] for digit in value) 22 | 23 | if isinstance(value, bool): 24 | return '✅' if value else '❌' 25 | 26 | return '❌' 27 | 28 | def format_large_num(num): 29 | suffixes = ['', 'K', 'M', 'B', 'T', 'Q', 'QQ'] 30 | 31 | if num == 0: 32 | return '0' 33 | 34 | abs_num = abs(num) 35 | magnitude = 0 36 | 37 | while abs_num >= 1000 and magnitude < len(suffixes) - 1: 38 | abs_num /= 1000 39 | magnitude += 1 40 | 41 | formatted_num = '{:.2f}'.format(abs_num).rstrip('0').rstrip('.') 42 | 43 | return '{} {}'.format(formatted_num, suffixes[magnitude]) 44 | 45 | def get_server_usage(): 46 | memory = psutil.virtual_memory() 47 | mem_usage = memory.used / 1e6 48 | mem_total = memory.total / 1e6 49 | mem_percent = memory.percent 50 | cpu_percent = psutil.cpu_percent() 51 | 52 | return { 53 | 'memory_usage_MB': mem_usage, 54 | 'memory_total_MB': mem_total, 55 | 'memory_percent' : mem_percent, 56 | 'cpu_percent' : cpu_percent 57 | } -------------------------------------------------------------------------------- /src/database/acc.py: -------------------------------------------------------------------------------- 1 | from .models import accounts, hamsterKombat 2 | from peewee import DoesNotExist, IntegrityError 3 | 4 | class acc: 5 | def fetch(value: str | int): 6 | try: 7 | find = {} 8 | account_fields = set(accounts._meta.fields.keys()) 9 | hamster_fields = set(hamsterKombat._meta.fields.keys()) 10 | 11 | query = ( 12 | accounts 13 | .select( 14 | accounts, 15 | hamsterKombat 16 | ) 17 | .join(hamsterKombat, on=(accounts.user_id == hamsterKombat.user_id)) 18 | ) 19 | 20 | if isinstance(value, int): 21 | find = query.where(accounts.user_id == value).dicts().get() 22 | 23 | elif isinstance(value, str): 24 | find = query.where(accounts.session_file == value).dicts().get() 25 | 26 | result = {} 27 | hamster_info = {} 28 | 29 | for key, value in find.items(): 30 | if key in hamster_fields and key != 'user_id': 31 | hamster_info[key] = value 32 | else: 33 | result[key] = value 34 | 35 | result['hamsterKombat'] = hamster_info 36 | 37 | except DoesNotExist as e: 38 | result = {} 39 | 40 | return result 41 | 42 | def insertOrUpdate(user_id, **kwargs): 43 | try: 44 | record = accounts.get(accounts.user_id == user_id) 45 | for key, value in kwargs.items(): 46 | setattr(record, key, value) 47 | record.save() 48 | except DoesNotExist: 49 | try: 50 | accounts.create(user_id=user_id, **kwargs) 51 | except IntegrityError as e: 52 | print(f"IntegrityError: {e}") 53 | raise -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hamster Clicker 🐹 2 | ![Untitled](https://github.com/RoseGoli/Hamster/assets/70085500/8f673aeb-a823-43a5-bf80-f0410fcc00f2) 3 | 4 | ## Overview 5 | Hamster is a Python-based project designed to collect hamster-coins 🎉. This guide will help you clone, set up, and run the project on your local machine. 6 | 7 | ## Prerequisites 8 | - Python 3.x 9 | - Git 10 | 11 | ## Installation 12 | 13 | ### Clone the Repository 14 | First, clone the repository to your local machine using the following command: 15 | 16 | ```sh 17 | git clone https://github.com/RoseGoli/Hamster.git 18 | cd Hamster 19 | ``` 20 | 21 | ### Create a Virtual Environment 22 | It's recommended to create a virtual environment to manage dependencies. You can create and activate a virtual environment using the following commands: 23 | 24 | For Unix/macOS: 25 | ```sh 26 | python3 -m venv venv 27 | source venv/bin/activate 28 | ``` 29 | 30 | For Windows: 31 | ```sh 32 | python -m venv venv 33 | venv\Scripts\activate 34 | ``` 35 | 36 | ### Install Dependencies 37 | With the virtual environment activated, install the required dependencies: 38 | 39 | ```sh 40 | pip install -r requirements.txt 41 | ``` 42 | 43 | ### Configure Environment Variables 44 | Copy the example environment configuration file and edit it as necessary: 45 | 46 | ```sh 47 | cp .env.example .env 48 | ``` 49 | 50 | Open the `.env` file and configure the necessary environment variables. 51 | add the following variables with their respective values: 52 | ``` 53 | API_ID = your_api_id 54 | API_HASH = your_api_hash 55 | TG_TOKEN = your_tg_token 56 | OWNERS = [owner1,owner2] 57 | ``` 58 | Make sure to replace your_api_id, your_api_hash, your_tg_token, and owners with the actual values. 59 | 60 | ### Run the Project 61 | Finally, run the main script to start the project: 62 | 63 | ```sh 64 | python main.py 65 | ``` 66 | 67 | ## Contact 68 | [@TheUser](https://t.me/TheUser) 69 | 70 | Feel free to modify the sections like "Overview", "Contributing" and "Contact" according to your specific project details. 71 | -------------------------------------------------------------------------------- /src/utils/request.py: -------------------------------------------------------------------------------- 1 | import json 2 | import aiohttp 3 | import asyncio 4 | 5 | from .logger import logger 6 | 7 | class Request: 8 | def __init__(self, base_url=None, base_headers=None): 9 | self.base_url = base_url 10 | self.base_headers = base_headers if base_headers is not None else {} 11 | self.headers = {} 12 | 13 | def set_header(self, key, value): 14 | self.headers[key] = value 15 | 16 | def update_headers(self, new_headers): 17 | self.headers.update(new_headers) 18 | 19 | async def send_request(self, method, url=None, endpoint=None, data=None, timeout=15, retries=3, backoff_factor=1): 20 | combined_headers = {**self.base_headers, **self.headers} 21 | 22 | if not (url or endpoint): 23 | return None, "URL or endpoint must be provided" 24 | 25 | if endpoint and self.base_url: 26 | url = f'{self.base_url}{endpoint}' 27 | 28 | elif endpoint: 29 | return None, "Base URL must be provided if using endpoint" 30 | 31 | elif url: 32 | url = url 33 | 34 | attempt = 0 35 | 36 | while attempt < retries: 37 | try: 38 | async with aiohttp.ClientSession(headers=combined_headers) as session: 39 | if method.upper() == 'GET': 40 | async with session.get(url, params=data, timeout=timeout) as response: 41 | return await self._handle_response(response) 42 | elif method.upper() == 'POST': 43 | async with session.post(url, json=data, timeout=timeout) as response: 44 | return await self._handle_response(response) 45 | else: 46 | return None, f"Unsupported HTTP method: {method}" 47 | except (aiohttp.ClientError, asyncio.TimeoutError) as e: 48 | attempt += 1 49 | if attempt == retries: 50 | return None, str(e) 51 | 52 | wait_time = backoff_factor * (2 ** (attempt - 1)) 53 | logger.error(f"Attempt {attempt}, waiting {wait_time} seconds before retry!") 54 | await asyncio.sleep(wait_time) 55 | 56 | return None, "Failed after retries" 57 | 58 | async def _handle_response(self, response, ignore_status :list = [422]): 59 | try: 60 | if response.status not in ignore_status: 61 | response.raise_for_status() 62 | try: 63 | return await response.json(), None 64 | except aiohttp.ContentTypeError: 65 | return json.loads(await response.text()), None 66 | except aiohttp.ClientResponseError as e: 67 | return None, str(e) -------------------------------------------------------------------------------- /src/utils/scripts.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import random 4 | import string 5 | import base64 6 | import hashlib 7 | 8 | from itertools import islice 9 | from src.config import settings 10 | from urllib.parse import unquote 11 | from fake_useragent import UserAgent 12 | 13 | def generate_random_visitor_id(): 14 | random_string = ''.join(random.choices(string.ascii_letters + string.digits, k=32)) 15 | visitor_id = hashlib.md5(random_string.encode()).hexdigest() 16 | 17 | return visitor_id 18 | 19 | def escape_html(text: str) -> str: 20 | return text.replace('<', '\\<').replace('>', '\\>') 21 | 22 | def decode_cipher(cipher: str) -> str: 23 | encoded = cipher[:3] + cipher[4:] 24 | return base64.b64decode(encoded).decode('utf-8') 25 | 26 | def find_best(balance, upgrades, old=None): 27 | if old is None: 28 | old = [] 29 | 30 | if not upgrades: 31 | return old 32 | 33 | max_upgrade = {'index': 0} 34 | max_upgrade_key = None 35 | 36 | for key, upgrade in enumerate(upgrades): 37 | if ( 38 | not upgrade['isAvailable'] or 39 | upgrade['isExpired'] or 40 | balance < upgrade['price'] 41 | ): 42 | continue 43 | 44 | index = upgrade['profitPerHourDelta'] / upgrade['price'] 45 | 46 | if index > max_upgrade['index']: 47 | max_upgrade_key = key 48 | max_upgrade = { 49 | 'id' : upgrade['id'], 50 | 'key' : key, 51 | 'price' : upgrade['price'], 52 | 'index' : index, 53 | 'profit' : upgrade['profitPerHourDelta'], 54 | 'level' : upgrade['level'] 55 | } 56 | 57 | if max_upgrade['index'] == 0: 58 | return old 59 | 60 | new_upgrades = [upgrade for i, upgrade in enumerate(upgrades) if i != max_upgrade_key] 61 | return find_best(balance - max_upgrade['price'], new_upgrades, old + [max_upgrade]) 62 | 63 | def get_mobile_user_agent() -> str: 64 | """ 65 | Function: get_mobile_user_agent 66 | 67 | This method generates a random mobile user agent for an Android platform. 68 | If the generated user agent does not contain the "wv" string, 69 | it adds it to the browser version component. 70 | 71 | :return: A random mobile user agent for Android platform. 72 | """ 73 | ua = UserAgent(platforms=['mobile'], os=['android']) 74 | user_agent = ua.random 75 | if 'wv' not in user_agent: 76 | parts = user_agent.split(')') 77 | parts[0] += '; wv' 78 | user_agent = ')'.join(parts) 79 | return user_agent 80 | 81 | def parse_webapp_url(auth_url: str) -> str: 82 | return unquote( 83 | string = unquote( 84 | string = auth_url.split('tgWebAppData=', maxsplit=1)[1].split('&tgWebAppVersion', maxsplit=1)[0])) 85 | 86 | def getSessions(): 87 | for file in os.listdir(settings.SESSION_PATH): 88 | if file.endswith('.session'): 89 | r = file.replace('.session', '') 90 | if r != settings.MAIN_NAME: 91 | yield r 92 | 93 | def chunk(arr_range, arr_size): 94 | arr_range = iter(arr_range) 95 | return iter(lambda: tuple(islice(arr_range, arr_size)), ()) -------------------------------------------------------------------------------- /src/telegram/multiClients.py: -------------------------------------------------------------------------------- 1 | import random 2 | import asyncio 3 | import traceback 4 | 5 | from time import time 6 | from telethon import functions 7 | from src.config import settings 8 | from src.database.acc import acc 9 | from src.utils.logger import logger 10 | from .telegramApp import TelegramApp 11 | from games.hamster.tapper import Tapper 12 | from src.database.hamster import hamster 13 | from src.utils.scripts import getSessions 14 | from src.utils.scripts import parse_webapp_url 15 | 16 | async def handleSession(session, bot: str, url: str, start_param: str = None): 17 | try: 18 | if bot == 'hamster_kombat_bot': 19 | search = acc.fetch(session) 20 | timer = search.get('hamsterKombat', {}).get('last_login', 0) 21 | token = search.get('hamsterKombat', {}).get('token', False) 22 | 23 | if (time() - timer >= settings.RENEW_AUTH) or not token: 24 | 25 | app = TelegramApp(session) 26 | await app.connect() 27 | 28 | info = (await app.getClient().get_me()) 29 | 30 | #await app.resove_peer(bot) 31 | await app.getClient()(functions.account.UpdateStatusRequest( 32 | offline=False 33 | )) 34 | 35 | url = await app.get_web_data( 36 | bot = bot, 37 | url = url, 38 | start_param = start_param, 39 | raw_url = True 40 | ) 41 | 42 | acc.insertOrUpdate( 43 | user_id = info.id, 44 | name = ' '.join(filter(None, [info.first_name, info.last_name])), 45 | username = info.username if (hasattr(info, 'username') and info.username != None) else None, 46 | phone_number = info.phone, 47 | session_file = session 48 | ) 49 | 50 | if url: 51 | hamster.insertOrUpdate( 52 | user_id = info.id, 53 | url = url, 54 | last_login = time() 55 | ) 56 | 57 | hamster_client = Tapper(session) 58 | new_token = await hamster_client.login(parse_webapp_url(url)) 59 | token = new_token if new_token != False else hamster.fetch(info.id)['token'] 60 | 61 | while not token: 62 | token = await hamster_client.login(parse_webapp_url(url)) 63 | sleep = random.randint(1, 10) 64 | logger.warning(f"{session} | sleep {sleep}sec befor token cache!") 65 | await asyncio.sleep(sleep) 66 | 67 | hamster.insertOrUpdate( 68 | user_id = info.id, 69 | token = token 70 | ) 71 | 72 | await app.disconnect() 73 | except Exception as e: 74 | print(traceback.format_exc()) 75 | pass 76 | 77 | async def connectAndCacheClients(bot: str, url: str, start_param: str = None): 78 | sessions = getSessions() 79 | tasks = [ 80 | asyncio.create_task(handleSession(session, bot, url, start_param)) 81 | for session in sessions 82 | ] 83 | 84 | await asyncio.gather(*tasks) -------------------------------------------------------------------------------- /src/telegram/telegramApp.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import shutil 4 | import asyncio 5 | 6 | from src.config import settings 7 | from src.utils.logger import logger 8 | from telethon.errors import FloodWaitError 9 | from src.utils.scripts import parse_webapp_url 10 | from telethon.tl.types import InputBotAppShortName 11 | from telethon import TelegramClient as Client, functions 12 | 13 | class TelegramApp: 14 | def __init__(self, session_name: str) -> None: 15 | self.client = None 16 | self.session = session_name 17 | 18 | self.getClient() 19 | 20 | def getClient(self): 21 | if self.client: 22 | return self.client 23 | 24 | data_path = f'{settings.SESSION_PATH}/{self.session}.json' 25 | 26 | if os.path.exists(data_path): 27 | with open(data_path, 'r') as file: 28 | session_data = json.load(file) 29 | else: 30 | session_data = {} 31 | 32 | api_id = session_data.get('api_id', settings.API_ID) 33 | api_hash = session_data.get('api_hash', settings.API_HASH) 34 | self.client = Client(session=f'sessions/{self.session}', api_id=api_id, api_hash=api_hash) 35 | 36 | return self.client 37 | 38 | async def connect(self): 39 | """Connects to the Telegram client.""" 40 | try: 41 | if not self.client.is_connected(): 42 | await self.client.connect() 43 | 44 | if not await self.client.is_user_authorized(): 45 | logger.error(f"Bad session: {self.session}") 46 | await self.disconnect() 47 | self.move_bad_session_files() 48 | 49 | else: 50 | logger.info(f"Connected client: {self.session}") 51 | except Exception as error: 52 | logger.error(f"Error connecting client: {self.session}, {str(error)}") 53 | raise Exception(self.session) 54 | 55 | async def disconnect(self): 56 | """Disconnects from the Telegram client.""" 57 | try: 58 | if self.client.is_connected(): 59 | await self.client.disconnect() 60 | logger.info(f"Disconnected client: {self.session}") 61 | except Exception as error: 62 | logger.error(f"Error disconnecting client: {self.session}, {str(error)}") 63 | raise Exception(self.session) 64 | 65 | async def join_channel(self, channel: str) -> bool: 66 | """Joins a Telegram channel.""" 67 | try: 68 | await self.client(functions.channels.JoinChannelRequest(channel)) 69 | return True 70 | except Exception as error: 71 | logger.error(f"{self.session} | Error during join: {error}") 72 | return False 73 | 74 | async def resove_peer(self, username: str): 75 | dialogs = self.client.iter_dialogs() 76 | async for dialog in dialogs: 77 | if ( 78 | dialog.entity 79 | and hasattr(dialog.entity, 'username') 80 | and dialog.entity.username == username 81 | ): 82 | break 83 | 84 | while True: 85 | try: 86 | peer = await self.client.get_entity(username) 87 | break 88 | except FloodWaitError as fl: 89 | logger.warning(f'{self.session} | FloodWait {fl}') 90 | raise 91 | # fls = fl.seconds 92 | # fls *= 2 93 | # logger.info(f'{self.session} | Sleep {fls}s') 94 | # #await asyncio.sleep(fls) 95 | 96 | async def get_web_data( 97 | self, bot: str, url: str, platform: str = 'android', 98 | start_param: str = None, raw_url: bool = False 99 | ) -> str: 100 | """Fetches web data from a bot.""" 101 | try: 102 | web_view = await self.client( 103 | functions.messages.RequestWebViewRequest( 104 | peer = bot, 105 | bot = bot, 106 | platform = platform, 107 | from_bot_menu = False, 108 | start_param = start_param, 109 | url = url 110 | ) 111 | ) 112 | 113 | auth_url = web_view.url 114 | tg_web_data = parse_webapp_url(auth_url) 115 | 116 | if raw_url: 117 | return auth_url 118 | 119 | return tg_web_data 120 | 121 | except Exception as error: 122 | logger.error(f"{self.session} | Failed to get web data: {error}") 123 | await asyncio.sleep(delay=3) 124 | return "" 125 | 126 | async def get_app_data(self, bot: str, platform: str = 'android', 127 | start_param: str = None, short_name: str = 'start', raw_url: bool = False 128 | ): 129 | try: 130 | web_view = await self.client( 131 | functions.messages.RequestAppWebViewRequest( 132 | peer = "me", 133 | platform = platform, 134 | start_param = start_param, 135 | app = InputBotAppShortName( 136 | bot_id = await self.client.get_input_entity(bot), 137 | short_name = short_name 138 | ) 139 | ) 140 | ) 141 | 142 | auth_url = web_view.url 143 | tg_web_data = parse_webapp_url(auth_url) 144 | 145 | if raw_url: 146 | return auth_url 147 | 148 | return tg_web_data 149 | 150 | except Exception as error: 151 | logger.error(f"{self.session} | Failed to get app data: {error}") 152 | await asyncio.sleep(delay=3) 153 | return "" 154 | 155 | def move_bad_session_files(self): 156 | extensions = ['.session', '.json'] 157 | moved_files = [] 158 | 159 | for ext in extensions: 160 | src = os.path.join(settings.SESSION_PATH, f"{self.session}{ext}") 161 | dst = os.path.join(settings.BAD_SESSIONS_PATH, f"{self.session}{ext}") 162 | 163 | if os.path.exists(src): 164 | shutil.move(src, dst) 165 | moved_files.append(src) 166 | 167 | if moved_files: 168 | logger.error(f"Session {self.session} moved to bad sessions: {moved_files}") 169 | raise Exception('not authorized') -------------------------------------------------------------------------------- /games/hamster/tapper.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import traceback 3 | 4 | from time import time 5 | from random import randint 6 | from datetime import datetime, timedelta 7 | 8 | from src.config import settings 9 | from src.database.acc import acc 10 | from src.utils.logger import logger 11 | from .fingerprint import FINGERPRINT 12 | from src.database.config import conf 13 | from src.utils.request import Request 14 | from src.database.hamster import hamster 15 | from src.utils.scripts import decode_cipher, find_best, get_mobile_user_agent 16 | 17 | class Tapper: 18 | def __init__(self, session): 19 | self.token = False 20 | self.session = session 21 | 22 | self.http_client = Request( 23 | base_url = 'https://api.hamsterkombatgame.io', 24 | base_headers = { 25 | 'Accept' : 'application/json', 26 | 'Accept-Language' : 'en-US,ru;q=0.9', 27 | 'Content-Type' : 'application/json', 28 | 'Connection' : 'keep-alive', 29 | 'Origin' : 'https://hamsterkombatgame.io', 30 | 'Referer' : 'https://hamsterkombatgame.io/', 31 | 'Sec-Fetch-Dest' : 'empty', 32 | 'Sec-Fetch-Mode' : 'cors', 33 | 'Sec-Fetch-Site' : 'same-site', 34 | 'Sec-Ch-Ua' : '"Android WebView";v="125", "Chromium";v="125", "Not.A/Brand";v="24"', 35 | 'Sec-Ch-Ua-mobile' : '?1', 36 | 'Sec-Ch-Ua-platform' : '"Android"', 37 | "User-Agent" : get_mobile_user_agent(), 38 | } 39 | ) 40 | 41 | self.getSetHeader() 42 | 43 | 44 | def getSetHeader(self): 45 | self.me = acc.fetch(self.session) 46 | self.token = self.me.get('hamsterKombat', {}).get('token', False) 47 | 48 | if self.token: 49 | self.http_client.update_headers({'Authorization' : f"Bearer {self.token}"}) 50 | return True 51 | 52 | return False 53 | 54 | async def login(self, tg_web_data: str): 55 | response, error = await self.http_client.send_request( 56 | method = 'POST', 57 | endpoint = '/auth/auth-by-telegram-webapp', 58 | data = { 59 | 'initDataRaw' : tg_web_data, 60 | 'fingerprint' : FINGERPRINT 61 | } 62 | ) 63 | 64 | if response and 'authToken' in response: 65 | self.token = response['authToken'] 66 | self.expire_date = datetime.now() + timedelta(minutes=60) 67 | 68 | return self.token 69 | else: 70 | logger.error( 71 | f"{self.session} | Failed [AuthToken]: {error or response}" 72 | ) 73 | return False 74 | 75 | async def add_referral(self, friendUserId = None): 76 | response, error = await self.http_client.send_request( 77 | method = 'POST', 78 | endpoint = '/clicker/add-referral', 79 | data = { 80 | "friendUserId": int(friendUserId or settings.OWNERS[0]) 81 | } 82 | ) 83 | 84 | if response and 'welcomeCoins' in response: 85 | return response 86 | else: 87 | logger.error( 88 | f"{self.session} | Failed [addReferral]: {error or response}" 89 | ) 90 | return False 91 | 92 | async def get_airdrop_tasks(self): 93 | response, error = await self.http_client.send_request( 94 | method = 'POST', 95 | endpoint = '/clicker/list-airdrop-tasks', 96 | data = {} 97 | ) 98 | 99 | if response and 'airdropTasks' in response: 100 | return response 101 | else: 102 | logger.error( 103 | f"{self.session} | Failed [airdropTasks]: {error or response}" 104 | ) 105 | return False 106 | 107 | async def get_me_telegram(self): 108 | response, error = await self.http_client.send_request( 109 | method = 'POST', 110 | endpoint = '/auth/me-telegram', 111 | data = {} 112 | ) 113 | 114 | if response and 'telegramUser' in response: 115 | return response['telegramUser'] 116 | else: 117 | logger.error( 118 | f"{self.session} | Failed [telegramUser]: {error or response}" 119 | ) 120 | return False 121 | 122 | async def get_profile_data(self): 123 | response, error = await self.http_client.send_request( 124 | method = 'POST', 125 | endpoint = '/clicker/sync', 126 | data = {} 127 | ) 128 | 129 | if response: 130 | return response.get('clickerUser') or response.get('found', {}).get('clickerUser', {}) 131 | else: 132 | logger.error( 133 | f"{self.session} | Failed [getProfileData]: {error or response}" 134 | ) 135 | return False 136 | 137 | async def get_config(self): 138 | response, error = await self.http_client.send_request( 139 | method = 'POST', 140 | endpoint = '/clicker/config', 141 | data = {} 142 | ) 143 | 144 | if response: 145 | return response 146 | else: 147 | logger.error( 148 | f"{self.session} | Failed [getConfig]: {error}" 149 | ) 150 | return False 151 | 152 | async def get_tasks(self): 153 | response, error = await self.http_client.send_request( 154 | method = 'POST', 155 | endpoint = '/clicker/list-tasks', 156 | data = {} 157 | ) 158 | 159 | if response and 'tasks' in response: 160 | return response['tasks'] 161 | else: 162 | logger.error( 163 | f"{self.session} | Failed [getTasks]: {error or response}" 164 | ) 165 | return False 166 | 167 | async def select_exchange(self, exchange_id: str): 168 | response, error = await self.http_client.send_request( 169 | method = 'POST', 170 | endpoint = '/clicker/select-exchange', 171 | data = {'exchangeId': exchange_id} 172 | ) 173 | 174 | if response: 175 | return True 176 | else: 177 | logger.error( 178 | f"{self.session} | Failed [selectExchange]: {error or response}" 179 | ) 180 | return False 181 | 182 | async def get_daily(self, id = 'streak_days'): 183 | response, error = await self.http_client.send_request( 184 | method = 'POST', 185 | endpoint = '/clicker/check-task', 186 | data = {'taskId': id} 187 | ) 188 | 189 | if response: 190 | return True 191 | else: 192 | logger.error( 193 | f"{self.session} | Failed [getDaily]: {error or response}" 194 | ) 195 | return False 196 | 197 | async def apply_boost(self, boost_id: str): 198 | response, error = await self.http_client.send_request( 199 | method = 'POST', 200 | endpoint = '/clicker/buy-boost', 201 | data = { 202 | 'timestamp' : time(), 203 | 'boostId' : boost_id 204 | } 205 | ) 206 | 207 | if response: 208 | return True 209 | else: 210 | logger.error( 211 | f"{self.session} | Failed [applyBoost]: {error or response}" 212 | ) 213 | return False 214 | 215 | async def get_upgrades(self): 216 | response, error = await self.http_client.send_request( 217 | method = 'POST', 218 | endpoint = '/clicker/upgrades-for-buy', 219 | data = {} 220 | ) 221 | 222 | if response and 'upgradesForBuy' in response: 223 | return response 224 | else: 225 | logger.error( 226 | f"{self.session} | Failed [getUpgrades]: {error or response}" 227 | ) 228 | return {} 229 | 230 | async def buy_upgrade(self, upgrade_id: str): 231 | response, error = await self.http_client.send_request( 232 | method = 'POST', 233 | endpoint = '/clicker/buy-upgrade', 234 | data = { 235 | 'timestamp' : time(), 236 | 'upgradeId' : upgrade_id 237 | } 238 | ) 239 | 240 | if response: 241 | return True, response.get('upgradesForBuy') or response.get('found', {}).get('upgradesForBuy', {}) 242 | else: 243 | logger.error( 244 | f"{self.session} | Failed [buyUpgrade]: {error or response}" 245 | ) 246 | return False, {} 247 | 248 | async def get_boosts(self): 249 | response, error = await self.http_client.send_request( 250 | method = 'POST', 251 | endpoint = '/clicker/boosts-for-buy', 252 | data = {} 253 | ) 254 | 255 | if response and 'boostsForBuy' in response: 256 | return response['boostsForBuy'] 257 | else: 258 | logger.error( 259 | f"{self.session} | Failed [getBoost]: {error or response}" 260 | ) 261 | return [] 262 | 263 | async def send_taps(self, available_energy: int, taps: int): 264 | response, error = await self.http_client.send_request( 265 | method = 'POST', 266 | endpoint = '/clicker/tap', 267 | data = { 268 | 'availableTaps' : available_energy, 269 | 'count' : taps, 270 | 'timestamp' : time() 271 | } 272 | ) 273 | 274 | if response: 275 | return response.get('clickerUser') or response.get('found', {}).get('clickerUser', {}) 276 | else: 277 | logger.error( 278 | f"{self.session} | Failed [sendTaps]: {error or response}" 279 | ) 280 | return False 281 | 282 | async def claim_daily_cipher(self, cipher: str): 283 | response, error = await self.http_client.send_request( 284 | method = 'POST', 285 | endpoint = '/clicker/claim-daily-cipher', 286 | data = {'cipher': cipher} 287 | ) 288 | 289 | if response and 'clickerUser' in response: 290 | return True 291 | else: 292 | logger.error( 293 | f"{self.session} | Failed [ClaimDailyCipher]: {error or response}" 294 | ) 295 | return False 296 | 297 | async def get_nuxt_builds(self): 298 | response, error = await self.http_client.send_request( 299 | method = 'GET', 300 | url = 'https://hamsterkombatgame.io/_nuxt/builds/meta/8ec5c889-d6a0-4342-8ac7-94a4abfcf5b1.json', 301 | ) 302 | 303 | if response: 304 | return True 305 | else: 306 | logger.error( 307 | f"{self.session} | Failed [getNuxtBuilds]: {error or response}" 308 | ) 309 | return False 310 | 311 | async def get_combo_cards(self): 312 | response, error = await self.http_client.send_request( 313 | method = 'GET', 314 | url = 'https://api21.datavibe.top/api/GetCombo' 315 | ) 316 | 317 | if response: 318 | return response 319 | else: 320 | logger.error( 321 | f"{self.session} | Failed [getComboCards]: {error or response}" 322 | ) 323 | return False 324 | 325 | async def claim_daily_combo(self): 326 | response, error = await self.http_client.send_request( 327 | method = 'POST', 328 | endpoint = '/clicker/claim-daily-combo', 329 | data = {} 330 | ) 331 | 332 | if response: 333 | return True 334 | else: 335 | logger.error( 336 | f"{self.session} | Failed [claimDailyCombo]: {error or response}" 337 | ) 338 | return False 339 | 340 | async def daily_events(self): 341 | if not conf.fetch('hamsterKombat'): 342 | logger.info(f"{self.session} | daily hamster clicker is offline!") 343 | return False 344 | 345 | if not self.token: 346 | return False 347 | 348 | logger.info(f"{self.session} | doing daily events!") 349 | 350 | await self.get_nuxt_builds() 351 | await self.get_me_telegram() 352 | await self.get_airdrop_tasks() 353 | 354 | game_config = await self.get_config() 355 | profile_data = await self.get_profile_data() 356 | 357 | if not profile_data: 358 | return False 359 | 360 | balance = int(profile_data.get('balanceCoins', 0)) 361 | last_passive_earn = int(profile_data.get('lastPassiveEarn', 0)) 362 | earn_on_hour = int(profile_data.get('earnPassivePerHour', 0)) 363 | 364 | logger.info( 365 | f"{self.session} | Last passive earn: +{last_passive_earn:,} | " 366 | f"balance coins : {balance} | " 367 | f"Earn every hour: {earn_on_hour:,}" 368 | ) 369 | 370 | hamster.insertOrUpdate(user_id = self.me['user_id'], balance=balance, profit=earn_on_hour) 371 | 372 | upgrades_data = await self.get_upgrades() 373 | 374 | if upgrades_data: 375 | upgrades = upgrades_data['upgradesForBuy'] 376 | daily_combo = upgrades_data.get('dailyCombo', 0) 377 | 378 | if daily_combo: 379 | bonus = daily_combo['bonusCoins'] 380 | is_claimed = daily_combo['isClaimed'] 381 | 382 | if not is_claimed: 383 | combo_cards = await self.get_combo_cards() 384 | cards = combo_cards['combo'] 385 | date = combo_cards['date'] 386 | 387 | available_combo_cards = [ 388 | data for data in upgrades 389 | if data['isAvailable'] is True 390 | and data['id'] in cards 391 | and data['id'] not in daily_combo['upgradeIds'] 392 | and data['isExpired'] is False 393 | and data.get('cooldownSeconds', 0) == 0 394 | and data.get('maxLevel', data['level']) >= data['level'] 395 | and (data.get('condition') is None 396 | or data['condition'].get('_type') == 'SubscribeTelegramChannel') 397 | ] 398 | 399 | if date == datetime.now().strftime("%d-%m-%y"): 400 | common_price = sum([upgrade['price'] for upgrade in available_combo_cards]) 401 | 402 | logger.info( 403 | f"{self.session} | " 404 | f"Found {len(available_combo_cards)} combo cards to get!" 405 | ) 406 | 407 | if common_price < bonus and balance > common_price: 408 | for upgrade in available_combo_cards: 409 | upgrade_id = upgrade['id'] 410 | level = upgrade['level'] 411 | price = upgrade['price'] 412 | profit = upgrade['profitPerHourDelta'] 413 | 414 | logger.info( 415 | f"{self.session} | " 416 | f"Sleep 5s before upgrade combo card {upgrade_id}" 417 | ) 418 | 419 | await asyncio.sleep(delay=5) 420 | 421 | status, upgrades = await self.buy_upgrade( 422 | upgrade_id = upgrade_id 423 | ) 424 | 425 | if status is True: 426 | earn_on_hour += profit 427 | balance -= price 428 | 429 | logger.success( 430 | f"{self.session} | " 431 | f"Successfully upgraded {upgrade_id} with price {price:,} to {level} lvl | " 432 | f"Earn every hour: {earn_on_hour:,} (+{profit:,}) | " 433 | f"Money left: {balance:,}" 434 | ) 435 | await asyncio.sleep(delay=1) 436 | 437 | await asyncio.sleep(delay=2) 438 | 439 | status = await self.claim_daily_combo() 440 | 441 | if status is True: 442 | logger.success( 443 | f"{self.session} | Successfully claimed daily combo | " 444 | f"Bonus: +{bonus:,}" 445 | ) 446 | 447 | tasks = await self.get_tasks() 448 | 449 | for task in tasks: 450 | if task['id'] not in ['streak_days', 'invite_friends', 'select_exchange']: 451 | if task['isCompleted'] is False: 452 | status = await self.get_daily(task['id']) 453 | if status is True: 454 | logger.success(f"{self.session} | Successfully get daily {task['id']}") 455 | await asyncio.sleep(delay=5) 456 | 457 | daily_task = tasks[-1] 458 | rewards = daily_task['rewardsByDays'] 459 | is_completed = daily_task['isCompleted'] 460 | days = daily_task['days'] 461 | daily_cipher = game_config.get('dailyCipher', False) 462 | 463 | await asyncio.sleep(delay=2) 464 | 465 | if is_completed is False: 466 | status = await self.get_daily() 467 | if status is True: 468 | logger.success( 469 | f"{self.session} | Successfully get daily reward | " 470 | f"Days: {days} | Reward coins: {rewards[days - 1]['rewardCoins']}" 471 | ) 472 | 473 | await asyncio.sleep(delay=2) 474 | 475 | if daily_cipher: 476 | cipher = daily_cipher['cipher'] 477 | bonus = daily_cipher['bonusCoins'] 478 | is_claimed = daily_cipher['isClaimed'] 479 | 480 | if not is_claimed and cipher: 481 | decoded_cipher = decode_cipher(cipher=cipher) 482 | status = await self.claim_daily_cipher(cipher=decoded_cipher) 483 | 484 | if status is True: 485 | logger.success( 486 | f"{self.session} | " 487 | f"Successfully claim daily cipher: {decoded_cipher} | " 488 | f"Bonus: +{bonus:,}" 489 | ) 490 | 491 | await asyncio.sleep(delay=2) 492 | 493 | exchange_id = profile_data.get('exchangeId') 494 | if not exchange_id or exchange_id == 'hamster': 495 | status = await self.select_exchange(exchange_id="bingx") 496 | if status is True: 497 | logger.success(f"{self.session} | Successfully selected exchange bingx") 498 | 499 | async def auto_upgrade(self, balance, earn_on_hour): 500 | if settings.AUTO_UPGRADE is True: 501 | upgrades = await self.get_upgrades() 502 | for _ in range(settings.UPGRADES_COUNT): 503 | if isinstance(upgrades, dict): 504 | upgrades = upgrades.get('upgradesForBuy', []) 505 | elif isinstance(upgrades, list): 506 | pass 507 | else: 508 | upgrades = [] 509 | 510 | available_upgrades = [ 511 | data for data in upgrades 512 | if data['isAvailable'] is True 513 | and data['isExpired'] is False 514 | and data.get('cooldownSeconds', 0) == 0 515 | and data.get('maxLevel', data['level']) >= data['level'] 516 | ] 517 | 518 | free_money = balance - settings.BALANCE_TO_SAVE 519 | queue = find_best(free_money, available_upgrades) 520 | 521 | if not queue: 522 | continue 523 | 524 | upgrade = queue[0] 525 | upgrade_id = upgrade['id'] 526 | level = upgrade['level'] 527 | price = upgrade['price'] 528 | profit = upgrade['profit'] 529 | 530 | logger.info(f"{self.session} | Sleep 5s before upgrade {upgrade_id}") 531 | await asyncio.sleep(delay=5) 532 | 533 | status, upgrades = await self.buy_upgrade( 534 | upgrade_id = upgrade_id 535 | ) 536 | 537 | if status is True: 538 | earn_on_hour += profit 539 | balance -= price 540 | 541 | logger.success( 542 | f"{self.session} | " 543 | f"Successfully upgraded {upgrade_id} with price {price:,} to {level} lvl | " 544 | f"Earn every hour: {earn_on_hour:,} (+{profit:,}) | " 545 | f"Money left: {balance:,}" 546 | ) 547 | 548 | hamster.insertOrUpdate(user_id = self.me['user_id'], balance=balance, profit=earn_on_hour) 549 | 550 | await asyncio.sleep(delay=1) 551 | continue 552 | 553 | async def auto_apply_boosts(self, available_energy): 554 | if available_energy < settings.MIN_AVAILABLE_ENERGY: 555 | boosts = await self.get_boosts() 556 | energy_boost = next((boost for boost in boosts if boost['id'] == 'BoostFullAvailableTaps'), {}) 557 | 558 | if ( 559 | settings.APPLY_DAILY_ENERGY is True 560 | and energy_boost.get("cooldownSeconds", 0) == 0 561 | and energy_boost.get("level", 0) <= energy_boost.get("maxLevel", 0) 562 | ): 563 | logger.info(f"{self.session} | Sleep 5s before apply energy boost") 564 | await asyncio.sleep(delay=5) 565 | 566 | status = await self.apply_boost(boost_id="BoostFullAvailableTaps") 567 | 568 | if status is True: 569 | logger.success(f"{self.session} | Successfully apply energy boost") 570 | await asyncio.sleep(delay=1) 571 | return True 572 | 573 | random_sleep = randint(settings.SLEEP_BY_MIN_ENERGY[0], settings.SLEEP_BY_MIN_ENERGY[1]) 574 | logger.info(f"{self.session} | Minimum energy reached: {available_energy}") 575 | logger.info(f"{self.session} | Sleep {random_sleep:,}s") 576 | await asyncio.sleep(delay=random_sleep) 577 | 578 | return False 579 | 580 | async def run(self): 581 | while True: 582 | try: 583 | if not conf.fetch('hamsterKombat'): 584 | logger.info(f"{self.session} | hamster clicker is offline!") 585 | await asyncio.sleep(delay=60) 586 | continue 587 | 588 | if not self.token: 589 | logger.info(f"{self.session} | invalid token!") 590 | await asyncio.sleep(delay=600) 591 | self.getSetHeader() 592 | continue 593 | 594 | 595 | logger.info(f"{self.session} | account fully loaded! | start working...") 596 | taps = randint(a=settings.RANDOM_TAPS_COUNT[0], b=settings.RANDOM_TAPS_COUNT[1]) 597 | 598 | profile_data = await self.get_profile_data() 599 | 600 | if profile_data: 601 | available_energy = profile_data.get('availableTaps', 0) 602 | else: 603 | available_energy = 100 604 | 605 | player_data = await self.send_taps( 606 | available_energy = available_energy, 607 | taps = taps 608 | ) 609 | 610 | if not player_data: 611 | continue 612 | 613 | available_energy = player_data.get('availableTaps', 0) 614 | new_balance = int(player_data.get('balanceCoins', 0)) 615 | calc_taps = abs(new_balance - balance) if 'balance' in locals() else 0 616 | balance = new_balance 617 | total = int(player_data.get('totalCoins', 0)) 618 | earn_on_hour = int(player_data.get('earnPassivePerHour', 0)) 619 | 620 | logger.success( 621 | f"{self.session} | Successful tapped! | " 622 | f"Balance: {balance:,} (+{calc_taps:,}) | Total: {total:,}" 623 | ) 624 | 625 | hamster.insertOrUpdate(user_id = self.me['user_id'], balance=balance, profit=earn_on_hour) 626 | await self.auto_upgrade(balance, earn_on_hour) 627 | is_boost = await self.auto_apply_boosts(available_energy) 628 | 629 | if is_boost: 630 | continue 631 | 632 | except Exception as error: 633 | print(traceback.format_exc()) 634 | logger.error(f"{self.session} | Unknown error: {error}") 635 | 636 | else: 637 | sleep_clicks = randint(a=settings.SLEEP_BETWEEN_TAP[0], b=settings.SLEEP_BETWEEN_TAP[1]) 638 | logger.info(f"{self.session} | sleep clicks {sleep_clicks}s") 639 | await asyncio.sleep(delay=sleep_clicks) -------------------------------------------------------------------------------- /games/hamster/fingerprint.py: -------------------------------------------------------------------------------- 1 | from src.utils.scripts import generate_random_visitor_id 2 | 3 | FINGERPRINT = { 4 | 'fingerprint': { 5 | 'version': '4.2.1', 6 | 'visitorId': generate_random_visitor_id(), 7 | 'components': { 8 | 'fonts': { 9 | 'value': [ 10 | 'Calibri', 11 | 'Franklin Gothic', 12 | 'MS UI Gothic', 13 | 'Marlett', 14 | 'Segoe UI Light', 15 | ], 16 | 'duration': 42, 17 | }, 18 | 'domBlockers': { 19 | 'duration': 0, 20 | }, 21 | 'fontPreferences': { 22 | 'value': { 23 | 'default': 149.3125, 24 | 'apple': 149.3125, 25 | 'serif': 149.3125, 26 | 'sans': 144.03125, 27 | 'mono': 121.53125, 28 | 'min': 9.34375, 29 | 'system': 147.875, 30 | }, 31 | 'duration': 20, 32 | }, 33 | 'audio': { 34 | 'value': 0.0000832115, 35 | 'duration': 31, 36 | }, 37 | 'screenFrame': { 38 | 'value': [ 39 | 0, 40 | 0, 41 | 0, 42 | 0, 43 | ], 44 | 'duration': 0, 45 | }, 46 | 'canvas': { 47 | 'value': { 48 | 'winding': True, 49 | 'geometry': 'data:image/png;base64,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', 50 | 'text': 'data:image/png;base64,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', 51 | }, 52 | 'duration': 41, 53 | }, 54 | 'osCpu': { 55 | 'duration': 0, 56 | }, 57 | 'languages': { 58 | 'value': [ 59 | [ 60 | "ru-RU", 61 | ], 62 | ], 63 | 'duration': 0, 64 | }, 65 | 'colorDepth': { 66 | 'value': 24, 67 | 'duration': 0, 68 | }, 69 | 'deviceMemory': { 70 | 'value': 8, 71 | 'duration': 0, 72 | }, 73 | 'screenResolution': { 74 | 'value': [ 75 | 800, 76 | 440, 77 | ], 78 | 'duration': 0, 79 | }, 80 | 'hardwareConcurrency': { 81 | 'value': 8, 82 | 'duration': 0, 83 | }, 84 | 'timezone': { 85 | 'value': "Europe/Moscow", 86 | 'duration': 2, 87 | }, 88 | 'sessionStorage': { 89 | 'value': True, 90 | 'duration': 0, 91 | }, 92 | 'localStorage': { 93 | 'value': True, 94 | 'duration': 0, 95 | }, 96 | 'indexedDB': { 97 | 'value': True, 98 | 'duration': 0, 99 | }, 100 | 'openDatabase': { 101 | 'value': False, 102 | 'duration': 0, 103 | }, 104 | 'cpuClass': { 105 | 'duration': 0, 106 | }, 107 | 'platform': { 108 | 'value': 'Android', 109 | 'duration': 0, 110 | }, 111 | 'plugins': { 112 | 'value': [], 113 | 'duration': 0, 114 | }, 115 | 'touchSupport': { 116 | 'value': { 117 | 'maxTouchPoints': 1, 118 | 'touchEvent': True, 119 | 'touchStart': True, 120 | }, 121 | 'duration': 1, 122 | }, 123 | 'vendor': { 124 | 'value': 'Google Inc.', 125 | 'duration': 0, 126 | }, 127 | 'vendorFlavors': { 128 | 'value': [ 129 | 'chrome', 130 | ], 131 | 'duration': 0, 132 | }, 133 | 'cookiesEnabled': { 134 | 'value': True, 135 | 'duration': 0, 136 | }, 137 | 'colorGamut': { 138 | 'value': 'srgb', 139 | 'duration': 0, 140 | }, 141 | 'invertedColors': { 142 | 'duration': 0, 143 | }, 144 | 'forcedColors': { 145 | 'value': False, 146 | 'duration': 0, 147 | }, 148 | 'monochrome': { 149 | 'value': 0, 150 | 'duration': 0, 151 | }, 152 | 'contrast': { 153 | 'value': 0, 154 | 'duration': 0, 155 | }, 156 | 'reducedMotion': { 157 | 'value': False, 158 | 'duration': 0, 159 | }, 160 | 'reducedTransparency': { 161 | 'value': False, 162 | 'duration': 0, 163 | }, 164 | 'hdr': { 165 | 'value': False, 166 | 'duration': 0, 167 | }, 168 | 'math': { 169 | 'value': { 170 | 'acos': 1.4473588658278522, 171 | 'acosh': 709.889355822726, 172 | 'acoshPf': 355.291251501643, 173 | 'asin': 0.12343746096704435, 174 | 'asinh': 0.881373587019543, 175 | 'asinhPf': 0.8813735870195429, 176 | 'atanh': 0.5493061443340548, 177 | 'atanhPf': 0.5493061443340548, 178 | 'atan': 0.4636476090008061, 179 | 'sin': 0.8178819121159085, 180 | 'sinh': 1.1752011936438014, 181 | 'sinhPf': 2.534342107873324, 182 | 'cos': -0.8390715290095377, 183 | 'cosh': 1.5430806348152437, 184 | 'coshPf': 1.5430806348152437, 185 | 'tan': -1.4214488238747245, 186 | 'tanh': 0.7615941559557649, 187 | 'tanhPf': 0.7615941559557649, 188 | 'exp': 2.718281828459045, 189 | 'expm1': 1.718281828459045, 190 | 'expm1Pf': 1.718281828459045, 191 | 'log1p': 2.3978952727983707, 192 | 'log1pPf': 2.3978952727983707, 193 | 'powPI': 1.9275814160560204e-50, 194 | }, 195 | 'duration': 0, 196 | }, 197 | 'pdfViewerEnabled': { 198 | 'value': False, 199 | 'duration': 0, 200 | }, 201 | 'architecture': { 202 | 'value': 255, 203 | 'duration': 0, 204 | }, 205 | 'applePay': { 206 | 'value': -1, 207 | 'duration': 0, 208 | }, 209 | 'privateClickMeasurement': { 210 | 'duration': 0, 211 | }, 212 | 'webGlExtensions': { 213 | 'value': { 214 | 'contextAttributes': [ 215 | 'alpha=true', 216 | 'antialias=true', 217 | 'depth=true', 218 | 'desynchronized=false', 219 | 'failIfMajorPerformanceCaveat=false', 220 | 'powerPreference=default', 221 | 'premultipliedAlpha=true', 222 | 'preserveDrawingBuffer=false', 223 | 'stencil=false', 224 | 'xrCompatible=false', 225 | ], 226 | 'parameters': [ 227 | 'ACTIVE_ATTRIBUTES=35721', 228 | 'ACTIVE_TEXTURE=34016=33984', 229 | 'ACTIVE_UNIFORMS=35718', 230 | 'ALIASED_LINE_WIDTH_RANGE=33902=1,1', 231 | 'ALIASED_POINT_SIZE_RANGE=33901=1,1024', 232 | 'ALPHA=6406', 233 | 'ALPHA_BITS=3413=8', 234 | 'ALWAYS=519', 235 | 'ARRAY_BUFFER=34962', 236 | 'ARRAY_BUFFER_BINDING=34964', 237 | 'ATTACHED_SHADERS=35717', 238 | 'BACK=1029', 239 | 'BLEND=3042=false', 240 | 'BLEND_COLOR=32773=0,0,0,0', 241 | 'BLEND_DST_ALPHA=32970=0', 242 | 'BLEND_DST_RGB=32968=0', 243 | 'BLEND_EQUATION=32777=32774', 244 | 'BLEND_EQUATION_ALPHA=34877=32774', 245 | 'BLEND_EQUATION_RGB=32777=32774', 246 | 'BLEND_SRC_ALPHA=32971=1', 247 | 'BLEND_SRC_RGB=32969=1', 248 | 'BLUE_BITS=3412=8', 249 | 'BOOL=35670', 250 | 'BOOL_VEC2=35671', 251 | 'BOOL_VEC3=35672', 252 | 'BOOL_VEC4=35673', 253 | 'BROWSER_DEFAULT_WEBGL=37444', 254 | 'BUFFER_SIZE=34660', 255 | 'BUFFER_USAGE=34661', 256 | 'BYTE=5120', 257 | 'CCW=2305', 258 | 'CLAMP_TO_EDGE=33071', 259 | 'COLOR_ATTACHMENT0=36064', 260 | 'COLOR_BUFFER_BIT=16384', 261 | 'COLOR_CLEAR_VALUE=3106=0,0,0,0', 262 | 'COLOR_WRITEMASK=3107=true,true,true,true', 263 | 'COMPILE_STATUS=35713', 264 | 'COMPRESSED_TEXTURE_FORMATS=34467=', 265 | 'CONSTANT_ALPHA=32771', 266 | 'CONSTANT_COLOR=32769', 267 | 'CONTEXT_LOST_WEBGL=37442', 268 | 'CULL_FACE=2884=false', 269 | 'CULL_FACE_MODE=2885=1029', 270 | 'CURRENT_PROGRAM=35725', 271 | 'CURRENT_VERTEX_ATTRIB=34342', 272 | 'CW=2304', 273 | 'DECR=7683', 274 | 'DECR_WRAP=34056', 275 | 'DELETE_STATUS=35712', 276 | 'DEPTH_ATTACHMENT=36096', 277 | 'DEPTH_BITS=3414=24', 278 | 'DEPTH_BUFFER_BIT=256', 279 | 'DEPTH_CLEAR_VALUE=2931=1', 280 | 'DEPTH_COMPONENT16=33189', 281 | 'DEPTH_COMPONENT=6402', 282 | 'DEPTH_FUNC=2932=513', 283 | 'DEPTH_RANGE=2928=0,1', 284 | 'DEPTH_STENCIL=34041', 285 | 'DEPTH_STENCIL_ATTACHMENT=33306', 286 | 'DEPTH_TEST=2929=false', 287 | 'DEPTH_WRITEMASK=2930=true', 288 | 'DITHER=3024=true', 289 | 'DONT_CARE=4352', 290 | 'DST_ALPHA=772', 291 | 'DST_COLOR=774', 292 | 'DYNAMIC_DRAW=35048', 293 | 'ELEMENT_ARRAY_BUFFER=34963', 294 | 'ELEMENT_ARRAY_BUFFER_BINDING=34965', 295 | 'EQUAL=514', 296 | 'FASTEST=4353', 297 | 'FLOAT=5126', 298 | 'FLOAT_MAT2=35674', 299 | 'FLOAT_MAT3=35675', 300 | 'FLOAT_MAT4=35676', 301 | 'FLOAT_VEC2=35664', 302 | 'FLOAT_VEC3=35665', 303 | 'FLOAT_VEC4=35666', 304 | 'FRAGMENT_SHADER=35632', 305 | 'FRAMEBUFFER=36160', 306 | 'FRAMEBUFFER_ATTACHMENT_OBJECT_NAME=36049', 307 | 'FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE=36048', 308 | 'FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE=36051', 309 | 'FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL=36050', 310 | 'FRAMEBUFFER_BINDING=36006', 311 | 'FRAMEBUFFER_COMPLETE=36053', 312 | 'FRAMEBUFFER_INCOMPLETE_ATTACHMENT=36054', 313 | 'FRAMEBUFFER_INCOMPLETE_DIMENSIONS=36057', 314 | 'FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT=36055', 315 | 'FRAMEBUFFER_UNSUPPORTED=36061', 316 | 'FRONT=1028', 317 | 'FRONT_AND_BACK=1032', 318 | 'FRONT_FACE=2886=2305', 319 | 'FUNC_ADD=32774', 320 | 'FUNC_REVERSE_SUBTRACT=32779', 321 | 'FUNC_SUBTRACT=32778', 322 | 'GENERATE_MIPMAP_HINT=33170=4352', 323 | 'GEQUAL=518', 324 | 'GREATER=516', 325 | 'GREEN_BITS=3411=8', 326 | 'HIGH_FLOAT=36338', 327 | 'HIGH_INT=36341', 328 | 'IMPLEMENTATION_COLOR_READ_FORMAT=35739=6408', 329 | 'IMPLEMENTATION_COLOR_READ_TYPE=35738=5121', 330 | 'INCR=7682', 331 | 'INCR_WRAP=34055', 332 | 'INT=5124', 333 | 'INT_VEC2=35667', 334 | 'INT_VEC3=35668', 335 | 'INT_VEC4=35669', 336 | 'INVALID_ENUM=1280', 337 | 'INVALID_FRAMEBUFFER_OPERATION=1286', 338 | 'INVALID_OPERATION=1282', 339 | 'INVALID_VALUE=1281', 340 | 'INVERT=5386', 341 | 'KEEP=7680', 342 | 'LEQUAL=515', 343 | 'LESS=513', 344 | 'LINEAR=9729', 345 | 'LINEAR_MIPMAP_LINEAR=9987', 346 | 'LINEAR_MIPMAP_NEAREST=9985', 347 | 'LINES=1', 348 | 'LINE_LOOP=2', 349 | 'LINE_STRIP=3', 350 | 'LINE_WIDTH=2849=1', 351 | 'LINK_STATUS=35714', 352 | 'LOW_FLOAT=36336', 353 | 'LOW_INT=36339', 354 | 'LUMINANCE=6409', 355 | 'LUMINANCE_ALPHA=6410', 356 | 'MAX_COMBINED_TEXTURE_IMAGE_UNITS=35661=32', 357 | 'MAX_CUBE_MAP_TEXTURE_SIZE=34076=16384', 358 | 'MAX_FRAGMENT_UNIFORM_VECTORS=36349=1024', 359 | 'MAX_RENDERBUFFER_SIZE=34024=16384', 360 | 'MAX_TEXTURE_IMAGE_UNITS=34930=16', 361 | 'MAX_TEXTURE_SIZE=3379=16384', 362 | 'MAX_VARYING_VECTORS=36348=30', 363 | 'MAX_VERTEX_ATTRIBS=34921=16', 364 | 'MAX_VERTEX_TEXTURE_IMAGE_UNITS=35660=16', 365 | 'MAX_VERTEX_UNIFORM_VECTORS=36347=4095', 366 | 'MAX_VIEWPORT_DIMS=3386=32767,32767', 367 | 'MEDIUM_FLOAT=36337', 368 | 'MEDIUM_INT=36340', 369 | 'MIRRORED_REPEAT=33648', 370 | 'NEAREST=9728', 371 | 'NEAREST_MIPMAP_LINEAR=9986', 372 | 'NEAREST_MIPMAP_NEAREST=9984', 373 | 'NEVER=512', 374 | 'NICEST=4354', 375 | 'NONE=0', 376 | 'NOTEQUAL=517', 377 | 'NO_ERROR=0', 378 | 'ONE=1', 379 | 'ONE_MINUS_CONSTANT_ALPHA=32772', 380 | 'ONE_MINUS_CONSTANT_COLOR=32770', 381 | 'ONE_MINUS_DST_ALPHA=773', 382 | 'ONE_MINUS_DST_COLOR=775', 383 | 'ONE_MINUS_SRC_ALPHA=771', 384 | 'ONE_MINUS_SRC_COLOR=769', 385 | 'OUT_OF_MEMORY=1285', 386 | 'PACK_ALIGNMENT=3333=4', 387 | 'POINTS=0', 388 | 'POLYGON_OFFSET_FACTOR=32824=0', 389 | 'POLYGON_OFFSET_FILL=32823=false', 390 | 'POLYGON_OFFSET_UNITS=10752=0', 391 | 'RED_BITS=3410=8', 392 | 'RENDERBUFFER=36161', 393 | 'RENDERBUFFER_ALPHA_SIZE=36179', 394 | 'RENDERBUFFER_BINDING=36007', 395 | 'RENDERBUFFER_BLUE_SIZE=36178', 396 | 'RENDERBUFFER_DEPTH_SIZE=36180', 397 | 'RENDERBUFFER_GREEN_SIZE=36177', 398 | 'RENDERBUFFER_HEIGHT=36163', 399 | 'RENDERBUFFER_INTERNAL_FORMAT=36164', 400 | 'RENDERBUFFER_RED_SIZE=36176', 401 | 'RENDERBUFFER_STENCIL_SIZE=36181', 402 | 'RENDERBUFFER_WIDTH=36162', 403 | 'RENDERER=7937=WebKit WebGL', 404 | 'REPEAT=10497', 405 | 'REPLACE=7681', 406 | 'RGB565=36194', 407 | 'RGB5_A1=32855', 408 | 'RGB8=32849', 409 | 'RGB=6407', 410 | 'RGBA4=32854', 411 | 'RGBA8=32856', 412 | 'RGBA=6408', 413 | 'SAMPLER_2D=35678', 414 | 'SAMPLER_CUBE=35680', 415 | 'SAMPLES=32937=4', 416 | 'SAMPLE_ALPHA_TO_COVERAGE=32926', 417 | 'SAMPLE_BUFFERS=32936=1', 418 | 'SAMPLE_COVERAGE=32928', 419 | 'SAMPLE_COVERAGE_INVERT=32939=false', 420 | 'SAMPLE_COVERAGE_VALUE=32938=1', 421 | 'SCISSOR_BOX=3088=0,0,300,150', 422 | 'SCISSOR_TEST=3089=false', 423 | 'SHADER_TYPE=35663', 424 | 'SHADING_LANGUAGE_VERSION=35724=WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)', 425 | 'SHORT=5122', 426 | 'SRC_ALPHA=770', 427 | 'SRC_ALPHA_SATURATE=776', 428 | 'SRC_COLOR=768', 429 | 'STATIC_DRAW=35044', 430 | 'STENCIL_ATTACHMENT=36128', 431 | 'STENCIL_BACK_FAIL=34817=7680', 432 | 'STENCIL_BACK_FUNC=34816=519', 433 | 'STENCIL_BACK_PASS_DEPTH_FAIL=34818=7680', 434 | 'STENCIL_BACK_PASS_DEPTH_PASS=34819=7680', 435 | 'STENCIL_BACK_REF=36003=0', 436 | 'STENCIL_BACK_VALUE_MASK=36004=2147483647', 437 | 'STENCIL_BACK_WRITEMASK=36005=2147483647', 438 | 'STENCIL_BITS=3415=0', 439 | 'STENCIL_BUFFER_BIT=1024', 440 | 'STENCIL_CLEAR_VALUE=2961=0', 441 | 'STENCIL_FAIL=2964=7680', 442 | 'STENCIL_FUNC=2962=519', 443 | 'STENCIL_INDEX8=36168', 444 | 'STENCIL_PASS_DEPTH_FAIL=2965=7680', 445 | 'STENCIL_PASS_DEPTH_PASS=2966=7680', 446 | 'STENCIL_REF=2967=0', 447 | 'STENCIL_TEST=2960=false', 448 | 'STENCIL_VALUE_MASK=2963=2147483647', 449 | 'STENCIL_WRITEMASK=2968=2147483647', 450 | 'STREAM_DRAW=35040', 451 | 'SUBPIXEL_BITS=3408=4', 452 | 'TEXTURE0=33984', 453 | 'TEXTURE10=33994', 454 | 'TEXTURE11=33995', 455 | 'TEXTURE12=33996', 456 | 'TEXTURE13=33997', 457 | 'TEXTURE14=33998', 458 | 'TEXTURE15=33999', 459 | 'TEXTURE16=34000', 460 | 'TEXTURE17=34001', 461 | 'TEXTURE18=34002', 462 | 'TEXTURE19=34003', 463 | 'TEXTURE1=33985', 464 | 'TEXTURE20=34004', 465 | 'TEXTURE21=34005', 466 | 'TEXTURE22=34006', 467 | 'TEXTURE23=34007', 468 | 'TEXTURE24=34008', 469 | 'TEXTURE25=34009', 470 | 'TEXTURE26=34010', 471 | 'TEXTURE27=34011', 472 | 'TEXTURE28=34012', 473 | 'TEXTURE29=34013', 474 | 'TEXTURE2=33986', 475 | 'TEXTURE30=34014', 476 | 'TEXTURE31=34015', 477 | 'TEXTURE3=33987', 478 | 'TEXTURE4=33988', 479 | 'TEXTURE5=33989', 480 | 'TEXTURE6=33990', 481 | 'TEXTURE7=33991', 482 | 'TEXTURE8=33992', 483 | 'TEXTURE9=33993', 484 | 'TEXTURE=5890', 485 | 'TEXTURE_2D=3553', 486 | 'TEXTURE_BINDING_2D=32873', 487 | 'TEXTURE_BINDING_CUBE_MAP=34068', 488 | 'TEXTURE_CUBE_MAP=34067', 489 | 'TEXTURE_CUBE_MAP_NEGATIVE_X=34070', 490 | 'TEXTURE_CUBE_MAP_NEGATIVE_Y=34072', 491 | 'TEXTURE_CUBE_MAP_NEGATIVE_Z=34074', 492 | 'TEXTURE_CUBE_MAP_POSITIVE_X=34069', 493 | 'TEXTURE_CUBE_MAP_POSITIVE_Y=34071', 494 | 'TEXTURE_CUBE_MAP_POSITIVE_Z=34073', 495 | 'TEXTURE_MAG_FILTER=10240', 496 | 'TEXTURE_MIN_FILTER=10241', 497 | 'TEXTURE_WRAP_S=10242', 498 | 'TEXTURE_WRAP_T=10243', 499 | 'TRIANGLES=4', 500 | 'TRIANGLE_FAN=6', 501 | 'TRIANGLE_STRIP=5', 502 | 'UNPACK_ALIGNMENT=3317=4', 503 | 'UNPACK_COLORSPACE_CONVERSION_WEBGL=37443=37444', 504 | 'UNPACK_FLIP_Y_WEBGL=37440=false', 505 | 'UNPACK_PREMULTIPLY_ALPHA_WEBGL=37441=false', 506 | 'UNSIGNED_BYTE=5121', 507 | 'UNSIGNED_INT=5125', 508 | 'UNSIGNED_SHORT=5123', 509 | 'UNSIGNED_SHORT_4_4_4_4=32819', 510 | 'UNSIGNED_SHORT_5_5_5_1=32820', 511 | 'UNSIGNED_SHORT_5_6_5=33635', 512 | 'VALIDATE_STATUS=35715', 513 | 'VENDOR=7936=WebKit', 514 | 'VERSION=7938=WebGL 1.0 (OpenGL ES 2.0 Chromium)', 515 | 'VERTEX_ATTRIB_ARRAY_BUFFER_BINDING=34975', 516 | 'VERTEX_ATTRIB_ARRAY_ENABLED=34338', 517 | 'VERTEX_ATTRIB_ARRAY_NORMALIZED=34922', 518 | 'VERTEX_ATTRIB_ARRAY_POINTER=34373', 519 | 'VERTEX_ATTRIB_ARRAY_SIZE=34339', 520 | 'VERTEX_ATTRIB_ARRAY_STRIDE=34340', 521 | 'VERTEX_ATTRIB_ARRAY_TYPE=34341', 522 | 'VERTEX_SHADER=35633', 523 | 'VIEWPORT=2978=0,0,300,150', 524 | 'ZERO=0', 525 | ], 526 | 'shaderPrecisions': [ 527 | 'FRAGMENT_SHADER.LOW_FLOAT=127,127,23', 528 | 'FRAGMENT_SHADER.MEDIUM_FLOAT=127,127,23', 529 | 'FRAGMENT_SHADER.HIGH_FLOAT=127,127,23', 530 | 'FRAGMENT_SHADER.LOW_INT=31,30,0', 531 | 'FRAGMENT_SHADER.MEDIUM_INT=31,30,0', 532 | 'FRAGMENT_SHADER.HIGH_INT=31,30,0', 533 | 'VERTEX_SHADER.LOW_FLOAT=127,127,23', 534 | 'VERTEX_SHADER.MEDIUM_FLOAT=127,127,23', 535 | 'VERTEX_SHADER.HIGH_FLOAT=127,127,23', 536 | 'VERTEX_SHADER.LOW_INT=31,30,0', 537 | 'VERTEX_SHADER.MEDIUM_INT=31,30,0', 538 | 'VERTEX_SHADER.HIGH_INT=31,30,0', 539 | ], 540 | 'extensions': [ 541 | 'ANGLE_instanced_arrays', 542 | 'EXT_blend_minmax', 543 | 'EXT_clip_control', 544 | 'EXT_color_buffer_half_float', 545 | 'EXT_depth_clamp', 546 | 'EXT_disjoint_timer_query', 547 | 'EXT_float_blend', 548 | 'EXT_frag_depth', 549 | 'EXT_polygon_offset_clamp', 550 | 'EXT_shader_texture_lod', 551 | 'EXT_texture_compression_bptc', 552 | 'EXT_texture_compression_rgtc', 553 | 'EXT_texture_filter_anisotropic', 554 | 'EXT_texture_mirror_clamp_to_edge', 555 | 'EXT_sRGB', 556 | 'KHR_parallel_shader_compile', 557 | 'OES_element_index_uint', 558 | 'OES_fbo_render_mipmap', 559 | 'OES_standard_derivatives', 560 | 'OES_texture_float', 561 | 'OES_texture_float_linear', 562 | 'OES_texture_half_float', 563 | 'OES_texture_half_float_linear', 564 | 'OES_vertex_array_object', 565 | 'WEBGL_blend_func_extended', 566 | 'WEBGL_color_buffer_float', 567 | 'WEBGL_compressed_texture_s3tc', 568 | 'WEBGL_compressed_texture_s3tc_srgb', 569 | 'WEBGL_debug_renderer_info', 570 | 'WEBGL_debug_shaders', 571 | 'WEBGL_depth_texture', 572 | 'WEBGL_draw_buffers', 573 | 'WEBGL_lose_context', 574 | 'WEBGL_multi_draw', 575 | 'WEBGL_polygon_mode', 576 | ], 577 | 'extensionParameters': [ 578 | 'CLIP_DEPTH_MODE_EXT=37725', 579 | 'CLIP_ORIGIN_EXT=37724', 580 | 'COLOR_ATTACHMENT0_WEBGL=36064', 581 | 'COLOR_ATTACHMENT10_WEBGL=36074', 582 | 'COLOR_ATTACHMENT11_WEBGL=36075', 583 | 'COLOR_ATTACHMENT12_WEBGL=36076', 584 | 'COLOR_ATTACHMENT13_WEBGL=36077', 585 | 'COLOR_ATTACHMENT14_WEBGL=36078', 586 | 'COLOR_ATTACHMENT15_WEBGL=36079', 587 | 'COLOR_ATTACHMENT1_WEBGL=36065', 588 | 'COLOR_ATTACHMENT2_WEBGL=36066', 589 | 'COLOR_ATTACHMENT3_WEBGL=36067', 590 | 'COLOR_ATTACHMENT4_WEBGL=36068', 591 | 'COLOR_ATTACHMENT5_WEBGL=36069', 592 | 'COLOR_ATTACHMENT6_WEBGL=36070', 593 | 'COLOR_ATTACHMENT7_WEBGL=36071', 594 | 'COLOR_ATTACHMENT8_WEBGL=36072', 595 | 'COLOR_ATTACHMENT9_WEBGL=36073', 596 | 'COMPLETION_STATUS_KHR=37297', 597 | 'COMPRESSED_RED_GREEN_RGTC2_EXT=36285', 598 | 'COMPRESSED_RED_RGTC1_EXT=36283', 599 | 'COMPRESSED_RGBA_BPTC_UNORM_EXT=36492', 600 | 'COMPRESSED_RGBA_S3TC_DXT1_EXT=33777', 601 | 'COMPRESSED_RGBA_S3TC_DXT3_EXT=33778', 602 | 'COMPRESSED_RGBA_S3TC_DXT5_EXT=33779', 603 | 'COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT=36494', 604 | 'COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT=36495', 605 | 'COMPRESSED_RGB_S3TC_DXT1_EXT=33776', 606 | 'COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT=36286', 607 | 'COMPRESSED_SIGNED_RED_RGTC1_EXT=36284', 608 | 'COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT=36493', 609 | 'COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT=35917', 610 | 'COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT=35918', 611 | 'COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT=35919', 612 | 'COMPRESSED_SRGB_S3TC_DXT1_EXT=35916', 613 | 'CURRENT_QUERY_EXT=34917', 614 | 'DEPTH_CLAMP_EXT=34383', 615 | 'DRAW_BUFFER0_WEBGL=34853=1029', 616 | 'DRAW_BUFFER10_WEBGL=34863', 617 | 'DRAW_BUFFER11_WEBGL=34864', 618 | 'DRAW_BUFFER12_WEBGL=34865', 619 | 'DRAW_BUFFER13_WEBGL=34866', 620 | 'DRAW_BUFFER14_WEBGL=34867', 621 | 'DRAW_BUFFER15_WEBGL=34868', 622 | 'DRAW_BUFFER1_WEBGL=34854=1029', 623 | 'DRAW_BUFFER2_WEBGL=34855', 624 | 'DRAW_BUFFER3_WEBGL=34856', 625 | 'DRAW_BUFFER4_WEBGL=34857', 626 | 'DRAW_BUFFER5_WEBGL=34858', 627 | 'DRAW_BUFFER6_WEBGL=34859', 628 | 'DRAW_BUFFER7_WEBGL=34860', 629 | 'DRAW_BUFFER8_WEBGL=34861', 630 | 'DRAW_BUFFER9_WEBGL=34862', 631 | 'FRAGMENT_SHADER_DERIVATIVE_HINT_OES=35723=4352', 632 | 'FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT=33296', 633 | 'FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT=33297', 634 | 'FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT=33297', 635 | 'GPU_DISJOINT_EXT=36795=false', 636 | 'HALF_FLOAT_OES=36193', 637 | 'LOWER_LEFT_EXT=36001', 638 | 'MAX_COLOR_ATTACHMENTS_WEBGL=36063=8', 639 | 'MAX_DRAW_BUFFERS_WEBGL=34852=8', 640 | 'MAX_DUAL_SOURCE_DRAW_BUFFERS_WEBGL=35068', 641 | 'MAX_EXT=32776', 642 | 'MAX_TEXTURE_MAX_ANISOTROPY_EXT=34047=16', 643 | 'MIN_EXT=32775', 644 | 'MIRROR_CLAMP_TO_EDGE_EXT=34627', 645 | 'NEGATIVE_ONE_TO_ONE_EXT=37726', 646 | 'ONE_MINUS_SRC1_ALPHA_WEBGL=35067', 647 | 'ONE_MINUS_SRC1_COLOR_WEBGL=35066', 648 | 'POLYGON_OFFSET_CLAMP_EXT=36379', 649 | 'QUERY_COUNTER_BITS_EXT=34916', 650 | 'QUERY_RESULT_AVAILABLE_EXT=34919', 651 | 'QUERY_RESULT_EXT=34918', 652 | 'RGB16F_EXT=34843', 653 | 'RGBA16F_EXT=34842', 654 | 'RGBA32F_EXT=34836', 655 | 'SRC1_ALPHA_WEBGL=34185', 656 | 'SRC1_COLOR_WEBGL=35065', 657 | 'SRGB8_ALPHA8_EXT=35907', 658 | 'SRGB_ALPHA_EXT=35906', 659 | 'SRGB_EXT=35904', 660 | 'TEXTURE_MAX_ANISOTROPY_EXT=34046', 661 | 'TIMESTAMP_EXT=36392=0', 662 | 'TIME_ELAPSED_EXT=35007', 663 | 'UNMASKED_RENDERER_WEBGL=37446', 664 | 'UNMASKED_VENDOR_WEBGL=37445', 665 | 'UNSIGNED_INT_24_8_WEBGL=34042', 666 | 'UNSIGNED_NORMALIZED_EXT=35863', 667 | 'UNSIGNED_NORMALIZED_EXT=35863', 668 | 'UPPER_LEFT_EXT=36002', 669 | 'VERTEX_ARRAY_BINDING_OES=34229=null', 670 | 'VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE=35070', 671 | 'ZERO_TO_ONE_EXT=37727', 672 | ], 673 | }, 674 | 'duration': 21, 675 | }, 676 | }, 677 | } 678 | } 679 | --------------------------------------------------------------------------------