yasir68 commited on
Commit
09b15bb
·
verified ·
1 Parent(s): 39dc6e1

Upload folder using huggingface_hub (#1)

Browse files

- Upload folder using huggingface_hub (d778baa31b7ac660bc26940baf241458eab0df30)

.env-example ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ API_ID=
2
+ API_HASH=
3
+
4
+ GAMES_ENABLED=
5
+ GAME_POINTS=
6
+ SLEEP_BETWEEN_GAME=
7
+
8
+ SLEEP_BETWEEN_START=
9
+ ERRORS_BEFORE_STOP=
10
+ USE_PROXY_FROM_FILE=
.gitignore ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ pip-wheel-metadata/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ pytest_debug.log
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # PyBuilder
59
+ target/
60
+
61
+ # IPython
62
+ profile_default/
63
+ ipython_config.py
64
+
65
+ # pyenv
66
+ .python-version
67
+
68
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
69
+ __pypackages__/
70
+
71
+ # Environments
72
+ .env
73
+ .venv
74
+ env/
75
+ venv/
76
+ ENV/
77
+ env.bak/
78
+ venv.bak/
79
+
80
+ # mkdocs documentation
81
+ /site
82
+
83
+ # Pyre type checker
84
+ .pyre/
85
+
86
+ # pytype static type analyzer
87
+ .pytype/
88
+
89
+ # Profiling data
90
+ .prof
91
+
92
+ # vscode
93
+ .vscode/
94
+
95
+ # Visual Studio Code - workspace settings
96
+ .vscode/*
97
+
98
+ # Editors and IDEs
99
+ .idea/
100
+ .project
101
+ .classpath
102
+ .cproject
103
+ *.launch
104
+ .settings/
105
+
106
+ # Other files
107
+ .DS_Store
108
+ Thumbs.db
109
+
110
+ # Virtual Studio Code, more commonly
111
+ .vs/
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt requirements.txt
6
+
7
+ RUN pip3 install --upgrade pip setuptools wheel
8
+ RUN pip3 install --no-warn-script-location --no-cache-dir -r requirements.txt
9
+
10
+ COPY . .
11
+
12
+ ENTRYPOINT ["python3", "main.py"]
13
+ CMD ["-a", "2"]
INSTALL.bat ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @echo off
2
+ echo Creating virtual environment...
3
+ python -m venv venv
4
+ echo Activating virtual environment...
5
+ call venv\Scripts\activate
6
+ echo Installing dependencies...
7
+ pip install -r requirements.txt
8
+ echo Copying .env-example to .env...
9
+ copy .env-example .env
10
+ echo Please edit the .env file to add your API_ID and API_HASH.
11
+ pause
INSTALL.sh ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ install_python() {
4
+ echo "Select the Python version to install:"
5
+ echo "1) Python 3.10"
6
+ echo "2) Python 3.11"
7
+ echo "3) Python 3.12"
8
+ read -p "Enter the number of your choice: " choice
9
+
10
+ case $choice in
11
+ 1) version="3.10" ;;
12
+ 2) version="3.11" ;;
13
+ 3) version="3.12" ;;
14
+ *) echo "Invalid choice"; exit 1 ;;
15
+ esac
16
+
17
+ if command -v apt-get &> /dev/null; then
18
+ sudo apt-get update
19
+ sudo apt-get install -y python$version python$version-venv python$version-pip
20
+ elif command -v yum &> /dev/null; then
21
+ sudo yum install -y https://repo.ius.io/ius-release-el$(rpm -E %{rhel}).rpm
22
+ sudo yum install -y python$version python$version-venv python$version-pip
23
+ elif command -v dnf &> /dev/null; then
24
+ sudo dnf install -y python$version python$version-venv python$version-pip
25
+ else
26
+ echo "Package manager not supported. Please install Python manually."
27
+ exit 1
28
+ fi
29
+ }
30
+
31
+ if ! command -v python3 &> /dev/null; then
32
+ install_python
33
+ fi
34
+
35
+ echo "Creating virtual environment..."
36
+ python3 -m venv venv
37
+
38
+ echo "Activating virtual environment..."
39
+ source venv/bin/activate
40
+
41
+ echo "Installing dependencies..."
42
+ pip install -r requirements.txt
43
+
44
+ echo "Copying .env-example to .env..."
45
+ cp .env-example .env
46
+
47
+ echo "Please edit the .env file to add your API_ID and API_HASH."
48
+ read -p "Press any key to continue..."
README-RU.md ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Бот для [Blum](https://alexell.pro/cc/blum)
2
+
3
+ ![img1](.github/images/demo.png)
4
+
5
+ > 🇺🇸 README in english available [here](README.md)
6
+
7
+ ## Функционал
8
+ | Функция | Поддерживается |
9
+ |----------------------------------------------------------------|:---------------:|
10
+ | Многопоточность | ✅ |
11
+ | Привязка прокси к сессии | ✅ |
12
+ | Получение ежедневной награды | ✅ |
13
+ | Получение награды за друзей | ✅ |
14
+ | Получение награды за задания | ✅ |
15
+ | Автоматический фарминг | ✅ |
16
+ | Автоматические игры | ✅ |
17
+ | Docker | ✅ |
18
+
19
+ ## [Настройки](https://github.com/Alexell/BlumBot/blob/main/.env-example)
20
+ | Опция | Описание |
21
+ |-------------------------|----------------------------------------------------------------------------|
22
+ | **API_ID / API_HASH** | Данные платформы для запуска сессии Telegram (по умолчанию: Android) |
23
+ | **GAMES_ENABLED** | Игры включены (True / False) |
24
+ | **GAME_POINTS** | Рандомное число очков за игру (напр. [100,200], макс. 280) |
25
+ | **SLEEP_BETWEEN_GAME** | Рандомная задержка в секундах между играми (напр. [10,20]) |
26
+ | **SLEEP_BETWEEN_START** | Задержка перед запуском каждой сессии (напр. [20,360]) |
27
+ | **ERRORS_BEFORE_STOP** | Количество неудачных запросов, по достижению которых, бот остановится |
28
+ | **USE_PROXY_FROM_FILE** | Использовать-ли прокси из файла `proxies.txt` (True / False) |
29
+
30
+ **API_ID** и **API_HASH** вы можете получить после создания приложения на [my.telegram.org/apps](https://my.telegram.org/apps)
31
+
32
+ ## Быстрый старт
33
+ ### Windows
34
+ 1. Убедитесь, что у вас установлен **Python 3.10** или более новая версия.
35
+ 2. Используйте `INSTALL.bat` для установки, затем укажите ваши API_ID и API_HASH в .env
36
+ 3. Используйте `START.bat` для запуска бота (или в консоли: `python main.py`)
37
+
38
+ ### Linux
39
+ 1. Клонируйте репозиторий: `git clone https://github.com/Alexell/BlumBot.git && cd BlumBot`
40
+ 2. Выполните установку: `chmod +x INSTALL.sh START.sh && ./INSTALL.sh`, затем укажите ваши API_ID и API_HASH в .env
41
+ 3. Используйте `./START.sh` для запуска бота (или в консоли: `python3 main.py`)
42
+
43
+ ## Запуск в Docker
44
+ ```
45
+ $ git clone https://github.com/Alexell/BlumBot.git
46
+ $ cd BlumBot
47
+ $ cp .env-example .env
48
+ $ nano .env # укажите ваши API_ID и API_HASH, остальное можно оставить по умолчанию
49
+ ```
50
+ ### Docker Compose (рекомендуется)
51
+ ```
52
+ $ docker-compose run bot -a 1 # первый запуск для авторизации (переопределяем аргументы)
53
+ $ docker-compose start # запуск в фоновом режиме (аргументы по умолчанию: -a 2)
54
+ ```
55
+ ### Docker
56
+ ```
57
+ $ docker build -t blum_bot .
58
+ $ docker run --name BlumBot -v .:/app -it blum_bot -a 1 # первый запуск для авторизации
59
+ $ docker rm BlumBot # удаляем контейнер для пересоздания с аргументами по умолчанию
60
+ $ docker run -d --restart unless-stopped --name BlumBot -v .:/app blum_bot # запуск в фоновом режиме (аргументы по умолчанию: -a 2)
61
+ ```
62
+
63
+ ## Ручная установка
64
+ Вы можете скачать [**Репозиторий**](https://github.com/Alexell/BlumBot) клонированием на вашу систему и установкой необходимых зависимостей:
65
+ ```
66
+ $ git clone https://github.com/Alexell/BlumBot.git
67
+ $ cd BlumBot
68
+
69
+ # Linux
70
+ $ python3 -m venv venv
71
+ $ source venv/bin/activate
72
+ $ pip3 install -r requirements.txt
73
+ $ cp .env-example .env
74
+ $ nano .env # укажите ваши API_ID и API_HASH, остальное мож��о оставить по умолчанию
75
+ $ python3 main.py
76
+
77
+ # Windows (сначала установите Python 3.10 или более новую версию)
78
+ > python -m venv venv
79
+ > venv\Scripts\activate
80
+ > pip install -r requirements.txt
81
+ > copy .env-example .env
82
+ > # укажите ваши API_ID и API_HASH, остальное можно оставить по умолчанию
83
+ > python main.py
84
+ ```
85
+
86
+ Также для быстрого запуска вы можете использовать аргументы:
87
+ ```
88
+ $ python3 main.py --action (1/2)
89
+ # или
90
+ $ python3 main.py -a (1/2)
91
+
92
+ # 1 - создать сессию
93
+ # 2 - запустить бот
94
+ ```
95
+
96
+ ## Запуск бота в фоновом режиме (Linux)
97
+ ```
98
+ $ cd BlumBot
99
+
100
+ # с логированием
101
+ $ setsid venv/bin/python3 main.py --action 2 >> app.log 2>&1 &
102
+
103
+ # без логирования
104
+ $ setsid venv/bin/python3 main.py --action 2 > /dev/null 2>&1 &
105
+
106
+ # Теперь вы можете закрыть консоль и бот продолжит свою работу.
107
+ ```
108
+
109
+ ### Найти процесс бота
110
+ ```
111
+ $ ps aux | grep "python3 main.py" | grep -v grep
112
+ ```
START.bat ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ @echo off
2
+ echo Activating virtual environment...
3
+ call venv\Scripts\activate
4
+ echo Starting the bot...
5
+ python main.py
6
+ pause
START.sh ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ echo "Activating virtual environment..."
4
+ source venv/bin/activate
5
+
6
+ echo "Starting the bot..."
7
+ python main.py
8
+
9
+ echo "Press any key to continue..."
10
+ read -n 1 -s
bot/core/bot.py ADDED
@@ -0,0 +1,431 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio, aiohttp, random, json
2
+ from time import time
3
+ from urllib.parse import unquote
4
+ from typing import Any, Dict
5
+ from aiohttp_proxy import ProxyConnector
6
+ from better_proxy import Proxy
7
+ from pyrogram import Client
8
+ from pyrogram.errors import Unauthorized, UserDeactivated, AuthKeyUnregistered
9
+ from pyrogram.raw.functions.messages import RequestWebView
10
+
11
+ from bot.utils.logger import log
12
+ from bot.utils.settings import config
13
+ from .headers import headers
14
+
15
+ class CryptoBot:
16
+ def __init__(self, tg_client: Client):
17
+ self.session_name = tg_client.name
18
+ self.tg_client = tg_client
19
+ self.errors = 0
20
+
21
+ async def get_tg_web_data(self, proxy: str | None) -> str:
22
+ if proxy:
23
+ proxy = Proxy.from_str(proxy)
24
+ proxy_dict = dict(
25
+ scheme=proxy.protocol,
26
+ hostname=proxy.host,
27
+ port=proxy.port,
28
+ username=proxy.login,
29
+ password=proxy.password
30
+ )
31
+ else:
32
+ proxy_dict = None
33
+
34
+ self.tg_client.proxy = proxy_dict
35
+
36
+ try:
37
+ if not self.tg_client.is_connected:
38
+ try:
39
+ await self.tg_client.connect()
40
+ except (Unauthorized, UserDeactivated, AuthKeyUnregistered) as error:
41
+ raise RuntimeError(str(error)) from error
42
+ web_view = await self.tg_client.invoke(RequestWebView(
43
+ peer=await self.tg_client.resolve_peer('BlumCryptoBot'),
44
+ bot=await self.tg_client.resolve_peer('BlumCryptoBot'),
45
+ platform='android',
46
+ from_bot_menu=False,
47
+ url='https://telegram.blum.codes/'
48
+ ))
49
+ auth_url = web_view.url
50
+ tg_web_data = unquote(
51
+ string=auth_url.split('tgWebAppData=', maxsplit=1)[1].split('&tgWebAppVersion', maxsplit=1)[0])
52
+ if self.tg_client.is_connected:
53
+ await self.tg_client.disconnect()
54
+
55
+ return tg_web_data
56
+
57
+ except RuntimeError as error:
58
+ raise error
59
+
60
+ except Exception as error:
61
+ log.error(f"{self.session_name} | Authorization error: {error}")
62
+ await asyncio.sleep(delay=3)
63
+
64
+ async def login(self, init_data: str) -> tuple[str | bool, str]:
65
+ url = 'https://user-domain.blum.codes/api/v1/auth/provider/PROVIDER_TELEGRAM_MINI_APP'
66
+ try:
67
+ log.info(f"{self.session_name} | Trying to login...")
68
+ self.http_client.headers.pop('Authorization', None)
69
+ json_data = {'query': init_data}
70
+ response = await self.http_client.post(url, json=json_data)
71
+ response.raise_for_status()
72
+ response_json = await response.json()
73
+ token = response_json.get('token', {})
74
+ access_token = token.get('access', '')
75
+ refresh_token = token.get('refresh', '')
76
+ log.success(f"{self.session_name} | Login successful")
77
+ return access_token, refresh_token
78
+ except aiohttp.ClientResponseError as error:
79
+ if error.status == 401: self.authorized = False
80
+ self.errors += 1
81
+ log.error(f"{self.session_name} | Login http error: {error}")
82
+ await asyncio.sleep(delay=3)
83
+ return False, ''
84
+ except Exception as error:
85
+ log.error(f"{self.session_name} | Login error: {error}")
86
+ await asyncio.sleep(delay=3)
87
+ return False, ''
88
+
89
+ async def get_profile(self) -> Dict[str, Any]:
90
+ url = 'https://game-domain.blum.codes/api/v1/user/balance'
91
+ try:
92
+ await self.http_client.options(url)
93
+ response = await self.http_client.get(url)
94
+ response.raise_for_status()
95
+ response_json = await response.json()
96
+ return response_json
97
+ except aiohttp.ClientResponseError as error:
98
+ if error.status == 401: self.authorized = False
99
+ self.errors += 1
100
+ log.error(f"{self.session_name} | Profile data http error: {error}")
101
+ await asyncio.sleep(delay=3)
102
+ return {}
103
+ except Exception as error:
104
+ log.error(f"{self.session_name} | Profile data error: {error}")
105
+ await asyncio.sleep(delay=3)
106
+ return {}
107
+
108
+ async def daily_reward(self) -> bool:
109
+ url = 'https://game-domain.blum.codes/api/v1/daily-reward?offset=-180'
110
+ await self.http_client.options(url)
111
+ response = await self.http_client.post(url)
112
+ content_type = response.headers.get('Content-Type', '') # the response can contain both text and json
113
+ try:
114
+ if 'application/json' in content_type:
115
+ response_json = await response.json()
116
+ if response_json.get('message', '') != 'same day':
117
+ log.warning(f"{self.session_name} | Unknown response in daily reward: {str(response_json)}")
118
+ return False
119
+ else:
120
+ response_text = await response.text()
121
+ if response_text == 'OK':
122
+ return True
123
+ else:
124
+ log.warning(f"{self.session_name} | Unknown response in daily reward: {response_text}")
125
+ return False
126
+ except aiohttp.ClientResponseError as error:
127
+ if error.status == 401: self.authorized = False
128
+ log.error(f"{self.session_name} | Daily reward http error: {str(error)}")
129
+ return False
130
+ except Exception as error:
131
+ log.error(f"{self.session_name} | Daily reward error: {str(error)}")
132
+ return False
133
+
134
+ async def farming_claim(self) -> bool:
135
+ url = 'https://game-domain.blum.codes/api/v1/farming/claim'
136
+ try:
137
+ await self.http_client.options(url)
138
+ response = await self.http_client.post(url)
139
+ response.raise_for_status()
140
+ response_json = await response.json()
141
+ balance = response_json.get('availableBalance', False)
142
+ if balance is not False:
143
+ self.balance = int(float(balance))
144
+ return True
145
+ else: return False
146
+ except aiohttp.ClientResponseError as error:
147
+ if error.status == 401: self.authorized = False
148
+ self.errors += 1
149
+ log.error(f"{self.session_name} | Claim http error: {error}")
150
+ await asyncio.sleep(delay=3)
151
+ return False
152
+ except Exception as error:
153
+ log.error(f"{self.session_name} | Claim error: {error}")
154
+ await asyncio.sleep(delay=3)
155
+ return False
156
+
157
+ async def farming_start(self) -> bool:
158
+ url = 'https://game-domain.blum.codes/api/v1/farming/start'
159
+ try:
160
+ await self.http_client.options(url)
161
+ response = await self.http_client.post(url)
162
+ response.raise_for_status()
163
+ return True
164
+ except aiohttp.ClientResponseError as error:
165
+ if error.status == 401: self.authorized = False
166
+ self.errors += 1
167
+ log.error(f"{self.session_name} | Starting farm http error: {error}")
168
+ await asyncio.sleep(delay=3)
169
+ return False
170
+ except Exception as error:
171
+ log.error(f"{self.session_name} | Starting farm error: {error}")
172
+ await asyncio.sleep(delay=3)
173
+ return False
174
+
175
+ async def perform_friend_rewards(self) -> None:
176
+ balance_url = 'https://user-domain.blum.codes/api/v1/friends/balance'
177
+ claim_url = 'https://user-domain.blum.codes/api/v1/friends/claim'
178
+ try:
179
+ await self.http_client.options(balance_url)
180
+ response = await self.http_client.get(balance_url)
181
+ response_json = await response.json()
182
+ claim_amount = float(response_json.get('amountForClaim', 0))
183
+ can_claim = response_json.get('canClaim', False)
184
+ if claim_amount > 0 and can_claim is not False:
185
+ log.info(f"{self.session_name} | Reward for friends available")
186
+ await asyncio.sleep(3)
187
+ await self.http_client.options(claim_url)
188
+ response = await self.http_client.post(claim_url)
189
+ response_json = await response.json()
190
+ claim_amount = float(response_json.get('claimBalance', 0))
191
+ if claim_amount > 0:
192
+ log.success(f"{self.session_name} | Claimed {str(claim_amount)} points")
193
+ self.errors = 0
194
+ else:
195
+ log.warning(f"{self.session_name} | Unable to claim friend reward")
196
+ else:
197
+ log.info(f"{self.session_name} | Reward for friends not available")
198
+ except aiohttp.ClientResponseError as error:
199
+ if error.status == 401: self.authorized = False
200
+ self.errors += 1
201
+ log.error(f"{self.session_name} | Friend reward http error: {error}")
202
+ await asyncio.sleep(delay=3)
203
+ except Exception as error:
204
+ log.error(f"{self.session_name} | Friend reward error: {error}")
205
+ await asyncio.sleep(delay=3)
206
+
207
+ async def perform_games(self, games: int) -> None:
208
+ start_url = 'https://game-domain.blum.codes/api/v1/game/play'
209
+ finish_url = 'https://game-domain.blum.codes/api/v1/game/claim'
210
+
211
+ if games > 4: games = 4 # play a maximum of 4 games in a row
212
+ while games > 0:
213
+ try:
214
+ await self.http_client.options(start_url)
215
+ response = await self.http_client.post(start_url)
216
+ response.raise_for_status()
217
+ response_json = await response.json()
218
+ game_id = response_json.get('gameId', False)
219
+
220
+ if game_id is not False:
221
+ log.info(f"{self.session_name} | Game started")
222
+ await asyncio.sleep(30)
223
+ points = random.randint(*config.GAME_POINTS)
224
+ json_data = {'gameId': game_id, "points": points}
225
+ await self.http_client.options(finish_url)
226
+ response = await self.http_client.post(finish_url, json=json_data)
227
+ response.raise_for_status()
228
+ response_text = await response.text()
229
+ if response_text == 'OK':
230
+ log.success(f"{self.session_name} | Game completed (+{points} points)")
231
+ self.errors = 0
232
+ games -= 1
233
+ await asyncio.sleep(random.randint(*config.SLEEP_BETWEEN_GAME))
234
+ except aiohttp.ClientResponseError as error:
235
+ if error.status == 401: self.authorized = False
236
+ self.errors += 1
237
+ log.error(f"{self.session_name} | Games http error: {error}")
238
+ await asyncio.sleep(delay=3)
239
+ except Exception as error:
240
+ log.error(f"{self.session_name} | Games error: {error}")
241
+ await asyncio.sleep(delay=3)
242
+
243
+ async def perform_tasks(self) -> None:
244
+ url = 'https://earn-domain.blum.codes/api/v1/tasks'
245
+ try:
246
+ await self.http_client.options(url)
247
+ response = await self.http_client.get(url)
248
+ response.raise_for_status()
249
+ response_json = await response.json()
250
+ started = 0
251
+ completed = 0
252
+ for category in response_json:
253
+ tasks = category.get('tasks', [])
254
+ for task in tasks:
255
+ if started == 2 or completed == 2: break
256
+ if task['status'] == 'FINISHED'or task.get('isHidden', False): continue
257
+ if 'socialSubscription' not in task or task.get('socialSubscription', {}).get('openInTelegram', False): continue
258
+ log.info(f"{self.session_name} | Processing task {task['id']}")
259
+ if task['status'] == 'NOT_STARTED':
260
+ await self.http_client.post(f"https://earn-domain.blum.codes/api/v1/tasks/{task['id']}/start")
261
+ await asyncio.sleep(random.randint(4, 8))
262
+ started += 1
263
+ elif task['status'] == 'READY_FOR_CLAIM':
264
+ await self.http_client.post(f"https://earn-domain.blum.codes/api/v1/tasks/{task['id']}/claim")
265
+ await asyncio.sleep(1)
266
+ log.success(f"{self.session_name} | Task {task['id']} completed and reward claimed")
267
+ self.errors = 0
268
+ await asyncio.sleep(random.randint(2, 4))
269
+ completed += 1
270
+ sub_sections = category.get('subSections', [])
271
+ for sub_section in sub_sections:
272
+ tasks = sub_section.get('tasks', [])
273
+ for task in tasks:
274
+ if started == 2 or completed == 2: break
275
+ if task['status'] == 'FINISHED' or task.get('isHidden', False): continue
276
+ if 'socialSubscription' not in task or task.get('socialSubscription', {}).get('openInTelegram', False): continue
277
+ log.info(f"{self.session_name} | Processing task {task['id']}")
278
+ if task['status'] == 'NOT_STARTED':
279
+ await self.http_client.post(f"https://earn-domain.blum.codes/api/v1/tasks/{task['id']}/start")
280
+ await asyncio.sleep(random.randint(4, 8))
281
+ started += 1
282
+ elif task['status'] == 'READY_FOR_CLAIM':
283
+ await self.http_client.post(f"https://earn-domain.blum.codes/api/v1/tasks/{task['id']}/claim")
284
+ await asyncio.sleep(1)
285
+ log.success(f"{self.session_name} | Task {task['id']} completed and reward claimed")
286
+ self.errors = 0
287
+ await asyncio.sleep(random.randint(2, 4))
288
+ completed += 1
289
+ except aiohttp.ClientResponseError as error:
290
+ if error.status == 401: self.authorized = False
291
+ self.errors += 1
292
+ log.error(f"{self.session_name} | Tasks http error: {error}")
293
+ await asyncio.sleep(delay=3)
294
+ except Exception as error:
295
+ log.error(f"{self.session_name} | Tasks error: {error}")
296
+ self.errors += 1
297
+ await asyncio.sleep(delay=3)
298
+
299
+ async def refresh_tokens(self) -> str | bool:
300
+ url = 'https://user-domain.blum.codes/api/v1/auth/refresh'
301
+ try:
302
+ await self.http_client.options(url)
303
+ json_data = {'refresh': self.refresh_token}
304
+ response = await self.http_client.post(url, json=json_data)
305
+ response.raise_for_status()
306
+ response_json = await response.json()
307
+ self.access_token = response_json.get('access', '')
308
+ self.refresh_token = response_json.get('refresh', '')
309
+ return True if self.access_token != '' else False
310
+ except aiohttp.ClientResponseError as error:
311
+ if error.status == 401: self.authorized = False
312
+ self.errors += 1
313
+ log.warning(f"{self.session_name} | Refresh auth tokens http error")
314
+ return False
315
+ except Exception:
316
+ log.warning(f"{self.session_name} | Refresh auth tokens error")
317
+ return False
318
+
319
+ async def check_proxy(self, proxy: Proxy) -> None:
320
+ try:
321
+ response = await self.http_client.get(url='https://httpbin.org/ip', timeout=aiohttp.ClientTimeout(5))
322
+ ip = (await response.json()).get('origin')
323
+ log.info(f"{self.session_name} | Proxy IP: {ip}")
324
+ except Exception as error:
325
+ log.error(f"{self.session_name} | Proxy: {proxy} | Error: {error}")
326
+
327
+ async def run(self, proxy: str | None) -> None:
328
+ access_token_created_time = 0
329
+ proxy_conn = ProxyConnector().from_url(proxy) if proxy else None
330
+
331
+ async with aiohttp.ClientSession(headers=headers, connector=proxy_conn) as http_client:
332
+ self.http_client = http_client
333
+ if proxy:
334
+ await self.check_proxy(proxy=proxy)
335
+
336
+ self.authorized = False
337
+ while True:
338
+ if self.errors >= config.ERRORS_BEFORE_STOP:
339
+ log.error(f"{self.session_name} | Bot stopped (too many errors)")
340
+ break
341
+ try:
342
+ if not self.authorized:
343
+ tg_web_data = await self.get_tg_web_data(proxy=proxy)
344
+ access_token, refresh_token = await self.login(init_data=tg_web_data)
345
+ if access_token is not False and refresh_token != '':
346
+ self.authorized = True
347
+ self.access_token = access_token
348
+ self.refresh_token = refresh_token
349
+ self.http_client.headers['Authorization'] = 'Bearer ' + access_token
350
+ access_token_created_time = time()
351
+ else: continue
352
+
353
+ if time() - access_token_created_time >= 3600:
354
+ refresh_success = await self.refresh_tokens()
355
+ if refresh_success:
356
+ self.http_client.headers['Authorization'] = 'Bearer ' + self.access_token
357
+ access_token_created_time = time()
358
+ else:
359
+ self.authorized = False
360
+ continue
361
+
362
+ daily_claimed = await self.daily_reward()
363
+ if daily_claimed:
364
+ log.success(f"{self.session_name} | Daily reward claimed")
365
+ self.errors = 0
366
+ else:
367
+ log.info(f"{self.session_name} | Daily reward not available")
368
+
369
+ profile = await self.get_profile()
370
+ self.balance = profile['availableBalance']
371
+ log.info(f"{self.session_name} | Balance: {self.balance}")
372
+
373
+ await asyncio.sleep(random.randint(2, 4))
374
+ await self.perform_friend_rewards()
375
+
376
+ games_left = profile['playPasses']
377
+ system_time = profile['timestamp'] // 1000
378
+ farming = profile.get('farming', {})
379
+ farm_start = farming.get('startTime', 0) // 1000
380
+ farm_end = farming.get('endTime', 0) // 1000
381
+
382
+ await asyncio.sleep(random.randint(2, 4))
383
+ if farm_start == 0 or farm_end == 0:
384
+ log.info(f"{self.session_name} | Start farming...")
385
+ if await self.farming_start():
386
+ log.success(f"{self.session_name} | Farming started successfully")
387
+ self.errors = 0
388
+ elif system_time > farm_end:
389
+ log.info(f"{self.session_name} | Time to claim and restart farming")
390
+ if await self.farming_claim():
391
+ log.success(f"{self.session_name} | Claim successful")
392
+ self.errors = 0
393
+ else: continue
394
+ if await self.farming_start():
395
+ log.success(f"{self.session_name} | Farming restarted successfully")
396
+ self.errors = 0
397
+ else: continue
398
+
399
+ await asyncio.sleep(random.randint(2, 4))
400
+ await self.perform_tasks()
401
+
402
+ if config.GAMES_ENABLED and games_left > 0:
403
+ await asyncio.sleep(random.randint(2, 4))
404
+ await self.perform_games(games=games_left)
405
+
406
+ # Log current balance
407
+ profile = await self.get_profile()
408
+ self.balance = profile['availableBalance']
409
+ log.info(f"{self.session_name} | Balance: {self.balance}")
410
+
411
+ if system_time < farm_end:
412
+ claim_wait = farm_end - system_time
413
+ hours = claim_wait // 3600
414
+ minutes = (claim_wait % 3600) // 60
415
+ log.info(f"{self.session_name} | Waiting for {hours} hours and {minutes} minutes before claiming and restarting")
416
+ await asyncio.sleep(claim_wait)
417
+
418
+ except RuntimeError as error:
419
+ raise error
420
+ except Exception as error:
421
+ log.error(f"{self.session_name} | Unknown error: {error}")
422
+ await asyncio.sleep(delay=3)
423
+ else:
424
+ log.info(f"Sleep 1 min")
425
+ await asyncio.sleep(delay=60)
426
+
427
+ async def run_bot(tg_client: Client, proxy: str | None):
428
+ try:
429
+ await CryptoBot(tg_client=tg_client).run(proxy=proxy)
430
+ except RuntimeError as error:
431
+ log.error(f"{tg_client.name} | Session error: {str(error)}")
bot/core/headers.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ headers = {
2
+ 'Accept': '*/*',
3
+ 'Accept-Language': 'en-US,en;q=0.9,uk-UA;q=0.8,uk;q=0.7,ru;q=0.6,zh-CN;q=0.5,zh;q=0.4',
4
+ 'Connection': 'keep-alive',
5
+ 'Lang': 'en',
6
+ 'Origin': 'https://telegram.blum.codes',
7
+ 'Sec-Ch-Ua': '"Chromium";v="126", "Google Chrome";v="126", "Not;A Brand";v="99"',
8
+ 'Sec-Ch-Ua-Mobile': '?1',
9
+ 'Sec-Ch-Ua-Platform': '"Android"',
10
+ 'Sec-Fetch-Dest': 'empty',
11
+ 'Sec-Fetch-Mode': 'cors',
12
+ 'Sec-Fetch-Site': 'same-site',
13
+ 'User-Agent': 'Mozilla/5.0 (Linux; Android 9; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.82 Mobile Safari/537.36',
14
+ 'X-Requested-With': 'org.telegram.messenger.web',
15
+ }
bot/core/launcher.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import asyncio
3
+ import random
4
+ from argparse import ArgumentParser
5
+ from pathlib import Path
6
+ from itertools import cycle
7
+ from pyrogram import Client
8
+ from better_proxy import Proxy
9
+ from bot.utils.logger import log
10
+ from bot.utils.settings import config, logo
11
+ from bot.core.bot import run_bot
12
+
13
+ start_text = logo + """
14
+ Select an action:
15
+ 1. Create session
16
+ 2. Run bot
17
+ """
18
+
19
+ def get_session_names() -> list[str]:
20
+ session_path = Path('sessions')
21
+ session_files = session_path.glob('*.session')
22
+ session_names = sorted([file.stem for file in session_files])
23
+ return session_names
24
+
25
+ async def register_sessions() -> None:
26
+ session_name = input('\nEnter the session name (press Enter to exit): ')
27
+ if not session_name: return None
28
+
29
+ if not os.path.exists(path='sessions'): os.mkdir(path='sessions')
30
+
31
+ session = Client(
32
+ name=session_name,
33
+ api_id=config.API_ID,
34
+ api_hash=config.API_HASH,
35
+ workdir="sessions/"
36
+ )
37
+
38
+ async with session: user_data = await session.get_me()
39
+ log.success(f"Session added successfully: {user_data.username or user_data.id} | "
40
+ f"{user_data.first_name or ''} {user_data.last_name or ''}")
41
+
42
+ def get_proxies() -> list[Proxy]:
43
+ if config.USE_PROXY_FROM_FILE:
44
+ with open(file='proxies.txt', encoding='utf-8-sig') as file:
45
+ proxies = sorted([Proxy.from_str(proxy=row.strip()).as_url for row in file if row.strip()])
46
+ else:
47
+ proxies = []
48
+
49
+ return proxies
50
+
51
+ async def get_tg_clients() -> list[Client]:
52
+ session_names = get_session_names()
53
+
54
+ if not session_names:
55
+ raise FileNotFoundError("Not found session files")
56
+
57
+ tg_clients = [Client(
58
+ name=session_name,
59
+ api_id=config.API_ID,
60
+ api_hash=config.API_HASH,
61
+ workdir='sessions/',
62
+ plugins=dict(root='bot/plugins')
63
+ ) for session_name in session_names]
64
+
65
+ return tg_clients
66
+
67
+ async def run_bot_with_delay(tg_client, proxy, delay):
68
+ if delay > 0:
69
+ log.info(f"{tg_client.name} | Wait {delay} seconds before start")
70
+ await asyncio.sleep(delay)
71
+ await run_bot(tg_client=tg_client, proxy=proxy)
72
+
73
+ async def run_clients(tg_clients: list[Client]):
74
+ proxies = get_proxies()
75
+ proxies_cycle = cycle(proxies) if proxies else cycle([None])
76
+ tasks = []
77
+ delay = 0
78
+ for index, tg_client in enumerate(tg_clients):
79
+ if index > 0:
80
+ delay = random.randint(*config.SLEEP_BETWEEN_START)
81
+ proxy = next(proxies_cycle)
82
+ task = asyncio.create_task(run_bot_with_delay(tg_client=tg_client, proxy=proxy, delay=delay))
83
+ tasks.append(task)
84
+ await asyncio.gather(*tasks)
85
+
86
+ async def start() -> None:
87
+ if not config:
88
+ log.warning(f"Please fix the above errors in the .env file")
89
+ return
90
+ parser = ArgumentParser()
91
+ parser.add_argument('-a', '--action', type=int, choices=[1, 2], help='Action to perform (1 or 2)')
92
+ log.info(f"Detected {len(get_session_names())} sessions | {len(get_proxies())} proxies")
93
+ action = parser.parse_args().action
94
+
95
+ if not action:
96
+ print(start_text)
97
+ while True:
98
+ action = input('> ').strip()
99
+ if action.isdigit() and action in ['1', '2']:
100
+ action = int(action)
101
+ break
102
+ log.warning("Action must be a number (1 or 2)")
103
+
104
+ if action == 1:
105
+ await register_sessions()
106
+ elif action == 2:
107
+ tg_clients = await get_tg_clients()
108
+ await run_clients(tg_clients=tg_clients)
bot/utils/logger.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from loguru import logger
3
+
4
+ logger.remove()
5
+ logger.add(sink=sys.stdout, format="<white>{time:YYYY-MM-DD HH:mm:ss}</white>"
6
+ " | <level>{level: <8}</level>"
7
+ " | <cyan><b>{line}</b></cyan>"
8
+ " | <white><b>{message}</b></white>")
9
+ log = logger.opt(colors=True)
bot/utils/settings.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic_settings import BaseSettings, SettingsConfigDict
2
+ from bot.utils.logger import log
3
+
4
+ logo = """
5
+
6
+ ██████ ██ ██ ██ ███ ███ ██████ ██████ ████████
7
+ ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ██ ██
8
+ ██████ ██ ██ ██ ██ ████ ██ ██████ ██ ██ ██
9
+ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
10
+ ██████ ███████ ██████ ██ ██ ██████ ██████ ██
11
+
12
+ """
13
+
14
+ class Settings(BaseSettings):
15
+ model_config = SettingsConfigDict(env_file=".env", env_ignore_empty=True)
16
+
17
+ API_ID: int
18
+ API_HASH: str
19
+
20
+ GAMES_ENABLED: bool = True
21
+ GAME_POINTS: list[int] = [100, 200]
22
+ SLEEP_BETWEEN_GAME: list[int] = [10, 20]
23
+
24
+ SLEEP_BETWEEN_START: list[int] = [20, 360]
25
+ ERRORS_BEFORE_STOP: int = 3
26
+ USE_PROXY_FROM_FILE: bool = False
27
+
28
+ try:
29
+ config = Settings()
30
+ except Exception as error:
31
+ log.error(error)
32
+ config = False
docker-compose.yml ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3'
2
+ services:
3
+ bot:
4
+ container_name: 'BlumBot'
5
+ stop_signal: SIGINT
6
+ build:
7
+ context: .
8
+ working_dir: /app
9
+ volumes:
10
+ - .:/app
11
+ entrypoint: "python3 main.py"
12
+ command: ["-a", "2"]
13
+ restart: unless-stopped
14
+ env_file: .env
main.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from bot.core import launcher
3
+ from bot.utils.logger import log
4
+
5
+ async def main():
6
+ await launcher.start()
7
+
8
+
9
+ if __name__ == '__main__':
10
+ try:
11
+ asyncio.run(main())
12
+ except KeyboardInterrupt:
13
+ log.info("Bot stopped by user")
mmm/HEAD ADDED
@@ -0,0 +1 @@
 
 
1
+ ref: refs/heads/main
mmm/config ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [core]
2
+ repositoryformatversion = 0
3
+ filemode = true
4
+ bare = false
5
+ logallrefupdates = true
6
+ [remote "origin"]
7
+ url = https://github.com/Alexell/BlumBot
8
+ fetch = +refs/heads/*:refs/remotes/origin/*
9
+ [branch "main"]
10
+ remote = origin
11
+ merge = refs/heads/main
mmm/description ADDED
@@ -0,0 +1 @@
 
 
1
+ Unnamed repository; edit this file 'description' to name the repository.
mmm/hooks/applypatch-msg.sample ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ #
3
+ # An example hook script to check the commit log message taken by
4
+ # applypatch from an e-mail message.
5
+ #
6
+ # The hook should exit with non-zero status after issuing an
7
+ # appropriate message if it wants to stop the commit. The hook is
8
+ # allowed to edit the commit message file.
9
+ #
10
+ # To enable this hook, rename this file to "applypatch-msg".
11
+
12
+ . git-sh-setup
13
+ commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
14
+ test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
15
+ :
mmm/hooks/commit-msg.sample ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ #
3
+ # An example hook script to check the commit log message.
4
+ # Called by "git commit" with one argument, the name of the file
5
+ # that has the commit message. The hook should exit with non-zero
6
+ # status after issuing an appropriate message if it wants to stop the
7
+ # commit. The hook is allowed to edit the commit message file.
8
+ #
9
+ # To enable this hook, rename this file to "commit-msg".
10
+
11
+ # Uncomment the below to add a Signed-off-by line to the message.
12
+ # Doing this in a hook is a bad idea in general, but the prepare-commit-msg
13
+ # hook is more suited to it.
14
+ #
15
+ # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
16
+ # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
17
+
18
+ # This example catches duplicate Signed-off-by lines.
19
+
20
+ test "" = "$(grep '^Signed-off-by: ' "$1" |
21
+ sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
22
+ echo >&2 Duplicate Signed-off-by lines.
23
+ exit 1
24
+ }
mmm/hooks/fsmonitor-watchman.sample ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/perl
2
+
3
+ use strict;
4
+ use warnings;
5
+ use IPC::Open2;
6
+
7
+ # An example hook script to integrate Watchman
8
+ # (https://facebook.github.io/watchman/) with git to speed up detecting
9
+ # new and modified files.
10
+ #
11
+ # The hook is passed a version (currently 2) and last update token
12
+ # formatted as a string and outputs to stdout a new update token and
13
+ # all files that have been modified since the update token. Paths must
14
+ # be relative to the root of the working tree and separated by a single NUL.
15
+ #
16
+ # To enable this hook, rename this file to "query-watchman" and set
17
+ # 'git config core.fsmonitor .git/hooks/query-watchman'
18
+ #
19
+ my ($version, $last_update_token) = @ARGV;
20
+
21
+ # Uncomment for debugging
22
+ # print STDERR "$0 $version $last_update_token\n";
23
+
24
+ # Check the hook interface version
25
+ if ($version ne 2) {
26
+ die "Unsupported query-fsmonitor hook version '$version'.\n" .
27
+ "Falling back to scanning...\n";
28
+ }
29
+
30
+ my $git_work_tree = get_working_dir();
31
+
32
+ my $retry = 1;
33
+
34
+ my $json_pkg;
35
+ eval {
36
+ require JSON::XS;
37
+ $json_pkg = "JSON::XS";
38
+ 1;
39
+ } or do {
40
+ require JSON::PP;
41
+ $json_pkg = "JSON::PP";
42
+ };
43
+
44
+ launch_watchman();
45
+
46
+ sub launch_watchman {
47
+ my $o = watchman_query();
48
+ if (is_work_tree_watched($o)) {
49
+ output_result($o->{clock}, @{$o->{files}});
50
+ }
51
+ }
52
+
53
+ sub output_result {
54
+ my ($clockid, @files) = @_;
55
+
56
+ # Uncomment for debugging watchman output
57
+ # open (my $fh, ">", ".git/watchman-output.out");
58
+ # binmode $fh, ":utf8";
59
+ # print $fh "$clockid\n@files\n";
60
+ # close $fh;
61
+
62
+ binmode STDOUT, ":utf8";
63
+ print $clockid;
64
+ print "\0";
65
+ local $, = "\0";
66
+ print @files;
67
+ }
68
+
69
+ sub watchman_clock {
70
+ my $response = qx/watchman clock "$git_work_tree"/;
71
+ die "Failed to get clock id on '$git_work_tree'.\n" .
72
+ "Falling back to scanning...\n" if $? != 0;
73
+
74
+ return $json_pkg->new->utf8->decode($response);
75
+ }
76
+
77
+ sub watchman_query {
78
+ my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
79
+ or die "open2() failed: $!\n" .
80
+ "Falling back to scanning...\n";
81
+
82
+ # In the query expression below we're asking for names of files that
83
+ # changed since $last_update_token but not from the .git folder.
84
+ #
85
+ # To accomplish this, we're using the "since" generator to use the
86
+ # recency index to select candidate nodes and "fields" to limit the
87
+ # output to file names only. Then we're using the "expression" term to
88
+ # further constrain the results.
89
+ if (substr($last_update_token, 0, 1) eq "c") {
90
+ $last_update_token = "\"$last_update_token\"";
91
+ }
92
+ my $query = <<" END";
93
+ ["query", "$git_work_tree", {
94
+ "since": $last_update_token,
95
+ "fields": ["name"],
96
+ "expression": ["not", ["dirname", ".git"]]
97
+ }]
98
+ END
99
+
100
+ # Uncomment for debugging the watchman query
101
+ # open (my $fh, ">", ".git/watchman-query.json");
102
+ # print $fh $query;
103
+ # close $fh;
104
+
105
+ print CHLD_IN $query;
106
+ close CHLD_IN;
107
+ my $response = do {local $/; <CHLD_OUT>};
108
+
109
+ # Uncomment for debugging the watch response
110
+ # open ($fh, ">", ".git/watchman-response.json");
111
+ # print $fh $response;
112
+ # close $fh;
113
+
114
+ die "Watchman: command returned no output.\n" .
115
+ "Falling back to scanning...\n" if $response eq "";
116
+ die "Watchman: command returned invalid output: $response\n" .
117
+ "Falling back to scanning...\n" unless $response =~ /^\{/;
118
+
119
+ return $json_pkg->new->utf8->decode($response);
120
+ }
121
+
122
+ sub is_work_tree_watched {
123
+ my ($output) = @_;
124
+ my $error = $output->{error};
125
+ if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
126
+ $retry--;
127
+ my $response = qx/watchman watch "$git_work_tree"/;
128
+ die "Failed to make watchman watch '$git_work_tree'.\n" .
129
+ "Falling back to scanning...\n" if $? != 0;
130
+ $output = $json_pkg->new->utf8->decode($response);
131
+ $error = $output->{error};
132
+ die "Watchman: $error.\n" .
133
+ "Falling back to scanning...\n" if $error;
134
+
135
+ # Uncomment for debugging watchman output
136
+ # open (my $fh, ">", ".git/watchman-output.out");
137
+ # close $fh;
138
+
139
+ # Watchman will always return all files on the first query so
140
+ # return the fast "everything is dirty" flag to git and do the
141
+ # Watchman query just to get it over with now so we won't pay
142
+ # the cost in git to look up each individual file.
143
+ my $o = watchman_clock();
144
+ $error = $output->{error};
145
+
146
+ die "Watchman: $error.\n" .
147
+ "Falling back to scanning...\n" if $error;
148
+
149
+ output_result($o->{clock}, ("/"));
150
+ $last_update_token = $o->{clock};
151
+
152
+ eval { launch_watchman() };
153
+ return 0;
154
+ }
155
+
156
+ die "Watchman: $error.\n" .
157
+ "Falling back to scanning...\n" if $error;
158
+
159
+ return 1;
160
+ }
161
+
162
+ sub get_working_dir {
163
+ my $working_dir;
164
+ if ($^O =~ 'msys' || $^O =~ 'cygwin') {
165
+ $working_dir = Win32::GetCwd();
166
+ $working_dir =~ tr/\\/\//;
167
+ } else {
168
+ require Cwd;
169
+ $working_dir = Cwd::cwd();
170
+ }
171
+
172
+ return $working_dir;
173
+ }
mmm/hooks/post-update.sample ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ #
3
+ # An example hook script to prepare a packed repository for use over
4
+ # dumb transports.
5
+ #
6
+ # To enable this hook, rename this file to "post-update".
7
+
8
+ exec git update-server-info
mmm/hooks/pre-applypatch.sample ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ #
3
+ # An example hook script to verify what is about to be committed
4
+ # by applypatch from an e-mail message.
5
+ #
6
+ # The hook should exit with non-zero status after issuing an
7
+ # appropriate message if it wants to stop the commit.
8
+ #
9
+ # To enable this hook, rename this file to "pre-applypatch".
10
+
11
+ . git-sh-setup
12
+ precommit="$(git rev-parse --git-path hooks/pre-commit)"
13
+ test -x "$precommit" && exec "$precommit" ${1+"$@"}
14
+ :
mmm/hooks/pre-commit.sample ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ #
3
+ # An example hook script to verify what is about to be committed.
4
+ # Called by "git commit" with no arguments. The hook should
5
+ # exit with non-zero status after issuing an appropriate message if
6
+ # it wants to stop the commit.
7
+ #
8
+ # To enable this hook, rename this file to "pre-commit".
9
+
10
+ if git rev-parse --verify HEAD >/dev/null 2>&1
11
+ then
12
+ against=HEAD
13
+ else
14
+ # Initial commit: diff against an empty tree object
15
+ against=$(git hash-object -t tree /dev/null)
16
+ fi
17
+
18
+ # If you want to allow non-ASCII filenames set this variable to true.
19
+ allownonascii=$(git config --type=bool hooks.allownonascii)
20
+
21
+ # Redirect output to stderr.
22
+ exec 1>&2
23
+
24
+ # Cross platform projects tend to avoid non-ASCII filenames; prevent
25
+ # them from being added to the repository. We exploit the fact that the
26
+ # printable range starts at the space character and ends with tilde.
27
+ if [ "$allownonascii" != "true" ] &&
28
+ # Note that the use of brackets around a tr range is ok here, (it's
29
+ # even required, for portability to Solaris 10's /usr/bin/tr), since
30
+ # the square bracket bytes happen to fall in the designated range.
31
+ test $(git diff --cached --name-only --diff-filter=A -z $against |
32
+ LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
33
+ then
34
+ cat <<\EOF
35
+ Error: Attempt to add a non-ASCII file name.
36
+
37
+ This can cause problems if you want to work with people on other platforms.
38
+
39
+ To be portable it is advisable to rename the file.
40
+
41
+ If you know what you are doing you can disable this check using:
42
+
43
+ git config hooks.allownonascii true
44
+ EOF
45
+ exit 1
46
+ fi
47
+
48
+ # If there are whitespace errors, print the offending file names and fail.
49
+ exec git diff-index --check --cached $against --
mmm/hooks/pre-merge-commit.sample ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ #
3
+ # An example hook script to verify what is about to be committed.
4
+ # Called by "git merge" with no arguments. The hook should
5
+ # exit with non-zero status after issuing an appropriate message to
6
+ # stderr if it wants to stop the merge commit.
7
+ #
8
+ # To enable this hook, rename this file to "pre-merge-commit".
9
+
10
+ . git-sh-setup
11
+ test -x "$GIT_DIR/hooks/pre-commit" &&
12
+ exec "$GIT_DIR/hooks/pre-commit"
13
+ :
mmm/hooks/pre-push.sample ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+
3
+ # An example hook script to verify what is about to be pushed. Called by "git
4
+ # push" after it has checked the remote status, but before anything has been
5
+ # pushed. If this script exits with a non-zero status nothing will be pushed.
6
+ #
7
+ # This hook is called with the following parameters:
8
+ #
9
+ # $1 -- Name of the remote to which the push is being done
10
+ # $2 -- URL to which the push is being done
11
+ #
12
+ # If pushing without using a named remote those arguments will be equal.
13
+ #
14
+ # Information about the commits which are being pushed is supplied as lines to
15
+ # the standard input in the form:
16
+ #
17
+ # <local ref> <local oid> <remote ref> <remote oid>
18
+ #
19
+ # This sample shows how to prevent push of commits where the log message starts
20
+ # with "WIP" (work in progress).
21
+
22
+ remote="$1"
23
+ url="$2"
24
+
25
+ zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
26
+
27
+ while read local_ref local_oid remote_ref remote_oid
28
+ do
29
+ if test "$local_oid" = "$zero"
30
+ then
31
+ # Handle delete
32
+ :
33
+ else
34
+ if test "$remote_oid" = "$zero"
35
+ then
36
+ # New branch, examine all commits
37
+ range="$local_oid"
38
+ else
39
+ # Update to existing branch, examine new commits
40
+ range="$remote_oid..$local_oid"
41
+ fi
42
+
43
+ # Check for WIP commit
44
+ commit=$(git rev-list -n 1 --grep '^WIP' "$range")
45
+ if test -n "$commit"
46
+ then
47
+ echo >&2 "Found WIP commit in $local_ref, not pushing"
48
+ exit 1
49
+ fi
50
+ fi
51
+ done
52
+
53
+ exit 0
mmm/hooks/pre-rebase.sample ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ #
3
+ # Copyright (c) 2006, 2008 Junio C Hamano
4
+ #
5
+ # The "pre-rebase" hook is run just before "git rebase" starts doing
6
+ # its job, and can prevent the command from running by exiting with
7
+ # non-zero status.
8
+ #
9
+ # The hook is called with the following parameters:
10
+ #
11
+ # $1 -- the upstream the series was forked from.
12
+ # $2 -- the branch being rebased (or empty when rebasing the current branch).
13
+ #
14
+ # This sample shows how to prevent topic branches that are already
15
+ # merged to 'next' branch from getting rebased, because allowing it
16
+ # would result in rebasing already published history.
17
+
18
+ publish=next
19
+ basebranch="$1"
20
+ if test "$#" = 2
21
+ then
22
+ topic="refs/heads/$2"
23
+ else
24
+ topic=`git symbolic-ref HEAD` ||
25
+ exit 0 ;# we do not interrupt rebasing detached HEAD
26
+ fi
27
+
28
+ case "$topic" in
29
+ refs/heads/??/*)
30
+ ;;
31
+ *)
32
+ exit 0 ;# we do not interrupt others.
33
+ ;;
34
+ esac
35
+
36
+ # Now we are dealing with a topic branch being rebased
37
+ # on top of master. Is it OK to rebase it?
38
+
39
+ # Does the topic really exist?
40
+ git show-ref -q "$topic" || {
41
+ echo >&2 "No such branch $topic"
42
+ exit 1
43
+ }
44
+
45
+ # Is topic fully merged to master?
46
+ not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
47
+ if test -z "$not_in_master"
48
+ then
49
+ echo >&2 "$topic is fully merged to master; better remove it."
50
+ exit 1 ;# we could allow it, but there is no point.
51
+ fi
52
+
53
+ # Is topic ever merged to next? If so you should not be rebasing it.
54
+ only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
55
+ only_next_2=`git rev-list ^master ${publish} | sort`
56
+ if test "$only_next_1" = "$only_next_2"
57
+ then
58
+ not_in_topic=`git rev-list "^$topic" master`
59
+ if test -z "$not_in_topic"
60
+ then
61
+ echo >&2 "$topic is already up to date with master"
62
+ exit 1 ;# we could allow it, but there is no point.
63
+ else
64
+ exit 0
65
+ fi
66
+ else
67
+ not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
68
+ /usr/bin/perl -e '
69
+ my $topic = $ARGV[0];
70
+ my $msg = "* $topic has commits already merged to public branch:\n";
71
+ my (%not_in_next) = map {
72
+ /^([0-9a-f]+) /;
73
+ ($1 => 1);
74
+ } split(/\n/, $ARGV[1]);
75
+ for my $elem (map {
76
+ /^([0-9a-f]+) (.*)$/;
77
+ [$1 => $2];
78
+ } split(/\n/, $ARGV[2])) {
79
+ if (!exists $not_in_next{$elem->[0]}) {
80
+ if ($msg) {
81
+ print STDERR $msg;
82
+ undef $msg;
83
+ }
84
+ print STDERR " $elem->[1]\n";
85
+ }
86
+ }
87
+ ' "$topic" "$not_in_next" "$not_in_master"
88
+ exit 1
89
+ fi
90
+
91
+ <<\DOC_END
92
+
93
+ This sample hook safeguards topic branches that have been
94
+ published from being rewound.
95
+
96
+ The workflow assumed here is:
97
+
98
+ * Once a topic branch forks from "master", "master" is never
99
+ merged into it again (either directly or indirectly).
100
+
101
+ * Once a topic branch is fully cooked and merged into "master",
102
+ it is deleted. If you need to build on top of it to correct
103
+ earlier mistakes, a new topic branch is created by forking at
104
+ the tip of the "master". This is not strictly necessary, but
105
+ it makes it easier to keep your history simple.
106
+
107
+ * Whenever you need to test or publish your changes to topic
108
+ branches, merge them into "next" branch.
109
+
110
+ The script, being an example, hardcodes the publish branch name
111
+ to be "next", but it is trivial to make it configurable via
112
+ $GIT_DIR/config mechanism.
113
+
114
+ With this workflow, you would want to know:
115
+
116
+ (1) ... if a topic branch has ever been merged to "next". Young
117
+ topic branches can have stupid mistakes you would rather
118
+ clean up before publishing, and things that have not been
119
+ merged into other branches can be easily rebased without
120
+ affecting other people. But once it is published, you would
121
+ not want to rewind it.
122
+
123
+ (2) ... if a topic branch has been fully merged to "master".
124
+ Then you can delete it. More importantly, you should not
125
+ build on top of it -- other people may already want to
126
+ change things related to the topic as patches against your
127
+ "master", so if you need further changes, it is better to
128
+ fork the topic (perhaps with the same name) afresh from the
129
+ tip of "master".
130
+
131
+ Let's look at this example:
132
+
133
+ o---o---o---o---o---o---o---o---o---o "next"
134
+ / / / /
135
+ / a---a---b A / /
136
+ / / / /
137
+ / / c---c---c---c B /
138
+ / / / \ /
139
+ / / / b---b C \ /
140
+ / / / / \ /
141
+ ---o---o---o---o---o---o---o---o---o---o---o "master"
142
+
143
+
144
+ A, B and C are topic branches.
145
+
146
+ * A has one fix since it was merged up to "next".
147
+
148
+ * B has finished. It has been fully merged up to "master" and "next",
149
+ and is ready to be deleted.
150
+
151
+ * C has not merged to "next" at all.
152
+
153
+ We would want to allow C to be rebased, refuse A, and encourage
154
+ B to be deleted.
155
+
156
+ To compute (1):
157
+
158
+ git rev-list ^master ^topic next
159
+ git rev-list ^master next
160
+
161
+ if these match, topic has not merged in next at all.
162
+
163
+ To compute (2):
164
+
165
+ git rev-list master..topic
166
+
167
+ if this is empty, it is fully merged to "master".
168
+
169
+ DOC_END
mmm/hooks/pre-receive.sample ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ #
3
+ # An example hook script to make use of push options.
4
+ # The example simply echoes all push options that start with 'echoback='
5
+ # and rejects all pushes when the "reject" push option is used.
6
+ #
7
+ # To enable this hook, rename this file to "pre-receive".
8
+
9
+ if test -n "$GIT_PUSH_OPTION_COUNT"
10
+ then
11
+ i=0
12
+ while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
13
+ do
14
+ eval "value=\$GIT_PUSH_OPTION_$i"
15
+ case "$value" in
16
+ echoback=*)
17
+ echo "echo from the pre-receive-hook: ${value#*=}" >&2
18
+ ;;
19
+ reject)
20
+ exit 1
21
+ esac
22
+ i=$((i + 1))
23
+ done
24
+ fi
mmm/hooks/prepare-commit-msg.sample ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ #
3
+ # An example hook script to prepare the commit log message.
4
+ # Called by "git commit" with the name of the file that has the
5
+ # commit message, followed by the description of the commit
6
+ # message's source. The hook's purpose is to edit the commit
7
+ # message file. If the hook fails with a non-zero status,
8
+ # the commit is aborted.
9
+ #
10
+ # To enable this hook, rename this file to "prepare-commit-msg".
11
+
12
+ # This hook includes three examples. The first one removes the
13
+ # "# Please enter the commit message..." help message.
14
+ #
15
+ # The second includes the output of "git diff --name-status -r"
16
+ # into the message, just before the "git status" output. It is
17
+ # commented because it doesn't cope with --amend or with squashed
18
+ # commits.
19
+ #
20
+ # The third example adds a Signed-off-by line to the message, that can
21
+ # still be edited. This is rarely a good idea.
22
+
23
+ COMMIT_MSG_FILE=$1
24
+ COMMIT_SOURCE=$2
25
+ SHA1=$3
26
+
27
+ /usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE"
28
+
29
+ # case "$COMMIT_SOURCE,$SHA1" in
30
+ # ,|template,)
31
+ # /usr/bin/perl -i.bak -pe '
32
+ # print "\n" . `git diff --cached --name-status -r`
33
+ # if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;;
34
+ # *) ;;
35
+ # esac
36
+
37
+ # SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
38
+ # git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE"
39
+ # if test -z "$COMMIT_SOURCE"
40
+ # then
41
+ # /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE"
42
+ # fi
mmm/hooks/push-to-checkout.sample ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+
3
+ # An example hook script to update a checked-out tree on a git push.
4
+ #
5
+ # This hook is invoked by git-receive-pack(1) when it reacts to git
6
+ # push and updates reference(s) in its repository, and when the push
7
+ # tries to update the branch that is currently checked out and the
8
+ # receive.denyCurrentBranch configuration variable is set to
9
+ # updateInstead.
10
+ #
11
+ # By default, such a push is refused if the working tree and the index
12
+ # of the remote repository has any difference from the currently
13
+ # checked out commit; when both the working tree and the index match
14
+ # the current commit, they are updated to match the newly pushed tip
15
+ # of the branch. This hook is to be used to override the default
16
+ # behaviour; however the code below reimplements the default behaviour
17
+ # as a starting point for convenient modification.
18
+ #
19
+ # The hook receives the commit with which the tip of the current
20
+ # branch is going to be updated:
21
+ commit=$1
22
+
23
+ # It can exit with a non-zero status to refuse the push (when it does
24
+ # so, it must not modify the index or the working tree).
25
+ die () {
26
+ echo >&2 "$*"
27
+ exit 1
28
+ }
29
+
30
+ # Or it can make any necessary changes to the working tree and to the
31
+ # index to bring them to the desired state when the tip of the current
32
+ # branch is updated to the new commit, and exit with a zero status.
33
+ #
34
+ # For example, the hook can simply run git read-tree -u -m HEAD "$1"
35
+ # in order to emulate git fetch that is run in the reverse direction
36
+ # with git push, as the two-tree form of git read-tree -u -m is
37
+ # essentially the same as git switch or git checkout that switches
38
+ # branches while keeping the local changes in the working tree that do
39
+ # not interfere with the difference between the branches.
40
+
41
+ # The below is a more-or-less exact translation to shell of the C code
42
+ # for the default behaviour for git's push-to-checkout hook defined in
43
+ # the push_to_deploy() function in builtin/receive-pack.c.
44
+ #
45
+ # Note that the hook will be executed from the repository directory,
46
+ # not from the working tree, so if you want to perform operations on
47
+ # the working tree, you will have to adapt your code accordingly, e.g.
48
+ # by adding "cd .." or using relative paths.
49
+
50
+ if ! git update-index -q --ignore-submodules --refresh
51
+ then
52
+ die "Up-to-date check failed"
53
+ fi
54
+
55
+ if ! git diff-files --quiet --ignore-submodules --
56
+ then
57
+ die "Working directory has unstaged changes"
58
+ fi
59
+
60
+ # This is a rough translation of:
61
+ #
62
+ # head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX
63
+ if git cat-file -e HEAD 2>/dev/null
64
+ then
65
+ head=HEAD
66
+ else
67
+ head=$(git hash-object -t tree --stdin </dev/null)
68
+ fi
69
+
70
+ if ! git diff-index --quiet --cached --ignore-submodules $head --
71
+ then
72
+ die "Working directory has staged changes"
73
+ fi
74
+
75
+ if ! git read-tree -u -m "$commit"
76
+ then
77
+ die "Could not update working tree to new HEAD"
78
+ fi
mmm/hooks/update.sample ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ #
3
+ # An example hook script to block unannotated tags from entering.
4
+ # Called by "git receive-pack" with arguments: refname sha1-old sha1-new
5
+ #
6
+ # To enable this hook, rename this file to "update".
7
+ #
8
+ # Config
9
+ # ------
10
+ # hooks.allowunannotated
11
+ # This boolean sets whether unannotated tags will be allowed into the
12
+ # repository. By default they won't be.
13
+ # hooks.allowdeletetag
14
+ # This boolean sets whether deleting tags will be allowed in the
15
+ # repository. By default they won't be.
16
+ # hooks.allowmodifytag
17
+ # This boolean sets whether a tag may be modified after creation. By default
18
+ # it won't be.
19
+ # hooks.allowdeletebranch
20
+ # This boolean sets whether deleting branches will be allowed in the
21
+ # repository. By default they won't be.
22
+ # hooks.denycreatebranch
23
+ # This boolean sets whether remotely creating branches will be denied
24
+ # in the repository. By default this is allowed.
25
+ #
26
+
27
+ # --- Command line
28
+ refname="$1"
29
+ oldrev="$2"
30
+ newrev="$3"
31
+
32
+ # --- Safety check
33
+ if [ -z "$GIT_DIR" ]; then
34
+ echo "Don't run this script from the command line." >&2
35
+ echo " (if you want, you could supply GIT_DIR then run" >&2
36
+ echo " $0 <ref> <oldrev> <newrev>)" >&2
37
+ exit 1
38
+ fi
39
+
40
+ if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
41
+ echo "usage: $0 <ref> <oldrev> <newrev>" >&2
42
+ exit 1
43
+ fi
44
+
45
+ # --- Config
46
+ allowunannotated=$(git config --type=bool hooks.allowunannotated)
47
+ allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch)
48
+ denycreatebranch=$(git config --type=bool hooks.denycreatebranch)
49
+ allowdeletetag=$(git config --type=bool hooks.allowdeletetag)
50
+ allowmodifytag=$(git config --type=bool hooks.allowmodifytag)
51
+
52
+ # check for no description
53
+ projectdesc=$(sed -e '1q' "$GIT_DIR/description")
54
+ case "$projectdesc" in
55
+ "Unnamed repository"* | "")
56
+ echo "*** Project description file hasn't been set" >&2
57
+ exit 1
58
+ ;;
59
+ esac
60
+
61
+ # --- Check types
62
+ # if $newrev is 0000...0000, it's a commit to delete a ref.
63
+ zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
64
+ if [ "$newrev" = "$zero" ]; then
65
+ newrev_type=delete
66
+ else
67
+ newrev_type=$(git cat-file -t $newrev)
68
+ fi
69
+
70
+ case "$refname","$newrev_type" in
71
+ refs/tags/*,commit)
72
+ # un-annotated tag
73
+ short_refname=${refname##refs/tags/}
74
+ if [ "$allowunannotated" != "true" ]; then
75
+ echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
76
+ echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
77
+ exit 1
78
+ fi
79
+ ;;
80
+ refs/tags/*,delete)
81
+ # delete tag
82
+ if [ "$allowdeletetag" != "true" ]; then
83
+ echo "*** Deleting a tag is not allowed in this repository" >&2
84
+ exit 1
85
+ fi
86
+ ;;
87
+ refs/tags/*,tag)
88
+ # annotated tag
89
+ if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
90
+ then
91
+ echo "*** Tag '$refname' already exists." >&2
92
+ echo "*** Modifying a tag is not allowed in this repository." >&2
93
+ exit 1
94
+ fi
95
+ ;;
96
+ refs/heads/*,commit)
97
+ # branch
98
+ if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
99
+ echo "*** Creating a branch is not allowed in this repository" >&2
100
+ exit 1
101
+ fi
102
+ ;;
103
+ refs/heads/*,delete)
104
+ # delete branch
105
+ if [ "$allowdeletebranch" != "true" ]; then
106
+ echo "*** Deleting a branch is not allowed in this repository" >&2
107
+ exit 1
108
+ fi
109
+ ;;
110
+ refs/remotes/*,commit)
111
+ # tracking branch
112
+ ;;
113
+ refs/remotes/*,delete)
114
+ # delete tracking branch
115
+ if [ "$allowdeletebranch" != "true" ]; then
116
+ echo "*** Deleting a tracking branch is not allowed in this repository" >&2
117
+ exit 1
118
+ fi
119
+ ;;
120
+ *)
121
+ # Anything else (is there anything else?)
122
+ echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
123
+ exit 1
124
+ ;;
125
+ esac
126
+
127
+ # --- Finished
128
+ exit 0
mmm/index ADDED
Binary file (1.75 kB). View file
 
mmm/info/exclude ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # git ls-files --others --exclude-from=.git/info/exclude
2
+ # Lines that start with '#' are comments.
3
+ # For a project mostly in C, the following would be a good set of
4
+ # exclude patterns (uncomment them if you want to use them):
5
+ # *.[oa]
6
+ # *~
mmm/logs/HEAD ADDED
@@ -0,0 +1 @@
 
 
1
+ 0000000000000000000000000000000000000000 66ee6c8f8c3ab11d282e5172739f82add0d98f70 root <root@5ac4beed821f.(none)> 1727800164 +0000 clone: from https://github.com/Alexell/BlumBot
mmm/logs/refs/heads/main ADDED
@@ -0,0 +1 @@
 
 
1
+ 0000000000000000000000000000000000000000 66ee6c8f8c3ab11d282e5172739f82add0d98f70 root <root@5ac4beed821f.(none)> 1727800164 +0000 clone: from https://github.com/Alexell/BlumBot
mmm/logs/refs/remotes/origin/HEAD ADDED
@@ -0,0 +1 @@
 
 
1
+ 0000000000000000000000000000000000000000 66ee6c8f8c3ab11d282e5172739f82add0d98f70 root <root@5ac4beed821f.(none)> 1727800164 +0000 clone: from https://github.com/Alexell/BlumBot
mmm/objects/pack/pack-aeffb8b789c28e81cd6b74f0146362187813dd32.idx ADDED
Binary file (4.32 kB). View file
 
mmm/objects/pack/pack-aeffb8b789c28e81cd6b74f0146362187813dd32.pack ADDED
Binary file (53.1 kB). View file
 
mmm/packed-refs ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # pack-refs with: peeled fully-peeled sorted
2
+ 66ee6c8f8c3ab11d282e5172739f82add0d98f70 refs/remotes/origin/main
mmm/refs/heads/main ADDED
@@ -0,0 +1 @@
 
 
1
+ 66ee6c8f8c3ab11d282e5172739f82add0d98f70
mmm/refs/remotes/origin/HEAD ADDED
@@ -0,0 +1 @@
 
 
1
+ ref: refs/remotes/origin/main
proxies.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ type://user:pass@ip:port
2
+ type://user:pass:ip:port
3
+ type://ip:port:user:pass
4
+ type://ip:port@user:pass
5
+ type://ip:port
requirements.txt ADDED
Binary file (274 Bytes). View file