jramseygreen / osu_bot_framework-v3

A powerful framework to create and manage bots for osu
MIT License
19 stars 7 forks source link

osu_bot_framework-v3

The most accessible platform for creating, sharing and using osu multiplayer bots.

This software has been designed to be as accessible as possible, whether you are a Python developer, Javascript developer or just a normal user.

alt text

This is a work in progress!

Windows

Unix

Please! If you like this software consider donating to me so that I can continue to develop it. Paypal

Features

Credits

Contributer Osu profile Github profile contribution
qqzzy https://osu.ppy.sh/u/qqzzy https://github.com/jramseygreen/ Project Creator, Coded entire project
meowhal https://osu.ppy.sh/u/meowhal https://github.com/meowhal Smoothed logo, prototype webapp design, bug fixing, inspiration for the project
martin71 https://osu.ppy.sh/u/martin71 https://github.com/MerePebble logo design, initial design,extensive testing, help writing docs

Getting started

How to download

Simply click on the green code button and choose download ZIP. this

Obtaining login username and password

Go to this site to get your irc credentials for logging in to the bot framework. They are different from your regular login details

How to get the framework working

Once you have your username and password, open launch.exe

If all works correctly, it should look like this.: this

Using the webapp

Copy the link the app gives you after successful login. (Visible on the picture above: Webapp server started at http://localhost:8080/ - you might have to use a different port number.) Next open any browser (E.g. Chrome/Internet Explorer...) and browse to it. You should be presented with the webapp interface below. this

If you are greeted with login page, the password is the same you used to log in in the command prompt.

If you want to get right into the action, click on the make room or join channel buttons.

Making a room

For clarification: When you make a room, you create a real lobby in the osu! multiplayer where anyone can join. If you make too many of them bancho will not allow you to make more. Don't forget to close them via Close room button in the channel options, as it takes some time for them to close on their own after an inactivity period.

here

As you can see, there are lots of initial options on what kind of lobby to make.

  1. Title and password

    Those are pretty straightforward. Anything but special characters works.

  2. Game Mode

    Pick your desired game mode for the lobby. E.g.: If you go for the mania game mode - the osu will automatically convert all the beatmaps picked if they are not mania. Also, there is a ton of other features where the framework checks for the game mode.

  3. Team type & Score type

    Nothing fancy, these work the same way they do in normal osu! multiplayer.

  4. Beatmap ID

    Pick the first beatmap you want to display in the lobby. (Beatmap ID can be obtained like this:) this

  5. Logic profile

    You can choose your desired logic profile for the lobby. There are quite a few premade and you can find out more about them in the Logic profiles section. For now just know that logic profiles are what determines how the lobby works e.g. who gets the host, what are the conditions to get the host, what are the conditions to play a beatmap, how does picking a beatmap work and much more!

  6. Room size

    Number of available slots in the room.

  7. Invite players

    Sends an invite link to all the chosen players. Your name is in the list by default but you can easily add or remove other players. (Just don't forget to click the small blue + next to the username bar to actually add the player to the list.)

Note: You don't have to fill anything to create a lobby, you can set everything afterwards in the lobby options. More on that in the Channel options section.

Joining a channel (or existing lobby)

After you type the name of the channel you simply join it. You can join the public channels like #osu etc. but you will have limited options for what you can do there since it is not an actual match lobby. (Works even without # ,e.g. lobby would connect you to the #lobby channel.)

The same goes for an existing game room, just get the match link and paste it in the test box.

this Remember that you can easily rejoin your own lobbies but if you join a lobby you did not create yourself or you are not a referee of the lobby nothing will work in the webapp because bancho won't give the webapp permissions to do anything.

Personal messages

They are connected to the game chat, meaning that you can chat with anyone without even opening the game! this

Miscellaneous

Before we dive deeper into the functions of the lobbies, there are still some features in the webapp remaining outside of the lobbies.

  1. Security

    By default the app runs in the localhost mode, meaning that only you from your device can open the webapp.

    If you run the app outside of just your device, authentication is advised. Authentication is a feature which encrypts the communication with the webapp using AES (Advanced Encryption Standard).

    • How to use authentication

    To get the authentication working you head into the folder where you open the launch.exe, there open the config directory and you will find file called bot_config.conf. You can see some settings as well as your saved credentials in the file. But more importantly you are looking for server_authentication": false, If you change the false to true the webapp will demand a password for you to get inside the next time you restart the app. The password is the same one you use to open the app and if you saved it it's listed in the bot_config.conf.

    this

    Note: You need to restart the app if you make any changes in the files for them to implement!

    • Remember me

    To remember the password the webapp uses a cookie. The cookie lasts for exactly one day as long as you are using the same browser on the same device.

    You can also manually wipe the cookie by pressing the Clear cookies button on the first page after logging in.

  2. Logic profile manager

    this A simple way of downloading and sharing logic profiles.

  3. Global player blacklist

    shut_down

    More info on blacklists in the Blacklist (a player) section.

  4. Shut down

    You can turn the app off with the Shut down button. It shuts down the app completely at the host device. shut_down

    You can also shut down the program by typing exit in the command prompt

Channel options

After you successfully create a lobby like this: lobby

A new channel will pop up on the side. Click on the channel to see its options.lobby

In the pictures the numbers correspond to where you can see/change each setting.

  1. To edit the title, use the gray Edit title button in the right upper corner.
  2. To edit password, use the blue Edit password button with number two assigned to it in the second picture.
  3. There are multiple options ranging from difficulty settings to the type of maps allowed - check the beatmap checker section if you think they don't work.
  4. Beatmap ID does not have a button assigned to it. If you want to use the beatmap of your choice, you can manually type command: !mp map (e.g.)1192807 (0-3) 0-osu!, 1-taiko, 2-catch the beat, 3-mania. Usually you specify gamemode only if you want to convert the beatmap.
  5. Logic profile determines the way the bot behaves, more on them in Logic profiles section.
  6. If you click on the little square with the number of present players you will see a simple window to change the number of free slots.

Advanced options

lobby

  1. Beatmap checker

    Beatmap checker automatically aborts all the matches if the beatmap conditions are not met. Not only does it abort but by default it prevents you from choosing beatmaps which are not fulfilling the conditions you set. (In rare cases it will allow you to choose a beatmap which doesn't meet the requirements. However, it will always abort.)

    If you ever feel like something is not right and the lobby doesn't care about the conditions you set for the maps. Check if the beatmapchecker is on!

  2. Allow Beatmap Conversion

    Make the bot automatically convert beatmaps for the selected game mode.

    For instance: In the webapp I select game mode mania. Now all the picked beatmaps are going to be automatically converted to mania. - Works only for game modes where osu! supports conversion.

  3. Allow unsubmitted beatmaps

    Not only does it allow you to pick the unsubmitted beatmaps it also allows unsubmitted beatmaps to be picked by the !randmap command. Works basically the same way the Map Status options work located next to the Mods options.

  4. Maintain room tile & Maintain room password & Maintain room size

    These three options save the settings and after every finished match they revert everything back to what was set with the last !mp comand.

  5. Start match automatically when all players are ready

    Very self explanatory. If all the players pressed ready, match starts.

  6. Queue match to start when beatmap is selected (in seconds)

    Match starts automatically after given time period.

    Note: -1 = infinity; Any positive whole number will work.

  7. Welcome message

    It is sent to whoever joins the lobby no matter how many times they join.

  8. Download beatmaps automatically from Chimu.moe

    You don't need to click on the download button in osu lobby. You don't even need to have osu! running. All selected beatmaps get downloaded with the snap of fingers. By default they are saved to the freamwork files location.(They literally get saved next to the launch.exe)

  9. Download with video (if available)

    The same as regular downloading with or without video.

  10. Open beatmaps automatically after downloading

    After you download the beatmap it is opened no matter the file location. Since osu! automatically deletes extracted beatmaps, they get deleted wherever you chose the download location and they are saved as folders in your osu! songs directory, where all your beatmaps are.

  11. Redownload owned beatmaps

    If you give the app your osu folder location. It will check for the beatmap set ID. If it confirms you have it. The download is not going to proceed.

  12. Blacklists and Whitelists (for beatmaps)

    • Blacklist bans beatmap from being picked or found by the !randmap command.

    • Whitelist orders the framework to only allow beatmap picks included in the Whitelist or limits beatmaps found with the !randmap command.

User options

lobby By clicking on any player's name in the list of the players you will see a menu with simple and working options. (You can view someone's profile by clicking on his profile icon or his name as well.)

Blacklist (a player)

lobby

You can ban players from entering your lobby either lobby-wide or app-wide. In other words, the players you ban will not be able to join the concrete lobby or any lobby you created within the current session. Also, banning people who are present in the lobby will kick them automatically immediately.

lobby

To access the (app-wide) global blacklist you just head to the home page and press the Global player blacklist button.

Config page

On the top of the page right below the title of the room you can click the view configuration page link or write !config in the chat to get the link and a new configuration page will open with all the current settings. lobby The link is adaptable, meaning that every time you change any setting, it will get updated and produce a new config page with the current settings.

Broadcasts

The Broadcasts button broadcasts infinitely the message you set until the bot leaves the lobby. Just set the period for the message and it will get resent every cycle. (Great caution recommended when it comes to the big channels like #osu and #lobby. If the moderators see your message pop up very often, you are likely to get chat banned for spam very quickly.)

lobby

Logic Profiles

logic_profiles

For those who want to confirm the functions of default Logic profiles.:

  • Autohost - The bot keeps the host and picks random maps within the set conditions.
  • AutoHostRotate - Passes the host to the next player in the queue.
  • Autosong - Keeps the host and randomly picks beatmaps matching the conditions after you finish a map and gives the next start countdown.
  • HighRollers - Who !rolls the highest number up to 100 wins the host.
  • KingOfTheHill - Who wins, gets the host.
  • LinerHostRotate - Host is passed always down to next player.
  • Manager - Profile with all the commands for someone who wants to test things or have an interactive lobby.
  • Template - Simple template useful to start with when creating your own logic profiles.
  • TemplateJs - Same thing as the Tample but made with Java Script

Config file (bot_config.conf)

lobby

This is an important file you might need to interact with at some point. It is located in the config folder. Here is a basic rundown.:

Programatically managing osu

The startup file

You can control what happens upon booting the framework with the startup.py file located in the config directory.

# any code here will run on starting the framework
def startup(bot):
    bot.start()
    # Add below line
    # ---------------------------------------------

As we can see, this is where the bot instance actually starts and connects to the osu irc server, as well as starting up the webapp server.

Any code added to this method will be run, meaning you can invoke any method belonging to the Bot class. For example, you could programaticaly create a game room, implement a logic profile and change the allowed difficulty range:

channel = bot.make_room(title="My room", logic_profile="Manager")
channel.set_diff_range((4, 5))

Messages

Messages are dictionaries and have three components;


Dealing with channels and game rooms

You can join and part channels and game rooms in much the same way with a couple of differences.

Managing logic profiles

You can implement logic profiles and even swap out logic on the fly programatically. Logic profiles can be written in either Javascript or Python.

Bot methods

Channel methods


Swapping out logic / changing event methods

Individual event methods can be changed at any time. Simply invoke the on_event_method() of your choice with a new method or None to disable it.

Example

# Replacement event method
def new_on_join_method(username):
    channel.send_message("on_join was replaced")

# make a game room  
channel = bot.make_room()

# implement a logic profile
channel.implement_logic_profile("AutoHostRotate")

# swap out the on_join logic
channel.on_join(new_on_join_method)

Sharing logic profiles

Logic profiles can be easily shared and downloaded. This is achieved through the use of paste2.org as a secure intermediary.

Managing room attributes

You can analyse, import and clone all channel attributes including broadcasts and toggles.

Creating Logic Profiles

Logic profiles are designed to be self contained bots which can run logic independently in joined channels. Logic profiles are contained in the logic_profiles directory and are automatically loaded when the framework is launched.

Event method architecture

The basis of logic profiles are overridable event methods. These methods are designed to run on individual threads and are only executed after any room attributes have been updated.

Example - Template.py

A logic profile will be named according to the file name and class name assigned to it (they must match). In the initialiser, the current bot object and channel object will be passed as parameters.

# logic profile named Template
class Template:
    # a logic profile must always take a bot object and a channel object as parameters
    def __init__(self, bot, channel):
        self.bot = bot
        self.channel = channel

Example - TemplateJs.js A Javascript logic profile is named after its file name. The constructor method is mandatory and has the bot and channel objects passed as parameters.

You can interact with everything in the same way as Python, only with Javascript syntax!

LogicProfile = {
  constructor: function (bot, channel) {
    LogicProfile.bot = bot;
    LogicProfile.channel = channel;
  },
}

The available overridable event methods are listed below. Some methods have optional parameters which you can include if needed. By including any of the methods in your logic profile, the code contained inside will be run when that event has occured.

Python


# runs when a personal message is received
# message is a dictionary containing the username, channel and message
# channel parameter in message dictionary is the bot username
def on_personal_message(self, message):
    pass

# runs when a message is received in the channel
# message is a dictionary containing the username, channel and message
def on_message(self, message):
    pass

# runs when a user joins the channel
# slot is an integer of the slot number joined
# username is a string of the user's username who joined
def on_join(self, username, slot):
    pass

# runs when a user leaves the channel
# slot is an integer of the slot number joined
# username is a string of the user's username who joined
def on_part(self, username, slot):
    pass

# runs when the match is started
def on_match_start(self):
    pass

# runs when the match finishes normally
def on_match_finish(self):
    pass

# runs when a match is aborted with either a command or the method channel.abort_match()
def on_match_abort(self):
    pass

# runs when the host is changed
# old_host is the username of the user who had host before the change
# new_host is the username of the user who has just received host
def on_host_change(self, old_host, new_host):
    pass

# runs when a user changes team
# username is the username of the user who changed team
# team is the colour of the team joined
def on_team_change(self, username, team):
    pass

# runs when a user is added to a team
# username is the username of the user who changed team
# team is the colour of the team joined
def on_team_addition(self, username, team):
    pass

# runs when a user changes slot
# slot is an integer of the slot number joined
# username is a string of the user's username who joined
def on_slot_change(self, username, slot):
    pass

# runs when all players in the room have readied up
def on_all_players_ready(self):
    pass

# runs when the beatmap is changed
# old beatmap is the previous beatmap before the change
# new_beatmap is the beatmap which hasw just been changed to
def on_beatmap_change(self, old_beatmap, new_beatmap):
    pass

# runs when host enters map select
def on_changing_beatmap(self):
    pass

# runs when a game room is closed
def on_room_close(self):
    pass

# runs when the host is cleared
# old_host is the host prior to clearing the room
def on_clear_host(self, old_host):
    pass

# runs when an enforced attribute is violated
# error contains:
# type - the type of on_rule_violation
# message - a message to explain what is going on
def on_rule_violation(self, error):
    pass

Javascript

// runs when a personal message is received
// message is a dictionary containing the username, channel and message
// channel parameter in message dictionary is the bot username
on_personal_message: function (message) {
    return;
},

// runs when a message is received in the channel
// message is a dictionary containing the username, channel and message
on_message: function (message) {
    return;
},
// runs when a user joins the channel
// slot is an integer of the slot number joined
// username is a string of the user's username who joined
on_join: function (username, slot) {
    return;
},

// runs when a user leaves the channel
// slot is an integer of the slot number joined
// username is a string of the user's username who joined
on_part: function (username, slot) {
    return;
},

// runs when the match is started
on_match_start: function () {
    return;
},

// runs when the match finishes normally
on_match_finish: function () {
    return;
},

// runs when a match is aborted with either a command or the method channel.abort_match()
on_match_abort: function () {
    return;
},

// runs when the host is changed
// old_host is the username of the user who had host before the change
// new_host is the username of the user who has just received host
on_host_change: function (old_host, new_host) {
    return;
},

// runs when a user changes team
// username is the username of the user who changed team
// team is the colour of the team joined
on_team_change: function (username, team) {
    return;
},

// runs when a user is added to a team
// username is the username of the user who changed team
// team is the colour of the team joined
on_team_addition: function (username, team) {
    return;
},

// runs when a user changes slot
// slot is an integer of the slot number joined
// username is a string of the user's username who joined
on_slot_change: function (username, slot) {
    return;
},

// runs when all players in the room have readied up
on_all_players_ready: function () {
    return;
},

// runs when the beatmap is changed
// old beatmap is the previous beatmap before the change
// new_beatmap is the beatmap which hasw just been changed to
on_beatmap_change: function (old_beatmap, new_beatmap) {
    return;
},

// runs when host enters map select
on_changing_beatmap: function () {
    return;
},

// runs when a game room is closed
on_room_close: function () {
    return;
},

// runs when the host is cleared
// old_host is the host prior to clearing the room
on_clear_host: function (old_host) {
    return;
},

// runs when an enforced attribute is violated
// error contains:
// type - the type of on_rule_violation
// message - a message to explain what is going on
on_rule_violation: function (error) {
    return;
}

Commands

You can easily create commands inside of the initialiser / constructor function of your logic profile.

Use the set_command() method to add a command to the current channel.

Example

Using a custom method for a command means that you can receive a message as a parameter. Messages have three components;


Certain pre-made methods have been included for common use case commands, accessible through a channel object.

Example

# command selecting a random map
channel.set_command("!randmap", channel.common_commands.randmap, "When host or referee, searches for a random beatmap matching the room's limits and ranges")

Available pre defined command methods

Method Description
channel.common_commands.config_link Returns a link to the game room configuration page
channel.common_commands.randmap When host or referee, searches for a random beatmap matching the room's limits and ranges
channel.common_commands.altlink Returns an alternate link for the current beatmap from Chimu.moe
channel.common_commands.ar_range Sets the ar range for the room
channel.common_commands.od_range Sets the od range for the room
channel.common_commands.hp_range Sets the hp range for the room
channel.common_commands.cs_range Sets the cs range for the room
channel.common_commands.bpm_range Sets the bpm range for the room
channel.common_commands.diff_range Sets the difficulty range for the room
channel.common_commands.length_range Sets the length range for the room in seconds
channel.common_commands.map_status Sets the allowed map statuses for the room
channel.common_commands.mods Sets the allowed mods for the room
channel.common_commands.scoring_type Sets the allowed scoring mode for the room
channel.common_commands.team_type Sets the allowed team mode for the room
channel.common_commands.game_mode Sets the allowed game mode for the room
channel.common_commands.welcome_message Sets the welcome message for the room
channel.common_commands.add_broadcast Starts a broadcast in the channel
channel.common_commands.del_broadcast Stops a broadcast in the channel given it's ID
channel.common_commands.add_beatmap_blacklist adds a beatmap ID to the blacklist
channel.common_commands.del_beatmap_blacklist Removes a beatmap ID from the blacklist
channel.common_commands.add_beatmap_whitelist adds a beatmap ID to the whitelist
channel.common_commands.del_beatmap_whitelist Removes a beatmap ID from the whitelist
channel.common_commands.add_beatmap_creator_blacklist Adds a beatmap creator to the blacklist
channel.common_commands.add_beatmap_creator_whitelist Adds a beatmap creator to the whitelist
channel.common_commands.del_beatmap_creator_whitelist Removes a beatmap creator from the whitelist
channel.common_commands.del_beatmap_creator_blacklist Removes a beatmap creator from the blacklist
channel.common_commands.add_artist_whitelist Adds an artist to the whitelist
channel.common_commands.add_artist_blacklist Adds an artist to the blacklist
channel.common_commands.del_artist_whitelist Removes an artist from the whitelist
channel.common_commands.del_artist_blacklist Removes an artist from the blacklist
channel.common_commands.implement_logic_profile Implements a logic profile
channel.common_commands.get_logic_profiles Shows available logic profiles
channel.common_commands.enable_beatmap_checker Enables beatmap checker
channel.common_commands.disable_beatmap_checker Disables beatmap checker
channel.common_commands.start_timer When host or referee, starts the game with optional countdown timer
channel.common_commands.abort_start_timer When host or referee, aborts start timer
channel.common_commands.enable_start_on_players_ready enables starting the match when all players are ready
channel.common_commands.disable_start_on_players_ready disables starting the match when all players are ready
channel.common_commands.set_auto_start_timer Automatically adds start countdown after map is selected
channel.common_commands.add_player_blacklist adds a player to the blacklist
channel.common_commands.del_player_blacklist Removes a player from the blacklist
channel.common_commands.enable_maintain_title Enables maintaining title
channel.common_commands.disable_maintain_title disables maintaining title
channel.common_commands.enable_maintain_password Enables maintaining password
channel.common_commands.disable_maintain_password disables maintaining password
channel.common_commands.enable_maintain_size Enables maintaining size
channel.common_commands.disable_maintain_size Disables maintaining size
channel.common_commands.enable_auto_download Enables automatic downloading of maps for the bot administrator
channel.common_commands.disable_auto_download Disables automatic downloading of maps for the bot administrator
channel.common_commands.topdiff When host, upgrades the beatmap to the highest difficulty within the room limits and ranges
channel.common_commands.update_beatmap Updates current beatmap
channel.common_commands.allow_convert Allows beatmap conversion
channel.common_commands.disallow_convert Disallows beatmap conversion
channel.common_commands.allow_unsubmitted Allows unsubmitted beatmaps
channel.common_commands.disallow_unsubmitted Disallows unsubmitted beatmaps
channel.common_commands.make_room Creates a game room with title which is managed by the bot
channel.common_commands.join Joins a channel with the bot
channel.common_commands.clone clones the channel into the current channel
channel.common_commands.fight Fight another user! Victories stack up

You can check if a command exists using channel.has_command(command) returning True where command is the string assigned to the given command.

Useful methods

Things to note

User methods

Bot methods

Channel methods

Game room methods

Score methods

Scores are stored as a dictionary with lots of information

Methods

Team methods

Teams are returned as strings blue or red

Managing broadcasts

Broadcasts can be added programatically in much the same way as in the webapp. This applies to any game room or public channel. You must have joined a channel you wish to broadcast to.

The broadcast controller

Broadcasts are managed by an external object called the broadcast controller. The broadcast controller ensures that each broadcast always has a unique ID and is responsible for managing threads created for sending a message to the specified channel on the timer set.

Bot commands

Channel methods

Broadcast Controller methods

These methods shouldn't be used as they are accessible through the bot object.

Limits and ranges

The beatmap checker

The beatmap checker is responsible for checking attributes of selected beatmaps and enforcing limits and ranges set within the framework. It can be toggled with channel.set_beatmap_checker(True)

The beatmap checker will revert any beatmap selections back to the previous selection if any of the beatmap attributes violate attributes set in the framework. A message will also be sent to the channel informing users of the rejection of the beatmap.

If a user somehow starts the match before the map has been reverted, then the game will automatically be aborted without triggering the on_match_abort() event method, instead triggering the on_rule_violation() event method.

Methods available pertaining to the beatmap checker

When setting ranges, you must provide a tuple as such: (min, max) with the min and max being integers or floats.

Other limits

When these limits are set, a match will abort immediately upon start if there is a rule violation.

Whitelists and blacklists

There are 3 whitelists and 3 blacklists available:

These whitelists and blacklists apply to the beatmap checker such that:


Other game room controls

Virtually every aspect of a game room can be controlled programmatically with this framework.

Channel commands

Game room commands

Fetching from osu.ppy.sh

Certain methods exist to fetch data directly from osu.ppy.sh

Bot methods

Game room methods

Getter methods rely on the most recently fetched match history.

Vote manager

Vote managers can be used to hold votes with one or more options. To invoke a vote manager, the channel.new_vote_manager(method) method returns a vote manager object for use in a channel. In order to use the vote manager, you must also provide a method to be executed when the threshold for voting has been reached.

Example

Casting ballots and holding votes

When you want to hold a vote, you can choose to enforce votes from a specific selection or you can simply allow anything to be added to the results. To hold a vote, use the vote_manager.hold_vote(choices, threshold) method, with optional parameters.

vote_manager methods

Example logic profile

Chimu.moe wrapper

This is a wrapper for the Chimu.moe api and allows:

Methods

Paste2.org functionality

With this framework you can easily upload information to paste2.org as a secure way to transfer information to other users which you may not want to send over the irc.

This functionality is built into the framework with the game room configuration link accessible through the webapp and as a default command !config. This results in the ability to also download paste2.org entries as lists containing the lines of the paste as strings.

Bot methods

Game room methods

These are more to do with the extended functionality of the game room

Extended game room controls

These methods extend the functionality of the lobby in some way (and I didn't know where else to put them...)

Bot methods

Game room methods

Changing configuration file settings

All the options which exist in config/bot_config.conf can be edited while the framework is running. Note that these changes only override the configuration file for the current session.

Misc.

These methods can be used to change certain aspects of the functionality of the framework

Bot methods

Auto Host Rotate Example Walkthrough

In this section I will take you through a simple design for an auto host rotate logic profile.

What we aim to do is create a queue of players who join the game room and cycle through the queue after a game finishes.

  1. Set up the logic profile

    • Python

      class AutoHostRotateExample:
      def __init__(self, bot, channel):
          self.bot = bot
          self.channel = channel
      
          self.queue = []
    • Javascript

      LogicProfile = {
      constructor: function (bot, channel) {
          LogicProfile.bot = bot;
          LogicProfile.channel = channel;
      
          LogicProfile.queue = [];
      },
      };
  2. Override the on_join() and on_part event methods to update the queue

    • Python

      
      def __init__(self, bot, channel):
      self.bot = bot
      self.channel = channel
      
      self.queue = []

    def on_join(self, username): self.queue.append(username)

    def on_part(self, username): self.queue.remove(username)

    - Javascript
    ```javascript
    LogicProfile = {
        constructor: function (bot, channel) {
            LogicProfile.bot = bot;
            LogicProfile.channel = channel;
    
            LogicProfile.queue = [];
        },
        on_join: function (username) {
            LogicProfile.queue.push(username);  
        },
    
        on_part: function (username) {
            LogicProfile.queue.splice(LogicProfile.queue.indexOf(username), 1);
        },
    };
  3. Override the on_match_finish() event method

    • Python

      
      def __init__(self, bot, channel):
      self.bot = bot
      self.channel = channel
      
      self.queue = []

    def on_join(self, username): self.queue.append(username)

    def on_part(self, username): self.queue.remove(username)

    def on_match_finish(self):

    rotate the queue

    self.queue.append(self.queue.pop(0))
    
    # set the new host
    self.channel.set_host(self.queue[0])
    - Javascript
    ```javascript
    LogicProfile = {
        constructor: function (bot, channel) {
            LogicProfile.bot = bot;
            LogicProfile.channel = channel;
    
            LogicProfile.queue = [];
        },
        on_join: function (username) {
            LogicProfile.queue.push(username);  
        },
    
        on_part: function (username) {
            LogicProfile.queue.splice(LogicProfile.queue.indexOf(username), 1);
        },
    
        on_match_finish: function () {
            // rotate the queue
            LogicProfile.queue.push(LogicProfile.queue.shift());
    
            // set the new host
            LogicProfile.channel.set_host(LogicProfile.queue[0]);
        },
    };
  4. Make sure that the first player in the room receives the host

    • Python

      
      def __init__(self, bot, channel):
      self.bot = bot
      self.channel = channel
      
      self.queue = []

    def on_join(self, username): self.queue.append(username)

    # check if the game room only has the new user in it
    if self.channel.get_users() == [username]:
        # give them the host
        self.channel.set_host(username)

    def on_part(self, username): self.queue.remove(username)

    def on_match_finish(self):

    rotate the queue

    self.queue.append(self.queue.pop(0))
    
    # set the new host
    self.channel.set_host(self.queue[0])
    - Javascript
    ```javascript
    LogicProfile = {
        constructor: function (bot, channel) {
            LogicProfile.bot = bot;
            LogicProfile.channel = channel;
    
            LogicProfile.queue = [];
        },
        on_join: function (username) {
            LogicProfile.queue.push(username);
    
            // check if the game room only has 1 user in it
            if (LogicProfile.channel.get_users().length === 1) {
                // give them the host
                LogicProfile.channel.set_host(username);
            }
        },
    
        on_part: function (username) {
            LogicProfile.queue.splice(LogicProfile.queue.indexOf(username), 1);
        },
    
        on_match_finish: function () {
            // rotate the queue
            LogicProfile.queue.push(LogicProfile.queue.shift());
    
            // set the new host
            LogicProfile.channel.set_host(LogicProfile.queue[0]);
        },
    };
  5. If the host leaves pass the host to the next player in the queue

    • Python

      
      def __init__(self, bot, channel):
      self.bot = bot
      self.channel = channel
      
      self.queue = []

    def on_join(self, username): self.queue.append(username)

    # check if the game room only has the new user in it
    if self.channel.get_users() == [username]:
        # give them the host
        self.channel.set_host(username)

    def on_part(self, username): self.queue.remove(username)

    # check if the leaving user is host and there are still users in the game room and is not in progress
    if self.channel.is_host(username) and self.channel.has_users() and not self.channel.in_progress():
        # change the host to the top of the queue
        self.set_host(queue[0])

    def on_match_finish(self):

    rotate the queue

    self.queue.append(self.queue.pop(0))
    
    # set the new host
    self.channel.set_host(self.queue[0])
    - Javascript
    ```javascript
    LogicProfile = {
        constructor: function (bot, channel) {
            LogicProfile.bot = bot;
            LogicProfile.channel = channel;
    
            LogicProfile.queue = [];
        },
        on_join: function (username) {
            LogicProfile.queue.push(username);
    
            // check if the game room only has 1 user in it
            if (LogicProfile.channel.get_users().length === 1) {
                // give them the host
                LogicProfile.channel.set_host(username);
            }
        },
    
        on_part: function (username) {
            LogicProfile.queue.splice(LogicProfile.queue.indexOf(username), 1);
    
            // check if the leaving user is host and the game room still has users and is not in progress
            if (LogicProfile.channel.is_host(username) && LogicProfile.channel.has_users() && !LogicProfile.channel.in_progress()) {
                // change the host to the top of the queue
                LogicProfile.channel.set_host(LogicProfile.queue[0]);
            }
        },
    
        on_match_finish: function () {
            // rotate the queue
            LogicProfile.queue.push(LogicProfile.queue.shift());
    
            // set the new host
            LogicProfile.channel.set_host(LogicProfile.queue[0]);
        },
    };
  6. Create a command to check the queue order

    • Python

      
      def __init__(self, bot, channel):
      self.bot = bot
      self.channel = channel
      
      self.queue = []
      
      # add a command with a method
      channel.set_command("!queue", self.show_queue, "Shows the queue of players")

    method to execute when !queue command is received

    def show_queue(self):

    send the queue of players to the channel

    self.channel.send_message("The current queue is: " + ", ".join(self.queue))

    def on_join(self, username): self.queue.append(username)

    # check if the game room only has the new user in it
    if self.channel.get_users() == [username]:
        # give them the host
        self.channel.set_host(username)

    def on_part(self, username): self.queue.remove(username)

    # check if the leaving user is host and there are still users in the game room and game is not in progress
    if self.channel.is_host(username) and self.channel.has_users() and not self.channel.in_progress():
        # change the host to the top of the queue
        self.set_host(queue[0])

    def on_match_finish(self):

    rotate the queue

    self.queue.append(self.queue.pop(0))
    
    # set the new host
    self.channel.set_host(queue[0])
    - Javascript
    ```javascript
    LogicProfile = {
        constructor: function (bot, channel) {
            LogicProfile.bot = bot;
            LogicProfile.channel = channel;
            // add a command with a method
            channel.set_command("!queue", LogicProfile.show_queue, "Shows the queue of players")
    
            LogicProfile.queue = [];
        },
        show_queue: function () {
            LogicProfile.channel.send_message("The current queue is: " + LogicProfile.queue.join(", "));
        },
        on_join: function (username) {
            LogicProfile.queue.push(username);
    
            // check if the game room only has 1 user in it
            if (LogicProfile.channel.get_users().length === 1) {
                // give them the host
                LogicProfile.channel.set_host(username);
            }
        },
    
        on_part: function (username) {
            LogicProfile.queue.splice(LogicProfile.queue.indexOf(username), 1);
    
            // check if the leaving user is host and the game room still has users and the match is not in progress
            if (LogicProfile.channel.is_host(username) && LogicProfile.channel.has_users() && !LogicProfile.channel.in_progress()) {
                // change the host to the top of the queue
                LogicProfile.channel.set_host(queue[0]);
            }
        },
    
        on_match_finish: function () {
            // rotate the queue
            LogicProfile.queue.push(LogicProfile.queue.shift());
    
            // set the new host
            LogicProfile.channel.set_host(LogicProfile.queue[0]);
        },
    };