[Назад] [Вся нить] [Первые 100 сообщений] [Последние 50 сообщений]
Ответ в нить
Имя
Captcha image [Д]
Animapcha image [@] [?] [Т]
Тема    ( ответ в 1000686)
Сообщение
flower
Предпросмотр
Файл 
Пароль  (для удаления файлов и сообщений)
Параметры   
140368429_p0.jpg - (2.48MB, 4000×4000)
1000686
No. 1000686  
Замечания и предложения по нему, а также их обсуждение.
Предыдущий тред тонет тут >>1000357.
No. 1000687  
142342729_p0.jpg - (520.74KB, 768×768)
1000687
>>1000685
На это первое апреля была музыка же.
>>/b/1006241
No. 1000690  
125129096_p0.webp - (1.87MB, 2160×3030)
1000690
В первом приближении bulk deletion для Автобуса готово.

В принципе, можно сюда, но без правок не выйдет. Даже по схеме успели разойтись! В одном столбце, правда.

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

1. Объеденить post_* в одну таблицу, выпилив текстоборды.
2. Посолить KU_RANDOMSEED'ом ipmd5, ибо в текущем виде это от утечки БД не защита; look-up таблица в 4 * (2 ** 32) = 16 GiB в оперативке ноутбука уместится. Или, наоборот, выпилить и ipmd5, и ipenc, сделав простое и быстрое поле в 4 байта.
3. Придумать индексы для таблиц. Скажем, (board_id, parent_id, id) в качестве кластерного для posts, (board_id, ip, id) для быстрого поиска по ip, (board_id, parent_id, posted_at) для бампов, и так далее.
3.a) Есть ли смысл включать флаги в индексы? has_sage, в силу перманентности, наверное стоит, сделав (board_id, parent_id, posted_at) фильтрованным. MariaDB в такое, правда, не может, но повод перейти на Postgres как раз таки. А вот is_deleted, хоть по нему фильтрация почти везде, может включением в индекс существенно замедлить удаление из-за необходимости в ребалансировке. Или нет? Не знаю. Почему-то я помню, что InnoDB, даже если столбец-флаг в конец ключа поставить, всё равно при измении внутри ребалансировку делает: сначала удаляет leaf, потом добавляет на то же место. Но это не точно.
3.b) Есть ли смысл поменять parentid=0 для открывающих постов тредов на parentid=id? Чтобы без union'ов select'нуть весь тред по const ref. Но тогда нельзя будет select'нуть ОП'ы тредов по const ref. Хотя опять же, проблема решается созданием filtered index. Так что всё-таки следует. Любопытно, что в class="ref|parentid|id" у нас именно такой формат.
3.c) Добавить столбец, где кэшировать отрезок из последних 7 постов треда, и столбец, где кэшировать вывод для read.php.
3.d) Пользуется ли кто-то watchedthreads? Если да, то не переделать ли на более щадящую архитектуру, где пользователь запоминает в local storage, какие он смотрит треды, и спрашивает у сервера только их последние IDшники? Если нет, то не выпилить ли?
3.e) И так далее.

Как смотрите? Сделать миграцию на Postgres, потом заняться конфигом, потом сделать-таки улучшенную версию скриптовой отправки поста, ибо одни только подкапотные изменения тоже не дело.
No. 1000693  
ПОЖЕЛАНИЕ!
Сделайте лучше нормальный тёмный стиль! И чтобы с сотового работало!
А то я даже не знаю как в мобильной версии переключить, чтобы хотябы на bluemoon. Который тоде недостаочно тёмен всё равно.
На пк у меня расширение со своим стилем, а на мобильном вроде тоже можно как-то настроить но жто разбьираться надо. И с работы когда или с ноутбука то тоже везде надо настроить ведь. А сейчас только на домашнем пк!
No. 1000694  
>>1000693
Android?
No. 1000695  
>>1000694
У мкеня и то и другое!
No. 1000696  
> И чтобы с сотового работало!
Тут тоже непонятно. Чтобы стиль работал, или вообще что-то другое работало. А то FBE всё адаптировали да адаптировали под эти мобилки, н ж уже респонсивный(tm) , а всё зря, постеры-то разбежались lol. Для будущих поколений работа прям..
No. 1000697  
Тоже есть FR. Раз уж аплоад-форма умеет выдавать сообщения теперь, пусть сообщает, в каких сообщениях была уже запощена картинка. Только не знаю, умеет ли это сообщение быть sticky, т.е остаться-показаться после перезагрузки страницы по прошедшему посту - т.к. мы не запрещаем отправку дубликатов.
Другими словами: уведомление юзеру, а не ошибка, о наличии дубликатов файлов.
No. 1000700  
136218327_p0.webp - (1.67MB, 1333×2000)
1000700
>>1000693
Сейчас переключить можно, открыв desktop'ную версию сайта, и переключившись по выбору нужного стиля обратно на мобильную. Либо так, либо некими браузерными дополнениями, где можно задать пользовательские стили для сайта.

Проблема в том, что, если я верно помню, некоторые свойства для мобилок у стилей, кроме Умночана, не определены.

И надо подумать, куда ставить кнопку переключения в мобильной версии. Может, отдельную форму настроек, где стиль, вариант капчи, и подобное можно будет поменять; сделать значок-шестерёнку и вместо/слева от watchedthreads её поставить?

> Сделайте лучше нормальный тёмный стиль!
> На пк у меня расширение со своим стилем
Запостите же.

>>1000697
Sticky нет, не умеет. Умеет зависать (lingering) на какое-то количество секунд, если PHP неожиданно сделало echo, по истечению которых происходит перенаправление. Сделать кнопку, чтоб закреплялось? И сделать, чтобы перенаправляло сразу, но по перенаправлению восстанавливало показываемый status bar. Может, также дать возможность открепить движением мышки, как с формой быстрого ответа?

Другое дело, что в текущем виде у status bar'а стиль ярко-красным кричащий об ошибке, а для чего-то нейтрального, наверное, и цвета понейтральнее?

На сервере, если по-простому, добавить exitWithNotification($message, $redirect_to).

Альтернативно, endpoint, проверяющий наличие hash'а в базе, чтобы можно было по выбору файла в JSе посчитать его md5 и тут же отправить на сервер, не дожидаясь отправки поста вместе с файлом целиком. read.php ведь позволяем без капч и ограничений; если сделать индекс по хэшу, будет не затратнее. Но это если индекс. Так что я бы сначала в целом базу поправил.
No. 1000701  
>>1000696
Да, я про стиль. Так по работе особо ничего поломанного не замечалось.

>>1000700
>открыв desktop'ную версию сайта, и переключившись
Я так на андроиде делаю, но айфон игнорирует переключение на полную версиюб и не пееключает почему-то. Хотя другие сайты переключает.
>Запостите же.
Там просто покрашеный умночан по мотивам одной другой картинколоски. Есть пару мест которые недоделаны, но я и так живу.
У меня там с годами совсем каша была и кучас своих пометочек, так что попросила ИИ поправить, вроде ничего не сломалось.
https://pastes.io/cBPq0Tsf
No. 1000706  
mint?.webp - (284.93KB, 2505×1046)
1000706
>>1000701
> https://pastes.io/cBPq0Tsf
В целом, цветовая схема понятна. Mint назвать?
Если Якуй одобрит, займусь кнопкой, чтобы и на мобильной версии переключалось, после >>1000697, наверное.
> У меня там с годами совсем каша была и кучас своих пометочек, так что попросила ИИ поправить, вроде ничего не сломалось.
Но сам CSS, буде тот приведён в соответсвие с https://codeberg.org/yakui-lover/fbe-410/src/branch/kotocafe/css/umnochan.css, наверное, можно и сейчас добавить. Пока вижу, что стиль кнопки переключения стилей отличается, и что спойлер не сплошным цветом и что с задержкой по наведению курсора исчезает (наверное, серым в тон текста сделать).
No. 1000707  
> Заголовок слева у предпросмотра теперь показывает «Предпросмотр»
Хорошо.
No. 1000708  
Сломан постинг видеов, поскольку теперь imgWidth/Height строго int и вывод ffprobe нужно конвертировать. Патч в архиве.
No. 1000709  
>>1000708
Принято.

>>1000690
>Объеденить post_* в одну таблицу, выпилив текстоборды.
Это надо будет не просто переписать всю логику запросов, но и логику инкремента счётчика постов. В Постгре-то точно счётчики есть...
>Посолить KU_RANDOMSEED'ом ipmd5, ибо в текущем виде это от утечки БД не защита
Пусть будет одно решение для паролей и ип, пусть и разные соли. Зачем в оригинале два столбца не слишком понятно.
...хотя "посмотреть посты с подсети" теоретически хорошая вещь.
>(board_id, ip, id) для быстрого поиска по ip
id тут не требуется.
>Есть ли смысл включать флаги в индексы?
Наверное не в наших объёмах. После всей фильтрации, они будут отрабатывать на, сколько, 250 постах максимум за раз? Ну, больше чтобы сделать индекс если мы будем бесконечно резиновыми или если подлодка.
>Есть ли смысл поменять parentid=0 для открывающих постов тредов на parentid=id? Чтобы без union'ов select'нуть весь тред по const ref.
Я в своей лабуде решал это через отдельную таблицу для тредов. Туда уже пихается sticky/locked/счётчики видимых постов.
>последних 7 постов треда
Последних N постов треда, это настраиваемая опция. И раз она конфиг-зависимая, наверное нет.
>столбец, где кэшировать вывод для read.php
Там разве вывод хоть как-то отличается? Или в том плане, чтобы шаблонизатор не дёргать? Тогда лучше жсон отдавать, ибо головная боль будет при пересборке кешей ещё и всю БД переписывать.

>>1000700
>Но это если индекс
file_md5 уже индексируется.
>Альтернативно, endpoint, проверяющий наличие hash'а в базе, чтобы можно было по выбору файла в JSе посчитать его md5 и тут же отправить на сервер, не дожидаясь отправки поста вместе с файлом целиком.
Удобно, можно и так.
>read.php ведь позволяем без капч и ограничений
И постопревью, да.

>>1000706
>Если Якуй одобрит
Мне-то что, я не против даже что сейчас дали залить.
No. 1000710  
Алсо, я всё ещё не >>1000340
No. 1000711  
>>1000700
Словом "sticky" хотелось передать поведение
> по перенаправлению восстанавливало показываемый status bar
Который висит и ждет нажатия юзверем крестика, после чего пропадает, стирается, исчезает.

> Сделать кнопку, чтоб закреплялось
> возможность открепить движением мышки, как с формой быстрого ответа?
Не нужно. Т.к. совсем не в том смысле.

> а для чего-то нейтрального, наверное, и цвета понейтральнее?
Да.

> endpoint, проверяющий наличие hash'а в базе
Тут подумалось, что можно сообщение "было запощено тут и тут" выводить в качестве ("ложного") "post preview", переиспользуя эту фичу.

>>1000710
> 2024
Так и вся жизнь пройдёт w.

На основном fbe зачем-то убрали каноничную сажирующую стрелочку вниз в "теме" у постов, которая была с нами годами! Заменив какой-то непонятной фигней, которую я рассматривал через экранную лупу, пытаясь понять, что это. Расстроили.
No. 1000712  
143989676_p0.jpg - (384.02KB, 445×800)
1000712
>>1000709
> но и логику инкремента счётчика постов
Добавить в boards столбец new_post_id, и insert'ить в posts как-то так.
WITH new_id_source AS (
  UPDATE boards
  SET new_post_id = new_post_id + 1
  WHERE board_id = :board_id
  RETURNING old.new_post_id AS post_id_to_insert
)
INSERT INTO posts
SELECT :board_id, post_id_to_insert, :parent_id, ...
FROM new_id_source


> Зачем в оригинале два столбца не слишком понятно.
Дело в том, что Kusaba'вый md5_encrypt шифрует с nonce'ом (N_once) — случайно генерируемым значением, из-за которого шифротекст всегда разный — даже если IPшники одинаковые.

Поэтому, если бы не ipmd5 столбец, пришлось бы ходить foreach'ем по SELECT `id`, `ip` FROM `posts_*` WHERE NOT IS_DELETED, чтобы найти все посты по нужному IP. Хотя, now that I think of it, md5_decrypt можно написать и на SQL, чтобы не дёргать лишнего с базы, но это всё равно ходить по всем постам, пусть и не в PHP.

Поэтому, если хочется этого избежать, то столбца нужно два. Либо два столбца; либо сменить алгоритм шифрования с md5_encrypt на что-то более удобное для поиска, что по-хорошему и стоит сделать.

https://www.cryptologie.net/posts/key-wrapping-and-nonce-misuse-resistance/
Насколько я понимаю, в самом PHP нету детерминистичных алгоритмов шифрования, не уязвимых к keywrap/keystream reuse attack, когда nonce константа.

Надо будет добавить extension=openssl в php.ini и взять AES-128-SIV оттуда; впрочем, ничего сложного.

> Пусть будет одно решение для паролей и ип, пусть и разные соли
Если делать 1 столбец для IP, то решение для паролей будет другое. Думается взять в качестве хэша либо bcrypt(sha512(password)), либо openssl_pbkdf2() с SHA-256.

Для файлов тоже SHA-256? Apparently, MD5 нету в браузерном Web Crypto API. А если какие-то дополнительные lib'ы тянуть, то появляется смысл перейти на blake2 (b2sum), который без коллизий и быстрее. Или даже на blake3, но тут lib'а уже и для PHP понадобится.

> id тут не требуется
Даже чтобы id-шники по IP шли в порядке возрастания, как оно, наверное, сейчас?

InnoDB по-тихому добавляет незадействованные столбцы кластерного индекса в конец других индексов. Что, если делать (board_id, parent_id, post_id) кластерным индексом, думаю, привело бы к тому, что IDшники в индексе по IP шли бы в порядке тредов, а не в порядке возрастания, как оно, предполагаю, сейчас.

Маны PostgreSQL говорят, что у них heap и нету кластерных индексов, так что будут отображаться в порядке добавления в таблицу? Наверное, и правда не надо. Хотя если вдруг захочется 'ORDER BY id DESC', могло бы быть полезно.

> Я в своей лабуде решал это через отдельную таблицу для тредов. Туда уже пихается sticky/locked/счётчики видимых постов.
С Postgres я всё-таки склоняюсь к тому, чтобы не нормализовать posts. Меньше join'ов писать и базе меньше join'ов делать. Postgres пропускает помеченные, как NULL, значения в записи строки, так что меньше места на диске база от выноса специфики про файлы и треды в отдельные талбицы занимать не станет.

> Последних N постов треда, это настраиваемая опция. И раз она конфиг-зависимая, наверное нет.
Добавить в таблицу posts столбец cache_timestamp, который заполнять и сравнивать с timestamp'ом конфига и timestamp'ом настроек доски для тредов, охватываемых генерацией их страниц и страниц досок.

> Там разве вывод хоть как-то отличается? Или в том плане, чтобы шаблонизатор не дёргать?
Отличается вывод или нет, точно не знаю, но да, чтобы отдать готовый HTML.

Насколько я понимаю, этот HTML поста иммутабельный и меняется разве что если посту файл удалить или настройки для доски/глобальные поменяются. Как правило, не меняются последние N постов тредов на страницах досок. Как правило, не меняются и сами эти страницы. Не всегда меняются +50 и -100 при удалении постов или их прикреплённых файлов. Пока не меняются настройки доски, форма постинга на её страницах иммутабельна за исключением "Ныне N уникальных клиентов".

Вообще говоря, при добавлении/удалении постов, из страниц всегда меняется только страница каталога.

Так что тут простор для кэширования довольно большой. Мне не кажется, что это очень уж сложно, хотя и требует переписать FBE значительных переделок.

Так что я бы пока добавил в boards и posts необходимые для кэшей столбцы, пусть будут DEFAULT NULL, чтобы не мешало тому, как оно сейчас есть.

И как-нибудь потом потихоньку, не трогая Кусабу, завёл в inc отдельную директорию services, где держал бы новые DeletePostService, CreatePostService, UpdatePostCacheService, GenerateThreadPageService и так далее, потихоньку привязывая endpoint'ы к ним.

