Skip to content

Releases: SeregaSrayk/joyreactorDownloader

v1.0.4

23 May 10:07

Choose a tag to compare

Поверх v1.0.3 — две большие функциональные фичи (режим «лента тега» + умная разбивка папки на подпапки), один внутренний рефактор сетевого слоя (горячее переключение SOCKS5/onion без перезапуска приложения) и набор устойчивости к JR-CDN-WAF. Совместимость с v1.0.3 полная, существующие пресеты и манифесты переносить не нужно.

Главное в этом релизе — режим «лента тега» (скачивание из лент Бездна / Лучшее / Хорошее / Новое конкретного тега, а не только через Query.search), разбивка папки на part-001, part-002 с гарантией, что пост никогда не разорвётся между подпапками, и горячее применение сетевых настроек — теперь Tor можно включать/выключать прямо в работающем приложении, сессия логина не теряется.

Режим «лента тега» (Tag.postPager)

Селектор сортировки превратился из 2-кнопочного («рейтинг / дата») в 6-кнопочный:

[ рейтинг ] [ дата ] | [ бездна ] [ лучшее ] [ хорошее ] [ новое ]
   search                       Tag.postPager (PostLineType)

Первые две — старый режим Query.search с полным набором серверных фильтров (рейтинг, NSFW, автор, исключения тегов, сортировка).

Четыре новых — переключают пайплайн на Tag.postPager с соответствующим PostLineType:

  • безднаPostLineType.ALL (всё, что есть в теге).
  • лучшееPostLineType.BEST.
  • хорошееPostLineType.GOOD.
  • новоеPostLineType.NEW.

Это ровно те же ленты, что на сайте джоя при клике на тег. У Tag.postPager нет серверных фильтров, поэтому требования:

  • Требуется ровно один тег в поле тегов (это всё, что принимает API). UI выводит подсказку под селектором, если тегов не один.
  • Фильтры рейтинга и NSFW применяются клиент-сайд — мы тянем посты с сервера и сами отфильтровываем то, что не подходит. Это означает, что если в ленте мало постов с нужным рейтингом, скачивание просто отдаст меньше файлов, чем заявленный лимит.
  • Сортировка ленты задаётся сервером (фактический порядок постов в ленте джоя), локальная пересортировка отключена — иначе перетасует то, что JR уже выстроил.

Пресет тоже умеет запоминать режим: в JSON-пресете теперь есть поле feed (наряду со старым sort), которое выигрывает над sort, если стоит. Старые пресеты с sort: "rating"/"date" продолжают работать без миграции.

Разбивка папки на part-001, part-002… по постам / страницам / файлам

В ⚙ → Структура папки появилась пара полей:

| Разбивать на подпапки part-001, part-002… по N (0 — не разбивать)  |
| Считать в чём: [ постах ▼ ]                                         |

Когда в задаче набегают тысячи файлов, Проводник/Finder начинают тормозить на плоском списке. С разбивкой каждые N единиц открывается новая подпапка part-XXX. Манифест продолжает дедуплицировать как обычно.

Главное отличие от того, как обычно делают такие штуки — что такое «N единиц»:

  • постах (по умолчанию) — границы между подпапками падают между постами. Многокартиночный комикс / фотосет всегда уезжает в одну part-XXX целиком. Это то, чего просили в комментарии к v1.0.3.
  • страницах — одна part-XXX на N страниц GraphQL-ленты. Посты по-прежнему не рвутся (страница ≥ пост), плюс легко мапить «страница 31-60» на конкретную папку.
  • файлах — точный счётчик по картинкам. Самый предсказуемый по числу файлов в папке, но многокартиночный пост может разорваться между двумя part-XXX, если попадёт на границу.

Резюме при повторных запусках (если задача в той же папке уже частично отработала):

  • Режим постах / страницах: всегда открываем part-(max+1) — мы не можем по содержимому диска определить, сколько постов лежит в существующей part-XXX, и риск разорвать пост перевешивает «лишнюю почти пустую папку».
  • Режим файлах: продолжаем заполнять самую последнюю part-XXX (если в ней меньше N файлов), как было в первой реализации.

