├── 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 | 
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 |
--------------------------------------------------------------------------------