Last active
July 11, 2026 08:04
-
-
Save splincode/7f490c668bdbf321f4405da640c253b9 to your computer and use it in GitHub Desktop.
Скрипт получает фолловеров со всех страниц, исключает уже существующие подписки и останавливается при первой ошибке.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| export LC_ALL=C | |
| TARGET="${1:-splincode}" | |
| DELAY_MIN="${DELAY_MIN:-5}" | |
| DELAY_MAX="${DELAY_MAX:-8}" | |
| YES="${YES:-0}" | |
| API_VERSION="${GITHUB_API_VERSION:-2026-03-10}" | |
| command -v gh >/dev/null 2>&1 || { | |
| echo "Ошибка: GitHub CLI не установлен." | |
| exit 1 | |
| } | |
| gh auth status -h github.com >/dev/null 2>&1 || { | |
| echo "Ошибка: сначала авторизуйся через gh auth login." | |
| exit 1 | |
| } | |
| if ((DELAY_MIN > DELAY_MAX)); then | |
| echo "Ошибка: DELAY_MIN не может быть больше DELAY_MAX." | |
| exit 1 | |
| fi | |
| gh_api() { | |
| gh api \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "X-GitHub-Api-Version: ${API_VERSION}" \ | |
| "$@" | |
| } | |
| random_delay() { | |
| local range=$((DELAY_MAX - DELAY_MIN + 1)) | |
| echo $((RANDOM % range + DELAY_MIN)) | |
| } | |
| WORK_DIR="$(mktemp -d)" | |
| FOLLOWERS_FILE="${WORK_DIR}/followers" | |
| TARGET_FOLLOWING_FILE="${WORK_DIR}/target-following" | |
| TARGET_USERS_FILE="${WORK_DIR}/target-users" | |
| MY_FOLLOWING_FILE="${WORK_DIR}/my-following" | |
| TODO_FILE="${WORK_DIR}/todo" | |
| cleanup() { | |
| rm -rf "$WORK_DIR" | |
| } | |
| trap cleanup EXIT | |
| CURRENT_USER="$(gh_api user --jq '.login')" | |
| echo "Активный аккаунт: @${CURRENT_USER}" | |
| echo "Целевой аккаунт: @${TARGET}" | |
| echo | |
| echo "Получаю фолловеров @${TARGET}..." | |
| gh_api \ | |
| --paginate \ | |
| "users/${TARGET}/followers?per_page=100" \ | |
| --jq '.[].login' \ | |
| >"$FOLLOWERS_FILE" | |
| echo "Получаю пользователей, на которых подписан @${TARGET}..." | |
| gh_api \ | |
| --paginate \ | |
| "users/${TARGET}/following?per_page=100" \ | |
| --jq '.[].login' \ | |
| >"$TARGET_FOLLOWING_FILE" | |
| # Объединяем followers и following целевого пользователя, | |
| # затем удаляем дубли. | |
| cat \ | |
| "$FOLLOWERS_FILE" \ | |
| "$TARGET_FOLLOWING_FILE" | | |
| sort -u \ | |
| >"$TARGET_USERS_FILE" | |
| echo "Получаю текущие подписки @${CURRENT_USER}..." | |
| gh_api \ | |
| --paginate \ | |
| "user/following?per_page=100" \ | |
| --jq '.[].login' | | |
| sort -u \ | |
| >"$MY_FOLLOWING_FILE" | |
| # Оставляем только пользователей, на которых мы еще не подписаны. | |
| # Также исключаем собственный аккаунт. | |
| comm -23 "$TARGET_USERS_FILE" "$MY_FOLLOWING_FILE" | | |
| awk -v current_user="$CURRENT_USER" '$0 != current_user' \ | |
| >"$TODO_FILE" | |
| FOLLOWERS_COUNT="$( | |
| sort -u "$FOLLOWERS_FILE" | | |
| wc -l | | |
| tr -d '[:space:]' | |
| )" | |
| TARGET_FOLLOWING_COUNT="$( | |
| sort -u "$TARGET_FOLLOWING_FILE" | | |
| wc -l | | |
| tr -d '[:space:]' | |
| )" | |
| UNIQUE_TARGET_COUNT="$( | |
| wc -l <"$TARGET_USERS_FILE" | | |
| tr -d '[:space:]' | |
| )" | |
| TOTAL="$( | |
| wc -l <"$TODO_FILE" | | |
| tr -d '[:space:]' | |
| )" | |
| echo | |
| echo "Фолловеров @${TARGET}: ${FOLLOWERS_COUNT}" | |
| echo "Подписок @${TARGET}: ${TARGET_FOLLOWING_COUNT}" | |
| echo "Уникальных пользователей: ${UNIQUE_TARGET_COUNT}" | |
| echo "Новых подписок для @${CURRENT_USER}: ${TOTAL}" | |
| if [[ "$TOTAL" -eq 0 ]]; then | |
| echo "Новых пользователей для подписки нет." | |
| exit 0 | |
| fi | |
| echo | |
| echo "Пауза между запросами: случайно от ${DELAY_MIN} до ${DELAY_MAX} секунд." | |
| if [[ "$YES" != "1" ]]; then | |
| read -r -p "Продолжить? [y/N] " ANSWER | |
| if [[ ! "$ANSWER" =~ ^[Yy]$ ]]; then | |
| echo "Отменено." | |
| exit 0 | |
| fi | |
| fi | |
| SUCCESS=0 | |
| FAILED=0 | |
| PROCESSED=0 | |
| while IFS= read -r USERNAME; do | |
| [[ -z "$USERNAME" ]] && continue | |
| PROCESSED=$((PROCESSED + 1)) | |
| printf '[%d/%d] Подписка на @%s... ' \ | |
| "$PROCESSED" \ | |
| "$TOTAL" \ | |
| "$USERNAME" | |
| if gh_api \ | |
| --method PUT \ | |
| "user/following/${USERNAME}" \ | |
| --silent; then | |
| echo "OK" | |
| SUCCESS=$((SUCCESS + 1)) | |
| else | |
| echo "ERROR" | |
| FAILED=$((FAILED + 1)) | |
| echo "Скрипт остановлен, чтобы не продолжать после ошибки." | |
| break | |
| fi | |
| # После последнего пользователя ждать уже не нужно. | |
| if ((PROCESSED < TOTAL)); then | |
| DELAY="$(random_delay)" | |
| echo "Пауза: ${DELAY} сек." | |
| sleep "$DELAY" | |
| fi | |
| done <"$TODO_FILE" | |
| echo | |
| echo "Готово." | |
| echo "Успешных подписок: ${SUCCESS}" | |
| echo "Ошибок: ${FAILED}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment