123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- import EventEmitter from 'events';
- import bancho from './bancho.js';
- import commands from './commands.js';
- import db from './database.js';
- import Config from './util/config.js';
- import {capture_sentry_exception} from './util/helpers.js';
- import {get_or_create_user} from './user.js';
- class BanchoLobby extends EventEmitter {
- constructor(channel) {
- super();
- this.id = parseInt(channel.substring(4), 10);
- this.channel = channel;
- this.invite_id = null;
- // A player is a full_player from the database (safe to assume they have a rank, etc)
- // It has an additional irc_username field, that can differ from their actual username.
- this.player_cache = [];
- this.players = [];
- this.scores = [];
- this.voteaborts = [];
- this.joined = false;
- this.playing = false;
- const match = db.prepare(`SELECT * FROM match WHERE match_id = ?`).get(this.id);
- if (!match) {
- db.prepare(`INSERT INTO match (match_id, start_time) VALUES (?, ?)`).run(this.id, Date.now());
- }
- }
- handle_line(line) {
- const parts = line.split(' ');
- if (line == `:${Config.osu_username}[email protected] PART :${this.channel}`) {
- this.joined = false;
- db.prepare(`UPDATE match SET end_time = ? WHERE match_id = ?`).run(Date.now(), this.id);
- bancho._lobbies = bancho._lobbies.filter((lobby) => lobby.id != this.id);
- bancho.joined_lobbies = bancho.joined_lobbies.filter((lobby) => lobby.id != this.id);
- this.emit('close');
- return;
- }
- if (parts[1] == '332' && parts[3] == this.channel) {
- this.joined = true;
- this.invite_id = parseInt(parts[6].substring(1), 10);
- db.prepare(`UPDATE match SET invite_id = ? WHERE match_id = ?`).run(this.invite_id, this.id);
- bancho.emit('lobbyJoined', {
- channel: this.channel,
- lobby: this,
- });
- return;
- }
- if (parts[1] == 'PRIVMSG' && parts[2] == this.channel) {
- const full_source = parts.shift();
- parts.splice(0, 2);
- let source = null;
- if (full_source.indexOf('!') != -1) {
- source = full_source.substring(1, full_source.indexOf('!'));
- }
- const message = parts.join(' ').substring(1);
- if (source == 'BanchoBot') {
- let m;
- const joined_regex = /(.+) joined in slot \d+\./;
- const left_regex = /(.+) left the game\./;
- const room_name_regex = /Room name: (.+), History: https:\/\/osu\.ppy\.sh\/mp\/(\d+)/;
- const room_name_updated_regex = /Room name updated to "(.+)"/;
- const beatmap_regex = /Beatmap: https:\/\/osu\.ppy\.sh\/b\/(\d+) (.+)/;
- const mode_regex = /Team mode: (.+), Win condition: (.+)/;
- const mods_regex = /Active mods: (.+)/;
- const players_regex = /Players: (\d+)/;
- const score_regex = /(.+) finished playing \(Score: (\d+), (.+)\)\./;
- const slot_regex = /Slot (\d+) +(.+?) +https:\/\/osu\.ppy\.sh\/u\/(\d+) (.+)/;
- const ref_add_regex = /Added (.+) to the match referees/;
- const ref_del_regex = /Removed (.+) from the match referees/;
- const beatmap_change_regex = /Changed beatmap to https:\/\/osu\.ppy\.sh\/b\/(\d+) (.+)/;
- const player_changed_beatmap_regex = /Beatmap changed to: (.+) \(https:\/\/osu.ppy.sh\/b\/(\d+)\)/;
- const new_host_regex = /(.+) became the host./;
- if (message == 'Cleared match host') {
- this.host = null;
- this.data.hostless = true;
- this.emit('host');
- } else if (message == 'The match has started!') {
- this.scores = [];
- this.voteaborts = [];
- this.playing = true;
- this.emit('matchStarted');
- } else if (message == 'The match has finished!') {
- this.playing = false;
- // Used for !skip command
- for (const player of this.players) {
- if (!player.matches_finished) {
- player.matches_finished = 0;
- }
- player.matches_finished++;
- }
- this.emit('matchFinished');
- } else if (message == 'Aborted the match') {
- this.playing = false;
- this.emit('matchAborted');
- } else if (message == 'All players are ready') {
- this.emit('allPlayersReady');
- } else if (message == 'Changed the match password') {
- this.passworded = true;
- this.emit('password');
- } else if (message == 'Removed the match password') {
- this.passworded = false;
- this.emit('password');
- } else if (m = score_regex.exec(message)) {
- const score = {
- player: this.players.find((p) => p.irc_username == m[1]),
- score: parseInt(m[2], 10),
- };
- this.scores.push(score);
- this.emit('score', score);
- } else if (m = room_name_regex.exec(message)) {
- this.name = m[1];
- this.id = parseInt(m[2], 10);
- } else if (m = room_name_updated_regex.exec(message)) {
- this.name = m[1];
- db.prepare(`UPDATE match SET name = ? WHERE match_id = ?`).run(this.name, this.id);
- } else if (m = beatmap_regex.exec(message)) {
- this.map_data = null;
- this.beatmap_id = parseInt(m[1], 10);
- this.beatmap_name = m[2];
- } else if (m = beatmap_change_regex.exec(message)) {
- this.map_data = null;
- this.beatmap_id = parseInt(m[1], 10);
- this.beatmap_name = m[2];
- this.emit('refereeChangedBeatmap');
- } else if (m = player_changed_beatmap_regex.exec(message)) {
- this.map_data = null;
- this.beatmap_id = parseInt(m[2], 10);
- this.beatmap_name = m[1];
- this.emit('playerChangedBeatmap');
- } else if (m = mode_regex.exec(message)) {
- this.team_mode = m[1];
- this.win_condition = m[2];
- } else if (m = mods_regex.exec(message)) {
- this.active_mods = m[1];
- } else if (m = players_regex.exec(message)) {
- this.player_cache = this.players;
- this.players = [];
- this.players_to_parse = parseInt(m[1], 10);
- } else if (m = ref_add_regex.exec(message)) {
- this.emit('refereeAdded', m[1]);
- } else if (m = ref_del_regex.exec(message)) {
- if (m[1] == Config.osu_username) {
- this.leave();
- }
- this.emit('refereeRemoved', m[1]);
- } else if (m = slot_regex.exec(message)) {
- // !mp settings - single user result
- const update_player = (player) => {
- player.join_time = player.join_time || Date.now();
- player.irc_username = m[4].substring(0, 15).trimEnd();
- player.state = m[2];
- player.is_host = m[4].substring(16).indexOf('Host') != -1;
- if (player.is_host) {
- this.host = player;
- }
- this.players.push(player);
- this.players_to_parse--;
- if (this.players_to_parse == 0) {
- this.emit('settings');
- }
- };
- const cached_player = this.player_cache.find((p) => p.user_id == m[3]);
- if (cached_player) {
- update_player(cached_player);
- } else {
- get_or_create_user(parseInt(m[3], 10)).then(update_player);
- }
- } else if (m = new_host_regex.exec(message)) {
- // host changed
- for (const player of this.players) {
- player.is_host = player.irc_username == m[1];
- this.host = player;
- }
- this.data.hostless = false;
- this.emit('host');
- } else if (m = joined_regex.exec(message)) {
- // player joined
- const player = {
- join_time: Date.now(),
- total_scores: 0,
- irc_username: m[1],
- };
- this.players.push(player);
- this.emit('playerJoined', player);
- } else if (m = left_regex.exec(message)) {
- // player left
- const irc_username = m[1];
- const leaving_player = this.players.find((p) => p.irc_username == irc_username);
- if (leaving_player != null) {
- this.players = this.players.filter((player) => player.irc_username != irc_username);
- this.emit('playerLeft', leaving_player);
- }
- }
- return;
- }
- this.emit('message', {
- from: source,
- message: message,
- });
- for (const cmd of commands) {
- const match = cmd.regex.exec(message);
- if (!match) continue;
- if (!cmd.modes.includes('lobby')) break;
- if (cmd.creator_only) {
- const user_is_host = this.host && this.host.irc_username == source;
- let user_is_creator = false;
- for (const player of this.players) {
- if (player.irc_username == source) {
- // NOTE: We also check for username, since user id is not populated on join
- // This allows lobby creators to skip maps before they start playing.
- user_is_creator = player.user_id == this.data.creator_id || player.irc_username == this.data.creator_name || player.username == this.data.creator_name;
- break;
- }
- }
- if (!user_is_host && !user_is_creator) {
- this.send(`${source}: You need to be the host or the lobby creator to use this command.`);
- break;
- }
- }
- cmd.handler({from: source, message: message}, match, this);
- break;
- }
- return;
- }
- }
- leave() {
- if (!this.joined) {
- return;
- }
- bancho._send('PART ' + this.channel);
- }
- async send(message) {
- if (!this.joined) {
- return;
- }
- return await bancho.privmsg(this.channel, message);
- }
- // Override EventEmitter to redirect errors to Sentry
- on(event_name, callback) {
- return super.on(event_name, (...args) => {
- try {
- Promise.resolve(callback(...args));
- } catch (err) {
- Sentry.setContext('lobby', {
- id: this.id,
- median_pp: this.median_overall,
- nb_players: this.players.length,
- data: this.data,
- task: event_name,
- });
- capture_sentry_exception(err);
- }
- });
- }
- }
- export {BanchoLobby};
|