GitHub показывает звёзды и форки, но не показывает, сколько раз скачали файлы из релизов. Цифра есть в API, поле download_count у каждого ассета. Я захотел видеть её сразу по всем своим проектам, да ещё с историей, и написал утилиту. Ниже — как она устроена и на что я налетел.

Откуда берутся данные

Всё делается тремя видами запросов к REST API:

  • users/{user} — подписчики;

  • users/{user}/repos?type=owner&per_page=100 — репозитории: звёзды, форки, язык, описание;

  • repos/{user}/{repo}/releases?per_page=100 — релизы, а в них assets[].download_count.

Форки и черновики релизов пропускаются. Если задан токен, добавляются traffic/views, traffic/clones и traffic/popular/referrers; из первых двух берутся и посуточные счётчики.

Что стоит помнить. download_count считает только ассеты релиза. «Download ZIP» на странице репозитория, git clone и автоархивы Source code в него не входят, так что это нижняя оценка реальной аудитории.

Лимит 60 запросов в час

Без токена GitHub даёт 60 запросов в час на IP. Обновление стоит 2 запроса плюс по одному на каждый проект (релизы). С токеном лимит 5000, но трафик добавляет ещё по 3 запроса на проект. Поэтому программа читает заголовки X-RateLimit-Remaining, X-RateLimit-Limit и, при отказе, X-RateLimit-Reset: в строке состояния видно, сколько запросов осталось, а при исчерпании лимита — во сколько можно попробовать снова. Над всеми разделами висит жёлтая полоса с напоминанием про 60 в час, чтобы лимит не оказался сюрпризом.

Трафик требует fine-grained токен с правом Administration: Read-only. Если GitHub отвечает 403/404, проект просто показывается без трафика, остальные данные не страдают.

История, без которой цифры бесполезны

GitHub отдаёт только текущее значение счётчика, прошлого у него не спросить. Поэтому после каждой проверки программа дописывает снимок в %APPDATA%\GitHubDownloads\history-<user>.json: общие скачивания, скачивания по проектам, звёзды, форки, подписчики. Снимок добавляется, только если что-то изменилось; хранятся 400 дней. Из этого файла считаются «Новых» (с прошлого снимка), «За неделю», графики и тренд-спарклайны в таблице проектов. Поля со звёздами добавлялись позже, поэтому в старых файлах их нет; для таких снимков стоит маркер -1, и расчёты по звёздам идут только среди снимков, где они есть.

Платформа: C# 5 и csc.exe из Windows

Захотелось, чтобы программа была одним exe без установки и без Visual Studio для сборки. Подошёл .NET Framework 4, который есть в любой Windows 10/11: компилятор csc.exe лежит в %WINDIR%\Microsoft.NET\Framework64\v4.0.30319. Весь код живёт в одном файле, а сборка — одна команда:

csc.exe /nologo /target:winexe /optimize+ /codepage:65001 /out:GhDownloads.exe /win32icon:src\app.ico /resource:src\app.ico,app.ico /r:System.Windows.Forms.dll /r:System.Drawing.dll /r:System.Web.Extensions.dll /r:System.Security.dll /r:System.Core.dll src\GhDownloads.cs

Цена — язык C# 5: нет интерполяции строк $"", оператора ?., nameof и => у членов класса. JSON разбирается через JavaScriptSerializer из System.Web.Extensions, лишних пакетов нет.

Несколько ловушек

TLS 1.2. .NET 4.x по умолчанию не подключается к api.github.com. Нужна строка при старте:

ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072; // TLS 1.2

Число 3072 — значение SecurityProtocolType.Tls12: в .NET 4.0 самого enum-члена ещё нет, поэтому приведение числом.

Токен. Хранится в реестре HKCU\Software\GitHubDownloads, зашифрованный через ProtectedData.Protect(..., DataProtectionScope.CurrentUser): расшифровать его может только та же учётная запись Windows. Отправляется только на api.github.com в заголовке Authorization: Bearer. Если токен в реестре не задан, берётся переменная окружения GITHUB_TOKEN.

Своя отрисовка. Страницы-дашборды нарисованы вручную на одном Canvas через TextRenderer и Graphics, а не набраны из готовых контролов. Функция Render возвращает высоту содержимого, прокрутка идёт через AutoScrollMinSize. Кликабельные области и подсказки собираются прямо при отрисовке. Таблица проектов — ListView с OwnerDraw и DoubleBuffered для плавности; мини-график «Тренд» рисуется в ячейке. Два подводных камня: TextRenderer нужен с флагом PreserveGraphicsTranslateTransform, иначе текст не учитывает прокрутку, а метки (Label) съедают & как ключ быстрого доступа (в «Releases & files» амперсанд пропадает, а следующая буква подчёркивается), для заголовков нужно UseMnemonic = false.

Два языка. Каждая надпись в коде — пара «ru, en». Строки нельзя держать в const и static readonly: они вычисляются один раз и язык не переключат. Только свойства и методы. Переключатель обновляет окно, столбцы, подробности и статус на лету; вместе с языком меняется и культура потока, чтобы числа шли как 1 234,5 или 1,234.5.

Что получилось

Пять разделов: обзор с графиком роста за три месяца, таблица проектов с трендами, релизы и файлы (доли по типам и платформам, оценка трафика как скачивания × размер), трафик, история. Лимит GitHub, к сожалению, остался лимитом: чем больше проектов, тем реже можно обновлять.

Ссылки

Программа не подписана цифровой подписью, поэтому SmartScreen может запросить подтверждение. Проект бесплатный. Предложения, какие ещё цифры показывать, — в комментариях или в Issues на GitHub.