diff --git a/.env.example b/.env.example index b9818cd9..cc54ed2f 100644 --- a/.env.example +++ b/.env.example @@ -82,8 +82,8 @@ UID_STARTS=10001 PHP_PATH= NAS_TOOLS_KEY= -MEILISEARCH_MASTER_KEY= -MEILISEARCH_SCHEMA=http +MEILISEARCH_SCHEME=http MEILISEARCH_HOST=127.0.0.1 MEILISEARCH_PORT=7700 +MEILISEARCH_MASTER_KEY= diff --git a/app/Console/Commands/MeiliSearchImport.php b/app/Console/Commands/MeiliSearchImport.php new file mode 100644 index 00000000..754fdcd7 --- /dev/null +++ b/app/Console/Commands/MeiliSearchImport.php @@ -0,0 +1,37 @@ +info("going to import torrents"); + $total = $rep->import(); + $this->info("import $total torrents."); + return Command::SUCCESS; + } +} diff --git a/app/Console/Commands/MeiliSearchStats.php b/app/Console/Commands/MeiliSearchStats.php new file mode 100644 index 00000000..70fabc34 --- /dev/null +++ b/app/Console/Commands/MeiliSearchStats.php @@ -0,0 +1,35 @@ +getClient()->stats()); + return Command::SUCCESS; + } +} diff --git a/app/Console/Commands/Test.php b/app/Console/Commands/Test.php index fbc619e0..471408e9 100644 --- a/app/Console/Commands/Test.php +++ b/app/Console/Commands/Test.php @@ -27,6 +27,7 @@ use App\Repositories\AgentAllowRepository; use App\Repositories\AttendanceRepository; use App\Repositories\ExamRepository; use App\Repositories\HitAndRunRepository; +use App\Repositories\MeiliSearchRepository; use App\Repositories\PluginRepository; use App\Repositories\SearchBoxRepository; use App\Repositories\SearchRepository; @@ -95,10 +96,26 @@ class Test extends Command */ public function handle() { - $r = log(10); - $r2 = exp(10); + $rep = new MeiliSearchRepository(); +// $r = $rep->doImportFromDatabase(); +// dd($r); +// $r = $rep->import(); - dd($r, $r2); + $r = $rep->search([ + 'search' => '200', + 'spstate' => 0, + 'incldead' => 0, + 'mode' => 4, +// 'cat401' => 1, + 'sort' => '4', + 'type' => 'desc', + 'search_mode' => 0, + 'inclbookmarked' => 0, + 'approval_status' => 1, +// 'size_end' => 20, +// 'added_end' => '2023-02-11', + ], 1, 'incldead=0&spstate=1&inclbookmarked=0&approval_status=1&size_begin=&size_end=&seeders_begin=&seeders_end=&leechers_begin=&leechers_end=×_completed_begin=×_completed_end=&added_begin=&added_end=&search=200&search_area=0&search_mode=0'); + dd($r); } } diff --git a/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php b/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php index cecc7ca8..ba9cca92 100644 --- a/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php +++ b/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php @@ -140,6 +140,12 @@ class EditSetting extends Page implements Forms\Contracts\HasForms ->label(__('label.setting.system.maximum_upload_speed')) ->helperText(__('label.setting.system.maximum_upload_speed_help')) , + Forms\Components\Radio::make('system.meilisearch_enabled') + ->options(self::$yesOrNo) + ->inline(true) + ->label(__('label.setting.system.meilisearch_enabled')) + ->helperText(__('label.setting.system.meilisearch_enabled_help')) + , ])->columns(2); $tabs = apply_filter('nexus_setting_tabs', $tabs); diff --git a/app/Repositories/MeiliSearchRepository.php b/app/Repositories/MeiliSearchRepository.php index 4aba571c..c25d2894 100644 --- a/app/Repositories/MeiliSearchRepository.php +++ b/app/Repositories/MeiliSearchRepository.php @@ -1,19 +1,552 @@ ['text' => 'and'], + self::SEARCH_MODE_EXACT => ['text' => 'exact'], + ]; + + const SEARCH_AREA_TITLE = '0'; + const SEARCH_AREA_DESC = '1'; + const SEARCH_AREA_OWNER = '3'; + const SEARCH_AREA_IMDB = '4'; + + private static array $searchAreas = [ + self::SEARCH_AREA_TITLE => ['text' => 'title'], + self::SEARCH_AREA_DESC => ['text' => 'desc'], + self::SEARCH_AREA_OWNER => ['text' => 'owner'], + self::SEARCH_AREA_IMDB => ['text' => 'imdb'], + ]; + + //cat401=1&source1=1&medium1=1&codec1=1&audiocodec1=1&standard1=1&processing1=1&team1=1&incldead=1&spstate=2&inclbookmarked=1&search=tr&search_area=1&search_mode=1 + private static array $queryFieldToTorrentFieldMaps = [ + 'cat' => 'category', + 'source' => 'source', + 'medium' => 'medium', + 'codec' => 'codec', + 'audiocodec' => 'audiocodec', + 'standard' => 'standard', + 'processing' => 'processing', + 'team' => 'team', + ]; + + private static array $sortFieldMaps = [ + '1' => 'name', +// '2' => 'numfiles', + '3' => 'comments', + '4' => 'added', + '5' => 'size', + '6' => 'times_completed', + '7' => 'seeders', + '8' => 'leechers', + '9' => 'owner', + ]; + + private static array $searchableAttributes = ["name", "small_descr", "descr", "url"]; + + private static array $filterableAttributes = [ + "id", "category", "source", "medium", "codec", "standard", "processing", "team", "audiocodec", "owner", + "sp_state", "visible", "banned", "approval_status", "size", "leechers", "seeders", "times_completed", "added", + ]; + + private static array $sortableAttributes = [ + "id", "name", "comments", "added", "size", "leechers", "seeders", "times_completed", "owner", + "pos_state", "anonymous" + ]; + + private static array $intFields = [ + "id", "category", "source", "medium", "codec", "standard", "processing", "team", "audiocodec", "owner", + "sp_state", "approval_status", "size", "leechers", "seeders", "times_completed", "url", "comments", + ]; + + private static array $timestampFields = ['added']; + + private static array $yesOrNoFields = ['visible', 'anonymous', 'banned']; + + + + public function getClient(): Client { - if (is_null($this->client)) { - + if (is_null(self::$client)) { + $config = nexus_config('nexus.meilisearch'); + $url = sprintf('%s://%s:%s', $config['scheme'], $config['host'], $config['port']); + do_log("get client with url: $url, master key: " . $config['master_key']); + self::$client = new Client($url, $config['master_key']); } + return self::$client; } public function isEnabled(): bool { - return true; + return Setting::get('system.meilisearch_enabled') == 'yes'; } + + public function import() + { + $client = $this->getClient(); + $stats = $client->stats(); + if (isset($stats['indexes'][self::INDEX_NAME])) { + $doSwap = true; + $indexName = self::INDEX_NAME . "_" . date('Ymd_His'); + } else { + $doSwap = false; + $indexName = self::INDEX_NAME; + } + do_log("indexName: $indexName will be created, doSwap: $doSwap"); + $index = $this->createIndex($indexName); + try { + $total = $this->doImportFromDatabase(null, $index); + if ($doSwap) { + $swapResult = $client->swapIndexes([[self::INDEX_NAME, $indexName]]); + $times = 0; + while (true) { + if ($times == 60) { + $msg = "total: $total, swap too long, times: $times, return false"; + do_log($msg); + throw new NexusException($msg); + } + sleep(1); + $task = $client->getTask($swapResult['taskUid']); + if ($task['status'] == 'succeeded') { + do_log("total: $total, swap success at times: $times"); + $client->deleteIndex($indexName); + return $total; + } + do_log("waiting swap success, times: $times"); + $times++; + } + } + return $total; + } catch (\Exception $exception) { + $client->deleteIndex($indexName); + throw $exception; + } + } + + private function createIndex($indexName) + { + $client = $this->getClient(); + $params = [ + 'primaryKey' => 'id', + ]; + $client->createIndex($indexName, $params); + $index = $client->index($indexName); + $settings = [ + "distinctAttribute" => "id", + "displayedAttributes" => $this->getRequiredFields(), + "searchableAttributes" => self::$searchableAttributes, + "filterableAttributes" => self::$filterableAttributes, + "sortableAttributes" => self::$sortableAttributes, + "rankingRules" => [ + "sort", + "words", +// "typo", +// "proximity", +// "attribute", +// "exactness" + ], + ]; + $index->updateSettings($settings); + + return $index; + + } + + public function getRequiredFields(): array + { + return array_values(array_unique(array_merge( + self::$filterableAttributes, self::$searchableAttributes, self::$sortableAttributes + ))); + } + + public function doImportFromDatabase($id = null, $index = null) + { + if (!$this->isEnabled() && $index === null) { + do_log("Not enabled!"); + return false; + } + $page = 1; + $size = 1000; + if (!$index instanceof Indexes) { + $index = $this->getIndex(); + } + $total = 0; + while (true) { + $query = NexusDB::table("torrents")->forPage($page, $size); + if ($id) { + $query->whereIn("id", Arr::wrap($id)); + } + $torrents = $query->get($this->getRequiredFields()); + $count = $torrents->count(); + $total += $count; + if ($count == 0) { + do_log("page: $page no data..."); + break; + } + do_log(sprintf('importing page: %s with id: %s, %s records...', $page, $id, $count)); + $data = []; + foreach ($torrents as $torrent) { + $row = []; + foreach ($torrent as $field => $value) { + $row[$field] = $this->formatValueForMeili($field, $value); + } + $data[] = $row; + } + $index->updateDocuments($data); + do_log(sprintf('import page: %s with id: %s, %s records success.', $page, $id, $count)); + $page++; + } + return $total; + } + + public function search(array $params, $user) + { + $results['total'] = 0; + $results['list'] = []; + if (!$this->isEnabled()) { + do_log("Not enabled!"); + return $results; + } + $filters = []; + //think about search area + $searchArea = $this->getSearchArea($params); + if ($searchArea == self::SEARCH_AREA_OWNER) { + $searchOwner = User::query()->where('username', trim($params['search']))->first(['id']); + if (!$searchOwner) { + //No user match, no results + return $results; + } else { + $filters[] = "owner = " . $searchOwner->id; + } + } + if (!($user instanceof User) || !$user->torrentsperpage || !$user->notifs) { + $user = User::query()->findOrFail(intval($user)); + } + $filters = array_merge($filters, $this->getFilters($params, $user)); + $query = $this->getQuery($params); + $page = isset($params['page']) && is_numeric($params['page']) ? $params['page'] : 0; + $perPage = $this->getPerPage($user); + $index = $this->getIndex(); + $searchParams = [ + "q" => $query, + "hitsPerPage" => $perPage, + //NP starts from 0, but meilisearch starts from 1 + "page" => $page + 1, + "filter" => $filters, + "sort" => $this->getSort($params), + "attributesToRetrieve" => $this->getAttributesToRetrieve(), + ]; + $searchResult = $index->search($query, $searchParams); + $total = $searchResult->getTotalHits(); + do_log("search params: " . nexus_json_encode($searchParams) . ", total: $total"); + $results['total'] = $total; + if ($total > 0) { + $torrentIdArr = array_column($searchResult->getHits(), 'id'); + $fields = Torrent::getFieldsForList(); + $idStr = implode(',', $torrentIdArr); + $results['list'] = Torrent::query() + ->select($fields) + ->whereIn('id', $torrentIdArr) + ->orderByRaw("field(id,$idStr)") + ->get() + ->toArray() + ; + } + return $results; + } + + /** + * @param array $params + * @param User $user + * @return array + */ + private function getFilters(array $params, User $user): array + { + $filters = []; + $taxonomies = []; + $categoryIdArr = []; + //[cat401][cat404][sou1][med1][cod1][sta2][sta3][pro2][tea2][aud2][incldead=0][spstate=3][inclbookmarked=2] + $userSetting = $user->notifs; + //cat401=1&source2=1&medium10=1&codec2=1&audiocodec2=1&standard3=1&processing2=1&team2=1&incldead=2&spstate=1&inclbookmarked=0&approval_status=&size_begin=&size_end=&seeders_begin=&seeders_end=&leechers_begin=&leechers_end=×_completed_begin=×_completed_end=&added_begin=&added_end=&search=a+b&search_area=0&search_mode=2 + $queryString = http_build_query($params); + //section + if (!empty($params['mode'])) { + $categoryIdArr = Category::query()->whereIn('mode', Arr::wrap($params['mode']))->pluck('id')->toArray(); + } + foreach (self::$queryFieldToTorrentFieldMaps as $queryField => $torrentField) { + if (isset($params[$queryField]) && $params[$queryField] !== '') { + $taxonomies[$torrentField][] = $params[$queryField]; + do_log("$torrentField from params through $queryField: {$params[$queryField]}"); + } elseif (preg_match_all("/{$queryField}(\d+)=/", $queryString, $matches)) { + if (count($matches) == 2 && !empty($matches[1])) { + foreach ($matches[1] as $match) { + $taxonomies[$torrentField][] = $match; + do_log("$torrentField from params through $queryField: $match"); + } + } + } else { + //get user setting + $pattern = sprintf("/\[%s([\d]+)\]/", substr($queryField, 0, 3)); + if (preg_match($pattern, $userSetting, $matches)) { + if (count($matches) == 2 && !empty($matches[1])) { + foreach ($matches[1] as $match) { + $taxonomies[$torrentField][] = $match; + do_log("$torrentField from user setting through $queryField: $match"); + } + } + } + } + } + if (empty($taxonomies['category']) && !empty($categoryIdArr)) { + //Restricted to the category of the specified section + $taxonomies['category'] = $categoryIdArr; + } + foreach ($taxonomies as $key => $values) { + if (!empty($values)) { + $filters[] = sprintf("%s IN [%s]", $key, implode(', ', $values)); + } + } + + $includeDead = 1; + if (isset($params['incldead'])) { + $includeDead = (int)$params['incldead']; + } elseif (preg_match("/\[incldead=(\d+)\]/", $userSetting, $matches)) { + $includeDead = $matches[1]; + } + if ($includeDead == 1) { + //active torrent + $filters[] = "visible = 1"; + do_log("visible = yes through incldead: $includeDead"); + } elseif ($includeDead == 2) { + //dead torrent + $filters[] = "visible = 0"; + do_log("visible = no through incldead: $includeDead"); + } + + $includeBookmarked = 0; + if (isset($params['inclbookmarked'])) { + $includeBookmarked = (int)$params['inclbookmarked']; + } elseif (preg_match("/\[inclbookmarked=(\d+)\]/", $userSetting, $matches)) { + $includeBookmarked = $matches[1]; + } + if ($includeBookmarked > 0) { + $userBookmarkedTorrentIdStr = Bookmark::query()->where('userid', $user->id)->pluck('torrentid')->implode(','); + if ($includeBookmarked == 1) { + //only bookmark + $filters[] = "id IN [$userBookmarkedTorrentIdStr]"; + do_log("bookmark through inclbookmarked: $includeBookmarked"); + } elseif ($includeBookmarked == 2) { + //only not bookmark + $filters[] = "id NOT IN [$userBookmarkedTorrentIdStr]"; + do_log("bookmark through inclbookmarked: $includeBookmarked"); + } + } + + $spState = 0; + if (isset($params['spstate'])) { + $spState = (int)$params['spstate']; + do_log("spstate from params"); + } elseif (preg_match("/\[spstate=(\d+)\]/", $userSetting, $matches)) { + $spState = $matches[1]; + do_log("spstate from user setting"); + } + if ($spState > 0) { + $filters[] = "sp_state = $spState"; + do_log("sp_state = $spState through spstate: $spState"); + } + + if (isset($params['approval_status']) && is_numeric($params['approval_status'])) { + $filters[] = "approval_status = " . $params['approval_status']; + do_log("approval_status = {$params['approval_status']} through approval_status: {$params['approval_status']}"); + } + + //size + if (!empty($params['size_begin'])) { + $atomicValue = intval($params['size_begin']) * 1024 * 1024 * 1024; + $filters[] = "size >= $atomicValue"; + do_log("size >= $atomicValue through size_begin: $atomicValue"); + } + if (!empty($params['size_end'])) { + $atomicValue = intval($params['size_end']) * 1024 * 1024 * 1024; + $filters[] = "size <= $atomicValue"; + do_log("size <= $atomicValue through size_end: $atomicValue"); + } + + + //seeders + if (!empty($params['seeders_begin'])) { + $atomicValue = intval($params['seeders_begin']); + $filters[] = "seeders >= $atomicValue"; + do_log("seeders >= $atomicValue through seeders_begin: $atomicValue"); + } + if (!empty($params['seeders_end'])) { + $atomicValue = intval($params['seeders_end']); + $filters[] = "seeders <= $atomicValue"; + do_log("seeders <= $atomicValue through seeders_end: $atomicValue"); + } + + //leechers + if (!empty($params['leechers_begin'])) { + $atomicValue = intval($params['leechers_begin']); + $filters[] = "leechers >= $atomicValue"; + do_log("leechers >= $atomicValue through leechers_begin: $atomicValue"); + } + if (!empty($params['leechers_end'])) { + $atomicValue = intval($params['leechers_end']); + $filters[] = "leechers <= $atomicValue"; + do_log("leechers <= $atomicValue through leechers_end: $atomicValue"); + } + + + //times_completed + if (!empty($params['times_completed_begin'])) { + $atomicValue = intval($params['times_completed_begin']); + $filters[] = "times_completed >= $atomicValue"; + do_log("times_completed >= $atomicValue through times_completed_begin: $atomicValue"); + } + if (!empty($params['times_completed_end'])) { + $atomicValue = intval($params['times_completed_end']); + $filters[] = "times_completed <= $atomicValue"; + do_log("times_completed <= $atomicValue through times_completed_end: $atomicValue"); + } + + //added + if (!empty($params['added_begin'])) { + $atomicValue = $params['added_begin']; + $filters[] = "added >= " . strtotime($atomicValue); + do_log("added >= $atomicValue through added_begin: $atomicValue"); + } + if (!empty($params['added_end'])) { + $atomicValue = Carbon::parse($params['added_end'])->endOfDay()->toDateTimeString(); + $filters[] = "added <= " . strtotime($atomicValue); + do_log("added <= $atomicValue through added_end: $atomicValue"); + } + + //permission see banned + if (isset($params['banned']) && in_array($params['banned'], ['yes', 'no'])) { + if ($params['banned'] == 'yes') { + $filters[] = "banned = 1"; + } else { + $filters[] = "banned = 0"; + } + } + + do_log("[GET_FILTERS]: " . json_encode($filters)); + return $filters; + } + + private function getQuery(array $params): string + { + $q = trim($params['search']); + $searchMode = self::SEARCH_MODE_AND; + if (isset($params['search_mode'], self::$searchModes[$params['search_mode']])) { + $searchMode = $params['search_mode']; + } + do_log("search mode: " . self::$searchModes[$searchMode]['text']); + if ($searchMode == self::SEARCH_MODE_AND) { + return $q; + } + return sprintf('"%s"', $q); + } + + private function getSearchArea(array $params) + { + if (isset($params['search_area'], self::$searchAreas[$params['search_area']])) { + return $params['search_area']; + } + return self::SEARCH_AREA_TITLE; + } + + public function getIndex(): \Meilisearch\Endpoints\Indexes + { + return $this->getClient()->index(self::INDEX_NAME); + } + + private function getSort(array $params): array + { + if (isset($params['sort'], self::$sortFieldMaps[$params['sort']]) && isset($params['type']) && in_array($params['type'], ['asc', 'desc'])) { + $sortField = self::$sortFieldMaps[$params['sort']]; + } else { + $sortField = "id"; + } + if (isset($params['type']) && in_array($params['type'], ['desc', 'asc'])) { + $sortType = $params['type']; + } else { + $sortType = "desc"; + } + if ($sortField == "id") { + return ["pos_state:desc", "$sortField:$sortType"]; + } else { + return ["pos_state:desc", "$sortField:$sortType", "id:desc"]; + } + + } + + private function getPerPage(User $user) + { + if ($user->torrentsperpage) { + $size = $user->torrentsperpage; + } elseif (($sizeFromConfig = Setting::get('main.torrentsperpage')) > 0) { + $size = $sizeFromConfig; + } else { + $size = 100; + } + return intval(min($size, 200)); + } + + private function formatValueForMeili($field, $value) + { + if (in_array($field, self::$intFields)) { + return intval($value); + } + if (in_array($field, self::$timestampFields)) { + return strtotime($value); + } + if (in_array($field, self::$yesOrNoFields)) { + return $value == 'yes' ? 1 : 0; + } + return strval($value); + } + + public function deleteDocuments($id) + { + if ($this->isEnabled()) { + return $this->getIndex()->deleteDocuments(Arr::wrap($id)); + } + } + + private function getAttributesToRetrieve(): array + { + if (nexus_env("APP_ENV") == 'production') { + return ['id']; + } + return ['*']; + } + + + + } diff --git a/config/nexus.php b/config/nexus.php index 4d865032..5bc94d8b 100644 --- a/config/nexus.php +++ b/config/nexus.php @@ -47,6 +47,13 @@ return [ ], 'ssl_verification' => nexus_env('ELASTICSEARCH_SSL_VERIFICATION', ''), - ] + ], + + 'meilisearch' => [ + 'scheme' => nexus_env('MEILISEARCH_SCHEME', 'http'), + 'host' => nexus_env('MEILISEARCH_HOST', '127.0.0.1'), + 'port' => (int)nexus_env('MEILISEARCH_PORT', '7700'), + 'master_key' => nexus_env('MEILISEARCH_MASTER_KEY', ''), + ], ]; diff --git a/include/cleanup.php b/include/cleanup.php index 1df86652..6dfef0a7 100644 --- a/include/cleanup.php +++ b/include/cleanup.php @@ -350,6 +350,7 @@ function docleanup($forceAll = 0, $printProgress = false) { if ($printProgress) { printProgress($log); } + //Priority Class 3: cleanup every 60 mins $res = sql_query("SELECT value_u FROM avps WHERE arg = 'lastcleantime3'"); $row = mysql_fetch_array($res); @@ -547,6 +548,18 @@ function docleanup($forceAll = 0, $printProgress = false) { printProgress($log); } + //sync to Meilisearch + $meiliRep = new \App\Repositories\MeiliSearchRepository(); + if ($meiliRep->isEnabled()) { + $meiliRep->import(); + } + $log = "sync to Meilisearch"; + do_log($log); + if ($printProgress) { + printProgress($log); + } + + //Priority Class 4: cleanup every 24 hours $res = sql_query("SELECT value_u FROM avps WHERE arg = 'lastcleantime4'"); $row = mysql_fetch_array($res); diff --git a/include/constants.php b/include/constants.php index e49a22d3..9886456e 100644 --- a/include/constants.php +++ b/include/constants.php @@ -1,6 +1,6 @@ '', ], $notify); } + $meiliSearchRep = new \App\Repositories\MeiliSearchRepository(); + $meiliSearchRep->deleteDocuments($idArr); } function pager($rpp, $count, $href, $opts = array(), $pagename = "page") { diff --git a/nexus/Install/Update.php b/nexus/Install/Update.php index 3421bc0d..481c08be 100644 --- a/nexus/Install/Update.php +++ b/nexus/Install/Update.php @@ -294,6 +294,7 @@ class Update extends Install $torrentRep = new TorrentRepository(); $torrentRep->removeDuplicateSnatch(); $this->runMigrate('database/migrations/2023_03_29_021950_handle_snatched_user_torrent_unique.php'); + $this->doLog("removeDuplicateSnatch and migrate 2023_03_29_021950_handle_snatched_user_torrent_unique"); } } @@ -493,7 +494,7 @@ class Update extends Install $tableName = 'snatched'; $result = NexusDB::select('show index from ' . $tableName); foreach ($result as $item) { - if (in_array($item->Column_name, ['torrentid', 'userid']) && $item->Non_unique == 0) { + if (in_array($item['Column_name'], ['torrentid', 'userid']) && $item['Non_unique'] == 0) { return true; } } diff --git a/public/mybonus.php b/public/mybonus.php index 577ff04f..0ad399c8 100644 --- a/public/mybonus.php +++ b/public/mybonus.php @@ -76,7 +76,7 @@ function bonusarray($option = 0){ $bonus['description'] = $lang_mybonus['text_buy_invite_note']; $results[] = $bonus; } - + //Tmp Invite $tmpInviteBonus = \App\Models\BonusLogs::getBonusForBuyTemporaryInvite(); if ($tmpInviteBonus > 0) { @@ -306,7 +306,7 @@ unset($msg); if (isset($do)) { if ($do == "upload") $msg = $lang_mybonus['text_success_upload']; - if ($do == "download") + elseif ($do == "download") $msg = $lang_mybonus['text_success_download']; elseif ($do == "invite") $msg = $lang_mybonus['text_success_invites']; @@ -410,7 +410,7 @@ for ($i=0; $i < count($allBonus); $i++) } elseif($bonusarray['art'] == 'invite') { - if (\App\Models\Setting::get('main.invitesystem') != 'yes') + if (\App\Models\Setting::get('main.invitesystem') != 'yes') print(""); elseif(!user_can($permission, false, 0)){ $requireClass = get_setting("authority.$permission"); @@ -420,7 +420,7 @@ for ($i=0; $i < count($allBonus); $i++) } elseif($bonusarray['art'] == 'tmp_invite') { - if (\App\Models\Setting::get('main.invitesystem') != 'yes') + if (\App\Models\Setting::get('main.invitesystem') != 'yes') print(""); elseif(!user_can($permission, false, 0)){ $requireClass = get_setting("authority.$permission"); diff --git a/public/search.php b/public/search.php index 3c6c9b54..67f0ee6b 100644 --- a/public/search.php +++ b/public/search.php @@ -16,54 +16,88 @@ if ($approvalStatusNoneVisible == 'no' && !user_can('torrent-approval')) { $approvalStatus = \App\Models\Torrent::APPROVAL_STATUS_ALLOW; } +//section +$modeArr = [\App\Models\SearchBox::getBrowseMode()]; +if (\App\Models\SearchBox::isSpecialEnabled() && user_can('view_special_torrent')) { + $modeArr[] = \App\Models\SearchBox::getSpecialMode(); +} + +//see banned +$banned = null; +if (!isset($CURUSER) || !user_can('seebanned')) { + $banned = "no"; +} + +$meilisearchEnabled = get_setting('system.meilisearch_enabled') == 'yes'; +$shouldUseMeili = $meilisearchEnabled && !empty($search); + $count = 0; $rows = []; if ($search) { $search = str_replace(".", " ", $search); $searchArr = preg_split("/[\s]+/", $search, 10,PREG_SPLIT_NO_EMPTY); - $tableTorrent = "torrents"; - $tableUser = "users"; - $tableCategory = "categories"; - $torrentQuery = \Nexus\Database\NexusDB::table($tableTorrent)->join($tableCategory, "$tableTorrent.category", "=", "$tableCategory.id"); - if (get_setting('main.spsct') == 'yes' && !user_can('view_special_torrent')) { - $torrentQuery->where("$tableCategory.mode", get_setting('main.browsecat')); - } - if ($searchArea == \App\Repositories\SearchRepository::SEARCH_AREA_TITLE) { - foreach ($searchArr as $queryString) { - $q = "%{$queryString}%"; - $torrentQuery->where(function (\Illuminate\Database\Query\Builder $query) use ($q, $tableTorrent) { - return $query->where("$tableTorrent.name", 'like', $q)->orWhere("$tableTorrent.small_descr", "like", $q); - }); + if ($shouldUseMeili) { + $searchRep = new \App\Repositories\MeiliSearchRepository(); + $searchParams = $_GET; + if ($approvalStatus != null) { + $searchParams['approval_status'] = $approvalStatus; } - } elseif ($searchArea == \App\Repositories\SearchRepository::SEARCH_AREA_DESC) { - foreach ($searchArr as $queryString) { - $q = "%{$queryString}%"; - $torrentQuery->where("$tableTorrent.descr", "like", $q); - } - } elseif ($searchArea == \App\Repositories\SearchRepository::SEARCH_AREA_OWNER) { - $torrentQuery->join($tableUser, "$tableTorrent.owner", "=", "$tableUser.id"); - foreach ($searchArr as $queryString) { - $q = "%{$queryString}%"; - $torrentQuery->where("$tableUser.username", "like", $q); - } - } elseif ($searchArea == \App\Repositories\SearchRepository::SEARCH_AREA_IMDB) { - foreach ($searchArr as $queryString) { - $q = "%{$queryString}%"; - $torrentQuery->where("$tableTorrent.url", "like", $q); + if ($banned != null) { + $searchParams['banned'] = $banned; } + //Include dead + $searchParams['incldead'] = 0; + $searchParams['mode'] = $modeArr; + $resultFromSearchRep = $searchRep->search($searchParams, $CURUSER['id']); + $count = $resultFromSearchRep['total']; } else { - foreach ($searchArr as $queryString) { - $q = "%{$queryString}%"; - $torrentQuery->where("$tableTorrent.name", "like", $q); - } - write_log("User " . $CURUSER["username"] . "," . $CURUSER["ip"] . " is hacking search_area field in" . $_SERVER['SCRIPT_NAME'], 'mod'); - } - if ($approvalStatus !== null) { - $torrentQuery->where("$tableTorrent.approval_status", $approvalStatus); - } - $torrentQuery->where("$tableTorrent.visible", 'yes'); + $tableTorrent = "torrents"; + $tableUser = "users"; + $tableCategory = "categories"; + $torrentQuery = \Nexus\Database\NexusDB::table($tableTorrent) + ->join($tableCategory, "$tableTorrent.category", "=", "$tableCategory.id") + ->whereIn("$tableCategory.mode", $modeArr) + ; - $count = $torrentQuery->count(); + if ($searchArea == \App\Repositories\SearchRepository::SEARCH_AREA_TITLE) { + foreach ($searchArr as $queryString) { + $q = "%{$queryString}%"; + $torrentQuery->where(function (\Illuminate\Database\Query\Builder $query) use ($q, $tableTorrent) { + return $query->where("$tableTorrent.name", 'like', $q)->orWhere("$tableTorrent.small_descr", "like", $q); + }); + } + } elseif ($searchArea == \App\Repositories\SearchRepository::SEARCH_AREA_DESC) { + foreach ($searchArr as $queryString) { + $q = "%{$queryString}%"; + $torrentQuery->where("$tableTorrent.descr", "like", $q); + } + } elseif ($searchArea == \App\Repositories\SearchRepository::SEARCH_AREA_OWNER) { + $torrentQuery->join($tableUser, "$tableTorrent.owner", "=", "$tableUser.id"); + foreach ($searchArr as $queryString) { + $q = "%{$queryString}%"; + $torrentQuery->where("$tableUser.username", "like", $q); + } + } elseif ($searchArea == \App\Repositories\SearchRepository::SEARCH_AREA_IMDB) { + foreach ($searchArr as $queryString) { + $q = "%{$queryString}%"; + $torrentQuery->where("$tableTorrent.url", "like", $q); + } + } else { + foreach ($searchArr as $queryString) { + $q = "%{$queryString}%"; + $torrentQuery->where("$tableTorrent.name", "like", $q); + } + write_log("User " . $CURUSER["username"] . "," . $CURUSER["ip"] . " is hacking search_area field in" . $_SERVER['SCRIPT_NAME'], 'mod'); + } + if ($approvalStatus !== null) { + $torrentQuery->where("$tableTorrent.approval_status", $approvalStatus); + } + if ($banned !== null) { + $torrentQuery->where("$tableTorrent.banned", $banned); + } + + $count = $torrentQuery->count(); + } } if ($CURUSER["torrentsperpage"]) @@ -106,12 +140,16 @@ list($pagertop, $pagerbottom, $limit, $offset, $size, $page) = pager($torrentspe stdhead(nexus_trans('search.global_search')); print("
"); if ($search && $count > 0) { - $fieldsStr = implode(', ', \App\Models\Torrent::getFieldsForList(true)); - $rows = $torrentQuery->selectRaw("$fieldsStr, categories.mode as search_box_id") - ->forPage($page + 1, $torrentsperpage) - ->orderBy("$tableTorrent.$column", $ascdesc) - ->get() - ->toArray(); + if ($shouldUseMeili) { + $rows = $resultFromSearchRep['list']; + } else { + $fieldsStr = implode(', ', \App\Models\Torrent::getFieldsForList(true)); + $rows = $torrentQuery->selectRaw("$fieldsStr, categories.mode as search_box_id") + ->forPage($page + 1, $torrentsperpage) + ->orderBy("$tableTorrent.$column", $ascdesc) + ->get() + ->toArray(); + } print($pagertop); torrenttable(json_decode(json_encode($rows), true)); print($pagerbottom); diff --git a/public/takeedit.php b/public/takeedit.php index fd636aa9..afe90007 100644 --- a/public/takeedit.php +++ b/public/takeedit.php @@ -285,7 +285,8 @@ if ($affectedRows == 1) { 'comment' => '', ], true); } - + $meiliSearch = new \App\Repositories\MeiliSearchRepository(); + $meiliSearch->doImportFromDatabase($row['id']); } $returl = "details.php?id=$id&edited=1"; diff --git a/public/takeupload.php b/public/takeupload.php index bf7aa86c..e84216ad 100644 --- a/public/takeupload.php +++ b/public/takeupload.php @@ -430,6 +430,9 @@ write_log("Torrent $id ($torrent) was uploaded by $anon"); $searchRep = new \App\Repositories\SearchRepository(); $searchRep->addTorrent($id); +$meiliSearch = new \App\Repositories\MeiliSearchRepository(); +$meiliSearch->doImportFromDatabase($id); + //===notify people who voted on offer thanks CoLdFuSiOn :) if ($is_offer) { diff --git a/public/torrents.php b/public/torrents.php index d8276748..3bb9efd9 100644 --- a/public/torrents.php +++ b/public/torrents.php @@ -5,6 +5,7 @@ require_once(get_langfile_path('torrents.php')); require_once(get_langfile_path('speical.php')); loggedinorreturn(); parked(); + //check searchbox switch (nexus()->getScript()) { case 'torrents': @@ -27,8 +28,9 @@ switch (nexus()->getScript()) { */ $tagRep = new \App\Repositories\TagRepository(); $allTags = $tagRep->listAll($sectiontype); -$elasticsearchEnabled = nexus_env('ELASTICSEARCH_ENABLED'); $filterInputWidth = 62; +$searchParams = $_GET; +$searchParams['mode'] = $sectiontype; $showsubcat = get_searchbox_value($sectiontype, 'showsubcat');//whether show subcategory (i.e. sources, codecs) or not $showsource = get_searchbox_value($sectiontype, 'showsource'); //whether show sources or not @@ -54,9 +56,13 @@ if ($showsubcat){ $searchstr_ori = htmlspecialchars(trim($_GET["search"] ?? '')); $searchstr = mysql_real_escape_string(trim($_GET["search"] ?? '')); -if (empty($searchstr)) - unset($searchstr); +if (empty($searchstr)) { + unset($searchstr); +} +$meilisearchEnabled = get_setting('system.meilisearch_enabled') == 'yes'; +$shouldUseMeili = $meilisearchEnabled && !empty($searchstr); +do_log("[SHOULD_USE_MEILI]: $shouldUseMeili"); // sorting by MarkoStamcar $column = ''; $ascdesc = ''; @@ -154,8 +160,11 @@ elseif ($inclbookmarked == 2) //not bookmarked } // ----------------- end bookmarked ---------------------// -if (!isset($CURUSER) || !user_can('seebanned')) - $wherea[] = "banned = 'no'"; +if (!isset($CURUSER) || !user_can('seebanned')) { + $wherea[] = "banned = 'no'"; + $searchParams["banned"] = 'no'; +} + // ----------------- start include dead ---------------------// if (isset($_GET["incldead"])) $include_dead = intval($_GET["incldead"] ?? 0); @@ -821,9 +830,11 @@ if ($approvalStatusIconEnabled == 'yes' || (user_can('torrent-approval') && $app if ($showApprovalStatusFilter && isset($_REQUEST['approval_status']) && is_numeric($_REQUEST['approval_status'])) { $approvalStatus = intval($_REQUEST['approval_status']); $wherea[] = "torrents.approval_status = $approvalStatus"; + $searchParams['approval_status'] = $approvalStatus; $addparam .= "approval_status=$approvalStatus&"; } elseif ($approvalStatusNoneVisible == 'no' && !user_can('torrent-approval')) { $wherea[] = "torrents.approval_status = " . \App\Models\Torrent::APPROVAL_STATUS_ALLOW; + $searchParams['approval_status'] = \App\Models\Torrent::APPROVAL_STATUS_ALLOW; } if (isset($_GET['size_begin']) && ctype_digit($_GET['size_begin'])) { @@ -903,12 +914,10 @@ else $sql = "SELECT COUNT(*), categories.mode FROM torrents LEFT JOIN categories ON category = categories.id " . ($search_area == 3 || $column == "owner" ? "LEFT JOIN users ON torrents.owner = users.id " : "") . $tagFilter . $where . " GROUP BY categories.mode"; } -if ($elasticsearchEnabled) { - $searchRep = new \App\Repositories\SearchRepository(); - $esParams = $_GET; - $esParams['mode'] = $sectiontype; - $resultFromElastic = $searchRep->listTorrentFromEs($esParams, $CURUSER['id'], $_SERVER['QUERY_STRING']); - $count = $resultFromElastic['total']; +if ($shouldUseMeili) { + $searchRep = new \App\Repositories\MeiliSearchRepository(); + $resultFromSearchRep = $searchRep->search($searchParams, $CURUSER['id']); + $count = $resultFromSearchRep['total']; } else { $res = sql_query($sql); $count = 0; @@ -957,7 +966,7 @@ if ($count) $query = "SELECT $fieldsStr, categories.mode as search_box_id FROM torrents ".($search_area == 3 || $column == "owner" ? "LEFT JOIN users ON torrents.owner = users.id " : "")." LEFT JOIN categories ON torrents.category=categories.id $tagFilter $where $orderby $limit"; // } do_log("[TORRENT_LIST_SQL] $query", 'debug'); - if (!$elasticsearchEnabled) { + if (!$shouldUseMeili) { $res = sql_query($query); } } else { @@ -1258,8 +1267,8 @@ elseif($inclbookmarked == 2) if ($count) { $rows = []; - if ($elasticsearchEnabled) { - $rows = $resultFromElastic['data']; + if ($shouldUseMeili) { + $rows = $resultFromSearchRep['list']; } else { while ($row = mysql_fetch_assoc($res)) { $rows[] = $row; diff --git a/resources/lang/en/label.php b/resources/lang/en/label.php index cff136da..2efcf48d 100644 --- a/resources/lang/en/label.php +++ b/resources/lang/en/label.php @@ -93,6 +93,8 @@ return [ 'cookie_valid_days' => 'Cookie Valid days', 'maximum_upload_speed' => 'Maximum upload speed', 'maximum_upload_speed_help' => 'A single torrent upload speed exceeding this value is instantly disabled for the account, in Mbps. For example: 100 Mbps = 12.5 MB/s', + 'meilisearch_enabled' => 'Whether to enable Meilisearch', + 'meilisearch_enabled_help' => 'Please install and configure it and import the data before enabling it, otherwise there is no data for torrent search.', ], ], 'user' => [ diff --git a/resources/lang/zh_CN/label.php b/resources/lang/zh_CN/label.php index 827ff724..7469218e 100644 --- a/resources/lang/zh_CN/label.php +++ b/resources/lang/zh_CN/label.php @@ -93,6 +93,8 @@ return [ 'cookie_valid_days' => 'Cookie 有效天数', 'maximum_upload_speed' => '最大上传速度', 'maximum_upload_speed_help' => '单种上传速度超过此值账号即刻禁用,单位 Mbps。如:100 Mbps = 12.5 MB/s', + 'meilisearch_enabled' => '是否启用 Meilisearch', + 'meilisearch_enabled_help' => '请先安装配置好并导入数据再启用,否则种子搜索无数据。', ], ], 'user' => [ diff --git a/resources/lang/zh_TW/label.php b/resources/lang/zh_TW/label.php index eb24e0d5..a382a9a4 100644 --- a/resources/lang/zh_TW/label.php +++ b/resources/lang/zh_TW/label.php @@ -92,6 +92,8 @@ return [ 'maximum_number_of_medals_can_be_worn' => '勛章最大可佩戴數', 'cookie_valid_days' => 'Cookie 有效天數', 'maximum_upload_speed_help' => '單種上傳速度超過此值賬號即刻禁用,單位 Mbps。如:100 Mbps = 12.5 MB/s', + 'meilisearch_enabled' => '是否啟用 Meilisearch', + 'meilisearch_enabled_help' => '請先安裝配置好並導入數據再啟用,否則種子搜索無數據。', ], ], 'user' => [