Некоторое свободное время/желание пока есть.
No. 1000714  
138691214_p0.jpg - (5.22MB, 2379×2868)
1000714
>>1000712
> либо openssl_pbkdf2() с SHA-256
On the other hand, всё-таки несколько медленноватое оно даже при не столь высоком количестве итераций. Если постер потрудится запомнить ещё 3—4 лишних символа или ещё одно слово, то hash_hmac('sha256', $pwd, KU_RANDOMSEED) будет с тем же эффектом, но без нагрузки на сервер. Явно указать где-нибудь в FAQ алгоритм хэширования и минимальную длину пароля, если хочется, чтобы и ASICом на 2**50 H/sec не проняло.
No. 1000715  
>>1000712
>Насколько я понимаю, в самом PHP нету детерминистичных алгоритмов шифрования, не уязвимых к keywrap/keystream reuse attack, когда nonce константа.
У атакующего нет возможности сделать произвольный айпишник.
>Даже чтобы id-шники по IP шли в порядке возрастания, как оно, наверное, сейчас?
Eh, оверкилл в общем случае наверное. Но стоит.
>Apparently, MD5 нету в браузерном Web Crypto API.
Там вообще хеширования нет, лол.
>то появляется смысл
Смысл есть использовать то, что используют все, чтобы поискав картинку на данбуре не приходилось пересчитывать хеш для поиска тут.
>Если делать 1 столбец для IP, то решение для паролей будет другое.
Айпишник тот же пароль, только короче. Хотя можно ipv6 форсировать.
>Не всегда меняются +50 и -100 при удалении постов или их прикреплённых файлов.
-100 меняется всегда кроме удаления верхних файлов.
>Вообще говоря, при добавлении/удалении постов, из страниц всегда меняется только страница каталога.
И страница треда.
>Так что тут простор для кэширования довольно большой.
Люди для этого обычно кеширующий сервер ставят...

>>1000714
Если я хочу использовать :) в качестве пароля, никот не должен мне это запрещать, особенно когда под этим паролем ничего кроме возможности удалить пост нет. ИМО.
No. 1000716  
sha256.webp - (146.77KB, 1659×996)
1000716
>>1000715
> У атакующего нет возможности сделать произвольный айпишник
https://hacker101.linuxsec.org/vulnerabilities/stream_reuse
А произвольный и не обязательно. Если nonce константа, то в зависимости от алгоритма, насколько я понимаю, достаточно одного поста в утёкшей базе, для которого атакующему IPшник уже известен, чтобы получать IPшники к остальным.

AES не streaming chipher, но там вроде какие-то свои проблемы, раз сделали AES-SIV специально для. Так что стоит использовать AES-SIV.

> Там вообще хеширования нет, лол.
Но оно там есть? Pic related.

> Cмысл есть использовать то, что используют все, чтобы поискав картинку на данбуре не приходилось пересчитывать хеш для поиска тут.
Действительно. Хотя какой-нибудь весельчак когда-нибудь начнёт пытаться постить файлы с одинаковыми или прикольными хэшами for lulz. Но, наверное, не особо страшно, хотя возможность одним запросом выбрать байт-в-байт одинаковые дубликаты теряется.

Другое дело в том, что картинки бывает пережимаются, и как поиск дубликатов содержимого оно в таком случае так себе. Стоит ли когда-нибудь потом запилить хэш ещё и для пикселей? Forgery оно не остановит, достаточно один пиксель поменять, но поможет найти пережатый с cwebp или jpegoptim файл.

> Айпишник тот же пароль, только короче.
Если мы для IPшников храним только хэш, то разве не теряем возможность забанить оставившего пост по подсети? И разве IP постеров в модерке (например, на странице жалоб) отображаются не в голом виде? (Хотя, можно отображать и хэш.)
А для паролей достаточно хранить только хэш.
В этом разница.

> Люди для этого обычно кеширующий сервер ставят...
Redis? Хм. Могу и так, но с БД-only в первом приближении несколько проще сделать, мне кажется.

> Если я хочу использовать :) в качестве пароля, никот не должен мне это запрещать, особенно когда под этим паролем ничего кроме возможности удалить пост нет. ИМО.
А запрещать и не предлагается. Предлагается поставить совет в FAQ. Или, что точнее, заметку.
No. 1000717  
>>1000716
>Хотя какой-нибудь весельчак когда-нибудь начнёт пытаться постить файлы с одинаковыми или прикольными хэшами for lulz.
В очередной раз, не тот масштаб... Плюс у нас нет запрета на репост если только не в том же треде. В архиве у меня параноя-мод и при коллизии сравниваются разрешение, размеры и затем побитово.
>Стоит ли ..?
Если интересно, то пусть делается, единственные две преграды что я вижу: 1. Это не работает 2. Это недоделано.
>картинки бывает пережимаются
Или кто-то в них раржпег кидает, ага.
>Если мы для IPшников храним только хэш, то разве не теряем возможность забанить оставившего пост по подсети
А, точно. И в голом, чтобы можно было подсеть определить в случае однотипных банов. Может для джениторов только хешем правда. Тогда вопроса нет. И я даже не уверен, влезет ли сейчас сюда ipv6.
В постгре под них даже свой тип, но без встроенного шифрования... но удобно было бы смотреть посты с подсети наверное.

Кстати о встроенном, есть https://dev.mysql.com/doc/refman/8.4/en/encryption-functions.html или https://www.postgresql.org/docs/current/pgcrypto.html. Хоть я и не люблю выносить код из кода в БД, и для постгри это отдельный модуль ставить, но может можно использовать так.
>с БД-only в первом приближении несколько проще сделать, мне кажется.
Мне просто кажется что проще просто выкинуть шаблоны и всё во фронте рисовать чем заниматься кешем ручками.
No. 1000718  
clusters_64.webp - (27.53KB, 451×1290)
1000718
Попробовал посмотреть, насколько применим fingerprint картинок, который phash, к Булочным картинкам.

Картинки брал с 15544700221 по 176095181827 без гифок, всего 16112 картинок.

Находится 113 групп по одинаковому phash, состоящих из двух или более картинок: только в одной из этих групп картинок 3, а в остальных по паре дубликатов.

Из них 6 false positive.
(>>/b/5852, >>/b/10732), (>>/b/3154, >>/b/3161), (>>/b/2512, >>/b/8466), (>>/b/7767, >>/b/19060), (>>/b/11092, >>/b/11456)
False positive в том плане, что это легитимные вариации той же картинки или творчество по мотивам другой, а нам скорее найти, было ли ровно то же самое уже запощено в оригинальном или переужатом виде или с другой шириной/высотой.

41 из тех 113 — целиком посты, у которых MD5 пикселей совпадают, то бишь lossless переужатие/оптимизация или какое-нибудь echo 'new_hash' >> file.jpg.

Остальные 113 - 41 - 6 = 66 — ужатые с потерями или с изменением разрешения дубликаты, которые попиксельным сравнением не найти. Например, (>>/b/12578, >>/b/12935), (>>/b/16642, >>/b/18597), (>>/b/3372, >>/b/4413).

Однако, увы, находит оно их ужатые с потерями не все.

Попробовал увеличить размер phash с 64 бит до 256, и, внезапно, нашло ранее не обнаруженый (>>/b/5816, >>/b/7377). Хотя казалось бы, большая специфичность, и я 16x16 окно чтобы false positives убрать тестировал. False positive там действительно получилось на 4 меньше, но появляется false negative и значимо чаще; всего найдено было 99 групп, когда phash 256 битов, против 113, когда phash 64.

Если повышать чувствительность, группируя phash'и, не чтобы равны были, а чтобы на растоянии не более чем в N отличающихся битах, false positives становятся куда менее опрятными, хотя настоящих дубликатов и находится больше.

Для 64-битного phash, когда N равно 4, посчитало равными >>/b/11948 и >>/b/16305, что уж совсем грубая ошибка. Впрочем, настолько грубая она там была единственная. Чаще (>>/b/22454, >>/b/22462), (>>/b/8097, >>/b/15811), (>>/b/8481, >>/b/8513, >>/b/8614). Что в определённых случаях даже и хорошо, когда интересно поискать похожие, а не только одинаковые картинки или удостовериться, что ничего одинакового почти наверняка нет. Но, хм. Скажем, видит человек >>/b/23618, решает >>/b/23620 edit сделать и запостить, а ему раздражающее уведомление про только что переделанный якобы дубликат, которое надо специально закрывать.

Так что если делать автоподгрузку ссылок на посты, где картинки скорее всего одинаковые, пусть, наверное, будет либо только по равенству MD5 пикселей или только по равенству phash'ей (?), но не по расстоянию меж. Как и куда вынести возможность поиска по расстоянию, если делать, надо будет думать. Особенно так, чтобы и без скриптов работало.
No. 1000720  
>>1000718
> Чаще (>>/b/22454, >>/b/22462)
Не >>/b/22454, а >>/b/22452, то бишь.

Это, и Булочные, то бишь, bulochka.org.
No. 1000725  
135017946_p0.webp - (12.36MB, 3368×7129)
1000725
Если запиливать IPv6, какой длины префикс брать для faptcha attempts?

> https://dreamserver.ro/en/lir-services/ipv6-lease/
5 евро в год за /48 и 100 евро в год за /32
> https://www.ipxo.com/free-ipv6/
Эти вот дают /36 на месяц даром.

Наверное, тут по-хорошему нужен какой-то сложный rate limiter.
No. 1000726  
144628915_p0.webp - (234.40KB, 1425×2132)
1000726
Маловероятно одновременное несовпадение фапчи у многих пользователей с той же /32 подсети. Так что пока пойду таким путём.

Для /64 пусть ограничение будет, как есть: до двух провальных попыток.

Смотрим на /64, но до этого смотрим на /n in range(32, 64, 4), где ставим threshold на количество провальных попыток, как 2 * (log(64 - n)**3), получая пределы 83, 73, 64, 53, 42, 30, 17, 5 на подсети с /32 по /64 соответственно.

Конечно, текущая капча в целом скорее декоративный забор, чем серьёзное препятствие человеку с намереньями. Но если делать на чёрный день более сложные её варианты, делать rate limit на прокрутку или на что-то ещё, будет задел.
No. 1000727  
Я думала, капча на сессию куки ставится, а не на айпи.
No. 1000728  
А, речь про неправильные попытки отгадывания, я бака. Наверно, имеет смысл взять принцип схожий с принципом, используемым с IPv4. То есть, если для каждого адреса IPv4 вводится ограничение (/32), то и для IPv6 адресов имеет смысл ограничить ровно такое же количесто энтропии (/32 подсеть для адреса). Но это так, рассуждения с дивана. Интуиция подсказывает, что это может быть слишком ограничительная мера, нужна реальная статистика использования v6 адресов на самом сайте и в Интернете вообще.
No. 1000729  
0.webp - (37.67KB, 975×308)
1000729
В RSS у ОПов тредов страница 0.html.
No. 1000730  
>014.cdn.ganbaranai.moe
Однако.
No. 1000731  
prefixa.webp - (38.89KB, 1785×555)
1000731
>>1000728
> то и для IPv6 адресов имеет смысл ограничить ровно такое же количесто энтропии (/32 подсеть для адреса)
https://networkingtoolbox.net/reference/ipv6-prefix-lengths
/32 подсеть в IPv6 — это целый провайдер. Но может быть и атакующий, арендовавший за $10 подсеть на месяц. В том проблема.

Как пишут, конечным пользователям обычно дают /64. Но могут давать и больше или меньше.
No. 1000732  
>>1000731
Блокировать /32 если в его пределах уже есть несколько (сколько?) заблокированных /64?
No. 1000733  
138160271_p0_sample.webp - (4.00MB, 3508×2018)
1000733
>>1000732
> сколько
Пока ничего умнее, чем
> Смотрим на /64, но до этого смотрим на /n in range(32, 64, 4), где ставим threshold на количество провальных попыток, как 2 * (log(64 - n)**3), получая пределы 83, 73, 64, 53, 42, 30, 17, 5 на подсети с /32 по /64 соответственно.
Не придумал.

Разве что добавлять некий scaling factor на основании hash(subnet, salt) и/или hash(time, salt), чтобы было сложнее понять, есть ли кто-то ещё в той же подсети или нет, перегрузив систему.

По схожему принципу ограничивать количество фапчесессий и частоту отправки тредов/постов, наверное.
No. 1000734  
Небольшое замечание:
https://014chan.org/favicon.png > 404
https://014chan.org/favicon.ico > favicon.ico который СОВСЕМ не ico - [0x89 0x50 0x4E 0x47] вместо [0x00 0x00 0x01 0x00]

Забавно.
No. 1000743  
Хотелось бы выяснить - где-то есть актуальная версия автобусодвижка в том виде, в каком он используется тут?
https://codeberg.org/yakui-lover/fbe-410 явно не соответствует ситуации на данный момент - даже сравнивая справку по разметке с тем что тут в коде. Это намеренно так?
No. 1000745  
Duck-ai-image-2026-05-29-14-18.jpg - (228.54KB, 1254×1254)
1000745
>>1000744
Вот же бака...
Благодарю.
No. 1000746  
>>1000745
Там всё ещё может быть лаг в пару незначительных правок.
Но всё же, ссылка снизу страницы ведёт именно туда.
No. 1000747  
Обсуждаете IPv6, который общее место что почти мёртв. Там давно в СМИ предлагают новые IPs сделать, сразу удобные для гос-в.
No. 1000748  
>>1000747
>предлагают
Вот когда в странах будет 80% adoption rate, тогда и приходите
No. 1000749  
>>1000747
Ты про https://www.ietf.org/archive/id/draft-thain-ipv8-00.html это?
No. 1000750  
>>1000749
Нет, раньше. Про https://www.linux.org.ru/forum/talks/17212183 (2023) и КНР с New IP (2020).
No. 1000751  
145364581_p0.jpg - (293.27KB, 1098×1537)
1000751
>>/b/1006585
> спойлер
Это-то я помню. Мне про https://410chan.org/dev/res/26066.html#28190 было интересно.

> Сейчас. Над движком 0141
Давайте тогда обсудим архитектуру. Не могу сказать, что она у меня в каком-то чётко оформленном состоянии в голове. Но попробую описать, как мне пока видится.

Мне кажется, здесь оказывается довольно уместной инкапсуляция по действиям, нежели по entities. То бишь, что-то ближе к transaction scripts. Скажем, DeletePostAction с функциями для удаления постов, CreatePostAction, ReportPostAction, ArchiveThreadAction, GenerateBoardPagesAction, и так далее. Для какой-нибудь entity или набора action'ов, где разделение на разные файлы излишне — какой-нибудь общий *Service или *Controller класс.

Endpoint'ы вроде board.php занимаются wiring'ом входящего JSONа или $_GET/$_POST переменных к Action'ам и Service'ам и высокоуровневой логикой.

Выполнение Action'а возвращает (анонимный) класс со статусом и результатами. Статусы соответствуют HTTP кодам. If applicable, должна быть функция, которая сообразно его типу этот результат в HTML или в JSON и выходит с ним. Не думаю пока, что где-то есть смысл делать отдельные View классы.

Пока это без особой мороки или overhead'а, если Action затрагивает несколько entities/таблиц, тянуть (lateral) join'ами информацию сразу с нескольких одним запросом. Например для modDelete пусть оно в один запрос получает board_id соответствующий board_name и проверяет права модератора для этого board_id, буде тот найден. Для CreatePostAction, пусть оно вместе с board_id, если enable_attachments, тянет строку про файлоформат для приложенного файла, а также thumbnail_width и thumbnail_height из таблицы-синглтона settings. Этот контекст пусть передаётся AttachmentService'у или Handler'у.

Распихать config.php с его KU_THUMBNAILWIDTH и KU_THUMBNAILHEIGHT по таблицам. Оставить в config.php параметры подключения к БД, соль и домен/пути. Для вещей, которые пока не понятно, как удобным образом хранить в табличном виде, хранить JSON. Сделать соответствующий пункт меню в админке. Не знаю, сейчас распихивать или как-нибудь в следующий раз.
No. 1000752  
tables_sql_zip.webp - (204.39KB, 1678×10082)
1000752
Черновик схемы.

Posts по-прежнему wide table. Но IP выносим вовне, поскольку для IPv6 точно нужен будет поиск по подсети, а значит фильтровать IP'шники в PHP, а не в базе.
No. 1000753  
board_php_zip.webp - (188.60KB, 2174×5264)
1000753
Черновик board.php.
No. 1000754  
aead.webp - (118.03KB, 1425×864)
1000754
Такой момент. Чтобы избежать blind index и двух столбцов для IP, как в оригинале: один шифрованный, другой с хэшем, я хочу использовать AES-SIV.

Увы, разработчики PHP случайно AEAD шифры.
https://github.com/php/php-src/pull/21872
https://github.com/php/php-src/pull/21874
Как я понимаю, пока ни в master, ни в 8.5 не смёржили.

Мой пока выход — скомпилировать версию предложившего исправление.
Авось, через месяц-другой оно станет официально.
No. 1000756  
1316536427477.jpg - (29.08KB, 225×281)
1000756
>>1000751
Вы предлагаете абсолютно полностью переписать всю логику. Это будет уже не FBE и даже не форк Кусабы. Скорее что-то Вакабоподобное. Это долго, рискованно и вы можете повторить судьбу Котобы и других деятелей, когда я откажусь ставить явно экспериментальную сборку наживую, оно повисит на тестовой доске с годик и сдохнет под весом нехватки фич.
>Например для modDelete пусть оно в один запрос получает board_id соответствующий board_name и проверяет права модератора для этого board_id, буде тот найден.
Чтобы потом копировать ту же самую проверку для всех других мод-действий? No. Проверка и синхронизация прав должна быть строго в одном месте с точки зрения кода, но в одной транзакции с точки зрения БД.
>Статусы соответствуют HTTP кодам.
No. Зачем эта головная боль и велосипеды? Что должен возвращать CreatePost: 200, 201, 204, 205, 302, 303? А если пост не удалось отправить потому что тред залочен, это 304, 401, 403, 405, 409? 415 на попытку отправить непонятный файл? А может проще ошибку выкинуть или вернуть 0? В целом, это имеет смысл делать только когда мы уже полностью переходим на апи вместо board.php. И то ошибки проще для 4xx-5xx.
>Не думаю пока, что где-то есть смысл делать отдельные View классы.
У нас уже есть два эндпойнта для рендеринга отдельных постов, так что есть. Даже если вы выкините весь рендеринг в бд, всё ещё есть превью поста в постформе.

