From 244d1177df21234df781b992601da3c9c0d402c3 Mon Sep 17 00:00:00 2001 From: flashwave Date: Sun, 11 Dec 2016 19:51:45 +0100 Subject: [PATCH] osu leaderboard --- app/Console/Command/CronCommand.php | 5 +- .../Command/OsuLeaderboardUpdateCommand.php | 75 ++++++++++ app/Controllers/OsuController.php | 45 ++++++ app/OsuLeaderboard.php | 111 +++++++++++++++ ...16_12_11_160011_osu_leaderboard_tables.php | 59 ++++++++ .../less/yuuno/bem/osu-leaderboard.less | 61 ++++++++ resources/assets/less/yuuno/main.less | 1 + resources/views/yuuno/master.twig | 5 +- resources/views/yuuno/osu/leaderboard.twig | 130 ++++++++++++++++++ routes.php | 8 +- 10 files changed, 495 insertions(+), 5 deletions(-) create mode 100644 app/Console/Command/OsuLeaderboardUpdateCommand.php create mode 100644 app/Controllers/OsuController.php create mode 100644 app/OsuLeaderboard.php create mode 100644 database/2016_12_11_160011_osu_leaderboard_tables.php create mode 100644 resources/assets/less/yuuno/bem/osu-leaderboard.less create mode 100644 resources/views/yuuno/osu/leaderboard.twig diff --git a/app/Console/Command/CronCommand.php b/app/Console/Command/CronCommand.php index c405a60..2e57b2f 100644 --- a/app/Console/Command/CronCommand.php +++ b/app/Console/Command/CronCommand.php @@ -25,10 +25,11 @@ class CronCommand extends ChainedCommand } public $commands = [ - PremiumPurgeCommand::class, PurgeInactiveUsersCommand::class, - RebuildForumCacheCommand::class, SessionPurgeCommand::class, + PremiumPurgeCommand::class, StatusCheckCommand::class, + RebuildForumCacheCommand::class, + OsuLeaderboardUpdateCommand::class, ]; } diff --git a/app/Console/Command/OsuLeaderboardUpdateCommand.php b/app/Console/Command/OsuLeaderboardUpdateCommand.php new file mode 100644 index 0000000..adeef39 --- /dev/null +++ b/app/Console/Command/OsuLeaderboardUpdateCommand.php @@ -0,0 +1,75 @@ + + */ +class OsuLeaderboardUpdateCommand extends Command +{ + /** + * A quick description of this command. + * @return string. + */ + public function brief(): string + { + return 'Connects to the osu!api and stores new data.'; + } + + /** + * Command. + */ + public function execute(): void + { + $this->getLogger()->writeln('Updating osu! leaderboard...'); + + $api_key = config('osu.api_key'); + + if ($api_key === null) { + $this->getLogger()->writeln("Your api key isn't set in the config, get one at https://osu.ppy.sh/p/api."); + return; + } + + $game_modes = [ + 'osu!', + 'osu!taiko', + 'osu!catch', + 'osu!mania', + ]; + + $osulb = new OsuLeaderboard($api_key); + $start = Carbon::now(); + + $users = DB::table('users') + ->whereNotNull('user_osu') + ->get(['user_id']); + + foreach ($game_modes as $mode => $name) { + $this->getLogger()->writeln(PHP_EOL . "=> {$name}"); + + foreach ($users as $user) { + $user = User::construct($user->user_id); + + $user_display = $user->osu . ($user->username !== $user->osu ? " ({$user->username})" : ''); + $this->getLogger()->writeln("==> Updating {$name} scores for {$user_display}..."); + + $osulb->update($user->osu, $user, $mode); + } + } + + $this->getLogger()->writeln(PHP_EOL . "Purging inactive data..."); + $osulb->purge($start); + } +} diff --git a/app/Controllers/OsuController.php b/app/Controllers/OsuController.php new file mode 100644 index 0000000..945a6aa --- /dev/null +++ b/app/Controllers/OsuController.php @@ -0,0 +1,45 @@ + + */ +class OsuController extends Controller +{ + private const MODES = [ + 'osu!standard', + 'osu!taiko', + 'osu!catch', + 'osu!mania', + ]; + + public function leaderboard(): string + { + return view('osu/leaderboard', ['gamemodes' => static::MODES]); + } + + public function leaderboardData(): string + { + $mode = $_GET['mode'] ?? -1; + + if (!array_key_exists($mode, static::MODES)) { + return $this->json(['error' => 'Invalid game mode.']); + } + + return $this->json(DB::table('osu_leaderboard') + ->where('osu_game_mode', $mode) + ->orderBy('osu_rank') + ->get()); + } +} diff --git a/app/OsuLeaderboard.php b/app/OsuLeaderboard.php new file mode 100644 index 0000000..d28cee2 --- /dev/null +++ b/app/OsuLeaderboard.php @@ -0,0 +1,111 @@ + + */ +class OsuLeaderboard +{ + private const ENDPOINT = "https://osu.ppy.sh/api/"; + + protected $apiKey; + + public function __construct(string $apiKey) + { + $this->apiKey = $apiKey; + } + + public function purge(Carbon $before): void + { + DB::table('osu_leaderboard') + ->where('updated_on', '<', $before) + ->delete(); + } + + public function update($osu, User $user, int $mode = 0): void + { + // skip if the user hasn't been online for a month + if ($user->lastOnline < time() - 30 * 24 * 60 * 60) { + return; + } + + $osu_user = $this->apiGetUser($osu, $mode); + + if (!$osu_user || $osu_user->pp_raw < 1) { + return; + } + + $exists = DB::table('osu_leaderboard') + ->where(is_int($osu) ? 'osu_user_id' : 'osu_username', $osu) + ->where('user_id', $user->id) + ->where('osu_game_mode', $mode) + ->count() > 1; + + $data = [ + 'user_id' => $user->id, + 'username' => $user->username, + + 'osu_user_id' => intval($osu_user->user_id), + 'osu_username' => $osu_user->username, + + 'osu_game_mode' => $mode, + + 'osu_count_300' => intval($osu_user->count300), + 'osu_count_100' => intval($osu_user->count100), + 'osu_count_50' => intval($osu_user->count50), + + 'osu_count_ss' => intval($osu_user->count_rank_ss), + 'osu_count_s' => intval($osu_user->count_rank_s), + 'osu_count_a' => intval($osu_user->count_rank_a), + + 'osu_play_count' => intval($osu_user->playcount), + 'osu_score_ranked' => intval($osu_user->ranked_score), + 'osu_score_total' => intval($osu_user->total_score), + 'osu_rank' => intval($osu_user->pp_rank), + + 'osu_country' => $osu_user->country, + 'osu_rank_country' => intval($osu_user->pp_country_rank), + + 'osu_performance' => floatval($osu_user->pp_raw), + 'osu_accuracy' => floatval($osu_user->accuracy), + 'osu_level' => floatval($osu_user->level), + + 'updated_on' => Carbon::now(), + ]; + + if ($exists) { + DB::table('osu_leaderboard') + ->where('osu_user_id', $data->osu_user_id) + ->where('user_id', $data->user_id) + ->where('osu_game_mode', $data->mode) + ->update($data); + } else { + DB::table('osu_leaderboard') + ->insert($data); + } + } + + private function apiGetUser($user, int $mode = 0): ?stdClass + { + $result = json_decode( + Net::request(static::ENDPOINT . "get_user?k={$this->apiKey}&u={$user}&m={$mode}") + ); + + if ($result === false || count($result) < 1) { + return null; + } + + return $result[0]; + } +} diff --git a/database/2016_12_11_160011_osu_leaderboard_tables.php b/database/2016_12_11_160011_osu_leaderboard_tables.php new file mode 100644 index 0000000..3188d6a --- /dev/null +++ b/database/2016_12_11_160011_osu_leaderboard_tables.php @@ -0,0 +1,59 @@ +create('osu_leaderboard', function (Blueprint $table) { + $table->integer('user_id'); + $table->string('username'); + + $table->integer('osu_user_id'); + $table->string('osu_username'); + + $table->tinyInteger('osu_game_mode'); + + $table->integer('osu_count_300'); + $table->integer('osu_count_100'); + $table->integer('osu_count_50'); + + $table->integer('osu_count_ss'); + $table->integer('osu_count_s'); + $table->integer('osu_count_a'); + + $table->integer('osu_play_count'); + $table->bigInteger('osu_score_ranked'); + $table->bigInteger('osu_score_total'); + $table->integer('osu_rank'); + + $table->char('osu_country', 2); + $table->integer('osu_rank_country'); + + $table->float('osu_performance'); + $table->float('osu_accuracy'); + $table->float('osu_level'); + + $table->timestamp('updated_on'); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + $schema = DB::getSchemaBuilder(); + + $schema->drop('osu_leaderboard'); + } +} diff --git a/resources/assets/less/yuuno/bem/osu-leaderboard.less b/resources/assets/less/yuuno/bem/osu-leaderboard.less new file mode 100644 index 0000000..2fa3419 --- /dev/null +++ b/resources/assets/less/yuuno/bem/osu-leaderboard.less @@ -0,0 +1,61 @@ +.osu-leaderboard { + &__container { + display: flex; + align-items: center; + } + + &__ranking { + font-size: 40px; + min-width: 100px; + text-align: center; + text-shadow: 1px 1px 1px #222; + + &--rank-1 { + color: #D5CEA6; + } + + &--rank-2 { + color: #C8D7DA; + } + + &--rank-3 { + color: #C19E67; + } + } + + &__avatar { + width: 120px; + height: 120px; + } + + &__info { + padding-left: 5px; + } + + &__username { + font-family: @cute-font; + font-size: 20px; + } + + &__stats { + font-family: @cute-font; + font-size: 16px; + } + + &__ss, + &__s, + &__a { + font-style: italic; + font-weight: bold; + text-shadow: 1px 1px .5px #222; + } + + &__ss, + &__s { + color: #D5CEA6; + } + + &__a { + color: #299A0B; + } +} \ No newline at end of file diff --git a/resources/assets/less/yuuno/main.less b/resources/assets/less/yuuno/main.less index 0fe5196..833c8ae 100644 --- a/resources/assets/less/yuuno/main.less +++ b/resources/assets/less/yuuno/main.less @@ -25,6 +25,7 @@ @import "bem/link"; @import "bem/members"; @import "bem/news"; +@import "bem/osu-leaderboard"; @import "bem/post"; @import "bem/profile"; @import "bem/settings"; diff --git a/resources/views/yuuno/master.twig b/resources/views/yuuno/master.twig index 294e095..f48a9c5 100644 --- a/resources/views/yuuno/master.twig +++ b/resources/views/yuuno/master.twig @@ -176,20 +176,21 @@ -