diff --git a/.gitignore b/.gitignore
index b6e4761..58567c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,129 +1,26 @@
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
+# Windows thumbnails
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
-# C extensions
-*.so
+# Volatile files
+tokens/*
+users/*
+API/cache/*
+lobbies/*
+matches/*
+requests.json
+unalike.json
+users.json
-# Distribution / packaging
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-pip-wheel-metadata/
-share/python-wheels/
-*.egg-info/
-.installed.cfg
-*.egg
-MANIFEST
+# Files that are not appropriate for git
+created.txt
+description.txt
+title.txt
+start-wt.bat
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.nox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*.cover
-*.py,cover
-.hypothesis/
-.pytest_cache/
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-local_settings.py
-db.sqlite3
-db.sqlite3-journal
-
-# Flask stuff:
-instance/
-.webassets-cache
-
-# Scrapy stuff:
-.scrapy
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-target/
-
-# Jupyter Notebook
-.ipynb_checkpoints
-
-# IPython
-profile_default/
-ipython_config.py
-
-# pyenv
-.python-version
-
-# pipenv
-# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
-# However, in case of collaboration, if having platform-specific dependencies or dependencies
-# having no cross-platform support, pipenv may install dependencies that don't work, or not
-# install all needed dependencies.
-#Pipfile.lock
-
-# PEP 582; used by e.g. github.com/David-OConnor/pyflow
-__pypackages__/
-
-# Celery stuff
-celerybeat-schedule
-celerybeat.pid
-
-# SageMath parsed files
-*.sage.py
-
-# Environments
-.env
-.venv
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
-
-# Spyder project settings
-.spyderproject
-.spyproject
-
-# Rope project settings
-.ropeproject
-
-# mkdocs documentation
-/site
-
-# mypy
-.mypy_cache/
-.dmypy.json
-dmypy.json
-
-# Pyre type checker
-.pyre/
+# Sensitive information
+api_key.php
+local_secret.php
+account.php
\ No newline at end of file
diff --git a/API/index.php b/API/index.php
new file mode 100644
index 0000000..f5c09dd
--- /dev/null
+++ b/API/index.php
@@ -0,0 +1,301 @@
+ "beatmap-v2",
+ "id" => $beatmap,
+ );
+ }
+
+ if (!empty($_GET["u"]))
+ {
+ if (!empty($_GET["u"]))
+ {
+ $user = $_GET["u"];
+ }
+
+ $url = $base_url_v2 . "/users/{$user}/osu";
+
+ $cache_as = array(
+ "type" => "user",
+ "id" => $user,
+ );
+ }
+
+ $legacy_mode = false;
+ // y = [y] u do dis
+ if (!empty($_GET["y"]))
+ {
+ $beatmap = $_GET["y"];
+ $cross_mode = true;
+ $legacy_mode = true;
+ $url = $base_url_v1 . "/get_beatmaps?k={$api_key}&b={$beatmap}";
+
+ $cache_as = array(
+ "type" => "beatmap",
+ "id" => $beatmap,
+ );
+ }
+
+ $filter_diffs = false;
+ // f = difficulty id only [f]ilter
+ if (!empty($_GET["s"]) || !empty($_GET["f"]))
+ {
+ if (!empty($_GET["f"]))
+ {
+ $beatmapset = $_GET["f"];
+ $filter_diffs = true;
+ }
+ else
+ {
+ $beatmapset = $_GET["s"];
+ }
+ $url = $base_url_v1 . "/get_beatmaps?k={$api_key}&s={$beatmapset}";
+
+ $cache_as = array(
+ "type" => "beatmapset",
+ "id" => $beatmapset,
+ );
+ }
+
+ // m = [m]ultiplayer match
+ if (!empty($_GET["m"]))
+ {
+ $lobby = $_GET["m"];
+ $url = $base_url_v1 . "/get_match?k={$api_key}&mp={$lobby}";
+ }
+
+ if (strtolower($id) == "~unalike")
+ {
+ $token = "not-v2-so-no-key-needed-sry-peppy-for-including-this-field";
+ $online = true;
+ }
+ else
+ {
+ $token_file = getcwd() . "/../tokens/{$id}.json";
+ if (file_exists($token_file))
+ {
+ $token_json = json_decode(file_get_contents($token_file), true);
+ if (!empty($token_json["access_token"]))
+ {
+ $token = $token_json["access_token"];
+ $online = true;
+ }
+ }
+ }
+
+
+ // t = [t]est api access
+ if (!empty($_GET["t"]))
+ {
+ if (!empty($token_json["expires_at"]) && $token_json["expires_at"] > (time()+3600))
+ {
+ echo "YES";
+ }
+ else if (!empty($token_json["expires_at"]) && $token_json["expires_at"] > time())
+ {
+ echo "YES+EXPIRING";
+ }
+ else
+ {
+ echo "NO";
+ }
+ exit(0);
+ }
+
+ if (!empty($cache_as["type"]) && !empty($cache_as["id"]))
+ {
+ $cache_type = $cache_as["type"];
+ $cache_id = $cache_as["id"];
+ $cache_location = "./cache/{$cache_type}/{$cache_id}.json";
+ }
+
+ if (!empty($cache_location) && file_exists($cache_location))
+ {
+ $data = file_get_contents($cache_location);
+ }
+ else if ($online && isset($url) && !empty($token))
+ {
+ // echo '
';
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_HEADER, false);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Authorization: Bearer {$token}", "Content-type: application/json" ]);
+ $data = curl_exec($ch);
+ curl_close($ch);
+
+ if (!empty($cache_location) && ($data != "{\"authentication\":\"basic\"}"))
+ {
+ if (!file_exists(dirname($cache_location)))
+ {
+ mkdir(dirname($cache_location), 0777, true);
+ }
+
+ file_put_contents($cache_location, $data);
+ }
+ }
+
+ if (!empty($data))
+ {
+ if ($diffs_redirect || $cross_mode)
+ {
+ $json = json_decode($data, true);
+ if ($legacy_mode)
+ {
+ if (!empty($json[0]["beatmapset_id"]))
+ {
+ $json["beatmapset"] = array("id" => $json[0]["beatmapset_id"]);
+ }
+ }
+ if (!empty($json["beatmapset"]["id"]))
+ {
+ $beatmapset = $json["beatmapset"]["id"];
+ if ($cross_mode)
+ {
+ echo $beatmapset;
+ exit(0);
+ }
+ else if ($diffs_redirect)
+ {
+ header("Location: " . "../f/" . strval($beatmapset));
+ }
+ }
+ }
+
+ header("Content-type: application/json");
+ if ($filter_diffs)
+ {
+ $output = array(
+ "id" => $beatmapset,
+ "artist" => "",
+ "title" => "",
+ "creator" => "",
+ "beatmaps" => [],
+ );
+
+ $modes = array(
+ "0" => "osu",
+ "1" => "taiko",
+ "2" => "fruits",
+ "3" => "mania",
+ );
+
+ $temp_beatmaps = array();
+ $json = json_decode($data, true);
+ foreach ($json as $map)
+ {
+ if (!empty($map["beatmap_id"]) &&
+ !empty($map["approved"]) &&
+ !empty($map["difficultyrating"]) &&
+ !empty($map["artist"]) &&
+ !empty($map["title"]) &&
+ !empty($map["version"]) &&
+ !empty($map["max_combo"]))
+ {
+ $output["artist"] = $map["artist"];
+ $output["title"] = $map["title"];
+ $output["creator"] = $map["creator"];
+
+ $map_output = array(
+ "id" => intval($map["beatmap_id"]),
+ "ranked" => intval($map["approved"]),
+ "difficulty_rating" => floatval($map["difficultyrating"]),
+ "version" => $map["version"],
+ "artist" => $map["artist"],
+ "title" => $map["title"],
+ "creator" => $map["creator"],
+ "max_combo" => $map["max_combo"],
+ );
+
+ if (in_array($map["mode"], array_keys($modes))) // v2 compliance
+ {
+ $map_output["mode"] = $modes[$map["mode"]];
+ }
+
+ $temp_beatmaps[] = $map_output;
+ }
+ }
+
+ usort($temp_beatmaps, function($a, $b) {
+ return $a["difficulty_rating"] > $b["difficulty_rating"] ? 1 : ($a["difficulty_rating"] < $b["difficulty_rating"] ? -1 : 0);
+ });
+ $output["beatmaps"] = $temp_beatmaps;
+
+ echo json_encode($output);
+ }
+ else
+ {
+ echo $data;
+ }
+ }
+ else
+ {
+ echo json_encode([]);
+ }
+
+
+}
+else
+{
+ echo "No authentication method was defined.";
+}
\ No newline at end of file
diff --git a/OAuth/index.php b/OAuth/index.php
new file mode 100644
index 0000000..2547df8
--- /dev/null
+++ b/OAuth/index.php
@@ -0,0 +1,95 @@
+ $client_id,
+ "client_secret" => $client_secret,
+ "code" => $code,
+ "grant_type" => "authorization_code",
+ "redirect_uri" => $callback_uri,
+);
+
+$poststrings = array();
+foreach ($postdata as $key => $value) $poststrings[] = "{$key}={$value}";
+$poststring = implode("&", $poststrings);
+
+$ch = curl_init();
+curl_setopt($ch, CURLOPT_URL, "https://osu.ppy.sh/oauth/token");
+curl_setopt($ch, CURLOPT_HEADER, false);
+curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+curl_setopt($ch, CURLOPT_POST, true);
+curl_setopt($ch, CURLOPT_POSTFIELDS, $poststring);
+$auth_data = curl_exec($ch);
+curl_close($ch);
+$auth_json = json_decode($auth_data, true);
+
+if (!empty($auth_data) && !empty($auth_json))
+{
+ $now = time();
+ $expiry_date = $now + intval($auth_json["expires_in"]);
+ $auth_json["expires_at"] = $expiry_date;
+ $token = $auth_json["access_token"];
+
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, "https://osu.ppy.sh/api/v2/me/osu");
+ curl_setopt($ch, CURLOPT_HEADER, false);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Authorization: Bearer {$token}", "Content-type: application/json" ]);
+ $profile_data = curl_exec($ch);
+ curl_close($ch);
+ $profile_json = json_decode($profile_data, true);
+
+ if (!empty($profile_data) && !empty($profile_json))
+ {
+ $id = $profile_json["id"];
+ file_put_contents("../users/{$id}.json", $profile_data);
+ file_put_contents("../tokens/{$id}.json", json_encode($auth_json));
+
+
+ $_SESSION["unalike-osu-username"] = $profile_json["username"];
+ $_SESSION["unalike-osu-id"] = $profile_json["id"];
+ $_SESSION["unalike-granted"] = true;
+
+ $db = [];
+ foreach (glob("../users/*.json") as $file)
+ {
+ unset($temp);
+ $temp = json_decode(file_get_contents($file), true);
+ $db[strtolower($temp["username"])] = $temp["id"];
+ $db[strtolower(str_replace(" ", "_", $temp["username"]))] = $temp["id"];
+ }
+
+ $db["~Unalike"] = "~Unalike";
+
+ file_put_contents("../users.json", json_encode($db));
+
+ header("Location: ../");
+ }
+ else
+ {
+ echo "Authorization code accepted but user could not be identified.";
+ }
+}
+else
+{
+ echo "Invalid authorization code.";
+}
diff --git a/action/index.php b/action/index.php
new file mode 100644
index 0000000..b743fe3
--- /dev/null
+++ b/action/index.php
@@ -0,0 +1,129 @@
+ ';
+ exit(0);
+ }
+ }
+ else if (isset($_GET["delay"]))
+ {
+ if (!empty($_GET["target"]) && $_GET["target"] >= 1 && $_GET["target"] <= 7)
+ {
+ $target = floatval($_GET["target"]);
+ $type = "set_delay";
+ }
+ }
+ else if (isset($_GET["invite"]))
+ {
+ if (!empty($_GET["target"])) // TARGET IS THE FILTER HERE!
+ {
+ $filter = $_GET["target"];
+ }
+ $target = $username_irc;
+ $type = "invite";
+ }
+ else if (isset($_GET["new"]))
+ {
+ $type = "new_lobby";
+ }
+ else if (isset($_GET["sync"]))
+ {
+ $type = "sync_all";
+ }
+ else if (isset($_GET["shutdown"]))
+ {
+ $type = "shutdown";
+ }
+ else if (isset($_GET["start"]))
+ {
+ $type = "start";
+ }
+
+ $action = $_GET;
+
+ if (!empty($type))
+ {
+
+ $action = array(
+ "type" => $type,
+ "issuer" => [
+ "username" => $username,
+ "username_irc" => $username_irc,
+ "id" => $id,
+ ]);
+
+ if (!empty($target))
+ {
+ $action["target"] = $target;
+ }
+
+ if (!empty($filter))
+ {
+ $action["filter"] = $filter;
+ }
+
+ $json = json_decode(file_get_contents("../requests.json"), true);
+ if (empty($json))
+ {
+ $json = array($action);
+ }
+ else
+ {
+ $json[] = $action;
+ }
+ file_put_contents("../requests.json", json_encode($json));
+
+ header("Location: ../");
+ }
+ else
+ {
+ echo "Unknown action.";
+ }
+}
+else
+{
+ echo "Authentication error.";
+}
\ No newline at end of file
diff --git a/async/index.php b/async/index.php
new file mode 100644
index 0000000..1373fc1
--- /dev/null
+++ b/async/index.php
@@ -0,0 +1,20 @@
+
+
+
+ [[ SONG_INFO ]]
+
+
+
+
+
[[ STATE ]]
+
+ 📤
+ Invite
+
+
❌
+
+
+ [[ PLAYERS ]]
+
\ No newline at end of file
diff --git a/async/lobby/index.php b/async/lobby/index.php
new file mode 100644
index 0000000..a3998db
--- /dev/null
+++ b/async/lobby/index.php
@@ -0,0 +1,37 @@
+ $score)
+ {
+ if (!empty($score["user_id"]))
+ {
+ $username = "Guest";
+ if (file_exists($users_prefix . $score["user_id"] . $users_suffix))
+ {
+ $user = json_decode(file_get_contents($users_prefix . $score["user_id"] . $users_suffix), true);
+ if (!empty($user) && !empty($user["username"]))
+ {
+ $username = $user["username"];
+ }
+ }
+ else if (!empty($_SESSION["unalike-osu-id"]) && !empty($_SESSION["unalike-osu-username"]) && !empty($_SESSION["unalike-granted"]) && $_SESSION["unalike-granted"] === true)
+ {
+ $caller = str_replace(" ", "_", $_SESSION["unalike-osu-username"]);
+ $url = $api_url . "/" . $caller . "/u/" . $score["user_id"];
+ $raw = file_get_contents($url);
+ $user = json_decode($raw, true);
+ if (!empty($user) && !empty($user["username"]))
+ {
+ $username = $user["username"];
+ }
+ }
+ $output["games"][0]["scores"][$key]["username"] = $username;
+ }
+ }
+ }
+ }
+}
+else
+{
+ $all_matches = glob($relative_prefix . "*" . $original_suffix);
+ natsort($all_matches);
+ $all_matches = array_reverse($all_matches);
+ $recent_matches = array_slice($all_matches, 0, $recent_count);
+
+ $link_to_file = false;
+
+ foreach ($recent_matches as $relative_file)
+ {
+ if ($link_to_file)
+ {
+ $output[] = str_replace($relative_prefix, $absolute_prefix, $relative_file);
+ }
+ else
+ {
+ $output[] = str_replace($original_suffix, $call_suffix, str_replace($relative_prefix, $call_prefix, $relative_file));
+ }
+ }
+}
+
+if (isset($_GET["render"]))
+{
+ $needed_beatmaps = array();
+ foreach ($output["games"][0]["scores"] as $score)
+ {
+ $needed_beatmaps[] = $score["beatmap_id"];
+ }
+
+ $needed_beatmaps = array_unique($needed_beatmaps);
+
+ $userid = 0;
+ $beatmaps = array();
+ if (!empty($_SESSION["unalike-osu-id"]) && !empty($_SESSION["unalike-osu-username"]) && !empty($_SESSION["unalike-granted"]) && $_SESSION["unalike-granted"] === true)
+ {
+ $caller = str_replace(" ", "_", $_SESSION["unalike-osu-username"]);
+ $userid = $_SESSION["unalike-osu-id"];
+
+ foreach ($needed_beatmaps as $beatmap_id)
+ {
+ $url = $api_url . "/" . $caller . "/b/" . $beatmap_id;
+ $raw = file_get_contents($url);
+ $beatmaps[$beatmap_id] = json_decode($raw, true);
+ $beatmaps[$beatmap_id] = json_decode($raw, true);
+ }
+ }
+ else
+ {
+ foreach ($needed_beatmaps as $beatmap_id)
+ {
+ $beatmaps[$beatmap_id] = array( // dummy
+ "id" => $beatmap_id,
+ "url" => "login/",
+ "beatmapset" => [
+ "title" => "Results",
+ ],
+ );
+ }
+ }
+
+ $sample_beatmap = array_key_first($beatmaps);
+
+ $match_template = file_get_contents("match_template.html");
+ $score_template = file_get_contents("score_template.html");
+
+ $scores = "";
+
+ foreach ($output["games"][0]["scores"] as $score)
+ {
+ $name = $score["username"];
+ if ($userid == $score["user_id"])
+ {
+ $name = '' . $name . ' ';
+ }
+ $beatmap_id = $score["beatmap_id"];
+
+ $all_objects = 0;
+ if (!empty($beatmaps[$beatmap_id]["count_circles"])) $all_objects += $beatmaps[$beatmap_id]["count_circles"];
+ if (!empty($beatmaps[$beatmap_id]["count_sliders"]) && $score["game"]["mode"] != "taiko") $all_objects += $beatmaps[$beatmap_id]["count_sliders"];
+ if (!empty($beatmaps[$beatmap_id]["count_spinners"]) && $score["game"]["mode"] != "taiko") $all_objects += $beatmaps[$beatmap_id]["count_spinners"];
+
+ $maxcombo = $score["maxcombo"];
+ if (!empty($beatmaps[$beatmap_id]["max_combo"]))
+ {
+ if (isset($beatmaps[$beatmap_id]["convert"]) && $beatmaps[$beatmap_id]["convert"] != true)
+ {
+ $maxcombo .= "/" . $beatmaps[$beatmap_id]["max_combo"];
+ }
+ }
+
+ $stars = 0;
+ if (!empty($beatmaps[$beatmap_id]["difficulty_rating"]))
+ {
+ $stars = number_format($beatmaps[$beatmap_id]["difficulty_rating"], 2, '.', '');
+ }
+
+ $version = "";
+ if (!empty($beatmaps[$beatmap_id]["version"]))
+ {
+ $version = $beatmaps[$beatmap_id]["version"];
+ }
+
+ $accuracy = number_format($score["accuracy"], 2, '.', '');
+ $status = '';
+ if ($all_objects > $score["all_notes"])
+ {
+ $status = 'QUIT ';
+ }
+ else if ($score["pass"] != 1)
+ {
+ $status = 'FAILED ';
+ }
+ else if (!empty($beatmaps[$beatmap_id]["max_combo"]) && $score["maxcombo"] >= $beatmaps[$beatmap_id]["max_combo"])
+ {
+ $status = 'FC ';
+ }
+ else
+ {
+ // $status = 'PASS ';
+ }
+
+ $mode_text = "?";
+ if ($score["game"]["mode"] == "osu")
+ {
+ // $mode_text = "standard";
+ $mode_text = "osu";
+ }
+ else if ($score["game"]["mode"] == "taiko")
+ {
+ $mode_text = "taiko";
+ }
+ else if ($score["game"]["mode"] == "mania")
+ {
+ $mode_text = "mania";
+ }
+ else if ($score["game"]["mode"] == "fruits")
+ {
+ $mode_text = "catch";
+ }
+ // $mode_text = "osu!" . $mode_text;
+
+ $score_replaced = str_replace(
+ [
+ "[[ PLACE ]]",
+ "[[ NAME ]]",
+ "[[ STATUS ]]",
+ "[[ ACCURACY ]]",
+ "[[ MODE ]]",
+ "[[ MAXCOMBO ]]",
+ "[[ STARS ]]",
+ "[[ VERSION ]]",
+ ],
+ [
+ $score["place"],
+ $name,
+ $status,
+ $accuracy,
+ $mode_text,
+ $maxcombo,
+ $stars,
+ $version,
+ ],
+ $score_template);
+
+ $scores .= $score_replaced;
+ }
+
+ $cover = "/Unalike/img/covers/c4.jpg";
+ if (!empty($beatmaps[$sample_beatmap]["beatmapset"]["covers"]["cover@2x"]))
+ {
+ $cover = $beatmaps[$sample_beatmap]["beatmapset"]["covers"]["cover@2x"];
+ }
+ else if (!empty($beatmaps[$sample_beatmap]["beatmapset"]["covers"]["cover"]))
+ {
+ $cover = $beatmaps[$sample_beatmap]["beatmapset"]["covers"]["cover"];
+ }
+
+ $artist_components = array();
+ if (!empty($beatmaps[$sample_beatmap]["beatmapset"]["artist"]))
+ {
+ $artist_components[] = $beatmaps[$sample_beatmap]["beatmapset"]["artist"];
+ }
+ if (!empty($beatmaps[$sample_beatmap]["beatmapset"]["creator"]))
+ {
+ $artist_components[] = $beatmaps[$sample_beatmap]["beatmapset"]["creator"];
+ }
+
+ $artist = "";
+ if (!empty($artist_components))
+ {
+ $artist = implode(" // ", $artist_components);
+ }
+
+ $response = str_replace(
+ [
+ "[[ LINK ]]",
+ "[[ TITLE ]]",
+ "[[ ARTIST ]]",
+ "[[ COVER_IMAGE ]]",
+ "[[ SCORES ]]",
+ ],
+ [
+ $beatmaps[$sample_beatmap]["url"],
+ $beatmaps[$sample_beatmap]["beatmapset"]["title"],
+ $artist,
+ $cover,
+ $scores,
+ ],
+ $match_template);
+
+ echo $response;
+}
+else
+{
+ header("Content-Type: application/json");
+ echo json_encode($output);
+}
\ No newline at end of file
diff --git a/async/match/match_template.html b/async/match/match_template.html
new file mode 100644
index 0000000..5ff9507
--- /dev/null
+++ b/async/match/match_template.html
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/async/match/score_template.html b/async/match/score_template.html
new file mode 100644
index 0000000..833a11a
--- /dev/null
+++ b/async/match/score_template.html
@@ -0,0 +1,12 @@
+
+ #[[ PLACE ]]
+ [[ NAME ]]
+
+ [[ STATUS ]]
+ [[ ACCURACY ]]%
+
+ [[ MODE ]] [[ MAXCOMBO ]]x
+
+
+ [[ STARS ]]★ [[ VERSION ]]
+
diff --git a/compose/index.php b/compose/index.php
new file mode 100644
index 0000000..a7c7d93
--- /dev/null
+++ b/compose/index.php
@@ -0,0 +1,270 @@
+ array(
+ "match_id" => $match_id,
+ ),
+ "games" => array(),
+ );
+
+ $merged_game = array(
+ "game_id" => $match_id,
+ "beatmapset_id" => $beatmapset,
+ // "beatmapset" => [ "id" => $beatmapset ],
+ );
+
+ $modes = array(
+ "0" => "osu",
+ "1" => "taiko",
+ "2" => "fruits",
+ "3" => "mania",
+ );
+
+ $scores = array();
+ foreach ($source as $lobby)
+ {
+ $url = $url_base . $lobby["match"];
+
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_HEADER, false);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ $data = curl_exec($ch);
+ curl_close($ch);
+ $json = json_decode($data, true);
+
+ if (!empty($json) &&
+ !empty($json["match"]) &&
+ isset($json["match"]["name"]) &&
+ !empty($json["games"]))
+ {
+ $real_round = array_key_first($json["games"]);
+ $lobby["round"] = intval($lobby["round"]);
+ if ($lobby["round"] > 0)
+ {
+ $real_round = intval($lobby["round"]) - 1;
+ }
+ else if ($lobby["round"] == -1)
+ {
+ $real_round = array_key_last($json["games"]);
+ }
+
+
+ if (!empty($json["games"][$real_round]) &&
+ isset($json["games"][$real_round]["scores"]) )
+ {
+ $lobby_name = $json["match"]["name"];
+
+ $bancho_game = $json["games"][$real_round];
+
+ $lobby_beatmap = $bancho_game["beatmap_id"];
+
+ $api_url = $beatmap_url_v1 . "b=" . $lobby_beatmap;
+
+
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $api_url);
+ curl_setopt($ch, CURLOPT_HEADER, false);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ $api_response = curl_exec($ch);
+ curl_close($ch);
+ $api_json = json_decode($api_response, true);
+
+ $mode = "osu";
+ if (!empty($api_response) && !empty($api_json) && isset($api_json[0]["mode"]))
+ {
+ $mode = "osu";
+ if ($api_json[0]["mode"] != "0")
+ {
+ $mode = $modes[$api_json[0]["mode"]];
+ }
+ else if (isset($bancho_game["play_mode"]) && $bancho_game["play_mode"] != "0")
+ {
+ $mode = $modes[$bancho_game["play_mode"]];
+ }
+ }
+
+
+ foreach ($bancho_game["scores"] as $souce_score)
+ {
+ $score = array(
+ "user_id" => $souce_score["user_id"],
+ "place" => 0,
+ "accuracy" => 0,
+ "maxcombo" => 0,
+ "beatmap_id" => "0",
+ "score" => 0,
+ "game" => [],
+ );
+
+ // $score["source"] = $souce_score;
+ $score["source_id"] = $lobby["match"];
+ $score["source_round"] = $real_round + 1;
+ $score["lobby_name"] = $lobby_name;
+
+ $score["beatmap_id"] = $lobby_beatmap;
+ // $score["beatmap"] = array("id" => $lobby_beatmap);
+
+ $score["game"]["play_mode"] = $bancho_game["play_mode"];
+ $score["game"]["mode"] = $mode;
+ $score["game"]["match_type"] = $bancho_game["match_type"];
+ $score["game"]["scoring_type"] = $bancho_game["scoring_type"];
+ $score["game"]["team_type"] = $bancho_game["team_type"];
+ $score["game"]["mods"] = $bancho_game["mods"];
+
+ $hit0 = intval($souce_score["countmiss"]);
+ $hit50 = intval($souce_score["count50"]);
+ $hit100 = intval($souce_score["count100"]);
+ $hit300 = intval($souce_score["count300"]);
+ $hitGeki = intval($souce_score["countgeki"]);
+ $hitKatsu = intval($souce_score["countkatu"]);
+
+ $score["countmiss"] = $hit0;
+ $score["count50"] = $hit50;
+ $score["count100"] = $hit100;
+ $score["count300"] = $hit300;
+ $score["countgeki"] = $hitGeki;
+ $score["countkatu"] = $hitKatsu;
+
+ if ($mode == "mania")
+ {
+ $all_notes = $hit0 + $hit50 + $hit100 + $hitKatsu + $hit300 + $hitGeki;
+ $flat_score = ($hit50 * 50) + ($hit100 * 100) + ($hitKatsu * 200) + (($hit300 + $hitGeki) * 300);
+ $max_score = $all_notes * 300;
+ }
+ else if ($mode == "fruits")
+ {
+ $all_notes = $hit0 + $hitKatsu + $hit50 + $hit100 + $hit300;
+ $flat_score = $hit50 + $hit100 + $hit300;
+ $max_score = $all_notes;
+ }
+ else if ($mode == "taiko")
+ {
+ $all_notes = $hit0 + $hit100 + $hit300;
+ $flat_score = $hit100 + ($hit300 * 2);
+ $max_score = $all_notes * 2;
+ }
+ else
+ {
+ $all_notes = $hit0 + $hit50 + $hit100 + $hit300;
+ $flat_score = ($hit50 * 50) + ($hit100 * 100) + ($hit300 * 300);
+ $max_score = $all_notes * 300;
+ }
+
+ $accuracy = round(($flat_score * 100) / $max_score, 2);
+
+ $score["all_notes"] = $all_notes;
+ $score["accuracy"] = $accuracy;
+ $score["maxcombo"] = intval($souce_score["maxcombo"]);
+ $score["score"] = intval($souce_score["score"]);
+ $score["pass"] = intval($souce_score["pass"]);
+
+ $scores[] = $score;
+ }
+ }
+ }
+ }
+
+ usort($scores, function($a, $b) {
+ if ($a["pass"] == $b["pass"])
+ {
+ if ($a["accuracy"] == $b["accuracy"])
+ {
+ if ($a["maxcombo"] == $b["maxcombo"])
+ {
+ return 0;
+ }
+ else if ($a["maxcombo"] < $b["maxcombo"])
+ {
+ return 1;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+ else if ($a["accuracy"] < $b["accuracy"])
+ {
+ return 1;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+ else if ($a["pass"] < $b["pass"])
+ {
+ return 1;
+ }
+ else
+ {
+ return -1;
+ }
+ });
+
+ $counter = 1;
+ foreach ($scores as $key => $score)
+ {
+ $scores[$key]["place"] = $counter++;
+ }
+
+ $merged_game["scores"] = $scores;
+
+ $merged["games"][] = $merged_game;
+
+ file_put_contents("../matches/{$match_id}.json", json_encode($merged));
+
+ header("Content-Type: application/json");
+ echo json_encode(["status" => "success"]);
+ // echo json_encode($merged);
+ }
+ else
+ {
+ echo json_encode(["status" => "error", "error" => "missing_argument"]);
+ }
+}
+else
+{
+ echo json_encode(["status" => "error", "error" => "unauthenticated"]);
+}
\ No newline at end of file
diff --git a/img/covers/c1.jpg b/img/covers/c1.jpg
new file mode 100644
index 0000000..c1dfdf4
Binary files /dev/null and b/img/covers/c1.jpg differ
diff --git a/img/covers/c2.jpg b/img/covers/c2.jpg
new file mode 100644
index 0000000..b2c5fb5
Binary files /dev/null and b/img/covers/c2.jpg differ
diff --git a/img/covers/c3.jpg b/img/covers/c3.jpg
new file mode 100644
index 0000000..e9b3f33
Binary files /dev/null and b/img/covers/c3.jpg differ
diff --git a/img/covers/c4.jpg b/img/covers/c4.jpg
new file mode 100644
index 0000000..90ed8c6
Binary files /dev/null and b/img/covers/c4.jpg differ
diff --git a/img/covers/c5.jpg b/img/covers/c5.jpg
new file mode 100644
index 0000000..e69e503
Binary files /dev/null and b/img/covers/c5.jpg differ
diff --git a/img/covers/c6.jpg b/img/covers/c6.jpg
new file mode 100644
index 0000000..d955ba0
Binary files /dev/null and b/img/covers/c6.jpg differ
diff --git a/img/covers/c7.jpg b/img/covers/c7.jpg
new file mode 100644
index 0000000..8384f4e
Binary files /dev/null and b/img/covers/c7.jpg differ
diff --git a/img/covers/c8.jpg b/img/covers/c8.jpg
new file mode 100644
index 0000000..8563eb8
Binary files /dev/null and b/img/covers/c8.jpg differ
diff --git a/img/covers/c9.jpg b/img/covers/c9.jpg
new file mode 100644
index 0000000..c5399e0
Binary files /dev/null and b/img/covers/c9.jpg differ
diff --git a/img/favicon/128.png b/img/favicon/128.png
new file mode 100644
index 0000000..f1fd36c
Binary files /dev/null and b/img/favicon/128.png differ
diff --git a/img/favicon/16.png b/img/favicon/16.png
new file mode 100644
index 0000000..db8a70c
Binary files /dev/null and b/img/favicon/16.png differ
diff --git a/img/favicon/256.png b/img/favicon/256.png
new file mode 100644
index 0000000..de87b9a
Binary files /dev/null and b/img/favicon/256.png differ
diff --git a/img/favicon/32.png b/img/favicon/32.png
new file mode 100644
index 0000000..8e141e6
Binary files /dev/null and b/img/favicon/32.png differ
diff --git a/img/favicon/64.png b/img/favicon/64.png
new file mode 100644
index 0000000..18bd880
Binary files /dev/null and b/img/favicon/64.png differ
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..501ab75
--- /dev/null
+++ b/index.php
@@ -0,0 +1,1275 @@
+ $client_id,
+ "client_secret" => $client_secret,
+ "refresh_token" => $refresh_token,
+ "grant_type" => "refresh_token",
+ "redirect_uri" => $callback_uri,
+ );
+
+ $poststrings = array();
+ foreach ($postdata as $key => $value) $poststrings[] = "{$key}={$value}";
+ $poststring = implode("&", $poststrings);
+
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, "https://osu.ppy.sh/oauth/token");
+ curl_setopt($ch, CURLOPT_HEADER, false);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $poststring);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+ $auth_data = curl_exec($ch);
+ curl_close($ch);
+ $auth_json = json_decode($auth_data, true);
+
+ if (!empty($auth_data) && !empty($auth_json))
+ {
+ $now = time();
+ $expiry_date = $now + intval($auth_json["expires_in"]);
+ $auth_json["expires_at"] = $expiry_date;
+ $token_json = $auth_json;
+ }
+ else
+ {
+ $authenticated = false;
+ }
+ }
+ }
+
+ if (!empty($token_json["access_token"]))
+ {
+ $token = $token_json["access_token"];
+
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, "https://osu.ppy.sh/api/v2/me/osu");
+ curl_setopt($ch, CURLOPT_HEADER, false);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Authorization: Bearer {$token}", "Content-type: application/json" ]);
+ $profile_data = curl_exec($ch);
+ curl_close($ch);
+ $profile_json = json_decode($profile_data, true);
+
+ if (!empty($profile_data) && !empty($profile_json) && isset($profile_json["id"]) && $profile_json["id"] == $_SESSION["unalike-osu-id"])
+ {
+ $key_working = true;
+ $_SESSION["unalike-granted"] = true;
+ }
+ else
+ {
+ $_SESSION["unalike-granted"] = false;
+ }
+ }
+}
+
+$avatar_url = "";
+$cover_url = "/Unalike/img/covers/c4.jpg"; // some default pls
+if ($authenticated)
+{
+ $userfile = "users/" . $userid . ".json";
+ if (file_exists($userfile))
+ {
+ $userJson = json_decode(file_get_contents($userfile), true);
+ if (!empty($userJson["cover_url"]))
+ {
+ $cover_url = $userJson["cover_url"];
+ $avatar_url = $userJson["avatar_url"];
+ }
+ }
+}
+
+$json_display = false;
+?>
+
+
+
+
+ Unalike
+
+
+
+
+
+
+
+
+
+
+
+
+
Unalike (3rd-party osu! lobby manager)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Unalike
+
+
+
+
+ Unalike Access:
+ Granted ';
+ }
+ else
+ {
+ echo 'Denied!! ';
+ }
+ ?>
+
+ Log in to continue.
+
+
+
+
+
+
+
+
+
+
+ Authorize this app again to get an access key.
+
+ Either your refresh token has expired, you have revoked access manually, or the application has been unregistered from the osu! api clients.
+
+ You might need to re-authorize this application from your account settings page.
+
+
+
+
+ Please don't change the password because it breaks the invitation system. You are free to invite players, add any mods, change the team mode or win condition, and quit the match at any time, but the scores will only be evaluated after everyone has finished.
+
+
+
+
+
+
+ Unalike is a lobby synchronization system that allows you and your friends to play the same song but on different difficulties.
+ The results are calculated based on your accuracy.
+ Log in using your osu! account to get started.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Unalike JSON
+
+
Player JSON
+ ";
+ print_r($userJson);
+ echo "";
+ }
+ $cover_url = ""; // some default pls
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/login/index.php b/login/index.php
new file mode 100644
index 0000000..0c8cd9d
--- /dev/null
+++ b/login/index.php
@@ -0,0 +1,14 @@
+ 1:
+ msg = "[" + unalike_url + " Unalike > ] invite: " + " ".join(temp_lobbies)
+
+ print("Sending invite to: " + recipient)
+ print("Invite content: " + msg)
+ irc_send_pm(recipient, msg)
+
+def irc_sync_lobbies_to(originating_channel, beatmapset_id, beatmap_id):
+ global managed_lobbies, global_sync_init_timeout
+
+ timestamp_now = math.floor(time.time())
+ if global_last_mapset_played == beatmapset_id:
+ print(originating_channel + " tried to sync back the last played map!")
+ else:
+ syncAllowed = False
+ if originating_channel in managed_lobbies and "lastSyncInit" in managed_lobbies[originating_channel]:
+ if abs(timestamp_now - managed_lobbies[originating_channel]["lastSyncInit"]) > global_sync_init_timeout:
+ managed_lobbies[originating_channel]["lastSyncInit"] = timestamp_now
+ syncAllowed = True
+ else:
+ print(originating_channel + " attempted syncing too frequently!")
+ elif originating_channel == "WebUI":
+ syncAllowed = True
+
+ if syncAllowed:
+ for lobby in managed_lobbies:
+ if lobby != originating_channel and "beatmapset" in managed_lobbies[lobby] and managed_lobbies[lobby]["beatmapset"] != beatmapset_id:
+ skipSync = False
+ if "desynced" in managed_lobbies[lobby]:
+ if managed_lobbies[lobby]["desynced"] == True:
+ skipSync = True
+
+ if not skipSync:
+ if "channel" in managed_lobbies[lobby]:
+ print("Bringing " + managed_lobbies[lobby]["channel"] + " to compliance...")
+ irc_send_pm(managed_lobbies[lobby]["channel"], "!mp map " + str(beatmap_id))
+ managed_lobbies[lobby]["beatmapset"] = beatmapset_id
+ managed_lobbies[lobby]["beatmap"] = beatmap_id
+ else:
+ print("Attempted to set a non-existent lobby's map...")
+ else:
+ print("Skipping " + managed_lobbies[lobby]["channel"] + "... (Reason: desynced)")
+ else:
+ print(originating_channel + " does not have a sync property!")
+
+
+def fix_mp_prefix(channel_id):
+ if channel_id.find("#") == -1:
+ channel_id = "#mp_" + channel_id
+
+ return channel_id
+
+def irc_close_lobby(channel_id, abandon=False):
+ global managed_lobbies, lobbies_changed
+
+ channel_id = fix_mp_prefix(channel_id)
+
+ if not abandon:
+ irc_send_pm(channel_id, "!mp close")
+
+ managed_lobbies.pop(channel_id, None)
+
+ if abandon:
+ print(channel_id + " abandoned!")
+ else:
+ print(channel_id + " closed!")
+
+ lobbies_changed = True
+
+def irc_start_managing(lobby_channel, lobby_name=False, join=False):
+ global managed_lobbies, lobbies_changed
+
+ lobby_channel = fix_mp_prefix(lobby_channel)
+ print("Lobby registered: " + lobby_channel)
+
+ if join:
+ print("Joining channel: " + lobby_channel)
+ irc_send("JOIN", lobby_channel)
+
+
+ if not (lobby_channel in managed_lobbies):
+ managed_lobbies[lobby_channel] = {}
+
+ managed_lobbies[lobby_channel]["channel"] = lobby_channel
+ if lobby_name != False:
+ managed_lobbies[lobby_channel]["name"] = lobby_name
+ if join:
+ managed_lobbies[lobby_channel]["skipModeSetup"] = True
+
+ lobbies_changed = True
+
+def irc_auth(the_name, the_pass=""):
+ if the_pass != "":
+ irc_send("PASS", the_pass, custom_delay=0)
+ irc_send("NICK", the_name, custom_delay=0)
+ irc_send("USER", the_name, the_name, the_name, the_name, custom_delay=0)
+
+default_sender = "SERVER"
+class Command():
+ def __init__(self):
+ global default_sender
+ self.sender = default_sender
+ self.raw = ""
+ self.cmd = ""
+ self.args = []
+ self.message = ""
+ self.channel = ""
+
+def irc_to_Command(lines):
+ cmds = []
+ for line in lines:
+ args_start = 1
+ has_sender = False
+ if line.find(":") == 0:
+ args_start = 2
+ has_sender = True
+
+ expl = line.split(" ")
+
+ cmd = Command()
+ cmd.raw = line
+ cmd.cmd = expl[args_start-1].upper()
+ cmd.args = expl[args_start:]
+ if has_sender:
+ temp_sender = expl[0][1:]
+ if temp_sender.find("!") != -1:
+ temp_sender = temp_sender.split("!")[0]
+ cmd.sender = temp_sender
+ if cmd.cmd in [ "PRIVMSG", "001", "332", "372", "375", "376", "401", "403" ]:
+ cmd.channel = expl[args_start]
+ if (cmd.cmd in ["401", "403"]):
+ cmd.channel = expl[args_start+1]
+ temp_msg = " ".join(expl[(args_start+1):])
+ if len(temp_msg) > 1:
+ cmd.message = temp_msg[1:].replace(b'\x01ACTION'.decode(encoding), "[STATUS]").replace(b'\x01'.decode(encoding), "");
+
+ cmds.append(cmd)
+
+ return cmds
+
+def irc_read():
+ global irc, encoding, buffer_size
+ text = ""
+
+
+ reading = True
+ while reading:
+ remaining = ""
+
+ try:
+ remaining = irc.recv(buffer_size).decode(encoding, "replace")
+ except Exception:
+ pass
+
+ if remaining == "":
+ if (len(text) >= 2 and text[-1] == "\n") or text == "":
+ reading = False
+ else:
+ remaining = remaining.replace("\r\n", "\n") # unstandard bancho fix
+ text = text + remaining
+
+ if text == "":
+ return []
+ else:
+ return [x for x in text.split("\n") if len(x.strip()) > 0]
+
+def reset_lobby_states():
+ global global_playing, managed_lobbies, lobbies_changed
+
+ global_playing = False
+ for lobby in managed_lobbies:
+ managed_lobbies[lobby]["ready"] = False
+ managed_lobbies[lobby]["playing"] = False
+ lobbies_changed = True
+
+ print("Lobby states reset.")
+
+def generate_new_password():
+ global lobby_default_pass
+ lobby_default_pass = ''.join(random.choice(string.ascii_letters) for i in range(10))
+ print("A new default password has been generated.")
+
+if console_input_allowed:
+ print("--- HELP ---")
+ print("")
+ print("ESC: Quit")
+ print(" c: Command")
+ print(" t: Message")
+ print(" r: Reply (last channel)")
+ print(" b: Message BanchoBot")
+ print(" p: Responsivity test")
+ print(" n: Create lobby (+shift: auto-name)")
+ print(" s: Send links")
+ print(" x: Close lobby")
+ print(" Q: RESET STATES")
+ print("")
+
+print("")
+print("--- START ---")
+print("")
+
+with open(requests_file, "w") as json_file:
+ json.dump({}, json_file)
+print("Requests JSON reset.")
+
+with open(report_file, "w") as outfile:
+ json.dump({}, outfile)
+print("Report JSON reset.")
+
+managed_lobbies = {}
+lobbies_changed = False
+current_mapset = 0
+current_mapset_default_beatmap = 0
+api_player = ""
+global_playing = False
+global_rounds_played = 0
+global_polling_rate = float(1)/polling_rate
+global_last_mapset_played = -5
+generate_new_password()
+global_authenticated = False
+boot_timestamp = math.floor(time.time())
+current_irc_send_timeout = 0
+global_irc_send_queue = []
+print("First password: " + lobby_default_pass)
+
+tick = True
+
+print("Beginning authentication...")
+irc_auth(bot_name, bot_pass)
+last_channel = "BanchoBot"
+
+running = True
+while running:
+ time.sleep(global_polling_rate)
+ lines = irc_to_Command(irc_read())
+
+ request_json = {}
+ if path.exists(requests_file):
+ with open(requests_file, "r") as json_file:
+ request_json = json.load(json_file)
+ if len(request_json) > 0:
+ print("NEW REQUEST:", request_json)
+ with open(requests_file, "w") as json_file:
+ json.dump({}, json_file)
+
+ for request in request_json:
+ if "type" in request:
+ if request["type"] == "invite" and "target" in request:
+ filter = False;
+ if "filter" in request:
+ filter = fix_mp_prefix(request["filter"])
+
+ irc_send_links(request["target"].replace(" ", "_"), filter)
+ elif request["type"] == "new_lobby":
+ if len(managed_lobbies) < global_max_lobbies:
+ lobby_name = "Unalike"
+ if "name" in request:
+ lobby_name = request["name"]
+ irc_send_pm("BanchoBot", "!mp make "+lobby_name)
+ elif request["type"] == "close" and "target" in request:
+ irc_close_lobby(request["target"])
+ elif request["type"] == "set_delay" and "target" in request:
+ if str(request["target"]).isnumeric() and request["target"] >= 1 and request["target"] <= 7:
+ global_irc_send_timeout = float(request["target"])
+ print("Global delay has been updated: " + str(global_irc_send_timeout))
+ elif request["type"] == "results_fetched" and "target" in request:
+ if request["target"] in managed_lobbies and "finished" in managed_lobbies[request["target"]]:
+ managed_lobbies[request["target"]]["finished"] = False
+ lobbies_changed = True
+ elif request["type"] == "ping" and "target" in request:
+ channel_id = fix_mp_prefix(request["target"])
+ if channel_id in managed_lobbies:
+ print("Pinging " + str(channel_id) + " by request.")
+ irc_send_pm(channel_id, "[" + unalike_url + " Unalike > ] Someone pinged this lobby.")
+ elif request["type"] == "sync_all":
+ if current_mapset != 0 and current_mapset_default_beatmap != 0:
+ print("Syncing all lobbies...")
+ irc_sync_lobbies_to("WebUI", current_mapset, current_mapset_default_beatmap)
+ elif request["type"] == "register" and "target" in request:
+ print("Registering lobby: " + str(request["target"]))
+ irc_start_managing(request["target"], join=True)
+ elif request["type"] == "shutdown":
+ running = False
+ break
+
+ for line in lines:
+ if line.cmd in [ "001" ]:
+ print(line.message)
+ global_authenticated = True
+ elif line.cmd == "PRIVMSG" and line.sender == "BanchoBot":
+ if line.message.find("Created the tournament match ") != -1:
+ new_lobby_info = line.message.replace("Created the tournament match https://osu.ppy.sh/mp/", "").split(" ")
+
+ lobby_channel = new_lobby_info[0]
+ lobby_name = " ".join(new_lobby_info[1:])
+ last_channel = lobby_channel
+
+ irc_start_managing(lobby_channel, lobby_name)
+
+ elif line.message.find("The match has finished!") != -1 or line.message.find("Aborted the match") != -1:
+ lobby_channel = line.channel
+ print(line.channel + " ended.")
+
+ if not (lobby_channel in managed_lobbies):
+ managed_lobbies[lobby_channel] = {}
+
+ managed_lobbies[lobby_channel]["ready"] = False
+ managed_lobbies[lobby_channel]["playing"] = False
+ managed_lobbies[lobby_channel]["finished"] = True
+
+ lobbies_changed = True
+ elif line.message.find("Match starts in ") != -1 or line.message.find("Good luck, have fun!") != -1:
+ pass
+ elif line.message.find(" finished playing (") != -1:
+ player = line.message.split(" finished playing (")[0]
+
+ elif line.message.find("The match has started!") != -1:
+ lobby_channel = line.channel
+ print(line.channel + " started.")
+
+ if not (lobby_channel in managed_lobbies):
+ managed_lobbies[lobby_channel] = {}
+
+ managed_lobbies[lobby_channel]["playing"] = True
+
+ lobbies_changed = True
+ elif line.message.find("All players are ready") != -1:
+ lobby_channel = line.channel
+ print(line.channel + " is ready.")
+
+ if not (lobby_channel in managed_lobbies):
+ managed_lobbies[lobby_channel] = {}
+
+ managed_lobbies[lobby_channel]["ready"] = True
+
+ lobbies_changed = True
+ elif line.message.find(" left the game.") != -1:
+ lobby_channel = line.channel
+ player = line.message.split(" left the game.")[0]
+ print(player + " left." + "(" + lobby_channel + ")")
+
+ if not (lobby_channel in managed_lobbies):
+ managed_lobbies[lobby_channel] = {}
+
+ managed_lobbies[lobby_channel]["ready"] = False
+ if not ("players" in managed_lobbies[lobby_channel]):
+ managed_lobbies[lobby_channel]["players"] = []
+ managed_lobbies[lobby_channel]["players"].remove(player)
+ elif line.message.find(" joined in slot ") != -1:
+ lobby_channel = line.channel
+ player = line.message.split(" joined in slot ")[0]
+ print(player + " joined." + "(" + lobby_channel + ")")
+
+ temp_api_player = player.replace(" ", "_")
+ httpRequest = requests.get(unalike_api + "/" + temp_api_player + "/t/test")
+ if httpRequest.text.find("YES") != -1:
+ print("New API token provider elected: " + api_player + " -> " + temp_api_player)
+ api_player = temp_api_player
+ elif httpRequest.text.find("EXPIRING") != -1:
+ irc_send_pm(temp_api_player, "Your API key is expiring soon. Please click [" + unalike_url + " here] to refresh it.")
+ elif httpRequest.text.find("NO") != -1:
+ irc_send_pm(temp_api_player, "Your API key has expired. Please click [" + unalike_url + " here] to refresh it.")
+ irc_send_pm(lobby_channel, "!mp kick " + player)
+ else:
+ irc_send_pm(temp_api_player, "Unalike is having connection issues...")
+ print("API connection issues for player (unalike-side!): " + player)
+
+
+
+ if not (lobby_channel in managed_lobbies):
+ managed_lobbies[lobby_channel] = {}
+
+ managed_lobbies[lobby_channel]["ready"] = False
+
+ if not ("players" in managed_lobbies[lobby_channel]):
+ managed_lobbies[lobby_channel]["players"] = []
+
+ if len(managed_lobbies[lobby_channel]["players"]) < 1:
+ irc_send_pm(line.channel, "!mp host " + player) # auto host
+
+ managed_lobbies[lobby_channel]["players"].append(player)
+ elif line.message.find("Changed the match password") != -1:
+ lobby_channel = line.channel
+ print("Password set in " + line.channel)
+
+ if not (lobby_channel in managed_lobbies):
+ managed_lobbies[lobby_channel] = {}
+
+ managed_lobbies[lobby_channel]["password"] = lobby_default_pass
+ generate_new_password() # let's hope there won't be any random changes
+
+ lobbies_changed = True
+ elif line.message.find("Changed match settings to ") != -1:
+ lobby_channel = line.channel
+ lobby_settings = line.message.replace("Changed match settings to ", "").replace(" slots", "").split(", ") #Changed match settings to 1 slots, HeadToHead, Accuracy
+ lobby_size = int(lobby_settings[0])
+ lobby_mode = lobby_settings[1]
+ lobby_win_condition = lobby_settings[2]
+
+ print("New settings in " + line.channel + ":")
+ print(" Win Condition: " + lobby_win_condition)
+ print(" Mode: " + lobby_mode)
+ print(" Size: " + str(lobby_size))
+
+ if not (lobby_channel in managed_lobbies):
+ managed_lobbies[lobby_channel] = {}
+
+ managed_lobbies[lobby_channel]["winCondition"] = lobby_win_condition
+ managed_lobbies[lobby_channel]["mode"] = lobby_mode
+ managed_lobbies[lobby_channel]["size"] = lobby_size
+
+ lobbies_changed = True
+ elif line.message.find("Changed match host to ") != -1:
+ pass # already announced, this is a duplicate
+ elif line.message.find(" became the host.") != -1:
+ player = line.message.split(" became the host.")[0]
+ print("Host changed: " + player)
+ elif (line.message.find("Beatmap changed to:") != -1 or line.message.find("Changed beatmap to ") != -1) and line.message.find("osu.ppy.sh/b/"):
+ lobby_channel = line.channel
+ try:
+ temp = line.message.split("osu.ppy.sh/b/")[1].replace(")", "")
+ if temp.find(" ") != -1:
+ temp = temp.split(" ")[0]
+ beatmap_id = int(temp)
+ except Exception:
+ beatmap_id = -1
+
+ http_request = requests.get(unalike_api + "/" + api_player + "/y/" + str(beatmap_id))
+ if (http_request.text.isnumeric()):
+ beatmapset_id = int(http_request.text)
+ else:
+ beatmapset_id = -1
+
+ print("Beatmap changed to: " + str(beatmap_id) + " (" + str(beatmapset_id) + ")")
+
+ if not (lobby_channel in managed_lobbies):
+ managed_lobbies[lobby_channel] = {}
+
+ managed_lobbies[lobby_channel]["beatmap"] = beatmap_id
+ managed_lobbies[lobby_channel]["ready"] = False
+
+ if beatmapset_id > 0:
+ managed_lobbies[lobby_channel]["beatmapset"] = beatmapset_id
+
+ skipSync = False
+ if "desynced" in managed_lobbies[lobby_channel]:
+ if managed_lobbies[lobby_channel]["desynced"] == True:
+ skipSync = True
+
+ if skipSync:
+ print("Skipping sync of " + str(lobby_channel) + "... (Reason: desynced)")
+ else:
+ if current_mapset != beatmapset_id:
+ print("Syncing other lobbies to the new beatmapset... (" + str(current_mapset) + " previously)")
+ current_mapset = beatmapset_id
+ current_mapset_default_beatmap = beatmap_id
+ irc_sync_lobbies_to(lobby_channel, current_mapset, current_mapset_default_beatmap)
+ else:
+ print("The beatmap is a part of the current set.")
+
+ lobbies_changed = True
+ elif line.message.find("Host is changing map...") != -1:
+ lobby_channel = line.channel
+
+ beatmap_id = -2
+ print("Selecting beatmap...")
+
+ if not (lobby_channel in managed_lobbies):
+ managed_lobbies[lobby_channel] = {}
+
+ managed_lobbies[lobby_channel]["beatmap"] = beatmap_id
+ managed_lobbies[lobby_channel]["ready"] = False
+
+ lobbies_changed = True
+ else:
+ print("[" + line.channel + "] " + line.sender + ": " + line.message)
+
+ elif line.cmd == "PRIVMSG" and line.channel in managed_lobbies:
+ if len(line.message) >= 1 and line.message[0] == ".":
+ if line.message == ".close":
+ irc_close_lobby(line.channel)
+ elif line.message == ".desync":
+ managed_lobbies[line.channel]["desynced"] = True
+ print("Setting " + line.channel + " to desynced mode.")
+ irc_send_pm(channel_id, "[" + unalike_url + " Unalike > ] The mapset will not sync while desynced.")
+ lobbies_changed = True
+ elif line.message == ".sync":
+ managed_lobbies[line.channel]["desynced"] = False
+ print("Setting " + line.channel + " to synced mode.")
+ irc_send_pm(channel_id, "!mp map " + str(current_mapset_default_beatmap))
+ lobbies_changed = True
+ elif line.cmd == "PRIVMSG" and line.sender != "BanchoBot":
+ print("[" + line.channel + "] " + line.sender + ": " + line.message)
+ elif line.cmd in [ "401", "403" ]: # 401 = No such nick (bancho bug), 403 = No such channel
+ irc_close_lobby(line.channel, True) # abandon = don't announce close (don't loop endlessly)
+ elif line.cmd == "332": # Topic (has game number)
+ if line.message.find("multiplayer game #") != -1:
+ temp_topic = line.message.replace(":multiplayer game #", "").split(" ")
+ lobby_id = int(temp_topic[1])
+ channel_id = temp_topic[0]
+ print("Received lobby ID (" + str(lobby_id) + ") for channel " + channel_id + ".")
+
+ last_channel = channel_id
+
+ if channel_id.find("#") == -1:
+ channel_id = "#" + channel_id
+
+ if not (channel_id in managed_lobbies):
+ managed_lobbies[channel_id] = {}
+
+ managed_lobbies[channel_id]["channel"] = channel_id
+ managed_lobbies[channel_id]["lobby"] = lobby_id
+
+ lobbies_changed = True
+ elif line.cmd in [ "372", "375", "376", "333", "366", "353" ]:
+ pass
+ elif line.cmd == "QUIT":
+ pass
+ elif line.cmd in [ "JOIN", "PART" ]:
+ pass # other bancho messages are more useful
+ elif not (line.cmd in [ "PING", "MODE" ]):
+ print("")
+ print(line.raw)
+ print(line.sender)
+ print(line.cmd)
+ print(" ".join(line.args))
+ print("")
+
+ if line.cmd == "PING":
+ if len(line.args) >= 1:
+ irc_send("PONG", line.args[0])
+
+ if current_irc_send_timeout > 0:
+ current_irc_send_timeout -= global_polling_rate
+ if current_irc_send_timeout < 0:
+ current_irc_send_timeout = 0
+ elif len(global_irc_send_queue) > 0:
+ irc_enqueued_obj = global_irc_send_queue.pop(0)
+ if "line" in irc_enqueued_obj and "delay" in irc_enqueued_obj:
+ irc_send_real(irc_enqueued_obj["line"], irc_enqueued_obj["delay"])
+
+
+ if global_authenticated:
+ temp_output = {}
+ temp_output["lobbies"] = managed_lobbies
+ temp_output["playing"] = global_playing
+ temp_output["apiPlayer"] = api_player
+ temp_output["roundsPlayed"] = global_rounds_played
+ temp_output["timestamp"] = math.floor(time.time());
+ temp_output["boot"] = boot_timestamp;
+ temp_output["delay"] = global_irc_send_timeout;
+ temp_output["maxLobbies"] = global_max_lobbies;
+ temp_output["current_mapset"] = current_mapset;
+
+ # uncomment to expose commands to the public
+ # temp_output["command_queue"] = global_irc_send_queue;
+
+ with open(report_file, "w") as outfile:
+ json.dump(temp_output, outfile)
+
+ if lobbies_changed:
+ lobbies_changed = False
+ all_ready = True
+ all_finished = True
+ if len(managed_lobbies) > 0:
+ print("Managed lobbies (" + str(len(managed_lobbies)) + "):")
+ for channel_id in managed_lobbies:
+ print(" ", managed_lobbies[channel_id])
+ if not ("configured" in managed_lobbies[channel_id]):
+ managed_lobbies[channel_id]["configured"] = True
+ managed_lobbies[channel_id]["ready"] = False
+ managed_lobbies[channel_id]["playing"] = False
+ managed_lobbies[channel_id]["channel"] = channel_id
+ managed_lobbies[channel_id]["match"] = channel_id.replace("#mp_", "")
+ managed_lobbies[channel_id]["roundsPlayed"] = 0
+ managed_lobbies[channel_id]["players"] = []
+ managed_lobbies[channel_id]["finished"] = False
+ managed_lobbies[channel_id]["desynced"] = False
+ managed_lobbies[channel_id]["lastSyncInit"] = 0
+ if not ("skipModeSetup" in managed_lobbies[channel_id]):
+ managed_lobbies[channel_id]["skipModeSetup"] = False
+
+ print("Performing basic setup on " + channel_id + "...")
+ irc_send_pm(channel_id, "!mp password " + str(lobby_default_pass))
+ if not managed_lobbies[channel_id]["skipModeSetup"]:
+ irc_send_pm(channel_id, "!mp set 0 1 " + str(lobby_default_size))
+
+ lobbies_changed = True
+ if managed_lobbies[channel_id]["ready"] == False:
+ all_ready = False
+ if managed_lobbies[channel_id]["playing"] == True:
+ all_finished = False
+ with open(lobbies_dir + managed_lobbies[channel_id]["match"] + ".json", "w") as json_file:
+ json.dump(managed_lobbies[channel_id], json_file)
+ if global_playing:
+ if all_finished:
+ print("All lobbies finished playing!")
+ global_playing = False
+ for channel_id in managed_lobbies:
+ managed_lobbies[channel_id]["finished"] = False
+ # irc_send_pm(channel_id, "No results system yet, but the message works. xD")
+ global_rounds_played += 1
+
+ request_matches = []
+ for channel_id in managed_lobbies:
+ if "roundsPlayed" in managed_lobbies[channel_id] and "match" in managed_lobbies[channel_id]:
+ request_matches.append(str(managed_lobbies[channel_id]["match"]) + ",-1")
+
+ composer_url = unalike_composer + "secret=" + local_secret + "&source=" + ";".join(request_matches) + "&beatmapset=" + str(current_mapset);
+
+ print("Finished. Request forwarded to: Unalike Composer")
+
+ time.sleep(global_composer_delay)
+ requests.get(composer_url)
+
+ lobbies_changed = True
+ else:
+ if all_ready:
+ print("All lobbies are ready!")
+ global_playing = True
+ global_last_mapset_played = current_mapset
+ temp_timer = len(managed_lobbies) * global_irc_send_timeout
+ for channel_id in managed_lobbies:
+ managed_lobbies[channel_id]["roundsPlayed"] += 1
+ irc_send_pm(channel_id, "!mp start " + str(temp_timer))
+ print("Started " + channel_id + " with a " + str(temp_timer) + " second timer.")
+ temp_timer = temp_timer - global_irc_send_timeout
+ else:
+ print("No lobbies are managed by Unalike.")
+
+ if console_input_allowed:
+ if msvcrt.kbhit():
+ pressed_button = msvcrt.getch()
+
+ if pressed_button == b'\x1b': # ESC
+ print("EXITING APPLICATION")
+ break
+
+ elif pressed_button == b'p': # p
+ print("Pong!")
+
+ elif pressed_button == b'c': # c
+ print("COMMAND MODE")
+ temp_input = input("Command: ")
+ if temp_input.find(" "):
+ temp_args = temp_input.split()
+ else:
+ temp_args = [temp_input, ""]
+ irc_send(temp_args[0], " ".join(temp_args[1:]))
+
+ elif pressed_button == b't': # t
+ print("MESSAGE MODE")
+ temp_channel = input("Channel/User: ")
+ temp_message = input("Message: ")
+ irc_send_pm(temp_channel, temp_message)
+
+print("")
+print("--- END ---")
+print("")
+
+print("Starting shutdown process...")
+
+with open(requests_file, "w") as json_file:
+ json.dump({}, json_file)
+print("Requests JSON reset.")
+
+to_close_list = []
+for channel_id in managed_lobbies:
+ to_close_list.append(channel_id)
+
+if global_irc_send_timeout < 5:
+ global_irc_send_timeout = 5
+
+for channel_id in to_close_list:
+ irc_close_lobby(channel_id)
+
+irc_send("QUIT", custom_delay=0)
+
+time.sleep(global_irc_send_timeout)
+for irc_enqueued_obj in global_irc_send_queue:
+ if "line" in irc_enqueued_obj and "delay" in irc_enqueued_obj:
+ irc_send_real(irc_enqueued_obj["line"], irc_enqueued_obj["delay"])
+ # print("SENT: " + str(irc_enqueued_obj["line"]).replace("\r\n", ""))
+ time.sleep(irc_enqueued_obj["delay"])
+
+with open(report_file, "w") as outfile:
+ json.dump({}, outfile)
+print("Report JSON reset.")
+
+print("Quit request sent!")
\ No newline at end of file