>>1000752
>poster_ips
Нормализация, допустим. А смысл в двух строках статистики и привязке к доске?
>_checked_at
Зачем? Уж тем более не надо на все вариации.

>thread_
Если мы нормализуем ip, нормализуйте и тред.
>attachment_*
Если мы нормализуем ip, нормализуйте и файлы. Уж тем более когда их столько строк, мы позволяем постить файл в несколько тредов и это даёт мультиаттач в будущем. Количество стримов и кейфрейм выглядят ерундой, кейфреймы могут меняться а видеть больше одного стрима в проигрывателе всё равно нельзя. Флаги что метаданные вообще в изображении есть не дают ничего после процессинга: конечный пользователь будет хотеть смотреть всю метадату, если захочет, и там уже может методом тыка определить. extension обязано совпадать с именем файла. Вместо thumbnail_extension должно быть имя тумбнейла, потому что статичные превью или мультиспойлер со стороны сервера если мы когда-либо решим быть менее завязаны на жс.
>индексы
Нет индекса по lasthit и posted_at. lasthit и в самой таблице нет, впрочем.
>can_be
Во-первых, я не знаю формата анимации который не может быть статикой. Во-вторых, я не вижу, что даёт нам can_be_lossy/lossless что мы не будем скорее всего спрашивать у анализатора в любом случае. Зачем там parent?
>show_poster_ip_hashes
Значит нам надо их тоже хранить.
Индекс на блоттере имо переоценён.
>faptchas
Три айдишника явно перебор.
>posting_stats
Зачем и айпишник в inet инвалидирует любые попытки его скрыть.

>а значит фильтровать IP'шники в PHP, а не в базе.
Возможно имеет смысл хранить шифрованные подсети или просто класть айпишники в другую БД которая точно не может всплыть без доступа к серверу.

>>1000753
Если мы не даём постить с мылом, то нет смысла мыло хранить. Брать board_id из функции под названием ban_warning_check абсолютно по-дурацки выглядит, название надо сменить. Хотя бы потому что проверка должна проверять что доска вообще существует в том числе. Необходимость инстациировать Action чтобы затем делать ->perform выглядит глупо, особенно если мы дважды передаём db_conn. Алсо, почему ban_check_result не 100?

>>1000754
>компилировать собственную версию пыха
Ha-ha, no.

Отвлекаясь от: если мы таки будем делать возможность убирать метеданные, то это же меняет чексумму файла. Так что тем более phash было бы неплохо.
No. 1000758  
>Статусы соответствуют HTTP котам
http://http.cat/
No. 1000760  
>>1000751
> Это-то я помню. Мне про https://410chan.org/dev/res/26066.html#28190 было интересно.
Нет, это не я. Но руководство по установке хорошо бы сделать.

> Давайте тогда обсудим архитектуру. Не могу сказать, что она у меня в каком-то чётко оформленном состоянии в голове. Но попробую описать, как мне пока видится.
Ох, мудрёные вещи вы предлагаете, сестрица. И не совсем понятно мне, как оно связано со списком >>/b/1006573.
Пока предлагаю скоординироваться по списку задач, кто чем будет заниматься в какую очередь. Поразбираться с phash и окружающей математикой мне было бы интересно.
Я ещё думаю, что нужно запилить хотя бы минимальные тесты. Чтобы была уверенность при переходе на новые версии и вещей вроде включения strict_types (с ними есть проблемы, но код, который им подвержен, на 0141 не выполняется, насколько я понимаю).
Кроме того, мною найдено несколько проблем, о которых сообщу позже.
No. 1000761  
>>1000760
>о которых сообщу позже
No. 1000762  
board-post_class_php.webp - (67.15KB, 2019×796)
1000762
>>1000756
> Вы предлагаете абсолютно полностью переписать всю логику
You are quite right.

По большей части, у меня логика та и так по ходу дела частенько оказывается переписанной, как в случае с удалением и репортами на Автобусе вот. Или как в случае с парсером, от которого Кусабового осталось шиш да маленька.

> Зачем эта головная боль и велосипеды?
Чуть более дескриптивный, чем either ok or not, статус; полезно, если есть нужда как-то обработать результат с ошибкой или этой ошибки последствия. Ещё с этим статусом результат можно сразу деть в качестве ответа клиенту.

> Что должен возвращать CreatePost: 200, 201, 204, 205, 302, 303? А если пост не удалось отправить потому что тред залочен, это 304, 401, 403, 405, 409? 415 на попытку отправить непонятный файл? А может проще ошибку выкинуть или вернуть 0?
Можно пока остановиться на subset'е из 200, 400, 401, 403, 404 и 500. CreatePost пуст возвращает 200, а если тред залочен и у пользователя нету прав на постинг в залоченном — 403, несъедобный файл — 400.
А так, писать в документации к конкретным Action'ам, что оно возвращает и когда.

> В целом, это имеет смысл делать только когда мы уже полностью переходим на апи вместо board.php
У нас на постинг и так уже квази-API через fetch-call костыль, где exitWithErrorPage возвращает отнюдь не Page. А можно сдедующий major version сделать сразу и по-хорошему, раз кучу всего менять. Но пока без разбивки board.php на отдельные endpoint'ы, чтобы минимальные изменения в kusaba.js и чтобы не сломать Overchan/Куклоскрипт.

> У нас уже есть два эндпойнта для рендеринга отдельных постов, так что есть
read.php, а второй где? Есть показ удалённых тредов, и новости на главной может быть ещё. Только это вот разве что.

> всё ещё есть превью поста в постформе
In so far у нас preformat_post про BBCode у Parse, а не про BuildPost у Board. Стало быть, ParseAction, результат которого preformat_post и будет возвращать; не будет такого, что у этого результата бывает несколько разных отображений для передачи обратно клиенту; возможные HTML и JSON разницы не в счёт.

> Чтобы потом копировать ту же самую проверку для всех других мод-действий?
Чтобы было меньше запросов в базу. Но и правда не очень удобно, пусть будет AuthService, который мод-действия дёргают.

> Нормализация, допустим
Нормализация, но оно не ради нормализации нормализовано, а ради быстрого поиска и удаления по подсети. Уникальных клиентов в /b/ в разы меньше, чем постов. Меньше шифротекстов тянуть из базы.

> А смысл в двух строках статистики и привязке к доске?
Ныне n уникальных клиентов. Хотя можно отдельный счётчик в boards для того. У Postgres SELECT count(*); — всегда full scan.
> *_checked_at
Вариации в RegenerateThread(s) нужно обновлять в зависимости от posted_at, deleted_at и contents_modified_at у тредовых постов. При выполнении UPDATE запроса для этих вариаций нужно писать/сравнивать то время, которое было на момент проверки, поскольку за время 'рендеринга' что-то могло измениться. Но, наверное, действительно можно ограничиться одним _checked_at.

> Если мы нормализуем ip, нормализуйте и тред.
> Если мы нормализуем ip, нормализуйте и файлы.
Shall we? IP ведь нормализовано только ради быстрого поиска по подсети.

ЕМНИП, в некогда деланых мною бенчмарках уже два JOIN'а по foreign key в 10 раз замедляли SELECT'ы по сравнению с wide table и null столбцами. Although, в wide table где-то в 2 раза медленнее UPDATE'ы и INSERT'ы. Так что удобства/скорости ради я хотел бы в целом оставить wide table.

Мультиатач хороший аргумент нормализовать attachments вовне, но me thinks когда решим запиливать, чтобы attachment'ы по нескольку штук на пост, тогда уже и делать отдельную таблицу.

> Нет индекса по lasthit и posted_at
Мне тут подумалось, а нужен ли общий index по posted_at? Посты в тредах отображаются в порядке id, а не в порядке posted_at. bumped_at, когда не все посты с сажей, можно также считать по where id = max(id) без сажи. RSS тоже по ID тогда, если оно сейчас нет.

Now that I look at it, забыл,
CREATE INDEX ON posts (board_id, thread_is_stickied, bumped_at) INCLUDING (thread_id, post_id) WHERE thread_id = post_id
Для тредов.

>attachment_*
> Количество стримов и кейфрейм выглядит ерундой
Количество стримов, наверное, да, уберу. Хотя для модерации могло бы оказаться полезно. А вот keyframe, позвольте не согласиться. Для статичных изборажений keyframe — это само изображение и есть, а для видео ширину, высоту мы только для первого keyframe'а reliably посчитать и можем, если по-быстрому. phash тоже только для первого kf. У других кадров ширина и высота формально может отличаться. В интернете такие видео — скорее редкость, чем нет, но браузерами они поддерживаются. Помню, Мицгол как-то запостил проигрывающийся экземпляр.
> конечный пользователь будет хотеть смотреть всю метадату, если захочет, и там уже может методом тыка определить
Мне, как конечному пользователю, будет приятно понимать, есть ли у картинки следующие метаданные: EXIF, ICC (не очень-то и мета) и XMP. C php-vips это проверяется просто. Учитывая любовь Булочек к reencodes (и что circa все из них забывают -metadata all), полезно для сравнения, дубликаты перед тобой/ты постишь или нет.

> extension обязано совпадать с именем файла. Вместо thumbnail_extension должно быть имя тумбнейла
Зачем отдельно хранить имя тумбнейла? В текущем FBE cобственных thumbnail'ов у одного файла в FBE бывает несколько: один с 'c', другой с 's', но в целом их имя соответствует столбцу `filename`, который на самом деле basename и который хранится без extension'а. Extension к файлу (не к тумбнейлу) хранится в столбце `filetype`. У приложенных архивов и других вещей со статичным thumbnail'ом своих thumbnail-ов не бывает и определяется он в BuildPost.
> Во-первых, я не знаю формата анимации который не может быть статикой
Можно сделать is_video, is_picture и can_be_animated_picture, но количество столбцов будет то же.
> Во-вторых, я не вижу, что даёт нам can_be_lossy/lossless такого, что мы не будем скорее всего спрашивать у анализатора в любом случае
Оно чтобы для JPEGов не выводить lossy, а для PNG lossless, ибо из формата и так понятно. Также для того, чтобы не выводить lossy/lossless информацию для тех форматов, где анализатор понять lossy/lossless не может.
> Зачем там parent?
Затем, что у нас есть расширение jpg, у которого несколько алиасов. Указать свойства только для jpg, и указать его parent'ом к jpeg и jfif.
> show_poster_ip_hashes
> значит нам надо их тоже хранить.
Не обязательно. Во-первых, можно IPшник дешифровать и взять от него хэш. Во-вторых можно ничего не дешифровывать и взять хэш прямо от шифротекста или от UUID-ключа: оно для досок вроде 410chan'ового /d/. Можно назвать show_poster_ids или enable_poster_ids.

> faptchas
> Три айдишника явно перебор
IDшника там два: ID класса фапчи (кулинарная, как в /cu/, обычная, как в /b/ и /a/) и ID, собственно, капчи. class_name — текстовое имя для человека. On the other hand, может ли одна фапча быть сразу в нескольких классах? Наверное, может. Значит, разбивать на несколько таблиц indeed.

>posting_stats
> Зачем и айпишник в inet инвалидирует любые попытки его скрыть.
In so far me thinks that IPv6 вынуждает делать контроль за частотой постинга/выдачей капчесессий по подсетям, а фильтровать шифрованные IP — тянуть все их из таблицы, если дополнительно не хранить хэши подсетей.

Пока я решил, что раз faptcha_attempts прямо сейчас тоже хранит IP в plain text'е, если я тоже их временно в plain text'е буду хранить, будет по крайней мере не хуже, чем в оригинале.

Но плоховасто, да. Пока не вижу, как сделать по-хорошему. On the other hand, так ли страшно ходить foreach'ем по всем шифрованным за последний час IP? Не то, чтобы их очень уж много. Особенно если не по всем, а у которых совпадает хэш /64 подсети. Тем не менее, видится уязвимым для DoS и в какой-то степени для timing oracle атаки.

