Releases: SeregaSrayk/joyreactorDownloader
v1.0.4
Поверх 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), и пишет его в каждую задачу одного поста. Параллельные воркеры не могут переставить картинки одного поста в разные подпапки, потому что подпапка уже зафиксирована в задаче.
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
Поверх 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 уже скачаны. По этому файлу при следующих сканированиях фильтра решается, какие картинки уже есть локально (бейдж «✓» на плитке, пропуск в очереди загрузки). Раньше его можно было только удалить целиком («забыть всё»). Но если часть файлов в папке загрузок перетасовали мимо приложения (переименовали, перенесли между подпапками, дозалили картинки извне, импортировали папку из другого даунлоадера), то манифест и реальное содержимое уходили в рассинхрон, и приложение либо качало дубликаты, либо считало удалённые файлы скачанными.
В ⚙ → Манифест добавилась кнопка «🔄 Полностью пересобрать по файлам из директории…»:
- Указываешь папку для сканирования.
- Приложение рекурсивно её обходит, парсит имена в одном из четырёх поддерживаемых форматов:
<номер_поста>_<id_аттрибута>.<ext>— наш дефолтный «По ID».[тег1][тег2]..._<номер_поста>_<id_аттрибута>.<ext>— наш «По тегам (в скобках)».<номер_поста>_<0|1>_<id, padded к 9>__<тег1>-<тег2>...<ext>— наш новый «JoySave-совместимый» формат (см. ниже), а также имена, написанные JoySave.<slug-через-дефисы>-<id_аттрибута>.<ext>— формат JR CDN, то есть имена картинок, сохранённых из браузера правой кнопкой.
- Манифест приводится в точное соответствие с тем, что найдено на диске:
- неизвестные файлы — добавляются (с текущим 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 символов (
67890→000067890). - Тег-блок: первые 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
Главное в этом релизе — детектирование удалённых по 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-листенеру.
Как настроить
- Скачай и установи Tor Browser с torproject.org.
- Запусти его, нажми «Соединиться» на стартовом экране, дождись появления зелёного лука («Поздравляем»). Окно Tor Browser должно оставаться запущенным, пока пользуешься восстановлением — закроется он, наш downloader потеряет прокси.
- В downloader'е открой ⚙ → Сеть:
- Включи галку «Использовать SOCKS5-прокси для GraphQL». Поле «SOCKS5 адрес» по дефолту
127.0.0.1:9150(это порт Tor Browser; для отдельногоtor-демона —127.0.0.1:9050). - Нажми «🧅 Подставить .onion» — заполнит поле адреса зеркала.
- Нажми «🔌 Проверить» — это пробный запрос к зеркалу через прокси. Должен показать зелёный пилл «OK · <время> мс · » (3–6 секунд через Tor — нормально). Если красный — посмотри текст ошибки.
- Когда тест зелёный, поставь галку «Восстанавливать удалённые посты через .onion».
- Включи галку «Использовать SOCKS5-прокси для GraphQL». Поле «SOCKS5 адрес» по дефолту
- Перезапусти приложение — сетевой клиент строится один раз при старте, ему нужен фреш-старт чтобы подцепить прокси.
После перезапуска: когда в выдаче встречается 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
Точечные багфиксы поверх 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
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 не открывается, потому что разработчик не может быть проверен». Это не вирус — просто отсутствие подписи.
Обойти один раз любым из способов:
- System Settings (macOS 13+ Ventura и новее): открой
System Settings → Privacy & Security, проскролль вниз — там будет строчка «joyreactorDownloader.app was blocked from use» + кнопка «Open Anyway». - Терминал (любая версия) — снять флаг карантина руками:
xattr -d com.apple.quarantine /Applications/joyreactorDownloader.app
- Правый клик → Open (macOS до 14 Sonoma включительно): правый клик по
.app→ Open → в диалоге второй раз нажать «Open» (не «OK»).
После любого из этих шагов приложение запоминается как доверенное, дальше открывается обычным двойным кликом.
Лицензия
MIT.
Full Changelog: https://github.com/SeregaSrayk/joyreactorDownloader/commits/v1.0.0