Технически это работает так: продюсер задачи резервирует имя подпапки до того, как воркеры подбирают конкретные Job'ы (новое поле Job.SubdirHint), и пишет его в каждую задачу одного поста. Параллельные воркеры не могут переставить картинки одного поста в разные подпапки, потому что подпапка уже зафиксирована в задаче.

⚠️ При включённой разбивке файл считается уже скачанным только если он есть в манифесте (per-folder os.Stat-fast-path работает только в режиме без разбивки — мы не знаем, в какой part-XXX искать). Если у тебя на диске лежат старые файлы без манифеста — сначала запусти «🔄 Полностью пересобрать манифест» из v1.0.3.

Горячее применение SOCKS5 / .onion / троттлинга — без перезапуска

В v1.0.3 любое изменение в секции ⚙ → Сеть (включить/выключить SOCKS5, поменять адрес прокси, выставить .onion-зеркало) требовало перезапуска приложения — мы делали *graphql.Client один раз на старте и больше не трогали. После перезапуска приходилось логиниться заново, если cookies протухли.

В v1.0.4 эти настройки применяются на лету:

  • graphql.Client.SetTransport(t) атомарно подменяет http.RoundTripper под sync.RWMutex. Cookie jar (где живёт сессия логина) — тот же самый объект, ничего не теряется.
  • На уровне GUI это вызывается из нового applyNetworkSettings(), который выбирается и при старте приложения, и при сохранении настроек в модалке. Никаких различий между «свежий запуск» и «настройка только что поменялась» — один и тот же кодовый путь.
  • Из подсказки в UI убрана прошлая фраза «после смены сетевых настроек — перезапусти приложение».

Это даёт удобный сценарий: можешь начать запускать задачу без Tor → увидеть, что JR-CDN начал выдавать 403 → включить SOCKS5 → задача продолжит работать через прокси без рестарта приложения. Запущенные задачи переключатся на новый транспорт через первый же *Client.Do после смены настроек.

Устойчивость к JR-CDN: backoff 403/429/5xx + конфигурируемая пауза между запросами

Реактор для CDN-WAF использует не «честный» HTTP 429, а тихие HTTP 403 без Retry-After (наблюдается опытным путём — никакого хедера, окно блока ~минуты). До v1.0.4 этот 403 пробрасывался наверх как «ошибка задачи», и пользователь сам видел кучу красных ✖ в очереди.

В v1.0.4:

  • Автоматический ретрай с экспоненциальным backoff'ом в internal/client:
    • Триггеры: HTTP 403 (CDN-WAF burst-shield), 429 (явный rate-limit), 5xx (временные глюки CDN), сетевые ошибки уровня TCP/timeout.
    • Расписание: 1s → 2s → 4s → 8s → 16s, до 5 попыток. Кеп 16s ниже, чем у GraphQL (30s), потому что воркер скачивания живёт в пуле параллельных воркеров — одна медленная попытка не должна стопорить всех.
  • JR-специфичный Rate ... GraphQL-ответ. Реактор иногда сигнализирует rate-limit не через HTTP 429, а через {"errors":[{"message":"Rate ..."}]} поверх HTTP 200. Теперь этот префикс детектируется в internal/graphql.Client.doOnce и направляется через тот же exponential-backoff retry-цикл (ErrRateLimited), что и реальный 429 — раньше он всплывал как фатальная ошибка «graphql error: Rate …».
  • Конфигурируемая минимальная пауза между запросами CDN. Новое поле в ⚙ → Сеть — «Мин. пауза между запросами CDN, мс». Это проактивный троттл поверх «потоков»: следующий GET к CDN не уходит, пока не прошло заданное число миллисекунд после предыдущего. Сериализация — sync.Mutex + atomic.Int64 для самой настройки (можно менять на лету).
    • 0 (по умолчанию) — старое поведение, «качаем так быстро, как тянут воркеры».
    • Эмпирическое значение — 200–400 мс. Чувствительный момент: настройка влияет на старт запроса, само скачивание идёт стримом параллельно, так что «4 потока × 300 мс» ≈ 13 файлов/сек, а не 4 в одну секунду.
    • Применяется горячо через тот же applyNetworkSettings, без перезапуска.

