Spaces:
Build error
Build error
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 |
+

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