Compare commits

..

10 Commits

Author SHA1 Message Date
thayol 8bf8dd333f Renamed repository 2021-07-05 21:08:39 +02:00
thayol 209aee5632 Cleaned up code 2021-06-17 22:03:35 +02:00
thayol f5b751409e Optimizer logic fixed 2021-06-17 22:03:35 +02:00
thayol 72a5003c3a Extracted functions 2021-06-17 22:03:34 +02:00
thayol 397c7d3436 Optimizer semi-gardening 2021-06-17 22:03:34 +02:00
thayol 2eddcd905d Updated versioning 2021-06-17 22:03:33 +02:00
thayol 1fcb640029 Optimizer settings gardening 2021-06-17 22:03:33 +02:00
thayol 05117f6836 Fixed uneven brackets 2021-06-17 22:03:32 +02:00
thayol 0fa7358c4b Cacher gardening 2021-06-17 22:03:32 +02:00
thayol dd3085ca1d Made navigation more user-friendly 2021-06-17 22:03:31 +02:00
14 changed files with 295 additions and 221 deletions
+4 -3
View File
@@ -1,10 +1,10 @@
# osu! optimizer (PHP)
**Current version: Version 0.1**
**Current version: Version 0.1a**
[round boi](https://osu.ppy.sh/users/7357064) has seen most of my works and it was his idea to combine them together before I start writing another one from scratch.
[round boi][round-boi-osu] has seen most of my works and it was his idea to combine them together before I start writing another one from scratch.
> "Swiss Army Knife style osu! library manager" -- [round boi](https://osu.ppy.sh/users/7357064)
> "Swiss Army Knife style osu! library manager" -- [round boi][round-boi-osu]
## What's this?
@@ -60,3 +60,4 @@ If you know what you're doing, you could run it on a dedicated server that can h
[Windows-PHP]: https://www.php.net/manual/en/install.windows.tools.php
[Mac-OS-PHP]: https://www.php.net/manual/en/install.macosx.bundled.php
[round-boi-osu]: https://osu.ppy.sh/users/7357064
+83 -72
View File
@@ -65,28 +65,17 @@ class optimizer
public static function remove_videos(osu_library $library)
{
foreach ($library->get_videos() as $file)
{
if (file_exists($file)) unlink($file);
}
utils::array_delete_if_exists($library->get_videos());
}
public static function remove_storyboards(osu_library $library) : void
{
// exclude vital files if they are used in the storyboard
$storyboards_safe = $library->get_storyboards();
$storyboards_safe = array_diff($storyboards_safe, $library->get_backgrounds());
$storyboards_safe = array_diff($storyboards_safe, $library->get_audiofiles());
$vital = array_merge($library->get_backgrounds(), $library->get_audiofiles());
$sb_safe = array_diff($library->get_storyboards(), $vital);
foreach ($storyboards_safe as $file)
{
if (file_exists($file)) unlink($file);
}
foreach ($library->get_osb_files() as $file)
{
if (file_exists($file)) unlink($file);
}
utils::array_delete_if_exists($sb_safe);
utils::array_delete_if_exists($library->get_osb_files());
}
private static function build_removand_sublist(array &$queue, string $folder, bool $single_level = false)
@@ -116,45 +105,58 @@ class optimizer
return $queue;
}
public static function build_excluded_list(osu_library $library, array $except = array()) : array
public static function build_single_level_removand_list(osu_library $library)
{
if (in_array("backgrounds", $except)) $background_files = array();
else $background_files = $library->get_backgrounds();
if (in_array("videos", $except)) $video_files = array();
else $video_files = $library->get_videos();
if (in_array("audio", $except)) $audio_files = array();
else $audio_files = $library->get_audiofiles();
$essential_excluded = array_merge($background_files, $video_files, $audio_files);
if (in_array("osu", $except) ||in_array("osb", $except))
return self::build_removand_list($library);
}
public static function copy_unless_excluded(string $needle, array $value, array $excluded_list)
{
if (in_array($needle, $excluded_list))
{
if (in_array("osu", $except)) $osu_files = array();
else $osu_files = $library->get_osu_files();
if (in_array("osb", $except)) $osb_files = array();
else $osb_files = $library->get_osb_files();
$physical_excluded = array_merge($osu_files, $osb_files);
return array();
}
else
{
if (in_array("parsed", $except)) $physical_excluded = array();
else $physical_excluded = $library->get_parsed_files();
return $value;
}
}
public static function build_essential_list(osu_library $library, array $except = array()) : array
{
$bg = self::copy_unless_excluded("backgrounds", $library->get_backgrounds(), $except);
$vid = self::copy_unless_excluded("videos", $library->get_videos(), $except);
$aud = self::copy_unless_excluded("audio", $library->get_audiofiles(), $except);
return array_merge($bg, $vid, $aud);
}
public static function build_physical_list(osu_library $library, array $except = array()) : array
{
if (in_array("osu", $except) || in_array("osb", $except))
{
$osu_files = self::copy_unless_excluded("osu", $library->get_osu_files(), $except);
$osb_files = self::copy_unless_excluded("osb", $library->get_osb_files(), $except);
return array_merge($osu_files, $osb_files);
}
return self::copy_unless_excluded("parsed", $library->get_parsed_files(), $except);
}
public static function build_other_list(osu_library $library, array $except = array()) : array
{
$storyboards = self::copy_unless_excluded("storyboard", $library->get_storyboards(), $except);
$hitsounds = self::copy_unless_excluded("hitsounds", $library->get_hitsounds(), $except);
if (in_array("storyboard", $except)) $storyboard_files = array();
else $storyboard_files = $library->get_storyboards();
if (in_array("hitsounds", $except)) $hitsound_files = array();
else $hitsound_files = $library->get_hitsounds();
$other_excluded = array_merge($storyboard_files, $hitsound_files);
return array_merge($storyboards, $hitsounds);
}
public static function build_excluded_list(osu_library $library, array $except = array()) : array
{
$essential_excluded = self::build_essential_list($library, $except);
$physical_excluded = self::build_physical_list($library, $except);
$other_excluded = self::build_other_list($library, $except);
return array_merge($essential_excluded, $physical_excluded, $other_excluded);
}
@@ -189,44 +191,51 @@ class optimizer
return array_intersect_key($files, array_diff($files_lowercase, $exclusions_lowercase));
}
public static function get_junk_files(osu_library $library, $excluded = array(), $single_level = false) : array
{
$removand = self::build_removand_list($library, $single_level);
$exclusions = self::build_excluded_list($library, $excluded);
return self::array_diff_ver_peppy($removand, $exclusions);
}
public static function get_single_level_junk_files(osu_library $library, $excluded = array()) : array
{
return self::get_junk_files($library, $excluded, true);
}
public static function remove_skins(osu_library $library) : void
{
$removand = self::build_removand_list($library, true);
$exclusions = self::build_excluded_list($library);
$junk_files = self::get_single_level_junk_files($library);
$junk_files = self::array_diff_ver_peppy($removand, $exclusions);
foreach ($junk_files as $file)
foreach ($junk_files as $key => $file)
{
if (self::is_skinnable_image($file) || self::is_skinnable_other($file))
if ((!self::is_skinnable_image($file) || self::is_skinnable_other($file)))
{
if (file_exists($file)) unlink($file);
unset($junk_files[$key]);
}
}
utils::array_delete_if_exists($junk_files);
}
public static function remove_hitsounds(osu_library $library) : void
{
$removand = self::build_removand_list($library, true);
$exclusions = self::build_excluded_list($library, [ "hitsounds" ]);
$junk_files = self::get_single_level_junk_files($library, $exclusions);
$junk_files = self::array_diff_ver_peppy($removand, $exclusions);
foreach ($junk_files as $file)
foreach ($junk_files as $key => $file)
{
if (self::is_skinnable_sound($file))
if (!self::is_skinnable_sound($file))
{
if (file_exists($file)) unlink($file);
unset($junk_files[$key]);
}
}
utils::array_delete_if_exists($junk_files);
$hitsounds_safe = $library->get_hitsounds();
$hitsounds_safe = array_diff($hitsounds_safe, $library->get_audiofiles());
foreach ($hitsounds_safe as $file)
{
if (file_exists($file)) unlink($file);
}
utils::array_delete_if_exists($hitsounds_safe);
}
public static function full_nuke(osu_library $library) : void
@@ -244,16 +253,18 @@ class optimizer
{
$excluded_arg = array();
}
$removand = self::build_removand_list($library);
$exclusions = self::build_excluded_list($library, $excluded_arg);
$junk_files = self::array_diff_ver_peppy($removand, $exclusions);
$junk_files = self::get_junk_files($library, $excluded_arg, false);
foreach ($junk_files as $file)
foreach ($junk_files as $key => $file)
{
if (!$nuke && self::is_skinnable($file)) continue; // ignore default hitsounds
if (file_exists($file)) unlink($file);
if (!$nuke && self::is_skinnable($file))
{
unset($junk_files[$key]);;
}
}
utils::array_delete_if_exists($junk_files);
}
public static function repack_all(osu_library $library) : void
@@ -293,7 +304,7 @@ class optimizer
public static function cleanup_dir(string $dir, bool $recursion = false) : void
{
$dir = rtrim(str_replace("\\", "/", $dir), "/");
$dir = utils::to_unix_slashes_without_trail($dir);
$glob = glob(utils::globsafe($dir) . "/*");
foreach ($glob as $file)
{
+7 -11
View File
@@ -8,36 +8,32 @@ class optimizer_settings
public function __construct(string $path)
{
$this->path = $path;
if (file_exists($path))
{
$this->load();
}
}
public function load()
public function load() : void
{
$raw = file_get_contents($this->path);
$json = json_decode($raw, true);
$json = utils::load_json($this->path);
$this->osu_path = $json["osu_path"] ?? "";
}
public function save()
public function save() : void
{
$json = array();
$json["osu_path"] = $this->osu_path;
$json = [ "osu_path" => $this->osu_path ];
$raw = json_encode($json);
file_put_contents($this->path, $raw);
file_put_contents($this->path, json_encode($json));
}
public function set_osu_path($path) : void
{
$sanitized = str_replace("\\", "/", $path);
$sanitized = rtrim($sanitized, "/");
if (file_exists($path))
{
$this->osu_path = $sanitized;
$this->osu_path = utils::to_unix_slashes_without_trail($path);
}
}
+29 -20
View File
@@ -1,22 +1,23 @@
<?php
require_once "libraries/utils.php";
class osu_cacher
{
public static $hash_function = "md5";
// public static $hash_function = "crc32";
public static $hash_function = "md5"; // crc32 was another option, but md5 produced better filesizes
private $root;
private $original_root;
private $cache_root;
public function __construct(string $root, string $cache_root)
public function __construct(string $original_root, string $cache_root)
{
$this->root = $root;
$this->original_root = $original_root;
$this->cache_root = $cache_root;
if (!file_exists($cache_root)) mkdir($cache_root, 0777, true);
utils::make_directory($cache_root);
}
public function get_root() : string
public function get_original_root() : string
{
return $this->root;
return $this->original_root;
}
public function get_cache_root() : string
@@ -26,7 +27,7 @@ class osu_cacher
public function get_cached_path(string $path) : string
{
return str_replace($this->root, $this->cache_root, $path) . ".json";
return str_replace($this->get_original_root(), $this->cache_root, $path) . ".json";
}
public function is_cached(string $path) : bool
@@ -34,29 +35,37 @@ class osu_cacher
return file_exists($this->get_cached_path($path));
}
public function get_cache(string $path, $hash=false)// : array|bool // see you again in php8
public static function is_hash_invalid(array $json, string $hash) : bool
{
if (empty($hash)) return false; // empty means hash check has to be skipped
if (empty($json["hash"])) return true; // json without a hash is invalid
return $json["hash"] == $hash;
}
public function get_cache(string $path, string $hash="")// : array|bool // see you again in php8
{
if (!$this->is_cached($path)) return false;
$raw = file_get_contents($this->get_cached_path($path));
$json = json_decode($raw, true);
$json = utils::load_json($this->get_cached_path($path));
if (empty($json)) return false; // cache had empty save
if ($hash !== false && $hash != ($json["hash"] ?? false)) return false; // hash check failed
if (self::is_hash_invalid($json, $hash)) return false;
return $json;
}
public static function set_hash_if_not_present(array &$json, string $path) : void
{
if (!isset($json["hash"])) $json["hash"] = hash_file(self::$hash_function, $path);
}
public function set_cache(string $path, array $content) : void
{
$cache_path = $this->get_cached_path($path);
$cache_dir = dirname($cache_path);
if (!file_exists($cache_dir)) mkdir($cache_dir, 0777, true);
self::set_hash_if_not_present($content, $path);
if (!isset($content["hash"])) $content["hash"] = hash_file(self::$hash_function, $path);
$encoded = json_encode($content);
file_put_contents($cache_path, $encoded);
utils::make_directory(dirname($cache_path));
file_put_contents($cache_path, json_encode($content));
}
}
+4 -5
View File
@@ -24,11 +24,10 @@ class osu_library
public function scan_library(string $root) : void
{
$time_start = microtime(true); // measure scanning time
$root = str_ireplace("\\", "/", $root); // fuck windows backslash
$root = rtrim($root, "/"); // remove trailing slash(es)
$root = utils::to_unix_slashes_without_trail($root);
// giving a different library should always cause a full rescan
if ($this->get_root() !== $root)
if ($this->get_osu_root() !== $root)
{
$this->rescan_library($root);
}
@@ -176,7 +175,7 @@ class osu_library
if (isset($this->db["library"][$key])) unset($this->db["library"][$key]);
}
public function get_root() : string
public function get_osu_root() : string
{
return $this->db["root"] ?? "";
}
@@ -228,7 +227,7 @@ class osu_library
public function get_library_folder() : string
{
return $this->get_root() . "/" . self::$song_library_folder;
return $this->get_osu_root() . "/" . self::$song_library_folder;
}
public function get_library() : array
+92 -95
View File
@@ -34,7 +34,7 @@ class osu_parser
// fix backslash and double quotes
public static function fix_filename(string $filename) : string
{
return trim(str_replace("\\", "/", $filename), "\"");
return trim(utils::to_unix_slashes($filename), "\"");
}
public static function file_ver_peppy(string $path)// : array|bool // see you in php8
@@ -106,105 +106,102 @@ class osu_parser
continue; // this line has been analyzed
}
if (mb_strpos($line, "//") === 0 || // the editor puts hella lot of comments in the files
$section_type === false)
if (!(mb_strpos($line, "//") === 0 || // the editor puts hella lot of comments in the files
$section_type === false))
{
}
else if ($section_type == "key-value pairs")
{
// init empty array
if (!isset($parsed[$current_section])) $parsed[$current_section] = array();
$delimiter_position = mb_strpos($line, $delimiter);
// old maps had random lines and lines with different delimiters (why?)
if ($delimiter_position !== false)
{
$kv_key = mb_substr($line, 0, $delimiter_position);
$kv_value = mb_substr($line, $delimiter_position + strlen($delimiter));
if ($current_section == "Variables")
{
$variables["keys"][$kv_key] = $kv_key;
$variables["values"][$kv_key] = $kv_value;
}
else
{
// after some thinking, keeping the original names was a good idea
$parsed[$current_section][$kv_key] = $kv_value;
}
}
}
else if ($section_type == "lists")
{
$list = explode($delimiter, $line);
if ($current_section == "Events") // saving the whole thing would take up too much space
{
if (mb_strpos($line, " ") === 0 || mb_strpos($line, "_") === 0) continue; // skip storyboard details lines
list($event_type, $source_files) = self::gather_source_files($list, $variables);
if ($event_type === false)
{
}
else if ($event_type == "Background")
{
$parsed["background"] = $source_files[0] ?? "";
}
else if ($event_type == "Video")
{
$parsed["video"] = $source_files[0] ?? "";
}
else
{
// init empty array
if (!isset($parsed["storyboard"])) $parsed["storyboard"] = array();
// add the elements to the storyboard
foreach ($source_files as $source_file)
{
$parsed["storyboard"][] = $source_file;
}
}
}
else if ($current_section == "HitObjects") // saving the whole thing would take up too much space v2
{
$last = array_key_last($list);
if (!empty($list[$last]) && mb_strpos($list[$last], ":") !== false)
{
$hitSample = explode(":", $list[$last]);
$last_sample = array_key_last($hitSample);
if (!empty($hitSample[$last_sample]))
{
$filename = $hitSample[$last_sample];
$filename = self::fix_filename($filename);
// some maps leave out the extensions
// (and some maps have .wav filess pointing to .ogg files........)
if (empty(pathinfo($filename, PATHINFO_EXTENSION)))
{
$filename = $filename . ".wav"; // just to signal it's an "audio file"
}
// init empty array
if (!isset($parsed["hitsounds"])) $parsed["hitsounds"] = array();
// add the element to the hitsounds
$parsed["hitsounds"][] = $filename;
}
}
}
else
if ($section_type == "key-value pairs")
{
// init empty array
if (!isset($parsed[$current_section])) $parsed[$current_section] = array();
// just dump the non-events...
//
// at the point of writing this
// comment, this section will
// never get used...
$parsed[$current_section][] = $line;
$delimiter_position = mb_strpos($line, $delimiter);
// old maps had random lines and lines with different delimiters (why?)
if ($delimiter_position !== false)
{
$kv_key = mb_substr($line, 0, $delimiter_position);
$kv_value = mb_substr($line, $delimiter_position + strlen($delimiter));
if ($current_section == "Variables")
{
$variables["keys"][$kv_key] = $kv_key;
$variables["values"][$kv_key] = $kv_value;
}
else
{
// after some thinking, keeping the original names was a good idea
$parsed[$current_section][$kv_key] = $kv_value;
}
}
}
else if ($section_type == "lists")
{
$list = explode($delimiter, $line);
if ($current_section == "Events") // saving the whole thing would take up too much space
{
if (mb_strpos($line, " ") === 0 || mb_strpos($line, "_") === 0) continue; // skip storyboard details lines
list($event_type, $source_files) = self::gather_source_files($list, $variables);
else if ($event_type == "Background")
{
$parsed["background"] = $source_files[0] ?? "";
}
else if ($event_type == "Video")
{
$parsed["video"] = $source_files[0] ?? "";
}
else if ($event_type !== false)
{
// init empty array
if (!isset($parsed["storyboard"])) $parsed["storyboard"] = array();
// add the elements to the storyboard
foreach ($source_files as $source_file)
{
$parsed["storyboard"][] = $source_file;
}
}
}
else if ($current_section == "HitObjects") // saving the whole thing would take up too much space v2
{
$last = array_key_last($list);
if (!empty($list[$last]) && mb_strpos($list[$last], ":") !== false)
{
$hitSample = explode(":", $list[$last]);
$last_sample = array_key_last($hitSample);
if (!empty($hitSample[$last_sample]))
{
$filename = $hitSample[$last_sample];
$filename = self::fix_filename($filename);
// some maps leave out the extensions
// (and some maps have .wav filess pointing to .ogg files........)
if (empty(pathinfo($filename, PATHINFO_EXTENSION)))
{
$filename = $filename . ".wav"; // just to signal it's an "audio file"
}
// init empty array
if (!isset($parsed["hitsounds"])) $parsed["hitsounds"] = array();
// add the element to the hitsounds
$parsed["hitsounds"][] = $filename;
}
}
}
else
{
// init empty array
if (!isset($parsed[$current_section])) $parsed[$current_section] = array();
// just dump the non-events...
//
// at the point of writing this
// comment, this section will
// never get used...
$parsed[$current_section][] = $line;
}
}
}
}
+54
View File
@@ -26,4 +26,58 @@ class utils
}
}
}
// does nothing if the directory already exists
public static function make_directory(string $directory) : void
{
if (!file_exists($directory))
{
mkdir($directory, 0777, true);
}
}
public static function load_json(string $path) : array
{
try
{
$raw = file_get_contents($path);
}
catch (Exception $e)
{
$raw = "";
}
return json_decode($raw, true) ?? array();
}
public static function to_unix_slashes(string $path) : string
{
return str_replace("\\", "/", $path);
}
public static function remove_trailing_slashes(string $path) : string
{
return rtrim($path, "/");
}
public static function to_unix_slashes_without_trail(string $path) : string
{
return self::remove_trailing_slashes(self::to_unix_slashes($path));
}
public static function delete_if_exists(string $file) : void
{
if (file_exists($file))
{
unlink($file);
}
}
public static function array_delete_if_exists(array $files_list) : void
{
foreach ($files_list as $file)
{
self::delete_if_exists($file);
}
}
}
+4 -12
View File
@@ -1,12 +1,4 @@
<?php
// todo: cleanup repacker
// todo: cleanup cache
// todo: cleanup db
// todo: cleanup settings (full reset)
// todo: whitelist / blacklist
// todo: dupe checker
// todo: stardiff deleter
// todo: mode deleter
// git does not like empty folders
if (!file_exists("session")) mkdir("session");
@@ -33,7 +25,7 @@ $display = "start";
if (isset($_GET["settings"]) || empty($settings->get_osu_path()))
{
// $display = "start";
$display = "start";
}
else if (isset($_GET["cleanup"]))
{
@@ -150,13 +142,13 @@ if ($display == "main")
[ "./?settings", "Settings", "Go back to the setup/settings screen.", "Settings" ],
[ "./?scan", "Scan", "Only scan for changes.", "Scan" ],
[ "./?rescan", "Force rescan", "Fully rescan the library. <i>(cached)</i>", "Rescan", "Slow" ],
[ "./?blacken", "Remove backgrounds", "Replace the background files with 1x1 black images." ],
[ "./?blacken", "Remove backgrounds", "Replace the background files with 1x1 black images. So that osu! stays CAAAALLLLLLMMMMMMMM! And so that you can play without using the \"Background Dim\" setting." ],
[ "./?novid", "Remove videos", "" ],
[ "./?nosb", "Remove storyboards", "" ],
[ "./?noskin", "Remove beatmap skins", "Does not remove hitsounds &amp; storyboard elements.", null, "Slow" ],
[ "./?nohit", "Remove custom hitsounds", "Does not remove storyboard elements.", null, "Slow" ],
[ "./?purify", "Remove junk files", "Remove everything that isn't referenced in .osu or .osb files.", null, "Very slow" ],
[ "./?nuke", "NUKE", "Remove everything that isn't .osu or a referenced audio/background file. Note: old/bad maps might lose vital elements!", "NUKE" ],
[ "./?nuke", "NUKE", "Remove everything that isn't .osu or a referenced audio/background file.<br /><br /><b>Note:</b> old/bad maps might lose vital elements!", "NUKE" ],
[ "./?warn=repack&forward=" . urlencode("./?repack&all"), "Repack all", "Repack all maps to .osz files. Note: you should not share exported maps; always use official osu! links.", "Repack ALL", "EXTREMELY slow" ],
[ "./?cleanup", "Clean up", "Reset your settings, delete unused files, or clear cache. This does not touch your osu! folder. Useful after exporting many maps individually.", "Choose what to clean up" ],
[ "./splitter.php?page=1", "TBD", null, "Explore" ],
@@ -172,7 +164,7 @@ if ($display == "main")
}
$mapset_count = count($lib->get_library());
$osu_root = $lib->get_root();
$osu_root = $lib->get_osu_root();
$parse_time = round($parse_time, 3);
$scan_time = round($lib->get_scan_time(), 3);
+3
View File
@@ -0,0 +1,3 @@
<div id="options" class="options">{{ MAIN_OPTIONS }}</div>
<div id="loader"></div>
<script>{{ MAIN_OPTIONS_SCRIPT }}</script>
+1 -1
View File
@@ -1,4 +1,4 @@
<!-- https://github.com/Thayol/OsuOptimizerPHP -->
<!-- https://github.com/Thayol/osu-optimizer-php -->
<body id="body">
{{ CONTENT }}
</body>
+2 -2
View File
@@ -1,7 +1,7 @@
<div class="main-container">
<h1>osu! optimizer (PHP)</h1>
<h3>Version 0.1 by Thayol</h3>
<p><a href="https://github.com/Thayol/OsuOptimizerPHP">[ GitHub ]</a></p>
<h3>Version {{ VERSION }} by Thayol</h3>
<p><a href="https://github.com/Thayol/osu-optimizer-php" target="_blank">[ GitHub ]</a></p>
<div class="main-item">
<h2>Stats</h2>
<h3>
+1
View File
@@ -0,0 +1 @@
0.1a
+3
View File
@@ -0,0 +1,3 @@
<h2>MISSING DATABASE</h2>
<p>It seems like your database is empty but there is an osu! folder in your settings. </p>
<p>Do you wish to continue with a scan?</p>
+8
View File
@@ -0,0 +1,8 @@
<div class="warn">
{{ WARN_CONTENT }}
<div id="options">
<p style="text-align:center;"><a href="{{ WARN_FORWARD_LINK }}" onclick="displayLoading()">[ Yes ]</a> &nbsp;&nbsp; <a href="{{ WARN_BACKWARD_LINK }}">[ No ]</a></p>
</div>
<div id="loader"></div>
</div>
<script>{{ MAIN_OPTIONS_SCRIPT }}</script>