Поскольку оно целиком рантаймовое, вот это вот точно стоило бы вынести в какую-нибудь (отдельную) in-memory DB. Но как-нибудь в другой раз.
No. 1000763  
>>1000762
>А так, писать в документации к конкретным Action'ам, что оно возвращает и когда.
Тогда уж https://www.php.net/manual/en/language.types.enumerations.php
>У Postgres SELECT count(*); — всегда full scan.
IASM_pg_db=# EXPLAIN ANALYZE SELECT COUNT(*) FROM board_b.comments;
                                                                                QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Finalize Aggregate  (cost=34033.26..34033.27 rows=1 width=8) (actual time=75.114..81.980 rows=1.00 loops=1)
   Buffers: shared hit=47966
   ->  Gather  (cost=34033.04..34033.25 rows=2 width=8) (actual time=74.874..81.973 rows=3.00 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         Buffers: shared hit=47966
         ->  Partial Aggregate  (cost=33033.04..33033.05 rows=1 width=8) (actual time=70.871..70.877 rows=1.00 loops=3)
               Buffers: shared hit=47966
               ->  Parallel Index Only Scan using thread_re_indx on comments  (cost=0.43..30978.87 rows=821668 width=0) (actual time=0.164..45.123 rows=649968.00 loops=3)
                     Heap Fetches: 57416
                     Index Searches: 1
                     Buffers: shared hit=47966
 Planning:
   Buffers: shared hit=1
 Planning Time: 0.091 ms
 Execution Time: 82.235 ms
(16 rows)
IASM_pg_db=# \d board_b.thread_re_indx
    Index "board_b.thread_re_indx"
 Column |  Type   | Key? | Definition
--------+---------+------+------------
 parent | integer | yes  | parent
btree, for table "board_b.comments"

~2m записей btw. Ну да записью проще. Но это не "Ныне n уникальных клиентов", это "этот постер запостил N постов и удалены". За пределами мод-интроспекции это не нужно, а когда нужно оно будет использовать индекс.
>Посты в тредах отображаются в порядке id, а не в порядке posted_at
Насколько я помню все тайм парадоксы, наоборот. Плюс в нашем случае с импортом тем более.
> у которого несколько алиасов
Тогда лучше список алиасов, потому что быстро возникает вопрос что делать с парентом парента и как всё наследуется/перебивается.
>IDшника там два
А, я неправильно понял суть. Хмм. Так-то простоты ради можно не пихать это в БД а оставить парсер того текстофайла что есть и просто его переписывать, проблемы только с flock() чтобы двое его в одно время не редактировали.
No. 1000765  
144148002_p0.jpg - (1.70MB, 1551×2321)
1000765
>>1000756
> Это долго, рискованно и вы можете повторить судьбу Котобы
Если продолжу и основательно за это возьмусь, к концу августа что-то вполне может быть готово, думаю. Тем более если писать это буду не я один, сразу довольно чётко определив внутренний API и погодя обвешивая тестами.

Другое дело, будет ли у вас желание приложить effort за-try'ить получившееся?

> сдохнет под весом нехватки фич
Пусть те же фичи, которые сейчас есть, будут воспроизведены? Except watchedthreads и текстодоски как отдельный board type; и надо будет разобраться, как работать oekaki постам — по-хорошему, устроить бы это так, чтобы шло одним одним запросом к board.php, а не выхватывалось из KU_CGIDIR . '/kusabaoek'.

Разбить на Action'ы/Service'ы проверку модправ, капчей, постинг, удаление, репорты, архивацию и генерацию страниц, сделав board.php простым endpoint-ом–оркестратором и расчленив распиханное по разным файлам на роли. Структуру manage.class.php at large не трогать, делая изменения лишь в существующих therein функциях. board.php по отдельным endpoint'ом пока не разносить, either, чтобы Кукла/Overchan не сломались и HTML не править, хотя погодя его выносить в шаблоны where applicable/makes sence as per #3.

Отчасти я хочу сделать это потому, чтобы код для конкретного сложного действия и релевантных им функций был +- в одном месте.
Так логика удаления сейчас разнесена по board.php, board-post.class.php и manage.class.php. Пусть board.php и manage.class.php занимаются +- только загоном $_POST или JSON в DeleteAction, где будет вся логика касательно удаления, и пусть они дальше pipe'ят результат в BanService и в BoardPageGenerator, если надо. Висящее в public изменение про удаление для Автобуса как-то так и работает, although физически находится в Board классе и вызывает регенерацию страниц само—без middling'а со стороны board.php.
BTW, удаление постов и репорты ныне в board-post.class.php, но не создание, которое в board.php и posting.class.php.

Отчасти ещё потому, что для report'ов незачем инициализировать необходимые для бордогенерации вещи, такие как постформу или набор спойлеров; для удаления, пока оно не завершилось успешно, также незачем инициализировать генератор страниц и так далее. Although, как я понимаю, в ORMах оно решается lazy load'ом, но это дробить на мелкие запросы то, что можно сразу и меньшим их количеством. Ну и лично мне кажется удобней инкапсуляция по действиям, чем по entities.

BanWarningCheck был с той идеей, что вместе с board_id можно сразу выхватить enable_warnings, и проверять наличие/выхватывать информацию о warning'е к борде, если enable_warnings и не был найден бан. Что можно одним запросом по большому счёту.

> мы дважды передаём db_conn
В черновике ошибка.
> Необходимость инстациировать Action чтобы затем делать ->perform выглядит глупо
Там, где инстанциация would do nothing other than приёмку $db_conn, делать perform функцию static и принимающей $db_conn?

>>1000763
> Посты в тредах отображаются в порядке id, а не в порядке posted_at
> Насколько я помню все тайм парадоксы, наоборот. Плюс в нашем случае с импортом тем более.
OK, будет добавлено. В manage.class.php есть показ последних изображений и статистика, где это надо. Although, судя по порядку >>/b/24480 и >>/b/1000046 оно именно в порядке id, а не posted_at.

> Но это не "Ныне n уникальных клиентов", это "этот постер запостил N постов и удалены"
Если не делать борде счётчик, то можно SELECT count(*) FROM poster_ips WHERE board_id = $1 AND rows_count - deleted_posts_count > 0 же. Вполне может оказаться на порядок быстрее, чем SELECT count(DISTINCT poster_ip_id) WHERE board_id = $1 AND NOT is_deleted AND poster_ip_id IS NOT NULL.
No. 1000766  
143374396_p0.webp - (792.37KB, 2048×2732)
1000766
>>1000765
> SELECT count(DISTINCT poster_ip_id) WHERE board_id = $1 AND NOT is_deleted AND poster_ip_id IS NOT NULL
Или точнее
> SELECT count(DISTINCT poster_ip_id) WHERE board_id = $1 AND deleted_at IS NOT NULL AND poster_ip_id IS NOT NULL
Цк.

>>1000763
> Тогда лучше список алиасов, потому что быстро возникает вопрос что делать с парентом парента и как всё наследуется/перебивается.
OK.
> Так-то простоты ради можно не пихать это в БД а оставить парсер того текстофайла что есть и просто его переписывать, проблемы только с flock() чтобы двое его в одно время не редактировали.
Мне свиду кажется, что тут проще переписать, поскольку я хочу ограничить максимальное количество капчесессий на IP/подсеть, чтобы не давать пользователю генерировать тьму валидных капчесессий, что сейчас seems very doable, а стало быть там надо многое менять. В черновике схемы забыто enable_adaptive_faptcha в boards таблице.

Надо выкладывать новый черновик/скетч.
No. 1000767  
>>1000764
>is_deleted
Желательно тоже в индексы добавить, как минимум на отрисовку/подбор сколько постов в треде это будет всегда запрашиваться.
>rows_count - deleted_posts_count
Не проще ль сразу декрементировать rows_count.
>Отчасти я хочу сделать это потому, чтобы код для конкретного сложного действия и релевантных им функций был +- в одном месте.
Для этого не обязательно уходить от объектности, для этого достаточно причесать тот зоопарк что у нас есть сейчас. Более того, вам тогда тем более нужны View, чтобы объединить различные виды отображения поста в одном месте - по крайней мере пока мы не скинем всё в шаблоны. И тем более нужны Model чтобы было приведение типов при разговоре с базой (или даже между сервисом и экшном, как передача всех-всех-всех параметров при обработке файла) и функция конверции размера из битов в читабельное не валялась где-то в utils. В итоге из Action остаётся только manage.php, который уже и так так написан, а остальные приводятся к классам достаточно просто и лишь семантика.
>для report'ов незачем инициализировать необходимые для бордогенерации вещи, такие как постформу или набор спойлеров
Это всё же больше проблема как раз портянки board.php и того как мы делаем импорты.
>Другое дело, будет ли у вас желание приложить effort за-try'ить получившееся?
Явно не в конце августа, спросите в конце сентября.
No. 1000769  
___J.jpg - (51.54KB, 784×441)
1000769
Да, ящитаю, переписывать нужно. В том, что есть, можно замучаться проблемы исправлять.

Переписывать сразу или постепенно?

P.S. См. PR на Codeberg. Но вообще там всё плохо.
No. 1000770  
>>1000769
Тогда надобно и имя новое взять.
>Оформить как issue?
Yes please.
No. 1000771  
143780615_p0.webp - (1.42MB, 816×1456)
1000771
>>1000770
> Тогда надобно и имя новое взять
Вы о имени движка? Предлагаю, пускай будет Higanbana. Но с именами как-нибудь потом, наверное.

>>1000769
> Переписывать сразу или постепенно

Мне думалось, скорее сразу >>1000765, но по частям.
> Разбить на Action'ы/Service'ы проверку модправ, капчей, постинг, удаление, репорты, архивацию и генерацию страниц, сделав board.php простым endpoint-ом–оркестратором и расчленив распиханное по разным файлам на роли. Структуру manage.class.php at large не трогать, делая изменения лишь в существующих therein функциях. board.php по отдельным endpoint'ом пока не разносить, either, чтобы Кукла/Overchan не сломались и HTML не править, хотя погодя его выносить в шаблоны where applicable/makes sence as per #3.
Потом остальное, although из-за изменения СУБД и схемы всё равно в nigh каждой функции в manage.class.php код хоть, маленько, но менять.

Either way, мне кажется, сначала определить внутренний API, какие классы будут, какие у них будут public функции и их поведение. Если не API, то хотя бы схему.

> fix using bitwise OR in inappropriate places
Вообще, покуда у нас есть assertion, что strlen возвращает неотрицательный int, (strlen($a) | strlen($b)) > N и strlen($a) > N || strlen($b) > N должны быть, пусть не по скорости, идентичны результатом. Первый вариант чуть короче, и поэтому я его написал. Если inappropriate, не буду.
No. 1000772  
>>1000771
70 | 9 > 75
No. 1000773  
>>1000772
Мощивакегозаймасен. Видно, оно так работает только при сравнении со степенями двойки.
No. 1000775  
phash_zip.webp - (446.77KB, 2494×12161)
1000775
>>1000760
Про phash, вот реализация, на которой я остановился. Та самая, которую я описывал в >>/b/1006362.

Помимо вроде ничего не давших попыток сравнивать не с глобальной медианой, а с медианой сегмента/сектора, пробовал ещё считать трёхмерный DCT по saliency zoom cube'у. Оно получается несколько более устойчиво к crop'у и padding'у (например, нашло >>/b/4173 и >>/b/6116 пару), но такие схожести оно объединяет в один кластер на моменте, когда начинает «разваливаться», а частота false positives начинает возрастать при куда меньших дистанциях на ту же длину хэша.

В phash.zip.webp также лежит hamming_evo.py. Если скормить ему CSV файл с хэшами, который можно получить с php do_round_phash.php > stats_file_name.csv, сделает директории stats_file_name/hamming_distance/cluster_hash с симлинками на указанные в CSV файлы. do_round_phash.php не рекурсивен.

Если делать, чтобы информация про есть/нет похожие файлы шла в статусную строку по выбору файла пользователем, на сервер нужно отправлять phash, а не файл. Стало быть, повторить реализацию в JS. I wonder насколько проблема то, что алгоритм уменьшения изображений и приведение их к оттенкам серого отличается от оного у libvips.

https://github.com/libvips/libvips/wiki/HOWTO----Image-shrinking
Очень серьёзное узкое место по скорости — уменьшение картнки. У libvips есть поддержка codec-специфичных оптимизаций, но оно, помнится, не как надо обрабатывало прозрачность у WebP или какие-то ошибки были. Поэтому сейчас оно в do_round_phash, как есть.
No. 1000777  
___K.jpg - (959.35KB, 2048×1536)
1000777
Ещё денёк поиграюсь с платными ИИшками для поиска проблем, потом буду думать про архитектуру и ковырять phash.

Но там и так уже много найдено.

Будет «десерт», но он только Соуса заинтересует. Да и вообще хорошо бы было бэкпортировать все исправления в его репу.
No. 1000778  
145552978_p0.webp - (1.41MB, 2150×1767)
1000778
>>1000777
> [Pr. 2]
Скорее всего, задумка была в том, чтобы проверить, CurrentUserIsModeratorOfBoard что-то там.

Другое дело, что с удалениями, как я понимаю, тут тоже https://410chan.org/dev/res/26066.html#28080 проблема. На Автобусе решил через https://codeberg.org/FBE410/fbe-410/pulls/62/commits/934f8deb566c139758a5061057870c6a01bd6122 и https://codeberg.org/FBE410/fbe-410/pulls/64/commits/7bdccad9f45014c9ebbeefa164cb4d8453e6472d. Чтобы это сюда вмёржить, надо будет сделать некоторые исправления, поскольку выкинуто логирование, выкинут KU_APC (clearPostCache не только удалял из репортов, но очищал его), выкинут KU_SEARCH, и сажа не в отдельном столбце. Also, я не добавил в BulkDeletePosts проверку access_level у модератора. В board.php её вроде и не было, но если и другие вещи к ней привязывать...

> [Pr. 4]
Зашифровать ту COOKIE хотя бы Кусабовым md5_encrypt. А по-хорошему, брать openssl и аутентифицированное шифрование для такого.

> [Pr. 12]
> По идее, проблема с substr должна была проявляться в рантайме. Возможно, я чего-то не понимаю.
Если для символа русской кириллицы в UTF-8 всегда два байта и режет substr по чётному числу, то не проявилось поэтому, скорее всего.

> [Pr. 19]
Тут так и задумано, если я всё верно понимаю.

> [Pr. 22]
> А ещё оно пытается обратиться к KU_ROOTDIR . $board_from . '/thumb/' . $line['filename'] . 'c.' . $thumb_file_type, которого не существует (я не понимаю, что это за фича вообще)
As far as I know, это для просмотра изображений в удалённых тредах. https://410chan.org/b/thumb/178043101649c.png На 410 есть. А тут почему их ныне нет, не помню или не знаю.
No. 1000779  
Also, $board_from не обязательно в KU_ROOTDIR, но обаязательно в KU_BOARDSDIR.
No. 1000780  
Или даже не так, потому что loadbalancer.
No. 1000781  
1738949909024.gif - (75.76KB, 50×27)
1000781
>>1000778
c это для каталога, выпилено здесь потому что пикрелейтед.
No. 1000782  
>>1000769
PR merged.

>Pr. 2
Как сказано, проще просто переписать всё через bulkdelete.
>Pr. 3
Low priority. Если включено KU_DBUSEPERSISTENT то вообще ничего не будет работать, потому что PConnect не объявлено. Если не включено, то вывалиться с одной ошибкой или другой вопрос сугубо красивости.
>Pr. 4
Low priority. В там нет никакой интересной информации.
>Pr. 5
Да, в сейчас оно строка и в БД как строка. По идее пра запихивании в БД только должно стать строкой.
>Pr. 6
Low priority. Капча и так чтобы отвадить совсем уж хлебушков.
>Pr. 7
random_appeal использовался на Автобусе в первый год регулярных обращений, когда их было два или три, по одному из членов Супермаркета. В общем кода нафиг никому не сдался, да.
>Pr. 9
Должны. Но раз никто не спохватился, то пока хорошо. Удаляет один потому что Report() позволяет только один репорт на пост. Так что в запросе проблем нет.
>PR. 10
Вообще, звучит как хорошее, я не вижу причины НЕ делать глобалбан временным.
>Pr. 11
Проблемы пхпверсии, лол. 8.2.0 Case conversion no longer depends on the locale set with setlocale(). Only ASCII characters will be converted.
>Pr. 19
Not-a-bug
>Pr. 21
Low priority, текстодоска
>Pr. 22
Low priority. Там всё равно переписывать на выборочный перенос постов в том числе, и у нас всего полторы доски.
>Pr. 24
Я более чем уверен что поиск по БД даст гораздо больше информации чем сравнение в рантайме в этом случае. Low priority.
>Pr. 30
expand за пределами индекса никогда не вызывается, так что да.
>Pr. 37
Хм, и правда. Поправлено в PR
>Pr. 41
Low priority, текстодоска.
No. 1000784  
relative_ids.webp - (20.63KB, 1350×693)
1000784
> [Pr. 41]
> что такое relative_id
В тредах на текстодосках ссылки в тредах считаются не по глобальному ID, а по номеру поста в треде.
> возможно, во все запросы, где он вычисляется (read.php, inc/classes/parse.class.php), нужно дописать AND NOT IS_DELETED
Ие. Удалённые посты остаются и учитываются в относительной нумерации.

Как по мне, текстодоски проще выпилить для дальнейших изменений и вместо этого определить их, как имиджборды, где нельзя прикладывать файлы.
No. 1000785  
20.webp - (36.59KB, 1604×284)
1000785
>>/b/1004863
Другое дело, что я не понял, зачем %2C остаётся %2C в prettify_url, и теперь оно декодирует все printable ASCII символы, too.

https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
Снова убрать gen-delims и sub-delims, как декодируемые. Но хотя бы для тильды оставить percent декодирование.

Есть ли смысл как-нибудь когда-нибудь задуматься об умном алгоритме parsing'а ссылок в человекочитаемое, который будет понимать, где, скажем, %2C можно декодировать как ',', а где %2C sub-delim и смысл поменяется?
No. 1000786  
144249642_p1.jpg - (489.39KB, 1229×1210)
1000786
>>1000782
> [Pr. 24]
> Я более чем уверен что поиск по БД даст гораздо больше информации чем сравнение в рантайме в этом случае.
??

Теоретически, неконстантное по времени сравнение строк может позволить после (большого) количества запросов восстановить часть password_hash у интересующего поста, тем более если хэш несолёный md5. Что можно использовать для идентификации пользователей. Насколько оно на практике опасно, не знаю.

Проблема в том, что в Postgres, и даже в pg_crypto, не завезли функции для constant time сравнения. А значит, это тянуть хэши паролей из базы при bulk deletion, если делать сравнение time constant. Что, с другой стороны, наверное, не так уж и тяжко, если захардкодить максимальное количество постов на удаление по паролю за одно обращение к board.php, скажем, на 256.
No. 1000787  
144369002_p0.jpg - (940.30KB, 1195×1044)
1000787
> [Pr. 43]
> Удаление тредов не атомарно, если новый пост появится во время удаления, будет плохо. Удаление отдельных постов по какой-то причине сделано атомарно (begin_transaction/commit).
Мне кажется, для обеспечения consistency простых transaction'ов без select for update, lock'ов или дополнительных проверок, тут оказывается недостаточно.
Time 00: Connection D начинает транзакцию;
Time 01: Connection P начинает транзакцию;
Time 02: Connection D select'ит информацию о треде и его дочерних постах, чтобы потом удалить;
Time 03: Connection P select'ит информацию о неудалённом треде;
Time 04: Connection D update'ит дочерние посты и тред, ставит ему IS_DELETED=1;
Time 05: Connection D завершает транзакцию;
Time 06: Connection P insert'ит новый пост;
Time 05: Connection P завершает транзакцию;
Итог: orhpan reply, и никто никого не блокирует.
No. 1000788  
145127393_p0.jpg - (458.99KB, 720×960)
1000788
> На 32-bit PHP все выражения (1 << self::SShift) в файле будут нулём
Корректностью работы на старше 410'ого железе и роботах-пылесосах можно принебречь, думаю. И то на всяких роботах-пылесосах уже 10 лет как aarch64, поди.

> ($a & $b > $c) <==> ($a & ($b > $c))
PHP не без сюрпризов, однако.
No. 1000789  
130459862_p0.jpg - (2.42MB, 2894×3900)
1000789
Also, спасибо за труд.
No. 1000790  
>Pr. 44
No.

> php -E '$htmlfile = "/res/2.html"; echo(substr(basename($htmlfile), 0, strpos(basename($htmlfile), ".html")));
2

list это текстодоска, low priority.
>Pr. 44
Medium priority, сейчас балансер конечно есть, но сделан в обход этой функциональности.
>Pr. 55
Технически, оно работает, потому что видимость итератора в пределах функции, а не цикла.

>>1000787
поэтому давайте выносить можно просто Constraint повесить на таблицу.
Но в целом, в орфане ничего плохого нет, он просто не будет отображаться, емнип.
No. 1000791  
___L.jpg - (893.48KB, 1905×1070)
1000791
>>1000789
My pleasure.

>>1000790
> Технически, оно работает, потому что видимость итератора в пределах функции, а не цикла.
А если $results не пустой, но цикл ничего не нашёл, то в $line будет последняя строка, а не false/null.
No. 1000792  
>>1000791
>А если $results не пустой, но цикл ничего не нашёл
То выполняется условие ! $warning_record и функция завершается раньше.
No. 1000793  
___M.png - (808.01KB, 1800×1008)
1000793
Yatta!
No. 1000794  
>PR. 57
Работает не трогай. Плюс страницы с 0-индекса.
>PR. 63
Не работает, но в 8.4 без этого не работает.
No. 1000795  
aoba-pixelart.webp - (22.17KB, 640×640)
1000795
Ну что, предлагаю поправить известные проблемы (и #9 — там много что нужно поправить в модерке) прежде, чем приступать к переписыванию.

(А ещё там любой персонал может делать, по сути, что угодно. И это уже сложно исправить. Неактуально для нас?)

По поводу переписывания, исходя из того, что оно будет по частям, предлагаю обсудить следующее:

1. Нужен ли фреймворк? Можно постепенно переписывать фичи с использованием фреймворка.
2. Какие библиотеки брать для (если без фреймворка или фреймворк не предоставляет такой функциональности):
  • ORM или что-то подобное?
  • Routing (можно и без библиотек)?
  • Templating (сейчас используется Smarty вперемешку с конкатенацией строк)?
  • Логирование?
  • Тестирование (см. ниже)?
  • Аутентификация и шифрование?
3. Какая будет общая архитектура (MVC?) и структура кода?
4. Нужно ли покрыть хоть какую-то функциональность тестами перед переписыванием?
No. 1000796  
137946087_p0.webp - (791.17KB, 3384×1953)
1000796
>>1000795
In my не humble opinion,

> Нужен ли фреймворк?
> ORM или что-то подобное?
Предлагаю, давайте без Laravel'ов и Autowired магии через атрибуты с Exception stack'ами на 40 строк. Тем более, что у нас не простой CRUD.
> Templating (сейчас используется Smarty вперемешку с конкатенацией строк)?
Smarty. Или Twig или что-то ещё, где есть extension-интеграция с gettext.
> Аутентификация и шифрование?
Для шифрования и хэширования паролей обычные PHPшные функции и default'ный openssl extension у PHP. Для паролей hash_hmac('sha256') или openssl_pbkdf2 с SHA-256 на небольшое количество итераций, чтобы серверу было не сильно тяжело. Для шифрования IP в базе AES-256-SIV либо AES-256-GCM с hash_hmac blind index'ом, для шифрования печенек where applicable AES-256-GCM. Автентикация staff'а вытягиванием строки из staff таблицы. Для экспирации staff сессии, хранить её в cookie шифрованной с timestamp'ом.

Для phash, наверное, PostgreSQL'у взять pgvector extension (https://github.com/pgvector/pgvector) для HNSW индекса для быстрого approximate поиска по расстоянию Хэмминга.

Для phash и картинкофайлов libvips (php-vips), для нестатичных изображений и видео ffmpeg (брать ли обёртку? Популярная/поддерживаемая всё равно парсит консоль, а не вокруг libavcodec) для анимации/видео, file (https://www.darwinsys.com/file/, php-я-не-знаю) для проверок соответствия extension'а содержимому.

> Какая будет общая архитектура (MVC?) и структура кода?
Мои мысли насчёт описаны в >>1000751 и далее; что-то ближе к transaction scripts, SOA/modular monolith, но местами несколько более tightly coupled. Предлагайте, спрашивайте, критикуйте.

Предложите своё и вы!
No. 1000798  
Надо добавить adaptive_config.php в .gitignore.
No. 1000799  
aayh_aoba.png - (441.98KB, 595×823)
1000799
>>1000798
What for?

>>1000796
> file (https://www.darwinsys.com/file/, php-я-не-знаю

libmagic?
https://www.php.net/manual/en/fileinfo.installation.php

Ну как бы этот file, на который вы ссылаетесь, использует libmagic.

> Предложите своё и вы!
Ох, не ведаю я про всякие библиотеки для PHP и современные best practices. Поэтому просто доверюсь вам! Выбор основных компонент звучит разумно.

>>1000751
> Мне кажется, здесь оказывается довольно уместной инкапсуляция по действиям, нежели по entities. То бишь, что-то ближе к transaction scripts. Скажем, DeletePostAction с функциями для удаления постов, CreatePostAction, ReportPostAction, ArchiveThreadAction, GenerateBoardPagesAction, и так далее. Для какой-нибудь entity или набора action'ов, где разделение на разные файлы излишне — какой-нибудь общий Service или Controller класс.
Хм, а зачем выносить их в классы? Это ведь могут быть просто функции.
Какое внутреннее состояние будет у подобного класса? $tc_db и $smarty? Это должны быть если не глобальные переменные, то статические переменные одного-единственного класса.
В PHP какие-то проблемы с глобальными переменными?

А классы-entities-то нужны. Board, Post, User.

> Выполнение Action'а возвращает (анонимный) класс со статусом и результатами.
А почему анонимный?

> Статусы соответствуют HTTP кодам. If applicable, должна быть функция, которая сообразно его типу этот результат в HTML или в JSON и выходит с ним.
Это нужно будет создавать XxxResultToHtml()/XxxResultToJson()-двойники всех Action-классов?
Но зачем, если функция/метод может сразу возвращать результат, который может быть HttpTemplateResponse/JsonResponse/Redirect/…?

> Пока это без особой мороки или overhead'а, если Action затрагивает несколько entities/таблиц, тянуть (lateral) join'ами информацию сразу с нескольких одним запросом. Например для modDelete пусть оно в один запрос получает board_id соответствующий board_name и проверяет права модератора для этого board_id, буде тот найден. Для CreatePostAction, пусть оно вместе с board_id, если enable_attachments, тянет строку про файлоформат для приложенного файла, а также thumbnail_width и thumbnail_height из таблицы-синглтона settings. Этот контекст пусть передаётся AttachmentService'у или Handler'у.
Нам точно нужны подобные оптимизации?

>>1000775
> Стало быть, повторить реализацию в JS. I wonder насколько проблема то, что алгоритм уменьшения изображений и приведение их к оттенкам серого отличается от оного у libvips.
> Очень серьёзное узкое место по скорости — уменьшение картнки. У libvips есть поддержка codec-специфичных оптимизаций, но оно, помнится, не как надо обрабатывало прозрачность у WebP или какие-то ошибки были. Поэтому сейчас оно в do_round_phash, как есть.
А нельзя просто скомпилировать phash и cIMG в WebAssembly и использовать его и с клиента, и с сервера?
Если нельзя, можно переписать. Если я правильно понимаю, пайплайн такой: выбираем из изображения только luma-компонент → 7x7 convolution → nearest-neighbor interpolation (!) → 1000 строк в pHash на (два алгоритма + много левой лабуды).
Другое дело, что поддерживать кто-то это должен будет.
No. 1000800  
>>1000795
>Неактуально для нас
При текущей скорости нет, но апрстрим (Соус) запрашивал.
1.При том что это удобно, если мы будем пытаться придерживаться текущих идей, то чем меньше зависимостей, тем лучше.
2. ORM без разбивки постов по доскам имеет больше смысла... но у нас не так много моделей. Routing без фреймворка смысла пожалуй мало, и это будет ломать куклу. Шаблоны все на смарти было бы неплохо. Логи... в принципе текущих должно хватать в 90% ситуаций.
3. See >>1000767
4. Перед? Не обязательно. По крайней мере для здесь.

>>1000798
На самом деле, не вижу, что бы там такого могло слиться.
No. 1000801  
Лучи добра хорошему Зою с хорошим пингом! Спасибо Якуй-сан! Ура~!
No. 1000802  
137522071_p0.webp - (3.26MB, 1632×2912)
1000802
>>1000799
> libmagic?
Да, I guess.

> Хм, а зачем выносить их в классы? Это ведь могут быть просто функции.
Для инкапсуляции действия и его вариаций в один класс-сервис: чтобы функции, которым можно с этим сервисом общаться, были public, а его внутренние функции и определения были private.
> Какое внутреннее состояние будет у подобного класса?
В основном, похоже, stateless. Или state, определяемый единожды при создании. В целом, stateless натура PHP не способствует statefull вещам.

Скажем, пусть у условного BoardPageGenerator будет поле, куда при инстанциации пишется постформа для страниц досок и тредов. Или вот postbox. Конечно, после создания instance, например, количество unique user posts может поменяться, но поскольку по завершению действия, влияющего на это количество, следом должен создаваться/зваться генератор, страницы те не могут не оказаться eventually consistent.

> $tc_db и $smarty? Это должны быть если не глобальные переменные, то статические переменные одного-единственного класса.
https://codeberg.org/yakui-lover/fbe-410/issues/3
> Никаких глобальных переменных. Синглетон или передача на этапе инициализации класса.
Якуй определил такой guideline и переделал $tc_db согласно ему. Поэтому и я в своём скетче не стал менять этот аспект, но вернуть $tc_db на global или сделать статикой/singleton'ом я не против.

A speculation, но передача connection'а классу может немного упростить тестирование или облегчить переход на connection с другими креденциалами в зависимости от полномочий пользователя: чтобы если делать persistence модераторские действия не ждали транзакции обычных пользователей.

> создавать XxxResultToHtml()/XxxResultToJson()-двойники всех Action-классов?
> А почему анонимный?
Чтобы в простых случаях — скажем, массив IDшников проверить на валидное количество и формат — (в случае инвалидности) унасладовать от интерфейса или там AbstractResult, где to_html(), to_json() и emit_and_exit(), и in place определить классу только to_html() и to_json(), или даже только $status и только $description.

> Но зачем, если функция/метод может сразу возвращать результат, который может быть HttpTemplateResponse/JsonResponse/Redirect
Чтобы иметь возможность этот результат скормить другому Action или Service классу или что-то делать, в зависимости от того, каков этот резульутат. Пусть DeletePostsAction в своём успешном результате всегда хранит IDшнкики тредов, где были изменения, и IDшники тредов, которые оказались удалены полностью. Чтобы штука, которая DeletePostsAction вызвала, могла скормить result с этими ID'шниками BoardPageGenerator'у, но по успеху вывести результат DeletePostsAction. Или чтобы эти результаты можно было гибким образом компановать: при удалении с нескольких досок по IP в модерке, например.

> Нам точно нужны подобные оптимизации?
AuthService, indeed, пусть будет отдельным AuthService. То же с капчей/фапчей. А так, пока это без особой мороки и overhead'а, думаю, для, например, posting'а релевантные глобальные параметры, параметры доски и (опционально) параметры файлоформата можно одним запросом в базу без особой мороки, получая некий posting context, специфичный для действия: незачем тянуть всю информацию о борде, строить для неё постформу и прочее, пока создание поста не завершилось успехом. А для регенерации страниц не нужно тянуть banned_urls, banned_hashes и word_filter.

> А классы-entities-то нужны. Board, Post
Наиболее полным образом select'ит содержимое таблицы posts генерация треда и постов в нём. И то там никогда не нужны хэши файла/пароля. А для остального целиковая Post entity не нужна совсем, только некоторые её части. Возникает она разве что только при Insert'е нового поста — и то с некоторым количеством полей, поставленным в NULL.

Board entity нужна целиком для отображения и редактирования в модерке, но для этого, как и сейчас, можно обойтись ассоциативными массивами, или анонимными классами.

Есть и правда некая проблема с приведением типов.
PDO обращается с bytea, как с blob'ами, и либо делать select encode(ip_aes, 'hex') и его hex2bin'ить, либо читать LBOs. По моим тестам, первый вариант где-то на 20% быстрее второго. На 50% быстрее LBO использовать pg и делать pg_unescape_bytea, но pg не умеет автоматически конвертировать Postgres'ный 'T'/'F' boolean в PHP'шный boolean, а стобцов-флагов у нас не мало. Как я понимаю, ни PDO, ни pg не умеют конвертировать Postgres'ные массивы ни туда, ни обратно. Такие вот Драйвера Базы Данных, хех. Есть ли быстрая библиотека-конвертер для таких массивов, I wonder.

Делать ли в связи с этим отдельные Model классы? Как альтернатива, можно при чтении из базы столбцы, для которых необходимо декодирование, select'ить с hex или flattened суффиксом или преффиксом i.e.
SELECT
  encode(bans.ip_subnet_aes, 'hex') AS ban_ip_subnet_aes_hex,
  ARRAY(SELECT boards.name FROM boards WHERE boards.id = ANY(bans.boards)) AS ban_board_names_flattened,
  ...
FROM
  bans
WHERE
  ...

И сразу после запроса или по надобности в полученный с fetch_all ассоциативный массив добавлять ban_ip_subnet_aes (или даже сразу ban_ip_subnet) и ban_board_names.

Вижу смысл, чтобы AuthService в результате успешной автентикации возвращал AuthContext, где помимо is_suspened, type и access_level будут can_create_boards(), can_delete_locked_threads(), can_post_in_locked_board(), can_skip_captcha() и другие функции расшифровки staff.type и staff.access_level. On the other hand, can_delete_locked_threads() же не на всех бордах, а только на тех, которые staff модерирует. Делать BoardAuthContext? Но вот для редактирования банов (оно же ведь есть) нужен полный список, чтобы для разницы (борды_в_бане_до ^ борды_в_бане_после) проверить, зарегистривованы ли id этих борд, как те, которые модератор может модерировать. Eeh..

> Ох, не ведаю я про всякие библиотеки для PHP и современные best practices. Поэтому просто доверюсь вам!
Вполне возможно, что я осведомлён ещё меньше, чем вы.

Ну и best не best, но если код и его организация получатся такие, что вам глаза захочется вырвать или взять и всё переписать, а не работать с, то такой архитектуры не надо, наверное. Будет куда лучше, если вам будет приятно с этим работать. Так что ваша помощь очень нужна.

Also, ЕМНИП, в staff добавлялся концепт VIP'ов.. которые не очень-то staff, если подумать. Так что, наверное, User и users лучше название.
No. 1000803  
>>1000802
>Наиболее полным образом select'ит содержимое таблицы posts генерация треда и постов в нём.
Вот уж оптимизировать количество возвращаемых колонок такая микрооптимизация, что давайте не будем на ней заморачиваться.
No. 1000804  
aoba_sock.jpg - (287.19KB, 1612×2048)
1000804
>>1000802
> Для инкапсуляции действия и его вариаций в один класс-сервис: чтобы функции, которым можно с этим сервисом общаться, были public, а его внутренние функции и определения были private.

Понятно. В целом неплохо, но неясно, зачем вообще создавать инстансы класса, если это могут быть статические методы.
Наверное, на функциях можно сделать что-то похожее, используя namespaces (namespace Xxx, namespace Xxx\details).
Но я предпочту классы и статические методы, потому что там не нужно «переключать» неймспейсы при «переключении» public <-> private.

> В основном, похоже, stateless. Или state, определяемый единожды при создании. В целом, stateless натура PHP не способствует statefull вещам.

Предлагаю таскать везде первым аргументом один объект типа Wiring, в котором будет всё состояние. Так не придётся переписывать аргументы всех методов и функций везде, если вдруг добавится какое-то состояние (e.g. logging) или уберётся.

> Скажем, пусть у условного BoardPageGenerator будет поле, куда при инстанциации пишется постформа для страниц досок и тредов. Или вот postbox. Конечно, после создания instance, например, количество unique user posts может поменяться, но поскольку по завершению действия, влияющего на это количество, следом должен создаваться/зваться генератор, страницы те не могут не оказаться eventually consistent.

Это можно поместить в статическую переменную класса.

> > Никаких глобальных переменных. Синглетон или передача на этапе инициализации класса.

Okay. Но непонятно, зачем объект вообще нужно конструировать.

> Чтобы иметь возможность этот результат скормить другому Action или Service классу или что-то делать, в зависимости от того, каков этот резульутат. Пусть DeletePostsAction в своём успешном результате всегда хранит IDшнкики тредов, где были изменения, и IDшники тредов, которые оказались удалены полностью. Чтобы штука, которая DeletePostsAction вызвала, могла скормить result с этими ID'шниками BoardPageGenerator'у, но по успеху вывести результат DeletePostsAction. Или чтобы эти результаты можно было гибким образом компановать: при удалении с нескольких досок по IP в модерке, например.

Я думаю, эту логику следует вынести в классы-entities. Но нужно ещё подумать.

> > А классы-entities-то нужны. Board, Post
> Наиболее полным образом select'ит содержимое таблицы posts генерация треда и постов в нём. И то там никогда не нужны хэши файла/пароля. А для остального целиковая Post entity не нужна совсем, только некоторые её части. Возникает она разве что только при Insert'е нового поста — и то с некоторым количеством полей, поставленным в NULL.

> […]

> Делать ли в связи с этим отдельные Model классы? Как альтернатива, можно при чтении из базы столбцы, для которых необходимо декодирование, select'ить с hex или flattened суффиксом или преффиксом i.e.

Давайте уж entities (aka models) нормальные сделаем. Без ассоциативных массивов и анонимных классов. Будет Model, View (темплейты), Controller (class *Action).

> Вижу смысл, чтобы AuthService в результате успешной автентикации возвращал AuthContext, где помимо is_suspened, type и access_level будут can_create_boards(), can_delete_locked_threads(), can_post_in_locked_board(), can_skip_captcha() и другие функции расшифровки staff.type и staff.access_level.

We can call it, class User.

> On the other hand, can_delete_locked_threads() же не на всех бордах, а только на тех, которые staff модерирует. Делать BoardAuthContext?

У class User может быть метод can_delete_locked_threads_on($board). И его реализация уже будет делом класса, а не торчать наружу. Ну и не столько уж много досок может быть, чтобы мы не могли позволить себе хранить их список в памяти.

> Но вот для редактирования банов (оно же ведь есть) нужен полный список, чтобы для разницы (борды_в_бане_до ^ борды_в_бане_после) проверить, зарегистривованы ли id этих борд, как те, которые модератор может модерировать. Eeh..

Ну и будет полный список в памяти. Или запрашивать через NOT IN. What’s the problem?
No. 1000806  
aoba_hito.jpg - (232.85KB, 1398×2000)
1000806
Добавилось. Обложили всякими минорными багами с четырёх сторон, нэ. Но есть и то, что нужно зарепортить в личку.

Якуй, а куда-откуда-как тебе написать на почту, чтобы дошло? А то там в прошлый раз что-то пошло не так.
No. 1000807  
>>1000806
Пишите с того, что принимается яндопочтой, или хотя бы не поганит кодировки чтобы экзим их читал. А так, contacts.html на yakuji.moe
No. 1000808  
ao_sh.png - (227.38KB, 364×514)
1000808
>>1000807
Okay, it’s nothingburger. Там всё интересное либо неочевидным образом mitigated, либо может эксплуатироваться только персоналом (что мы сейчас исправить не пытаемся, ибо такого слишком много).

Sry.
No. 1000809  
51826313_p0.webp - (1.22MB, 910×1269)
1000809
Although, для этого надо искать/вспоминать команды openssl, но как альтернатива почте, есть вариант взять из HTTPS сертификата public key, зашифровать сообщение и выложить архивом или текстом сюда.

>>1000808
Раз nothingburger, намекните уж, где, а то заинтриговали-презаинтриговали.
No. 1000810  
64532514_p0.jpg - (916.70KB, 1024×1280)
1000810
> [Pr. 80]
> ~ это unreserved character в URL вообще (RFC 3986).
В RFC да. Я взял этот символ в качестве терминатора URLа, чтобы можно было явно укзазать URLа конец. Если отказываться от тильды в качестве терминатора, то брать другой символ-терминатор.
https://www.rfc-editor.org/info/rfc3986/#section-2.4
> For example, the octet corresponding to the tilde ("~") character is often encoded as "%7E" by older URI processing implementations; the "%7E" can be replaced by "~" without changing its interpretation
Можно вводить тильду в URL через %7E без изменений в смысле.

> То же самое с любой разметкой в URL
Было сделано, чтобы разметка тоже терминировала URL. Но, видимо, так не надо. Для исправления убрать строку с mark_subregex из определения url_end_regex.
No. 1000811  
145566845_p0.jpg - (465.50KB, 720×960)
1000811
>>1000804
> В основном, похоже, stateless.
Stateless за исключением circa $tc_db, I mean.
> но неясно, зачем вообще создавать инстансы класса, если это могут быть статические методы
Чтобы если одному методу сервиса передавать аргументы другому методу внутри того же сервиса, можно было при передаче условное $tc_db пропустить. Although, в PHP оно несколько менее актуально потому, что для обращения к состоянию класса надо всегда писать $this->, и аргументами при очень частом обращении может получиться короче и удобнее.

Чтобы, if applicable, по этому $tc_db выхватить некое состояние, которое окажется общим для методов сервиса и которое потому нет нужды выхватывать в каждом методе отдельно.

> то можно поместить в статическую переменную класса.
И в каждом public static методе класса писать if(!self::is_initialized) self::init(); или self::init_if_not_already() тогда?
Покуда статичное состояние нельзя инициализировать на этапе создания типа в статическом конструкторе без параметров (although, в PHP статических конструкторов похоже нет), есть резон делать объект объектом. Даже если объект создаётся единожды.

> Предлагаю таскать везде первым аргументом один объект типа Wiring, в котором будет всё состояние
Совсем-то всё, наверное, не надо. Какие поля/свойства будут у Wiring? $tc_db ($db_conn), maybe распаршенный из $_POST/$_GET или JSON'а therein запрос, maybe Logger, maybe User или AuthContext (User + session token management). Такое видится разумным и достаточно общим.

> Давайте уж entities (aka models)
Мне distinction видится в том, что модель — достаточно простой объект, +- соответствующий сообщению, загоняемому в базу или получаемому из базы, передаваемому из-в сервис, в то время как entity — нечто целостное и с более сложными операциями и проверками валидности состояния. Although, тут есть разные определения.

> Я думаю, эту логику следует вынести в классы-entities
Проблема с инкапсуляцией кода в целостные entities в том, что далеко не для всех операций эти entities нужны целиком. Например, к каким данным привязывать ReportPosts()? К Board, как сейчас? Но зачем для report'а постов тащить информацию о том, какие бывают файлы спойлеров и другие необходимые для генерации бордовых страниц вещи? От Board там нужен только id и enable_reports. Можно это решать lazy load'ом. А можно в целом от такого способа организации кода отойти во что-то ближе к SOA и к (?) CQRS. Что я сейчас и предлагаю.

Если затея причесать зоопарк таким образом по-прежнему кажется сомнительной, то давайте circa классический MVC или другие ваши (желательно, с хорошим описанием) предложения.
No. 1000813  
0262.jpg - (306.25KB, 1920×1080)
1000813
С чётким определением классов моделей вижу такую проблему, что местами это несколько негибко и что это дополнительный бойлерплейт, который муторно менять под конкретные запросы/действия. Вполне возможно, что много где окажется проще без него.
> У class User может быть метод can_delete_locked_threads_on($board). И его реализация уже будет делом класса, а не торчать наружу. Ну и не столько уж много досок может быть, чтобы мы не могли позволить себе хранить их список в памяти.
Хм, indeed.

>>1000803
Там при регенерации страниц в любом случае стоит сделать case when или self join на случай, когда хранимая в базе отрендеренная версия поста не соотвествует поменявшимся флагам/содержимому, чтобы эту отрендеренную версию не тянуть или не тянуть нужное для рендеринга: простого SELECT * не выходит.
No. 1000814  
Although, на регенерацию треда можно посты вытягивать в два запроса: один select'ит те, где можно переиспользовать нарендеренное, другой select'ит те, где были изменения и надо опять BuildPost.
No. 1000815  
Вы лучше распишите псевдокодом как выглядит перенос треда на другую доску, там быстро все особенности архитектуры проявятся.
No. 1000816  
https://codeberg.org/yakui-lover/fbe-410/src/commit/cce92a86749192ce7670e5a611178ce4e38b252a/inc/lang/ru/LC_MESSAGES/kusaba.po#L48-L53

Странный перевод. Что имелось в виду?

https://codeberg.org/yakui-lover/fbe-410/src/commit/cce92a86749192ce7670e5a611178ce4e38b252a/lib/javascript/kusaba.js#L471

Зачем выставлять его в null здесь? add_navbar_check_job() же вызвал setTimeout().

Ну и там ещё довольно много ошибок и мест, требующих обсуждений, в JS.
No. 1000817  
>>1000816
> Странный перевод. Что имелось в виду?
Когда-то Кусаба (или Серисса, dunno) позволяла embed'ить видео с других сайтов, вроде Youtube. Имелось ввиду это.

> Зачем выставлять его в null здесь?
Скорее всего, ошибка.
No. 1000818  
aoba_huh.png - (2.37MB, 1920×1080)
1000818
2 more PRs opened.

Предлагаю прежде всего заняться сейчас issue #9. Остальное не так уж важно.

XSS вроде более-менее понятно, как поправить. Про CSRF: расставлять везде проверку/выставление $_SESSION['${action_name}_csrf_token']? Инициализировать md5(openssl_random_pseudo_bytes(16))?

Это не означает, что я думаю, что нет смысла обсуждать параллельно будущую архитектуру, though. Думаю и пишу >>1000815 вот это.

>>1000789
Красивые у вас картинки.
No. 1000819  
>>1000809
Извините, что заинтриговали. Нет там ничего. Казалось, что в RSS инъекцию любого текста можно сделать.
No. 1000820  
>>1000818
> Про CSRF: расставлять везде проверку/выставление $_SESSION['${action_name}_csrf_token']?
Разве не будет достаточно session_start(['cookie_samesite' => 'Strict'])? И то же для печенек, которые не session token.
No. 1000821  
>>1000820
> Разве не будет достаточно session_start(['cookie_samesite' => 'Strict'])?
А вы знаете, например, что оно заставляет браузер не выставлять куки, даже если пользователь перешёл по ссылке с другого сайта? А ссылки с дружественных сайтов вполне могут делать. Так у пользователей пароли потеряются, а персонал будет вынужден заново логиниться — а ещё ведь нужно проверить, что ссылка была на 014chan.org, а не на 0l4chan.org.

> И то же для печенек, которые не session token.
А почему на session token не нужно выставлять?
No. 1000823  
При попытке постинга в >>/b/1005703 случается 500 Internal Server Error. С другими тредами такого нет.
No. 1000824  
>>1000823
У меня >>/b/1006671 запостилось. Возможно, дело в том, какой вы пытались отправить пост, и ошиька в обработке. Можете ли воспроизвести то, что пытались отправить?
No. 1000825  
IMG_0001.png - (269.02KB, 1280×798)
1000825
>>1000824
Любой пост не могу. Дело не в браузере, если что
No. 1000826  
>>1000825
Дело в том что вы тот тред отслеживаете.
> Updates to non-transactional tables can only be done in either autocommitted statements or single-statement transactions
Ну и дичь. Попробуйте сейчас.
No. 1000827  
>>1000826
Теперь работает
No. 1000829  
143793270_p0.jpg - (677.91KB, 2078×2976)
1000829
>>1000815
Примерно так?
SELECT * FROM boards WHERE id = :source_board_id OR id = :target_board_id FOR UPDATE
WITH
id_map AS (
  SELECT
    posts.id AS old_id,
    boards.next_post_id + row_number() OVER (ORDER BY posts.id) - 1 AS new_id
  FROM posts, boards
  WHERE
    posts.board_id = :source_board_id AND
    posts.thread_id = :source_thread_id AND
    boards.id = :target_board_id
),
target_thread_id AS (
  SELECT
    (SELECT id_map.new_id FROM id_map WHERE id_map.old_id = posts.thread_id)
  FROM posts
  WHERE posts.board_id = :source_board_id AND posts.thread_id = :source_thread_id
  LIMIT 1
),
update_posts_indecies AS (
  UPDATE posts SET
    posts.board_id = :target_board_id,
    posts.thread_id = target_thread_id,
    posts.id = id_map.new_id
  FROM id_map
  WHERE posts.board_id = :source_board_id AND posts.id = id_map.old_id
),
update_board_inserting_id AS (
  UPDATE boards SET
    boards.next_post_id = (SELECT MAX(id) FROM posts WHERE board_id = :target_board_id) + 1
  WHERE boards.id = :target_board_id
),
moved_ips AS (
  SELECT
    poster_ips.id AS old_id,
    poster_ips.ip_aes AS ip_aes,
    SUM(CASE WHEN deleted_at IS NOT NULL THEN 1 ELSE 0) AS posts_present
  FROM id_map
  INNER JOIN posts ON posts.board_id = :target_board_id AND posts.id = id_map.new_id AND posts.poster_ip_id IS NOT NULL
  INNER JOIN poster_ips ON poster_ips.board_id = posts.board_id AND posts.poster_ip_id = poster_ips.id
  GROUP BY poster_ips.id
),
update_source_board_poster_ips AS (
  UPDATE poster_ips SET
    poster_ips.posts_present = poster_ips.posts_preset - moved_ips.posts_present
  FROM moved_ips
  WHERE poster_ips.board_id = :source_board_id AND poster_ips.ip_aes = moved_ips.ip_aes
),
upsert_target_board_poster_ips AS (
  INSERT INTO poster_ips (board_id, id, ip_aes, posts_present)
  SELECT :target_board_id, uuidv4(), ip_aes, posts_present FROM moved_ips
  ON CONFLICT (board_id, ip_aes)
  UPDATE poster_ips SET poster_ips.posts_present + moved_ips.posts_present
),
update_poster_ip_id_in_posts AS (
  UPDATE posts SET
    poster_ip_id = poster_ips.id
  FROM id_map
  INNER JOIN moved_ips ON posts.poster_ip_id = moved_ips.old_id
  INNER JOIN poster_ips ON poster_ips.board_id = posts.board_id AND poster_ips.ip_aes = moved_ips.ip_aes
  WHERE posts.board_id = :target_board_id and posts.id = id_map.new_id
),
delete_reports AS (
  DELETE FROM reports
  USING id_map
  WHERE
    (SELECT enable_reports FROM boards WHERE id = :source_board_id) AND
    NOT (SELECT enable_reports FROM boards WHERE id = :target_board_id) AND
    reports.post_id = id_map.old_id
),
update_reports AS (
  UPDATE reports SET
    reports.board_id = :target_board_id
    reports.post_id = id_map.new_id
  FROM id_map
  WHERE
    (SELECT enable_reports FROM boards WHERE id = :source_board_id) AND
    (SELECT enable_reports FROM boards WHERE id = :target_board_id) AND
    reports.board_id = :source_board_id AND reports.post_id = id_map.old_id
),
SELECT
  old_id,
  new_id,
  message,
  attachment_basename,
  attachment_thumbnail_extension
FROM posts
INNER JOIN id_map ON post.board_id = :target_board_id AND posts.id = id_map.new_id


$rows.forEach(fix_reflinks_in_message, move_or_remove_attachment_files);
COMMIT
return ['status' => 'OK', 'old_thread_id' = $source_thread_id, 'new_thread_id' => $new_thread_id]
No. 1000830  
>>1000829
Нет, не так. Вы мне даёте sql-портянку, я прошу как это будет выглядеть в некоем абстрактном MoveThreadService. Начиная с авторизации. С вызовами пересборки и прочая. Можно не кодом, а взаимодействующими сущностями.
Но впрочем костыли связанные с привязкой айпишника к конкретной доске уже сейчас видны. Алсо, мускуль не может в deferred constraint, так что или никаких foreign key, или оно падает на update_posts_indecies, или ещё больше обвязки из вставить-удалить вместо обновить.
>reports
Не имеет смысла с точки зрения пользования. Что админ будет переносить треды не посмотрев на репорты сомнительно, а если и будет, удалять их не требуется, так как они не отсмотрены.

Ало, indices.
No. 1000831  
143426402_p0.jpg - (753.38KB, 2102×3030)
1000831
>>1000818
> Предлагаю прежде всего заняться сейчас issue #9
OK.
> XSS вроде более-менее понятно, как поправить
Мне кажется, там +- везде достаточно поставить валидацию, что ID действительно ASCII digits, и что имена борд/апплетов [\w].

> CSRF
>>1000821
Тогда пока поставить на 'cookie_samesite' => 'Lax' и 'cookie_domain' => KU_DOMAIN, although, ЕМНИП, в браузере есть какие-то проверки/ограничения на соответствие домена, выставившего cookie, и домена, на который cookie отправляется, и 'cookie_domain' самостоятельно ставить не надо.

> что оно заставляет браузер не выставлять куки
Насколько я понимаю, оно заставляет так поступать конкретно с phpsessid печенькой, к которой management действия привязаны, а другим печенькам можно ставить политику as neeed.

> 014chan.org, а не на 0l4chan.org
Разве нельзя на 0l4chan.org целиком MitM'ить соединение меж клиентом и подлинным 014chan.org-сервером? MitM-сервер поменяет домены печенек на 014chan.org при передаче серверу и вернёт обратно на 0l4chan.org при отдаче клиенту. Браузер счастлив. Клиент счастлив. Владелец фишингового сервера счастлив as well.
No. 1000832  
145751771_p2.jpg - (190.32KB, 640×696)
1000832
>>1000830
> я прошу как это будет выглядеть в некоем абстрактном MoveThreadService. Начиная с авторизации. С вызовами пересборки и прочая. Можно не кодом, а взаимодействующими сущностями.
Jawohl, ждите.

> Но впрочем костыли связанные с привязкой айпишника к конкретной доске уже сейчас видны
Если не нравится, давайте тогда, как сейчас, держать шифрованный IP прямо в posts, и делать SELECT DISTINCT по нему для поиска по подсети и числа уникальных клиентов, although это более медленные SELECT'ы.

> Не имеет смысла с точки зрения пользования. Что админ будет переносить треды не посмотрев на репорты сомнительно, а если и будет, удалять их не требуется, так как они не отсмотрены.
Удалять их требуется, если доска, на которую осуществляется перенос, не поддерживает репорты, чтобы не было orphan'ов.

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

Пускай база данных будет consistent, жалко что ли?

> Алсо, мускуль
?? Мы же на Postgres будем переходить. После решения #9 и прочих более насущных проблем в том, что есть.

> Ало, indices.
Аря? My bad.
No. 1000833  
>>1000832
Если постгря, то зачем last_post_id когда можно просто CREATE SEQUENCE?
No. 1000834  
>>1000833
Потому что у нас composite primary key (board_id, post_id). А sequence'ы, насколько я понимаю, не работают с composite pkey даже в Постре.
No. 1000835  
>>1000834
>composite primary key (board_id, post_id)
Да, не (board_id, last_post_id). Чем ручками его дёргать, nextval/curval/setval и по последовательности на доску.
No. 1000837  
139385864_p0.jpg - (6.35MB, 1718×2406)
1000837
>>1000835
Как-то так то бишь?
SELECT
  posts.id AS old_id,
  nextval(boards.post_id_sequence_name) AS new_id
FROM posts,boards
WHERE
  posts.board_id = :source_board_id AND
  posts.thread_id = :source_thread_id AND
  boards.id = :target_board_id

Ясно.
No. 1000838  
epi_cjs.png - (191.03KB, 506×468)
1000838
>>1000815
Inside (png+zip)-related.
Если перейдём на единую таблицу posts, можно будет взять ORM/Data Mapper/…. А пока так.

>>1000829
1. Отлаживать-поддерживать эту радость потом можно будет так же просто, как императивный код?
2. Мы уже прямо-таки собрались мигрировать на постгрес и единую таблицу posts?

>>1000831
> Тогда пока поставить на 'cookie_samesite' => 'Lax' и 'cookie_domain' => KU_DOMAIN, although, ЕМНИП, в браузере есть какие-то проверки/ограничения на соответствие домена, выставившего cookie, и домена, на который cookie отправляется, и 'cookie_domain' самостоятельно ставить не надо.
При этих ограничениях куки подставляются только в GET-запросы и только если запрос делается в результате клика. Но это не есть надлежащее решение: работать это будет до первой XSS или GET, меняющего состояние (у нас последнее, вроде как, сейчас присутствует).

> Насколько я понимаю, оно заставляет так поступать конкретно с phpsessid печенькой, к которой management действия привязаны, а другим печенькам можно ставить политику as neeed.
> Разве нельзя на 0l4chan.org целиком MitM'ить соединение меж клиентом и подлинным 014chan.org-сервером?
My point was that, если администратор будет вынужден логиниться заново при переходе по ссылке с другого сайта, то он может и забыть проверить, что это домен не фишинговый. А без strict он просто не будет вынужден логиниться заново в таком случае.

И, если уж совсем вредничать, у нас faptcha_type в $_SESSION хранится.
No. 1000839  
Энивей, до 19го я ПР рассматривать не могу.
No. 1000856  
phash_dupes.png - (283.53KB, 419×867)
1000856
>>1000775
Вы забыли про 7x7 box blur в phash. Я реализовал phash. Обнаружилось только два легитимных false-positive (см. пик). А ваша ранее не обнаруженная Роко нашлась.

Для исключения первого можно хэшировать цветовую компоненту в дополнение к grayscale.

Это на 19k Булочных картинках.
No. 1000858  
162283373169.webp - (1.50MB, 2108×3274)
1000858
>>1000856
Пожалуйста, опубликуйте вашу реализацию же.
> А ваша ранее не обнаруженная Роко
Чего? В реализации, которую я здесь опубликовал и про которую говорил в >>/b/1006362
, она обнаруживается на расстоянии 0 (равные хэши). Неужели совсем позапускать-посмотреть неинтересно было?
No. 1000860  
>>1000718-пост можете disregard'нуть entirely. Там, ЕМНИП, была некая серьёзная ошибка в реализации. Точно не вспомню, какая.
No. 1000866  
Файл
удалён
>>1000858
Результаты на ваших примерах:
Дубликаты:
> 16/512 — расстояние меж https://ii.yakuji.moe/b/src/1705971491904.jpg и https://ii.yakuji.moe/b/src/1688504889304.jpg
2/64
> 28/512 — меж >>9388 и >>9496
6/64
> 40/512 — меж https://ii.yakuji.moe/b/src/1648398268154.jpg и https://ii.yakuji.moe/b/src/1572851490178.jpg
2/64

Похожие, но не идентичные:
> Для сравнения, на расстоянии 40/512 можно обнаружить https://ii.yakuji.moe/b/src/1681308511905.jpg и https://ii.yakuji.moe/b/src/1683280427230.jpg.
2/64

Scaling/сдвиг/padding:
> 138/512 — >>15995 и >>18833
4/64
> 148/512 — >>3380 и >>9520
8/64
> Например, https://ii.yakuji.moe/b/src/1673088974447.jpg, 1674079476839.jpg, 1676900847615.jpg, 1678044547885.jpg, 1695834706311.jpg, 1708415679326.jpg. Хотя меж некоторыми парами картинок из набора прямое расстояние и меньше 100/512, связным граф становится только при предельном прямом расстоянии меж индивидуальными хэшами-нодами 171/512
Pic related.
Последняя и предпоследняя имеют одинаковый хэш.

Вариации/частичные изменения содержимого:
> меж >>3681 и >>4892 36/512
2/64
> >>8513 >>8614 76/512
4/64
> >>5390 >>5455 116/512
26/64
> >>6377 >>6379 140/512
12/64
> >>14826 >>14826
(Во втором вы имели в виду >>14827)
12/64
> >>16709 >>16721 192/512
12/64
No. 1000868  
myPhash_zipjpeg.jpg - (585.85KB, 2337×3098)
1000868
>>1000858
> Пожалуйста, опубликуйте вашу реализацию же.
Here. Пример на PHP included. Хотя логичнее уж запускать отдельный процесс каждый раз.

Caveats:
  • вся прозрачность переводится в чёрный цвет, это можно поправить;
  • stb_image не умеет в webp, можно взять любую другую библиотеку, на неё ничего не завязано особо;
  • convo в 3-4 раза медленне, чем могло бы быть (separable kernel).
Можете скомпилировать в wasm, rewrite it in Rust, сделать аналог для JavaScript, удолить и забыть.

Нужна libstb. Компилирвать через ./build.sh. Запускать так:
$ ./myphash /tmp/mypics/1572851490178.jpg
8976a19847e827f6


> Чего? В реализации, которую я здесь опубликовал и про которую говорил в >>/b/1006362, она обнаруживается на расстоянии 0 (равные хэши). Неужели совсем позапускать-посмотреть неинтересно было?
Таки где ваша последняя реализация? В >>1000775? А то запутаться можно. Её я и смотрел.
No. 1000869  
> convo в 3-4 раза медленне, чем могло бы быть (separable kernel).
Хотя оно уже раза в 2-3 быстрее, чем pHash, который на гитхабе, лол.
No. 1000871  
mpl_2.png - (10.28KB, 548×447)
1000871
>>1000866
Вот корректный пик. Gomen.
No. 1000872  
512 and 64.webp - (24.22KB, 2189×474)
1000872
>>1000868
Спасибо, потестирую. Может, тоже попробую поприменять blur.

> В >>1000775?
Она самая. Но находит пару картинок с той девочкой она на расстоянии 0. У вас не так?
No. 1000875  
Хочу добавить, что мой код сам реализует извлечение luma-канала и масштабирование. То есть после того, как у нас есть массив RGB, никакие зависимости нам не нужны. Это решает проблему с «там разные алгоритмы приведения к grayscale и масштабирования». Его можно скомпилировать в wasm или даже написать копию на JavaScript.

>>1000872
> Она самая. Но находит пару картинок с той девочкой она на расстоянии 0. У вас не так?
Конкретно на этой не запускал. Ориентировался на то, что вы написали. А оказалось, что это устарело.
No. 1000886  
>>1000856
Потестил вашу реализацию по всем JPEG и PNG из своих булочных картинок. Было бы неплохо, если бы выложили список, по которому тестируете, чтобы синхронизировать. CSV с результатами моей и вашей реализации в архиве.

На расстоянии 0 находятся ещё два "false positive" про незначительные вариации, кроме вами упомянутых: (>>/b/2996, >>/b/2997) и (>>/b/8825, >>/b/18633).

Первые серьёзные false positive появляются на расстоянии 6: (>>/b/8242, >>/b/16575), (>>/b/3908, >>/b/23698). Всего 2 таких пары среди 12 новых/обновлённых кластеров.

А вот на расстоянии 8, из 30 свежих кластеров где-то половина — пары нерелевантных картинок.

Моя реализация на длине хэша в 64 бита (threshold 72) и размере sample'а 32x32 начинает деградировать на тех же 6 и 8 расстояниях. Однако на расстоянии 0 находит больше дубликатов: например (>>/b/8637, >>/b/25626), (>>/b/18011, >>/b/18312) у меня 0/64, у вас 4/64. Количество недубликатов на расстоянии 0 такое же, но недубликаты немного другие: (>>/b/11092, >>/b/11456), (>>/b/6224, >>/b/6226) и (>>/b/3154, >>/b/3161) вместо (>>/b/2996, >>/b/2997), (>>/b/7767, >>/b/19060) и (>>/b/8825, >>/b/18633).

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

В целом, поскольку уже на расстоянии 8/64 где-то половина найденного — лабуда, но не без интересностей, я и начал пробовать большие длины хэша и размерности сэмпла: при длине в почти 512 бит (threshold 617) и размере сэмпла 128x128 первый серьёзный false positive находится на 172/512, и до его наступления интересных похожестей находится больше, чем до 8/64 включительно. Поэтому хочу сделать phash не в 8, а в 64 байта а-ля SHA-512.

>>1000868
Ещё, такая заметка. Покуда нам нужно только левое верхнее 8x8 окно из 32x32 DCT матрицы, не обязательно считать всю 32x32 матрицу, а можно посчитать только это окно, сделав матрицы коэффицентов прямоугольными: 8x32 и 32x8--транспонированная. (32 * 32 * 32 * 2) / (32 * 32 * 8 + 32 * 8 * 8) — в 6.4 раза быстрее расчёт будет, когда окно меньше sample'а по стороне в 4 раза. Although, по сравнению со scale'ингом это, наверное, мелочь.
No. 1000894  
aoba_oueo•myphash_zipjpeg.jpg - (839.88KB, 694×1000)
1000894
> Было бы неплохо, если бы выложили список, по которому тестируете, чтобы синхронизировать. CSV с результатами моей и вашей реализации в архиве.
В архиве (misc/*). Названия у вас, скорее всего, другие, поэтому сделал csv md5,filename.

> На расстоянии 0 находятся ещё два "false positive" про незначительные вариации, кроме вами упомянутых:
Понятно. Ну, я чисто визуально смотрел. Мне кажется, что если человек не может увидеть разницу на 150x150 — то phash тем более не может/не должен/не предназначен для такого.

Это был просто proof-of-concept, что можно без сложностей написать оригинальный phash. Идей для улучшения у меня много, но мне сначала нужно написать/улучшить свои скрипты для визуализации. И было бы хорошо, если бы у нас был единый список «как-то связаных между собой» картинок, которые должны находиться. Тогда можно будет автоматически оценивать.

Замена nearest-neighbour interpolation на bicubic сделала только хуже. А вот изменение размера блюра с 7 до 11 — сделала лучше (меньше Hamming distance у «как-то связанных между собой» картинок). А если выставить HASH_TYPE => HASH_TYPE_RADIAL (при тех C=>11, S=32, S1=>8), получается ещё лучше. Даже некоторые масштабированные находятся на расстоянии 0, которые раньше не. Каких-то false positives, которые не связаны между собой очевидным образом, нет.

Там в архиве новая версия. Можно играться с параметрами в core/params.h. Например, вашему будет примерно соответствовать C => 1, S => 128, S1 => 23, HASH_TYPE => HASH_TYPE_RADIAL. Только у меня масштабирование через nearest-neighbour interpolation, а у вас, вроде бы, lanczos3?

> Although, по сравнению со scale'ингом это, наверное, мелочь.
Scaling тоже мелочь (оно ведь O(S^2)). Узкое место — в свёртке (блюре). И оно сейчас O(W * H * C^2), а можно сделать O(W * H * C). Только пока руки не доходят.

Пора открывать /dev/?
No. 1000895  
found_banner.png - (47.90KB, 392×221)
1000895
>>1000894
> Даже некоторые масштабированные находятся на расстоянии 0, которые раньше не
Пример.
No. 1000905  
c.png - (15.92KB, 615×79)
1000905
>>1000894
> спойлер
Капча согласна.
А надо ли? Devочкотреда хватит наверное.
No. 1000912  
stack.webp - (2.34MB, 773×1386)
1000912
Как ваши дела, Аоба-сан?

>>1000894
> сделал csv md5,filename
Принято. Я решил исключить дубликаты, для которых RGB пикселей целиком совпадают, оставив только один экземпляр. Иначе слишком много Ханю.

> А если выставить HASH_TYPE => HASH_TYPE_RADIAL (при тех C=>11, S=32, S1=>8), получается ещё лучше
Когда R2_THRESHOLD = 64, хэш получается длиной 57 битов. Возможно, из-за меньшей длины.

Чтобы входило то же количество AC, то есть 64, нужно S1=10 и R2_THRESHOLD=72. Если соответствующим образом обновить parameters.h и phash_of_dct.h, то в вашей реализации (C => 7) не получается результатов, как в моей с тем же threshold.

Тестировалось только по JPG и PNG.

Для aoba_phash_32_8_8 из недубликатов на расстоянии 0 ранее не был упомянут (1871, 1872): https://014.yakuji.moe/b/res/25313.html#25341 и рядом. ID перед ':' не совпадают с ID поста. Сделать совпадающими?
Для aoba_phash_32_72 из недубликатов на расстоянии 0 оказывается (16614, 16658)[>>/b/7767], (4666, 12320)[>>/b/8825], (13602, 16347)[>>/b/5852], (4886, 16708), (2237, 17041).
do_round_phash.php 32 72 находит больше дубликатов на расстоянии 0. Недубликаты те же, что описаны в >>1000886.
CSV в архиве.

> Только у меня масштабирование через nearest-neighbour interpolation, а у вас, вроде бы, lanczos3?
Насколько я понимаю, lancoz3. Хотя я ещё буду опробывать shrink-on-load фичи + lancoz3.
Также, у меня DC не участвует в хэше, а у вас, хотя не участвует в выборе медианы, в расчёте хэша участвует.

Меня libvips привлекает помимо матричной арифметики тем, что там реализовано последовательное чтение картинки для форматов, которые это поддерживают: не нужно грузить всю картинку в память. Поэтому я хочу использовать его. BTW, я как-то пропустил это обстоятельство, но есть wasm-libvips. https://www.libvips.org/2020/09/01/libvips-for-webassembly.html Где-то 5 MiB весит. И тогда можно его же на клиенте крутить.
No. 1000913  
124370018_p0.jpg - (373.71KB, 1215×1920)
1000913
> Ну, я чисто визуально смотрел.
Так я тоже. Только не на 150x150 thumbnail'ы, а на оригиналы или их уменьшенные до вмещения в 15.6'' QHD монитор копии.

> Мне кажется, что если человек не может увидеть разницу на 150x150 — то phash тем более не может/не должен/не предназначен для такого.
So desu ka? Как по мне, phash всё-таки про картинки, а не про их уменьшенные копии, хоть те и берутся для его вычисления. Да и thumbnail'ы у нас не 150x150, а 200x200 на страницах.
No. 1000916  
> нужно S1=10 и R2_THRESHOLD=72
> S1=10
На самом деле нет и достаточно S1=9.
No. 1000917  
> lancoz3
Это ж надо было дважды имя Ланцоша так искаверкать. Прошу прошу прощения.
No. 1000918  
aoba_danoso.jpg - (1.06MB, 1975×3508)
1000918
>>1000912
Аобе нужен ещё день-два на эксперименты, десу.

1. Осозналось, что не нужно считать свёртку (будь то box blur, Gaussian blur или что-либо ещё) в каждом пикселе исходного изображения. Если у нас nearest-neighbor или lanczos, там вообще очень мало точек, в которых нужно её посчитать.

2. Появилась гипотеза, что лучше делать Gaussian blur. Причём прямоугольный и с динамически выбираемыми размерами свёртки. Ну, 1024x1024 нужно блюрить с большим радиусом, чем 32x32.

3. Пробоваю dhash (difference hash). Но там свои особенности: если использовать Hamming distance, то там большой кластер в районе <все нолики> будет. Ну, в теории ещё и во <все единички>, но на практике последнего кластера не образуется. В pgvector для битовых векторов, кроме Hamming distance есть ещё Jaccard distance. И вот тут уже оно работает хорошо. Только оно не спасает от теоретического кластера в районе <все единички>, нужно просто инвертировать хэш, если единичек получилось >66%.

4. Внезапно, Jaccard distance с phash тоже хорошо работает. По первым впечатлениям даже лучше, чем Hamming distance. Но нужно пристальнее смотреть.

5. Можно считать хэш от медианы от несколько меньшего квадрата/четверть-окружности и приписывать его копию несколько раз.

Это только то, что уже закожено.
No. 1000919  
104645504_p0.webp - (3.75MB, 6000×8000)
1000919
>>1000918
> Внезапно, Jaccard distance с phash тоже хорошо работает
В случае с нашим медианным phash, jaccard_distance — это монотонно возрастающая функция от hamming_distance. Поэтому работает в точности также.

jaccard_distance(a, b) = hash_length - hash_length * popcnt(a & b) / popcnt(a | b)
= hash_length * (popcnt(a | b) - popcnt(a & b)) / popcnt(a | b)
= hash_length * popcnt(a ^ b) / popcnt(a | b)
= popcnt(a ^ b) * hash_length / popcnt(a | b)
= popcnt(a ^ b) * hash_length / {[popcnt(a) + popcnt(b) + popcnt(a ^ b)] / 2}
= popcnt(a ^ b) * hash_length / {[hash_length / 2 + hash_length / 2 + popcnt(a ^ b)] / 2}

..потому что половина битов в a и в b еденица, потому что хэш по медиане.

Получаем
jaccard_distance(a, b)
= 2 * popcnt(a ^ b) * hash_length / [hash_length + popcnt(a ^ b)]
= {2 * popcnt(a ^ b) * hash_length + 2 * hash_length**2 - 2 * hash_length**2} / [popcnt(a ^ b) + hash_length]
= -2 * hash_length**2 / [hamming_distance(a, b) + hash_length] + 2 * hash_length


Такая вот прямая зависимость.
No. 1000920  
> прямая зависимость
Или гиперболическая. Не знаю, как правильно.
No. 1000921  
ping -c1 yakui-lover (regarding PRs).

>>1000919
Correct. Проблема была в моей методика подсчёта порога, когда появляются false positives. И зависимость действительно нелинейная.

Справедливости ради, в результате DCT могут получиться равные элементы. Но на ычанских картинках такое бывает, только если изображения полностью чёрные — там получаются все нули, и хэш — все нолики.
No. 1000923  
a_fruitless_experiment.webp - (17.53KB, 1499×460)
1000923
>>1000921
Не совсем related, но как вариант, можно попробовать dhash не по самой картинке, а по DCT. И по нему уже Jaccard distance.

>>1000918
> Можно считать хэш от медианы от несколько меньшего квадрата/четверть-окружности и приписывать его копию несколько раз.
Тоже пробовал было делать для phash подобное, и увы у меня толку особо не вышло, если вышло вообще. В round_phash.php именно посекторный обход в частности в связи с этими попытками.
No. 1000924  
Phash_CC.png - (299.98KB, 641×700)
1000924
Лучше всего себя показывает
blur=gauss_dyn[alpha=0.8,beta=4],scale=nn,S=64,post_blur=gauss[ksize=3,sigma=0.333333],hash=phash[Z=16,mask=circle]
с threshold в 36 бит (не overfitting, вычислено как 0.6 × когда_количество_дубликатов_начинает_превышать_80k).

Конфигурация должна быть понятна, кроме gauss_dyn[alpha=0.8,beta=4]. Там по каждой размерности вычисляется sigma = size_orig / size_scaled * alpha, kernel_size = ceil(sigma * beta) | 1. Здесь | 1 значит «прибавить 1, если чётное».

Оно находит всё то, что вы привели в качестве примеров того, что не находит, хотя получено в результате scaling/padding/crop.

Вот примеры из ычанских картинок.

Когда найду время, постараюсь причесать код и выложить.

>>1000923
> Не совсем related, но как вариант, можно попробовать dhash не по самой картинке, а по DCT. И по нему уже Jaccard distance.
Тогда уж кодировать знак разницы между соседними в zig-zag order в матрице, которая результат DCT. Но оно показывает себя чуть хуже.
No. 1000925  
Ах да, на этой конфигурации false positives нет. А для gauss_dyn установлен максимум на kernel_size в 255. Там всё равно узкое место в декодировании. Даже с нормальными библиотеками.
No. 1000926  
>>1000921
Сегодня посмотрю.
No. 1000927  
>>1000926
codeberg лежит \shrug
No. 1000928  
codebernd
No. 1000929  
>>1000921
It's up.
No. 1000930  
И >>1000909 тоже.
No. 1000931  
phash_plot_v3.png - (147.24KB, 1000×800)
1000931
>>1000929
Thx. Через несколько дней продолжу открывать PR с фиксами очевидного.

>>1000912
А нам, собственно, для чего нужно оптимизировать хэш?

Устроены все результаты, которые у меня получились, одинаково (пикрелейтед). x — threshold (в % от длины хэша), y — сколько новых картинок находятся при очередном увеличении threshold. Пояснения нарисованы для красного графика, но форма у всех графиков одинаковая. Чисто визуально по графику какой-либо вариации можно определить (1) и (2), всё, что между, нужно смотреть глазами. Всякие редкие масштабированные/растянутые/обрезанные продолжают существовать в промежутке между (1) и (2).

И вот, собственно, мой вопрос — как нам оценивать разные вариации? Чтобы как можно больше дубликатов/масштабированных/растянутых/обрезанных находилось до границы (1)? Или до границы (2)? Или просто нужно как можно больше найденных картинок в какой-то точке между (1) и (2), где оно ещё находит только related-картинки (для красного графика — около 15%)? Если последнее, то нужно опасаться overfitting’а для конкретного датасета.
No. 1000932  
brk.png - (80.28KB, 1481×747)
1000932
Штмл поломался.
No. 1000933  
>>1000932
fixed
No. 1000934  
119580783_p0.webp - (627.95KB, 2048×1443)
1000934
>>1000931
В идеале дубликаты/масштабирования/растягивания/обрезки должны строго на меньших расстояниях, чем средние/серьёзные изменения. Но с обрезками и сдвигами, увы, у меня не получилось.

> Или просто нужно как можно больше найденных картинок в какой-то точке между (1) и (2)
Я оценивал по количеству найденного до точки, когда начинаются серьёзные false positives (начинает путать девочек и помидоры), и до точки, когда false positives по сравению с true positives становится слишком много. Последняя упомянутая точка определялась на глаз.

В меньшей степени по характеру найденного (потому что с этим удач особых не было): скажем, одна из вариаций хэша по DCT zoom cube'а находила (>>/b/4173, >>/b/6116) на расстоянии 196/512, а do_round_phash.php на 223/512 (когда, если смотреть в общем, граф уже давно целиком связный и hamming_evo.py бесполезен).

По-хорошему нужно было бы
> единый список «как-то связаных между собой» картинок, которые должны находиться.
Построить такой список можно, выстроив пары в порядке возрастания phash или SSIM, присваивая вручную парам со схожим содержимым некий действительный similarity score. И даже так это очень много работы. Как вариант, посмотреть, что в OpenCV есть более современное и тяжёлое, чем эту работу можно упростить.

Или пока не заморачиваться, найти менее трудозатратными средствами вмещающийся в 512 бит вариант, и пусть будет MVP. И когда-нибудь если захочется заняться длинными серьёзными исследованиями или появится настоящий CV сварщик, то пытаться лучше.
No. 1000935  
>>1000934
Кажется saucenao/ascii2d/wait до сих пор не умеют в кропы и сдвиги, так что всё равно GJ.
c:hanako
No. 1000937  
aoba_maki.jpg - (258.74KB, 1448×2048)
1000937
Представляю вам на суд вариант
blur=gauss_dyn[alpha=0.8,beta=4],scale=nn,S=64,post_blur=gauss[ksize=3,sigma=0.333333],hash=phash[Z=16,mask=circle],post_sobel=1.
Размер хэша 193 бит (да, на единицу больше, чем 128+64, но это не специально), предлагаемое безопасное расстояние — 40〜42 бит (Hamming).

В репозитории https://codeberg.org/aoba/my_phash — со всеми bells and whistles, в архиве в прикреплённой картинке — минимум, нужный для компиляции хешера и скрипт для сборки. Теперь нужна SDL2_Image вместо stb, оно умеет в webp.

Мои выводы:

✳ Перед скейлингом нужно делать динамический прямоугольный Gaussian blur (размеры ядра считаются из размеров изображения, но ядро не больше 255x255, чтобы не DoS'или).
Ланцош плохо даунскейлит некоторые картинки: https://codeberg.org/aoba/my_phash/raw/commit/10507d8d7b1e996020568fb4484d86b6c96675b6/_demo/lanczos.png, потому что оно зависит только от маленькой доли всех пикселей исходного изображения (1.5% для 1024x1024 → 128x128).
Нам не нужно считать свертку blur'а в каждом пикселе исходного изображения, а только в тех, которые требуются алгоритмом масштабирования.
Для скорости (уменьшения количества точек, в которых нужно считать свёртку) можно использовать nearest-neighbour, после блюра нет разницы.

✳ Нужно также блюрить Гауссом после скейлинга: ядро 3x3, сигма 1/3 выглядят оптимальными (для 64x64, для 128x128 нужно больше).

✳ Обозначение: Z — сторона верхней левой подматрицы DCT, которую мы принимаем во внимание.
Вообще все варианты с Z=8, включая канонический phash, выдают один и тот же хэш на bad1.png, bad2.jpg, bad3.jpg (путают bad1 и bad2/bad3). Это картинки из архива Ычана 1614714858949.png, 1610896340790.jpg, 1610895433020.jpg. Копии можно найти в том же репозитории в _demo/.
Z=32 и даже Z=24 — это слишком много, начинает хуже находить legitimate дубликаты. Я остановился на Z=16.

✳ Нужно применять фильтр Собеля после скейлинга и размытия, иначе плохо отличает те же bad1.png vs bad2.jpg/bad3.jpg.
У некотрых вариантов с post_sobel=1 тоже есть failure mode: плохо отличают 1755031272746.jpg vs 1757275052034.jpg vs bad3.jpg.
Но представленный мной вариант отличает их с огромным запасом (расстояние около 40%).

✳ Безопасное расстояние для вашего хэша составляет не больше 139 бит. Были найдены следующие false-positives, первый из которых совсем unrelated:

# img1 img2 distance_in_bits
1610895433020.jpg 1693216927545.png 140
1712238432772.png 1470588477270.png 142
1595774788245.png 1588697792032.png 142
1641455194459.png 1623844005270.png 148
1556647751230.png 1557674602249.png 152
...

Про сравнение моего варианта и вашего будет в последующем посте.
No. 1000938  
aoba_sincos.png - (393.43KB, 900×1128)
1000938
“a” обозначает ваш вариант, “p” — мой.

Ваши примеры (исключая простые случаи, где оба выдают очень маленький процент; и исключая heavy padding, где ни один ни справляются):

https://014.cdn.ganbaranai.moe/b/src/170370140818.jpg vs https://014.cdn.ganbaranai.moe/b/src/168217548738.jpg:
a => 23.828%, p => 15.544%

https://014.cdn.ganbaranai.moe/b/src/163730500889.jpg vs https://014.cdn.ganbaranai.moe/b/src/158460154167.jpg:
a => 30.469%, p => 24.870%

https://ii.yakuji.moe/b/src/1673088974447.jpg vs https://ii.yakuji.moe/b/src/1674079476839.jpg:
a => 17.578%, p => 9.326%

Из архива Ычана были случайно выбраны 2000 изображений, у которых ширина и высота >= 500.
Они были подвергнуты следующим преобразованиям: даунскейлинг в 50%, 25%, 13%; кроп 5% сверху+снизу, справа+слева, со всех сторон; паддинг 5% чёрным сверху+снизу.
Затем вычислялось расстояние с исходным.
threshold для вашего был взят 27% (138 бит), для моего — 22% (42 бита).


# variant transform_index min max 90% average median num_caught_at_thres num_caught_at_90%_thres
# transforms: 0 => scale 50%, 1 => scale 25%, 2 => scale 13%, 3 => crop top+bottom, 4 => crop left+right, 5 => crop all, 6 => pad top+bottom

a 0 0.000% 51.953% 0.781% 0.501% 0.391% 1997 1997
p 0 0.000% 33.161% 2.073% 1.355% 1.036% 1997 1996

a 1 0.000% 51.953% 1.562% 0.886% 0.391% 1996 1996
p 1 0.000% 34.197% 4.145% 2.507% 2.073% 1995 1991

a 2 0.000% 51.953% 7.812% 4.728% 5.859% 1994 1993
p 2 0.000% 34.197% 7.254% 4.091% 3.109% 1988 1985

a 3 7.031% 51.953% 32.422% 27.404% 27.344% 938 431
p 3 0.000% 51.813% 24.870% 19.930% 19.689% 1466 1079

a 4 4.688% 54.688% 32.031% 26.402% 26.562% 1091 624
p 4 0.000% 36.269% 24.870% 19.134% 18.653% 1553 1212

a 5 8.594% 57.812% 44.922% 38.923% 39.062% 29 8
p 5 0.000% 63.212% 35.233% 29.402% 29.016% 134 55

a 6 8.984% 53.125% 30.078% 25.304% 25.391% 1394 822
p 6 0.000% 44.560% 23.834% 19.544% 19.689% 1559 1182

0〜2 можно игнорировать, с ними отлично справляются оба.

max значительно меньше на всех, кроме 5 (но с 5 вообще оба плохо справляются).
average и median значительно меньше на всех 3〜6.

num_caught_at_thres и num_caught_at_90%_thres в процентах от 2000 (исключая 0〜2, где везде >99%):
<= threshold:     47% → 73%, 55% → 78%, 1% → 7%, 70% → 78%.
<= 0.9*threshold: 22% → 54%, 31% → 61%, 0% → 3%, 41% → 59%.

Изменение ratio тоже пробовалось, но результаты такие же, как у 0〜2.

Код этого эксперимента и список файлов выложу позже.
No. 1000940  
aoba_aji.png - (655.54KB, 926×1348)
1000940
> И когда-нибудь если захочется заняться длинными серьёзными исследованиями или появится настоящий CV сварщик, то пытаться лучше.
А чего вообще в теории можно сделать-то?
В проекте pHash есть ещё хэш на интегралах Радона — его затратно считать, там O(n_rays * W * H), n_rays = 180.
Ещё там есть волночки (wavelet) — я не верю, что они существенно лучше, чем DCT.
CNN-based (как в https://github.com/idealo/imagededup) — как-то слишком круто для нас.
Average hash — оче плохой.
dhash, dhash-over-DCT, zigzag и прочее тоже работает хуже, чем DCT.

Только автоматическое убирание паддинга можно ещё сделать.

> Построить такой список можно
Ну я сделаль убирание кластеров из очень похожих изображений (mean_error.c и utils/hide_genuine_clusters.py) и viewer/. В том же репозитории.
No. 1000941  
> Код этого эксперимента и список файлов выложу позже.
In the repo now (_eval/).
No. 1000942  
94706770_p0.jpg - (1.24MB, 2676×3360)
1000942
>>1000937
Приятные результаты у вас. Детально посмотрю в воскресенье. Have been в разъездах.%

Пока, читаючи на смартфоне замечено, что по-прежнему включаете DC (то бишь, block[0]) в хэш. Так и задумано?
No. 1000943  
aoba_poI.png - (332.65KB, 703×1145)
1000943
>>1000942
> Пока, читаючи на смартфоне замечено, что по-прежнему включаете DC (то бишь, block[0]) в хэш. Так и задумано?
Во-первых, один бит из 193 не повлияет заметно на результат.
Во-вторых, имеет смысл выбросить буквально любой бит, кроме DC.

Обозначения: N — длина хэша, DC = hash[0], AC = hash[1:N].

Утверждение: при определённом допущении*, parity(AC) 【a.k.a. popcount(AC)%2】 есть функция от DC и N: parity(AC) = f(DC, N). Таким образом, мы можем выбрать любой бит из AC и отбросить его: его можно реконструировать как b = parity(AC_without_b) XOR f(DC, N).

* Допущение: либо изображение полностью одноцветное и hash состоит только из нулевых бит (и, следовательно, DC=0), либо DC=1 и у нас ровно половина (исключая саму медиану, если N-1 нечётное) элементов больше медианы (popcount(AC) = floor((N-1) / 2)).

Это допущение выполняется для всех изображений в архиве Ычана, допущение DC=1 — не выполняется для 19 из 95031.

Пруф запощу завтра.
No. 1000946  
Пруф: разберите все восемь вариантов ⟨N%4, DC⟩. Получится
def f(DC, N):
  if N % 4 == 0 or N % 4 == 3:
    return DC
  else:
    return 0
No. 1000947  
>Pr. 95
Про картинки уже обсуждалось, решается ограничением по макс. ширине/высоте.
Видео... Ну, в целом, такие лимиты сервер съест.
No. 1000949  
>>1000947
Понятно, что настолько продвинутый желающий навредить так или иначе найдёт способ, но конкретно это легко поправить: добавить к ffmpeg’у опцию -max_pixels 100M. Этого должно быть достаточно, но хорошо бы ещё запускать сам ffmpeg, предварительно сделав ulimit -v 20971520 (20G виртуальной памяти) и ulimit -t 30 (30 секунд чистого CPU time). Поскольку exec() в PHP уже запускает команду под /bin/sh -c ..., для этого даже не нужно создавать никаких новых процессов или что-то переделывать.
Удалить сообщение []
Пароль  
[Mod]