Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
#1
by
yasir68
- opened
- .env-example +10 -0
- .gitignore +111 -0
- Dockerfile +13 -0
- INSTALL.bat +11 -0
- INSTALL.sh +48 -0
- README-RU.md +112 -0
- START.bat +6 -0
- START.sh +10 -0
- bot/core/bot.py +431 -0
- bot/core/headers.py +15 -0
- bot/core/launcher.py +108 -0
- bot/utils/logger.py +9 -0
- bot/utils/settings.py +32 -0
- docker-compose.yml +14 -0
- main.py +13 -0
- mmm/HEAD +1 -0
- mmm/config +11 -0
- mmm/description +1 -0
- mmm/hooks/applypatch-msg.sample +15 -0
- mmm/hooks/commit-msg.sample +24 -0
- mmm/hooks/fsmonitor-watchman.sample +173 -0
- mmm/hooks/post-update.sample +8 -0
- mmm/hooks/pre-applypatch.sample +14 -0
- mmm/hooks/pre-commit.sample +49 -0
- mmm/hooks/pre-merge-commit.sample +13 -0
- mmm/hooks/pre-push.sample +53 -0
- mmm/hooks/pre-rebase.sample +169 -0
- mmm/hooks/pre-receive.sample +24 -0
- mmm/hooks/prepare-commit-msg.sample +42 -0
- mmm/hooks/push-to-checkout.sample +78 -0
- mmm/hooks/update.sample +128 -0
- mmm/index +0 -0
- mmm/info/exclude +6 -0
- mmm/logs/HEAD +1 -0
- mmm/logs/refs/heads/main +1 -0
- mmm/logs/refs/remotes/origin/HEAD +1 -0
- mmm/objects/pack/pack-aeffb8b789c28e81cd6b74f0146362187813dd32.idx +0 -0
- mmm/objects/pack/pack-aeffb8b789c28e81cd6b74f0146362187813dd32.pack +0 -0
- mmm/packed-refs +2 -0
- mmm/refs/heads/main +1 -0
- mmm/refs/remotes/origin/HEAD +1 -0
- proxies.txt +5 -0
- requirements.txt +0 -0
.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
|
|