В сумме: даже если CDN начал выдавать серии 403, задача не разваливается — каждый запрос ретраится с растущим бэкоффом, плюс пользователь может поставить мин. паузу и проактивно снизить нагрузку.

Прочие доработки UI

  • Последняя ошибка скачивания — внутри карточки задачи в очереди, рядом со счётчиком ✖, теперь показывается последняя ошибка из per-file fetch (GET https://… HTTP 403, write error и т.п.). Полный текст в tooltip-е счётчика. Раньше было видно только общее число ошибок, и приходилось лезть в логи wails dev/exe.
  • Колонка имени в очереди не растягивается под длинной строкой ошибки. Технически таблица очереди переведена на table-layout: fixed, что приводит к работе text-overflow: ellipsis для длинных error-строк.
  • Карточка пресета показывает папку. Раньше папка отображалась только в tooltip-е всей строки, теперь рендерится как 📁 D:\joyreactor\art-cat-ears под основной строкой пресета.
  • Кнопка «✏️📁 Сменить папку» на каждом пресете — для самого частого фут-гана «забыл поменять папку перед сохранением пресета». Только меняет outDir, остальные поля не трогает.
  • Диалог «Сохранить пресет» — два поля, имя + папка с пикером. Папка наследуется из текущей формы, можно поменять до сохранения. Если выбранная папка уже привязана к другому пресету — выскакивает confirm-диалог.

Скачивание

Тот же набор файлов, что и в v1.0.3 — Windows portable / NSIS-setup (с embedded WebView2), macOS universal .app (для Apple Silicon и Intel в одном бинарнике), Linux .tar.gz.

Лицензия

MIT.

Full Changelog: v1.0.3...v1.0.4

Full Changelog: v1.0.3...v1.0.4

v1.0.3

19 May 08:51

Choose a tag to compare

Поверх v1.0.2 — четыре функциональные доработки и пара фиксов производительности UI. Ничего из v1.0.2 не ломается, апдейт безопасный.

Главное в этом релизе — менеджер пресетов (отдельная панель со всеми пресетами разом, их расписаниями автовыгрузки, кнопками быстрого запуска), пересборка манифеста по диску (если папка разъехалась с .manifest.json) и формат имени файла JoySave-совместимый.

Менеджер пресетов

Сверху в шапке между ⚙ и 📋 появилась кнопка «📑 Пресеты». Открывает отдельную панель со всеми сохранёнными пресетами в виде компактных карточек — две строки на пресет:

| Имя · #теги · @автор · ★рейтинг · лимит        [▶ 📂 📋 🗑] |
| ☐ Авто-обновление  ⏱ N назад  → через X ч Y мин            |

Что показано в строке:

  • Сводка фильтров в одну строку: первые теги через ·, @автор, ★200+, NSFW only, лимит — то, что помогает опознать пресет на лету.
  • Папка — в tooltip на строке (мышью наводишь — видишь полный путь; чтобы открыть, жми 📂).
  • Чекбокс «Авто-обновление» — мгновенно переключает Preset.AutoPull, без необходимости загружать пресет в форму.
  • ⏱ «N назад» — сколько прошло с последней автовыгрузки (в tooltip — точная дата). «ни разу» если ещё не выгружался.
  • → «через N ч M мин» — live-обратный отсчёт до следующего автозапуска (тикает раз в секунду, пересчитывается из глобального интервала ⚙ → Авто-обновление). «готова к запуску», когда таймер достиг нуля. «автовыгрузка выключена», если чекбокс не отмечен.

Кнопки действий в строке:

  • — запустить пресет вручную сейчас. Создаёт задачу в очереди тем же путём, что и scheduler. Помечает LastAutoPullAt = now, поэтому следующий автозапуск произойдёт через полный интервал, а не сразу — «ручной запуск съедает запланированный».
  • 📂 — открыть папку пресета в проводнике.
  • 📋 — загрузить пресет в форму фильтров (для редактирования всех полей, включая теги/рейтинг/размеры).
  • 🗑 — удалить пресет с подтверждением.

Сортировка: пресеты с включённой автовыгрузкой первыми, затем по алфавиту.

В шапке панели показан текущий глобальный интервал автовыгрузки (24 ч по умолчанию). Меняется по-прежнему в ⚙ → Авто-обновление.

Пересборка манифеста по диску

.manifest.json — это файл, в котором приложение хранит, какие attribute.id уже скачаны. По этому файлу при следующих сканированиях фильтра решается, какие картинки уже есть локально (бейдж «✓» на плитке, пропуск в очереди загрузки). Раньше его можно было только удалить целиком («забыть всё»). Но если часть файлов в папке загрузок перетасовали мимо приложения (переименовали, перенесли между подпапками, дозалили картинки извне, импортировали папку из другого даунлоадера), то манифест и реальное содержимое уходили в рассинхрон, и приложение либо качало дубликаты, либо считало удалённые файлы скачанными.

В ⚙ → Манифест добавилась кнопка «🔄 Полностью пересобрать по файлам из директории…»:

  1. Указываешь папку для сканирования.
  2. Приложение рекурсивно её обходит, парсит имена в одном из четырёх поддерживаемых форматов:
    • <номер_поста>_<id_аттрибута>.<ext> — наш дефолтный «По ID».
    • [тег1][тег2]..._<номер_поста>_<id_аттрибута>.<ext> — наш «По тегам (в скобках)».
    • <номер_поста>_<0|1>_<id, padded к 9>__<тег1>-<тег2>...<ext> — наш новый «JoySave-совместимый» формат (см. ниже), а также имена, написанные JoySave.
    • <slug-через-дефисы>-<id_аттрибута>.<ext> — формат JR CDN, то есть имена картинок, сохранённых из браузера правой кнопкой.
  3. Манифест приводится в точное соответствие с тем, что найдено на диске:
    • неизвестные файлы — добавляются (с текущим timestamp);
    • записи, у которых файла на диске больше нет — удаляются;
    • совпавшие записи сохраняют свой исходный timestamp скачивания и URL.

Расширения принимаются только из списка медиа JR (jpeg/png/gif/bmp/tiff/mp4/webm/webp) — это сделано чтобы случайные посторонние файлы с числами в конце (IMG_20250115_NNN.jpg, RDT_*.jpg, отчёты 2025-01-15.jpg) не попали в манифест.

⚠️ Операция разрушительная. Если используется глобальный режим манифеста (один общий .manifest.json на всё приложение), пересборка по одной из папок сотрёт записи о файлах, лежащих в других папках. UI выводит соответствующий confirm-диалог с явным предупреждением.

Имя файла «JoySave-совместимый»

В ⚙ → Формат имени файла появился третий вариант «JoySave-совместимый». Имена строятся 1:1 как у JoySave (corax4/JoySave), что полезно если папка делится между двумя инструментами или импортируется готовый архив:

<номер_поста>_0_<id_аттрибута, padded к 9 нулями>__<до 4 тегов через `-`>.<ext>

Пример: пост 12345, картинка 67890, теги «art», «cat ears», «anime»:

12345_0_000067890__art-cat-ears-anime.jpeg

Детали:

  • _0_ — у JoySave это слот «откуда картинка»: 0 для тела поста, 1 для приложения в комментарии. У нас массовая загрузка ходит только за Post.Attributes через Query.search (картинок из комментариев в этой загрузке нет), так что _0_ захардкожен.
  • ID аттрибута выравнивается нулями слева до 9 символов (67890000067890).
  • Тег-блок: первые 4 тега в исходном порядке (без алфавитной сортировки — как у JoySave), разделитель -, пробелы внутри имени тега → -.
  • Запрещённые в Windows символы (\/:*?|<>") на всём имени → @ (post-process sweep как у JoySave).

Отличия от существующих форматов:

  • «По ID» (12345_67890.jpg) и «По тегам (в скобках)» ([art][cat][dog]_12345_67890.jpg) — наши собственные форматы, дают детерминированное имя (теги сортируются алфавитно).
  • «JoySave-совместимый» — побайтовая совместимость с JoySave: один и тот же пост может дать разные имена при двух запусках, если API вернул теги в разном порядке. Это осознанный tradeoff — обмен на то, что файлы 1:1 узнаются JoySave-ом.

Дедупликация при скачивании во всех случаях остаётся по attribute.id в манифесте — формат имени на это не влияет.

Выбранный в ⚙ → Формат имени файла формат применяется ко всем способам запуска задачи — ручному «+ Добавить в очередь», запуску ▶ из менеджера пресетов и автоматической выгрузке через scheduler.

UI: меньше перерисовок при массовой загрузке

В v1.0.2 при активной массовой загрузке UI ощутимо «дёргался» — мерцали hover-состояния кнопок, теги-чипы, выпадайка автокомплита, изредка прыгал каретка ввода. Причина: каждое сохранение картинки (job:update) триггерило полную перерисовку #app.innerHTML — топбар, форма фильтров, грид превью, всё, до 60 раз в секунду, хотя реально менялось максимум одна зелёная галка на превью раз в секунду.

В v1.0.3:

  • Обновление зелёных галок «уже скачано» на плитках теперь делается хирургически: новая функция проходит по уже отрендеренным плиткам и точечно добавляет/удаляет/обновляет только сам бейдж. Полная перерисовка не вызывается.
  • Обновления счётчиков задач (saved/skipped/failed) не вызывают рендер, если очередь задач закрыта (там нечему меняться). Открыли очередь — openQueue() сам подтянет накопленное состояние.
  • При открытой очереди счётчики обновляются с дебаунсом 250 мс (≤4 раза/сек вместо 60). Глазу разницы нет, но шелл больше не пересобирается.

Переходы между состояниями задач (running → done/canceled/error, удаление, добавление) по-прежнему отрабатываются мгновенно — топбар-бейдж и таблица очереди обновляются без задержки.

Скачивание

Тот же набор файлов, что и в v1.0.2 — Windows portable / NSIS-setup, macOS universal .app, Linux .tar.gz.

Лицензия

MIT.

Full Changelog: v1.0.2...v1.0.3

v1.0.2

18 May 02:42

Choose a tag to compare

Главное в этом релизе — детектирование удалённых по DMCA постов в выдаче и опциональная возможность восстановить картинки из таких постов через альтернативное зеркало JR. Плюс новая секция «Сеть» в настройках на случай, если хочется заворачивать GraphQL-запросы в свой SOCKS5-прокси.

Удалённые посты в выдаче

Часть постов на JR получает жалобу на копирайт, и сайт удаляет их метаданные из API. На превью такой пост раньше выглядел как обычная плитка с превьюшкой (превьюшка генерится по post.id и остаётся), но «Найти и скачать» молча давал ноль файлов — потому что у поста нет attributes в GraphQL-ответе.

  • Теперь такие посты помечаются маркером: серая плитка с надписью «🚫 УДАЛЕНО ПО КОПИРАЙТУ» в гриде превью. Кнопка ручного выбора на ней скрыта.
  • В ⚙ → Превью появилась галка «Скрывать удалённые посты» (по умолчанию on) — если включено, такие плитки в выдачу не попадают вообще.
  • В пайплайне скачивания (produce()) удалённые посты пропускаются явно, без артефактов в .manifest.json.

Детектор простой: GraphQL возвращает attributes: [] И Post.text содержит /censorship/ — это конвенция JR для DMCA-стабов.

Восстановление удалённых постов через зеркало

У JR существует независимое .onion-зеркало (reactorccdnf36aqvq34zbfzqyrcrpg3eyhilauovitrvmcjovsujmid.onion) — отдельная инсталляция JR-движка с собственной базой. Это зеркало не синхронизирует DMCA-уведомления с основным сайтом: метаданные удалённых постов там сохраняются. При этом сами файлы картинок физически остаются на основном CDN (img1..15.joyreactor.cc/pics/post/full/...) — JR не удаляет их с диска, только убирает упоминания из API.

Складывая одно с другим, можно достать оригинальные картинки из удалённых постов: HTML-скрапинг зеркала даёт attribute.id, а файл качается с основного CDN по стандартному URL.

Что нужно установить

Зеркало доступно только через Tor (это .onion-адрес). Нужен Tor Browser — официальный, с torproject.org/download. Альтернативно — отдельный tor-демон.

Приложение Tor не поставляет. Пользователь скачивает и запускает Tor сам, наш downloader только цепляется к локальному SOCKS5-листенеру.

Как настроить

  1. Скачай и установи Tor Browser с torproject.org.
  2. Запусти его, нажми «Соединиться» на стартовом экране, дождись появления зелёного лука («Поздравляем»). Окно Tor Browser должно оставаться запущенным, пока пользуешься восстановлением — закроется он, наш downloader потеряет прокси.
  3. В downloader'е открой ⚙ → Сеть:
    • Включи галку «Использовать SOCKS5-прокси для GraphQL». Поле «SOCKS5 адрес» по дефолту 127.0.0.1:9150 (это порт Tor Browser; для отдельного tor-демона — 127.0.0.1:9050).
    • Нажми «🧅 Подставить .onion» — заполнит поле адреса зеркала.
    • Нажми «🔌 Проверить» — это пробный запрос к зеркалу через прокси. Должен показать зелёный пилл «OK · <время> мс · » (3–6 секунд через Tor — нормально). Если красный — посмотри текст ошибки.
    • Когда тест зелёный, поставь галку «Восстанавливать удалённые посты через .onion».
  4. Перезапусти приложение — сетевой клиент строится один раз при старте, ему нужен фреш-старт чтобы подцепить прокси.

После перезапуска: когда в выдаче встречается DMCA-стаб и восстановление включено, приложение в фоне дёргает зеркало, парсит HTML, достаёт attribute.id, и плитка превращается из серой «удалено» в нормальную с картинкой. Качается всё с основного clearnet CDN, через Tor только метаданные ходят (так быстрее и не нагружает Tor-релеи).

Восстановление работает одинаково и в гриде превью (плитка из серой превращается в нормальную, файлы доступны для ручного отбора), и в массовой загрузке через очередь задач (+ Добавить в очередь): пайплайн постранично собирает DMCA-стабы из выдачи в батч, параллельно (4 воркера) скрейпит зеркало через тот же SOCKS5, и подмешивает восстановленные attribute.id в загрузку транзитом — те же фильтры по дате/тегам/размеру, та же дедупликация по манифесту, тот же лимит. Если recovery не сконфигурирован или конкретный пост на зеркале тоже отсутствует, стаб тихо пропускается и clearnet-часть продолжает работать. Бюджет восстановления — 90 секунд на страницу выдачи.

Что важно понимать

  • Восстановление опционально и off-by-default. Если ты этой функцией не пользуешься — никакой Tor устанавливать не нужно, всё работает как раньше через обычный clearnet GraphQL.
  • Это касается удалённых по копирайту постов. Если файл физически удалён с CDN (бывает на старых-старых постах, где JR прибрался) — никакое зеркало уже не поможет.
  • Картинки качаются с обычного CDN, не через Tor — для бинарных файлов Tor медленный и его сеть не для этого.

Сеть — SOCKS5 для GraphQL

Опционально: можно завернуть только GraphQL-запросы (не скачивание файлов) в локальный SOCKS5-прокси. Off-by-default, никаких прокси-бинарей в дистрибутиве нет — пользователь подключает свой.

Сценарии использования — на усмотрение пользователя. Тех же .onion-восстановлений достаточно для большинства, но кому-то может быть полезен и сам тогглер.

Скачивание

Тот же набор файлов, что и в v1.0.1 — Windows portable / NSIS-setup, macOS universal .app, Linux .tar.gz.

Лицензия

MIT.

Full Changelog: v1.0.1...v1.0.2

v1.0.1

17 May 22:38

Choose a tag to compare

Точечные багфиксы поверх v1.0.0 — без новых фич. Обновляться имеет смысл, если вы на Intel-Mac (v1.0.0 там не запускался), часто фильтруете теги через профильные блокировки, или замечали, что приложение разлогинивает после перезапуска.

macOS

  • Universal binary.app теперь содержит и arm64 (Apple Silicon), и amd64 (Intel) слои в одном файле. v1.0.0 был только под Apple Silicon и на Intel-Mac выдавал «приложение не поддерживается на этом Mac».
  • Правильная структура .app-bundle в zip — раньше архив разворачивался в Contents/ без верхней .app-папки, из-за чего бандл не опознавался macOS как приложение и не запускался с двойного клика.

Авторизация

  • Сессия больше не «забывается» между запусками. До этого session.json записывался только в момент логина и не обновлялся, когда JR ротировал session-cookie во время обычной работы. На следующем запуске приложение восстанавливало уже невалидный cookie и тихо отваливалось в режим «гостя».
  • Теперь сессия сохраняется на диск при выходе из приложения (OnShutdown) и периодически каждые 5 минут во время работы — на случай, если процесс убьют через taskmgr или комп выключится без штатного закрытия окна.
  • Если сейчас ты анонимный (например, после Logout) — периодический save не перезаписывает файл, так что валидная сессия из другого источника не затирается.

Фильтры

  • Alias-варианты заблокированных тегов. JR группирует разные написания тега (латиница/кириллица, регистр) под одной канонической записью через Tag.mainTag. v1.0.0 фильтровал только по точному имени, поэтому посты, помеченные alias-вариантом каноничного блокированного тега, проскакивали. Теперь сверка идёт и по имени, и по mainTag.name — фильтр ведёт себя так же, как сам JR на сайте.

UI

  • Поля фильтров и autocomplete не мигают во время активной загрузки. Раньше каждое событие job:update (одно на скачанный файл) триггерило полный rebuild DOM — тег-дропдаун исчезал прямо перед кликом, выбрать что-либо во время скачивания было нельзя.
  • Фокус ввода и позиция каретки сохраняются между перерисовками — если идёт фоновое обновление, пока ты печатаешь, курсор остаётся на том же месте, можно дальше набирать без перерывов.
  • Тег, выбранный через autocomplete (клик/Enter), теперь моментально появляется в виде чипа, даже если в этот момент идёт активная загрузка — раньше при включённом defer'е чип мог не показаться, пока фокус не уйдёт из инпута.

Скачивание

Тот же набор файлов, что и в v1.0.0 — Windows portable / NSIS-setup, macOS universal .app, Linux .tar.gz. Инструкции по запуску и Gatekeeper-обходу — см. release notes v1.0.0.

Лицензия

MIT.

Full Changelog: v1.0.0...v1.0.1

v1.0.0

17 May 18:14

Choose a tag to compare

First public release of joyreactorDownloader — кросс-платформенное (Windows / macOS / Linux) десктоп-приложение для пакетного скачивания картинок с joyreactor.cc по фильтрам.

Стек: Go + Wails v2. Один статический бинарник, без внешних рантаймов. Использует официальный GraphQL API Joyreactor — никакого HTML-скрейпа.

Ключевые возможности

  • Фильтры: текст, теги (включая исключения с поддержкой alias-вариантов: блокировка каноничного тега автоматически ловит его варианты написания), автор, рейтинг, NSFW/unsafe, «только избранное» (с логином), тип медиа, мин-размеры, диапазон дат, лимит файлов.
  • Пресеты с привязкой папки — последний выбранный восстанавливается при следующем запуске.
  • Очередь задач с паузой / возобновлением / отменой и параллельными воркерами; несколько задач одновременно.
  • Превью: сетка с бейджами (NSFW / тип медиа / число картинок / «уже скачано»), full-screen post overlay с комментариями, кликабельные теги-пилюли.
  • Ручной выбор: чекбоксы на тайлах + резинка-выделение мышкой; «+ Сохранить» для одиночного поста из оверлея.
  • Дедупликация через .manifest.json — два режима в Settings: per-folder или общий для всех папок.
  • Имена файлов: numeric IDs (12345_67890.jpg) или [tag1][tag2][tag3]_12345_67890.jpg (теги сортируются алфавитно → одинаковый пост даёт одинаковое имя).
  • Системные toast-уведомления при завершении задачи (нативные на Windows).
  • Single-instance lock — повторный запуск exe фокусирует уже открытое окно.

Скачивание и запуск

OS Файл Как запустить
Windows 10/11 — portable joyreactorDownloader-v1.0.0.exe Двойной клик. Нужен WebView2 Runtime (см. ниже).
Windows 10/11 — установщик joyreactorDownloader-v1.0.0-setup.exe NSIS-инсталлер. Сам поставит приложение в Program Files и доустановит WebView2 если его нет.
macOS 11+ joyreactorDownloader-v1.0.0-macos.zip Распаковать → перетащить joyreactorDownloader.app в /Applications/. См. «Запуск на macOS» ниже — первый раз Gatekeeper заблокирует.
Linux joyreactorDownloader-v1.0.0-linux.tar.gz tar xf …tar.gz && ./joyreactorDownloader. Нужны системные пакеты libwebkit2gtk-4.0-37 + libgtk-3-0 (стоят по умолчанию в Ubuntu 22.04, Fedora 38, Debian 12).

Сборка из исходников — см. README.

Запуск на Windows

Минимально поддерживается Windows 10 build 1803 (апрель 2018) или новее.
Windows 7 / 8 / 8.1 не поддерживаются — Wails использует WebView2 (нативный
Chromium-движок от Microsoft), а Microsoft перестал выпускать WebView2 Runtime
для этих ОС ещё в январе 2023 (вместе с EOL для Microsoft Edge на них). На Win
7/8 приложение не запустится.

Про сам WebView2 Runtime:

  • Windows 11 — предустановлен, никаких действий.
  • Windows 10 1803+ с актуальными обновлениями (де-факто 99% машин) — тоже предустановлен, ставится автоматически вместе с Edge через Windows Update с 2021 года.
  • Если на старой / LTSB / отключённый-Windows-Update машине его не оказалось, portable .exe скажет об этом ошибкой при запуске. Варианты:
    • Скачать Evergreen Standalone Installer от Microsoft и поставить руками.
    • Или просто скачать ...-setup.exe — он содержит встроенный WebView2-бутстраппер и поставит всё что нужно за один проход.

Запуск на macOS

Сборка не подписана Apple Developer ID (это платная подписка на $99/год, для free-opensource обычно не оформляют). При первом запуске macOS Gatekeeper выдаст «joyreactorDownloader не открывается, потому что разработчик не может быть проверен». Это не вирус — просто отсутствие подписи.

Обойти один раз любым из способов:

  1. System Settings (macOS 13+ Ventura и новее): открой System Settings → Privacy & Security, проскролль вниз — там будет строчка «joyreactorDownloader.app was blocked from use» + кнопка «Open Anyway».
  2. Терминал (любая версия) — снять флаг карантина руками:
    xattr -d com.apple.quarantine /Applications/joyreactorDownloader.app
  3. Правый клик → Open (macOS до 14 Sonoma включительно): правый клик по .app → Open → в диалоге второй раз нажать «Open» (не «OK»).

После любого из этих шагов приложение запоминается как доверенное, дальше открывается обычным двойным кликом.

Лицензия

MIT.

Full Changelog: https://github.com/SeregaSrayk/joyreactorDownloader/commits/v1.0.0