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.
apt install python3
or similar for your distroapt install python3-pip
(if not bundled with the python 3 installation) python3 -m pip install -r requirements.txt
Features
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 |
Simply click on the green code
button and choose download ZIP
.
Go to this site to get your irc credentials for logging in to the bot framework. They are different from your regular login details
Once you have your username and password, open launch.exe
If all works correctly, it should look like this.:
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.
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.
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.
As you can see, there are lots of initial options on what kind of lobby to make.
Those are pretty straightforward. Anything but special characters works.
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.
Nothing fancy, these work the same way they do in normal osu! multiplayer.
Pick the first beatmap you want to display in the lobby. (Beatmap ID can be obtained like this:)
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!
Number of available slots in the room.
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.
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.
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.
They are connected to the game chat, meaning that you can chat with anyone without even opening the game!
Before we dive deeper into the functions of the lobbies, there are still some features in the webapp remaining outside of the lobbies.
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).
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
.
Note: You need to restart the app if you make any changes in the files for them to implement!
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.
A simple way of downloading and sharing logic profiles.
More info on blacklists in the Blacklist (a player) section.
You can turn the app off with the Shut down
button. It shuts down the app completely at the host device.
You can also shut down the program by typing
exit
in the command prompt
After you successfully create a lobby like this:
A new channel will pop up on the side. Click on the channel to see its options.
In the pictures the numbers correspond to where you can see/change each setting.
Edit title
button in the right upper corner.Edit password
button with number two assigned to it in the second picture.!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.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!
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.
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.
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.
Start match automatically when all players are ready
Very self explanatory. If all the players pressed ready, match starts.
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.
Welcome message
It is sent to whoever joins the lobby no matter how many times they join.
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)
Download with video (if available)
The same as regular downloading with or without video.
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.
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.
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.
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.)
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.
To access the (app-wide) global blacklist you just head to the home page and press the Global player blacklist
button.
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.
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.
The page is divided in 3 big sections but basically just 2. The upper section is for the current settings and stats and the lower is for the available commands. (The number of available commands changes drastically depending on what logic profile you use.)
While the Config page is a useful overview of all the available options it is also great for copying the settings. You can create one lobby, set everything up and now if you want to send the same settings to your friend or save them for the next time you create a new lobby. All you need to do is to use the Import configuration
button and you just copy the link of the config site to transfer the settings.
The Clone from
button works the same way but it's quicker and works only locally with the existing lobbies in the list.
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.)
Logic profiles are what gives the bot its structure. Each contains unique commands and works differently to suit the needs of the players.
There are several premade logic profiles which show the possibilities of the framework. However, creating new logic profiles is highly encouraged as the default ones are meant to be more exemplary rather then widely used.
Basically whatever you can think of is possible. There many possibilities from picking automatically maps on some set conditions to change the host based off a simple !roll
. I recommend trying the premade out for yourself to see what they are capable of. More on how to create your logic profile later on. (The framework supports both java script and python!)
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
!roll
s 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
bot_config.conf
)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.:
Lines 2 and 3 store the login information you used during your first login, that is if you used the option to save them of course. You can manually rewrite them here if you want to change them or you can just delete them to get prompted again the next time you open the app.
Lines 4 and 5 are to stay like that until peppy decides to change the bancho bot site or port.
Server authentication on line 6 simply enables/disables password when opening the webapp. (Change with true/false)
On line 7 you change the IP of the app, there are 4 main options:
Using localhost
you can open the webapp only from the device where you run the app.
If you use your IP address
you can use the network on your LAN. To find your IP address (on Windows) simply open cmd
and write in ipconfig
then look for either IPv4 or IPv6 address depending on what you're using. Then just write it in the browser on your other device connected to your LAN network e.g. wifi and you should see the webapp interface.
Use 0.0.0.0
to make the webapp accessible on all available network interfaces in your enclosed LAN or in the Internet. (Recommended for people who want to use a server to run the app.)
Leaving the string blank "" will disable the webapp completely!
Line 8 allows you to choose any available port for the webapp.
Line 9 is a for all those who would need to change the port which the frontend uses to communicate with the backend.
On line 10 you can change the number of messages visible in the chat box both in the frontend and the backend.
Lines 11 and 12 dictate the rate at which requests are send to the Bancho bot. The default settings of cap:"2" & period:"1" have been tested and are thought to be the best possible for a good reaction time while you don't get silenced by the bot for sending too many requests.
Line 13 is simply a question of whether you want to make log files in ./config/logs
folder.
Verbose is the option to make all the messages which would normally get sent to the log file be sent straight in the cmd interface of the app.
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 are dictionaries and have three components;
-> {"channel": "#osu", "username": "qqzzy": "content": "Hi guys"}
When sending or receiving a message, that message gets added to the message log.
bot.send_personal_message(username, message)
bot.get_personal_message_log()
channel.send_message(message)
channel.get_message_log()
You can join and part channels and game rooms in much the same way with a couple of differences.
bot.get_channels()
bot.get_channel(channel)
bot.join(channel)
#osu
, #mp_123456
bot.part(channel)
bot.make_room(title, password, size, beatmapID, mods, game_mode, team_type, scoring_type, allow_convert, logic_profile, invite_list)
["any"]
"any"
"any"
"any"
True
channel.close()
on_close()
event methodYou can implement logic profiles and even swap out logic on the fly programatically. Logic profiles can be written in either Javascript or Python.
bot.refresh_logic_profiles()
bot.get_logic_profiles()
# get a logic profile from the logic profiles dictionary
Logic_Profile = bot.get_logic_profiles()["Manager"]
manager_instance = Logic_Profile(bot, channel)
manager_instance.on_join()
bot.get_logic_profile(profile)
bot.implement_logic_profile(profile)
on_personal_message()
event methodbot.load_logic_profile(profile)
logic_profiles
folderbot.load_js_logic_profile(profile)
logic_profiles
folderchannel.implement_logic_profile(profile)
on_join()
, on_part()
, on_message()
and on_personal_message()
availablechannel.get_logic_profile()
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)
channel.get_logic()
channel.clear_logic()
channel.clear_logic_profile()
Logic profiles can be easily shared and downloaded. This is achieved through the use of paste2.org as a secure intermediary.
bot.upload_logic_profile(profile)
bot.get_logic_profile_link(profile)
bot.get_logic_profile_links()
bot.download_logic_profile(url)
You can analyse, import and clone all channel attributes including broadcasts and toggles.
bot.clone(channel1, channel2)
channel.get_attrbiutes()
channel.import_attributes(attrbutes)
channel.get_attributes()
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.
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;
}
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
Python
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
channel.set_command("!simple_command", "Text to send as a response", "A description of the command")
channel.set_command("!advanced_command", self.response_method, "A description of the command")
def response_method(self, message):
self.channel.send_message(message["username"] + " sent " + message["content"] + " in " + message["channel"])
Javascript
LogicProfile = {
constructor: function(bot, channel) {
LogicProfile.bot = bot;
LogicProfile.channel = channel;
channel.set_command("!simple_command", "Text to send as a response", "A description of the command")
channel.set_command("!advanced_command", LogicProfile.response_method, "A description of the command")
},
response_method: function (message) {
LogicProfile.channel.send_message(message.username + " sent " + message.content + " in " + message.channel)
}
};
As we can see in the above example, a simple command can be set where some predefined text is provided as a response. Alternatively, a custom method can be employed utilising the event method architecture to execute the method on a thread.
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")
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.
channel.get_command(command)
channel.get_commands()
bot.fetch_user_profile(username)
bot.fetch_user_profile("qqzzy")
->> {"avatar_url":"https:\/\/a.ppy.sh\/10911588?1637935165.gif","country_code":"GB","default_group":"default","id":10911588,"is_active":true,"is_bot":false,"is_deleted":false,"is_online":true,"is_supporter":false,"last_visit":"2021-12-14T13:49:27+00:00","pm_friends_only":false,"profile_colour":null,"username":"qqzzy","cover_url":"https:\/\/osu.ppy.sh\/images\/headers\/profile-covers\/c3.jpg","discord":null,"has_supported":true,"interests":null,"join_date":"2017-09-27T15:49:08+00:00","kudosu":{"total":0,"available":0},"location":"United Kingdom","max_blocks":50,"max_friends":250,"occupation":null,"playmode":"osu","playstyle":null,"post_count":2,"profile_order":["me","recent_activity","top_ranks","medals","historical","beatmaps","kudosu"],"title":null,"title_url":null,"twitter":null,"website":null,"country":{"code":"GB","name":"United Kingdom"},"cover":{"custom_url":null,"url":"https:\/\/osu.ppy.sh\/images\/headers\/profile-covers\/c3.jpg","id":"3"},"is_admin":false,"is_bng":false,"is_full_bn":false,"is_gmt":false,"is_limited_bn":false,"is_moderator":false,"is_nat":false,"is_restricted":false,"is_silenced":false,"account_history":[{"description":"spamming in #osu","id":10349025,"length":1200,"timestamp":"2021-12-05T04:09:53+00:00","type":"silence"},{"description":"spamming in #lobby","id":10309070,"length":600,"timestamp":"2021-11-28T21:02:43+00:00","type":"silence"},{"description":"spamming in #lobby","id":10306375,"length":300,"timestamp":"2021-11-28T13:35:32+00:00","type":"silence"}],"active_tournament_banner":null,"badges":[],"beatmap_playcounts_count":4792,"comments_count":1,"favourite_beatmapset_count":10,"follower_count":141,"graveyard_beatmapset_count":2,"groups":[],"loved_beatmapset_count":0,"mapping_follower_count":0,"monthly_playcounts":[{"start_date":"2017-09-01","count":6},{"start_date":"2017-10-01","count":27},{"start_date":"2018-01-01","count":160},{"start_date":"2018-02-01","count":4},{"start_date":"2018-04-01","count":20},{"start_date":"2018-06-01","count":13},{"start_date":"2018-10-01","count":23},{"start_date":"2018-11-01","count":92},{"start_date":"2018-12-01","count":19},{"start_date":"2019-05-01","count":157},{"start_date":"2019-06-01","count":90},{"start_date":"2019-09-01","count":954},{"start_date":"2019-10-01","count":714},{"start_date":"2019-11-01","count":367},{"start_date":"2019-12-01","count":5},{"start_date":"2020-01-01","count":150},{"start_date":"2020-02-01","count":570},{"start_date":"2020-03-01","count":237},{"start_date":"2020-04-01","count":643},{"start_date":"2020-05-01","count":271},{"start_date":"2020-06-01","count":1155},{"start_date":"2020-07-01","count":1183},{"start_date":"2020-08-01","count":973},{"start_date":"2020-09-01","count":155},{"start_date":"2020-10-01","count":658},{"start_date":"2020-11-01","count":442},{"start_date":"2020-12-01","count":301},{"start_date":"2021-01-01","count":59},{"start_date":"2021-02-01","count":687},{"start_date":"2021-03-01","count":669},{"start_date":"2021-04-01","count":430},{"start_date":"2021-05-01","count":510},{"start_date":"2021-06-01","count":28},{"start_date":"2021-07-01","count":450},{"start_date":"2021-08-01","count":747},{"start_date":"2021-09-01","count":188},{"start_date":"2021-10-01","count":364},{"start_date":"2021-11-01","count":788},{"start_date":"2021-12-01","count":327}],"page":{"html":"
bot.format_username(username)
bot.set_player_blacklist(blacklist)
bot.add_player_blacklist(username)
bot.del_player_blacklist(username)
bot.get_player_blacklist()
bot.get_formatted_player_blacklist()
channel.add_user(username)
channel.del_user(username)
channel.get_users()
channel.get_formatted_users()
channel.has_user(username)
channel.get_creator()
channel.is_creator(username)
channel.add_user(username)
channel.del_user(username)
channel.kick_user(username)
channel.set_slot(slot, data)
channel.del_slot(slot)
channel.get_slot(username)
channel.get_formatted_slot(username)
channel.get_slot_num(username)
channel.get_slot_username(slot_num)
channel.get_formatted_slot_username(slot_num)
channel.get_next_empty_slot_num(offset)
channel.get_next_empty_slot(offset)
channel.get_next_full_slot_num(offset)
channel.get_next_full_slot(offset)
channel.get_slots()
channel.get_formatted_slots
channel.set_host(username)
channel.get_host()
channel.get_formatted_host()
channel.is_host(username)
channel.get_referees()
channel.get_formatted_referees()
channel.add_referee(username)
channel.del_referee(username)
channel.has_referee(username)
channel.set_player_blacklist(blacklist)
channel.add_player_blacklist(username)
channel.del_player_blacklist(username)
channel.get_player_blacklist()
channel.get_formatted_player_blacklist()
Scores are stored as a dictionary with lots of information
-> {"id":null,"username":"qqzzy","user_id":19001310,"accuracy":0.7921279212792128,"mods":[],"score":317840,"max_combo":79,"passed":true,"perfect":0,"statistics":{"count_50":10,"count_100":54,"count_300":195,"count_geki":30,"count_katu":12,"count_miss":12},"rank":null,"created_at":null,"best_id":null,"pp":null,"match":{"slot":0,"team":"none","pass":true}}
The scores come from the Match History page which is scraped and the most recent game's score data is applied to the scores in the slots. This occurs whenever a match concludes.
channel.get_score(username)
channel.get_scores()
channel.clear_scores()
channel.get_red_scores()
channel.get_blue_scores()
channel.get_ordered_scores()
channel.has_score(username)
Teams are returned as strings blue
or red
channel.get_red_team()
channel.get_blue_team()
channel.get_team(username)
channel.clear_teams()
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.
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.get_broadcast_controller()
bot.get_broadcasts()
-> {"id": id, "channel": channel, "message": message, "secs": float(secs)}
bot.get_broadcasts(channel)
#osu
or #mp_123456
bot.get_broadcast(id)
bot.has_broadcast_id(id)
bot.add_broadcast(channel, message, secs)
#osu
or #mp_123456
bot.del_broadcast(id)
channel.add_broadcast(message, secs)
channel.del_broadcast(id)
channel.has_broadcast(id)
channel.get_broadcasts()
-> {"id": id, "channel": channel, "message": message, "secs": float(secs)}
These methods shouldn't be used as they are accessible through the bot object.
BroadcastController.get_broadcast_controller()
BroadcastController.get_broadcasts()
-> {"id": id, "channel": channel, "message": message, "secs": float(secs)}
BroadcastController.get_broadcasts(channel)
#osu
or #mp_123456
BroadcastController.get_broadcast(id)
BroadcastController.has_id(id)
BroadcastController.add_broadcast(channel, message, secs)
#osu
or #mp_123456
BroadcastController.del_broadcast(id)
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.
When setting ranges, you must provide a tuple as such: (min, max)
with the min and max being integers or floats.
channel.set_beatmap_checker(status)
channel.beatmap_checker_on()
channel.set_ar_range(range)
channel.get_ar_range()
channel.set_od_range(range)
channel.get_od_range()
channel.set_hp_range(range)
channel.get_hp_range()
channel.set_cs_range(range)
channel.get_cs_range()
channel.set_bpm_range(range)
channel.get_bpm_range()
channel.set_diff_range(range)
channel.get_diff_range()
channel.set_length_range(range)
channel.get_length_range()
channel.set_allow_convert(status)
channel.is_allow_convert()
channel.set_beatmap_status(statuses)
any
, ranked
, loved
, qualified
, approved
, pending
, graveyard
, wip
,channel.get_beatmap_status()
channel.allow_unsubmitted(status)
channel.is_allow_unsubmitted()
When these limits are set, a match will abort immediately upon start if there is a rule violation.
channel.set_team_type(type)
any
, head-to-head
, team-vs
, tag-team-vs
, tag-coop
channel.get_team_type()
channel.set_game_mode(mode)
any
, osu
, taiko
, mania
, fruits
channel.get_game_mode()
channel.set_scoring_type(mode)
any
, score
, scorev2
, combo
, accuracy
channel.get_scoring_type()
channel.set_mods(mods)
any
, freemod
, DT
, HR
, HD
, EZ
, NC
, HT
, SD
, NF
, FL
, RX
, AP
, SO
channel.get_mods()
returns a list of strings of the currently allowed mods
There are 3 whitelists and 3 blacklists available:
These whitelists and blacklists apply to the beatmap checker such that:
channel.add_beatmap_whitelist(beatmapID)
channel.del_beatmap_whitelist(beatmapID)
channel.get_beatmap_whitelist()
channel.add_beatmap_blacklist(beatmapID)
channel.del_beatmap_blacklist(beatmapID)
channel.get_beatmap_blacklist()
channel.add_artist_whitelist(artist)
channel.del_artist_whitelist(artist)
channel.get_artist_whitelist()
channel.add_artist_blacklist(artist)
channel.del_artist_blacklist(artist)
channel.get_artist_blacklist()
channel.add_beatmap_creator_whitelist(creator)
channel.del_beatmap_creator_whitelist(creator)
channel.get_beatmap_creator_whitelist()
channel.add_beatmap_creator_blacklist(creator)
channel.del_beatmap_creator_blacklist(creator)
channel.get_beatmap_creator_blacklist()
Virtually every aspect of a game room can be controlled programmatically with this framework.
channel.get_channel()
#osu
, #mp_123456
channel.is_game()
channel.start_match(secs)
channel.abort_match()
channel.in_progress()
channel.set_password(password)
channel.get_password()
channel.set_beatmap(beatmap)
channel.change_beatmap(beatmapID)
channel.get_beatmap()
{'beatmapset_id': 3756, 'difficulty_rating': 0.67, 'id': 22538, 'mode': 'osu', 'status': 'graveyard','total_length': 114, 'user_id': 2, 'version': 'Gameplay basics', 'accuracy': 0, 'ar': 0,'bpm': 160.38, 'convert': False, 'count_circles': 4, 'count_sliders': 3, 'count_spinners': 1, 'cs': 3,'deleted_at': None, 'drain': 0, 'hit_length': 18, 'is_scoreable': False,'last_updated': '2014-03-10T16:31:10+00:00', 'mode_int': 0, 'passcount': 202069, 'playcount': 209908,'ranked': -2, 'url': 'https://osu.ppy.sh/beatmaps/22538','checksum': '3c8b50ebd781978beb39160c6aaf148c', 'failtimes': {'fail': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 1, 9, 2, 22, 6, 14, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],'exit': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 135, 153, 27, 18, 27,25, 13, 10, 7, 5, 2, 2, 2, 4, 1, 2, 2, 3, 1, 1, 1, 0, 2, 0, 1, 1, 1, 2, 1, 1, 0, 0, 2, 0, 0, 1, 1, 1,1, 0, 1, 1, 0, 0, 0, 1, 9, 18, 0, 1, 9, 2, 9, 9, 38, 5, 11, 4, 3, 4, 2, 1, 0, 3, 3, 1, 1, 0, 1, 0, 0,0, 0, 216]}, 'max_combo': 28}
channel.set_size(size)
channel.get_size()
channel.set_title(title)
channel.get_title()
channel.set_invite_link(link)
channel.get_invite_link()
channel.start_on_players_ready(status)
Ready
channel.is_start_on_players_ready()
ready
channel.lock()
channel.unlock()
channel.is_locked()
channel.lock()
and channel.unlock()
channel.move(username, slot_num)
channel.set_team(username, colour)
blue
or red
Certain methods exist to fetch data directly from osu.ppy.sh
bot.fetch_beatmap(beatmapID)
bot.fetch_beatmapset(beatmapsetID)
bot.fetch_parent_set(beatmapID)
bot.fetch_user_profile(username)
Getter methods rely on the most recently fetched match history.
channel.fetch_match_history()
channel.get_match_history()
channel.get_match_data()
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
Python
def __init__(self, bot, channel):
self.bot = bot
self.channel = channel
self.vote = channel.new_vote_manager(self.carry_vote)
def carry_vote(self, vote_manager):
self.channel.send_message("vote was carried!")
self.channel.send_message("results: " + vote_manager.get_results())
Javascript
constructor: function (bot, channel) {
LogicProfile.bot = bot;
LogicProfile.channel = channel;
LogicProfile.vote = channel.new_vote_manager(LogicProfile.carry_vote);
},
carry_vote: function (vote_manager) {
LogicProfile.channel.send_message("vote was carried!");
LogicProfile.channel.send_message("results: " + vote_manager.get_results());
}
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.
Choices
Threshold
The threshold parameter is the number of votes needed before the event method is triggered upon a vote's end.
You can choose to provide this as an integer, or the default behaviour is for a dynamic threshold which at all times is equal to
floor(number of users / 2) + 1
, usually resulting in half the number of users + 1 as a threshold, or when all players in a channel have voted.
The vote manager will collect votes and keep track of users who are present in the channel as well as those who have voted. When a player leaves the channel, the vote manager updates such that if that player has voted, the vote is rescinded. A player cannot vote twice.
Casting ballots
vote_manager.cast_ballot(username, choice)
method to cast a ballotEnding a vote
vote_manager.stop()
methodvote_manager.hold_vote(choices, threshold)
in_progress
status to Truevote_manager.in_progress()
vote_manager.stop()
vote_manager.restart(threshold)
vote_manager.cast_ballot(username, choice)
vote_manager.get_choices()
vote_manager.get_results(choice)
vote_manager.get_users(choice)
vote_manager.get_ballot(username)
vote_manager.get_ballot_number(choice)
vote_manager.get_majority_vote()
vote_manager.get_threshold()
vote_manager.set_threshold(threshold)
Python
# Logic profile to hold a start vote
class StartVoteExample:
def __init__(self, bot, channel):
self.bot = bot
self.channel = channel
# command to vote
channel.set_command("!start", self.vote_start, "Votes to start the match")
# vote manager
self.vote = channel.new_vote_manager(self.carry_vote)
# event method for !start command
def vote_start(self, message):
# cast ballot to start match if it isn't already in progress
if not self.channel.in_progress():
self.vote.cast_ballot(message["username"], "Start Match")
# event method to trigger on successful vote
def carry_vote(self, vote_manager):
# start the match
self.channel.start_match()
# event method triggered when a match starts
def on_match_start(self):
# stop any vote in progress
self.vote.stop()
Javascript
LogicProfile = {
constructor: function (bot, channel) {
LogicProfile.bot = bot;
LogicProfile.channel = channel;
// command to vote
channel.set_command("!start", LogicProfile.vote_start, "Votes to start the match");
// vote manager
LogicProfile.vote = channel.new_vote_manager(LogicProfile.carry_vote);
},
// event method for !start command
vote_start: function (message) {
// cast ballot to start match if it isn't already in progress
if (!LogicProfile.channel.in_progress()) {
LogicProfile.vote.cast_ballot(message.username, "Start Match");
}
},
// event method to trigger on successful vote
carry_vote: function (vote_manager) {
// start the match
LogicProfile.channel.start_match();
},
// event method triggered when a match starts
on_match_start: function () {
// stop any vote in progress
LogicProfile.vote.stop();
}
};
This is a wrapper for the Chimu.moe api and allows:
Fetching beatmaps / beatmapsets
bot.fetch_beatmap()
method
Tutorial from osu.ppy.sh -> {'beatmapset_id': 3756, 'difficulty_rating': 0.67, 'id': 22538, 'mode': 'osu', 'status': 'graveyard','total_length': 114, 'user_id': 2, 'version': 'Gameplay basics', 'accuracy': 0, 'ar': 0,'bpm': 160.38, 'convert': False, 'count_circles': 4, 'count_sliders': 3, 'count_spinners': 1, 'cs': 3,'deleted_at': None, 'drain': 0, 'hit_length': 18, 'is_scoreable': False,'last_updated': '2014-03-10T16:31:10+00:00', 'mode_int': 0, 'passcount': 202069, 'playcount': 209908,'ranked': -2, 'url': 'https://osu.ppy.sh/beatmaps/22538','checksum': '3c8b50ebd781978beb39160c6aaf148c', 'failtimes': {'fail': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 1, 9, 2, 22, 6, 14, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],'exit': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 135, 153, 27, 18, 27,25, 13, 10, 7, 5, 2, 2, 2, 4, 1, 2, 2, 3, 1, 1, 1, 0, 2, 0, 1, 1, 1, 2, 1, 1, 0, 0, 2, 0, 0, 1, 1, 1,1, 0, 1, 1, 0, 0, 0, 1, 9, 18, 0, 1, 9, 2, 9, 9, 38, 5, 11, 4, 3, 4, 2, 1, 0, 3, 3, 1, 1, 0, 1, 0, 0,0, 0, 216]}, 'max_combo': 28}
Tutorial from Chimu.moe -> {"BeatmapId":22538,"ParentSetId":3756,"DiffName":"Gameplay basics","FileMD5":"3c8b50ebd781978beb39160c6aaf148c","Mode":0,"BPM":160.375,"AR":0.0,"OD":0.0,"CS":3.0,"HP":0.0,"TotalLength":114,"HitLength":18,"Playcount":97903,"Passcount":94312,"MaxCombo":28,"DifficultyRating":0.696774,"OsuFile":"Peter Lambert - osu! tutorial (peppy) [Gameplay basics].osu","DownloadPath":"/d/3756"}
Downloading beatmaps / beatmapsets
.osz
filesSearching for beatmaps / beatmapsets
# Access the Chimu.moe wrapper through a Bot object with bot.chimu
bot.chimu.fetch_beatmap(22538)
->> {"BeatmapId":22538,"ParentSetId":3756,"DiffName":"Gameplay basics","FileMD5":"3c8b50ebd781978beb39160c6aaf148c","Mode":0,"BPM":160.375,"AR":0.0,"OD":0.0,"CS":3.0,"HP":0.0,"TotalLength":114,"HitLength":18,"Playcount":97903,"Passcount":94312,"MaxCombo":28,"DifficultyRating":0.696774,"OsuFile":"Peter Lambert - osu! tutorial (peppy) [Gameplay basics].osu","DownloadPath":"/d/3756"}
bot.chimu.fetch_beatmap(beatmapID)
bot.chimu.fetch_beatmapset(beatmapsetID)
bot.chimu.fetch_parent_set(beatmapID)
bot.chimu.search(query, pages, **attributes)
search_params = {
"min_ar": 9,
"max_od": 8
};
LogicProfiile.bot.chimu.search("", 5, search_params);
bot.chimu.search(min_ar=9, max_ar=10, min_diff=5.1)
bot.chimu.fetch_download_link(beatmapID)
bot.chimu.fetch_set_download_link(beatmapsetID)
bot.chimu.download_beatmap(beatmapID, path, with_video, auto_open, blocking)
.osz
filebot.chimu.download_beatmapset(beatmapsetID, path, with_video, auto_open, blocking)
.osz
filebot.chimu.fetch_random_beatmap(channel, **attributes)
search_params = {
"min_ar": 9,
"max_od": 8
};
LogicProfiile.bot.chimu.search(null, search_params);
bot.chimu.set_songs_directory(path)
bot.chimu.get_songs_directory()
bot.chimu.set_redownload(status)
bot.chimu.is_redownload()
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.
!config
.
This results in the ability to also download paste2.org entries as lists containing the lines of the paste as strings.bot.paste2_upload(description, content)
bot.paste2_download(url)
These are more to do with the extended functionality of the game room
channel.get_config_link()
channel.set_custom_config(text)
channel.get_custom_config()
channel.import_config(url)
These methods extend the functionality of the lobby in some way (and I didn't know where else to put them...)
bot.add_player_blacklist(username)
bot.del_player_blacklist(username)
bot.get_player_blacklist()
channel.set_welcome_message(message)
channel.get_welcome_message()
channel.maintain_password(status)
channel.is_maintain_password()
channel.maintain_title(status)
channel.is_maintain_title()
channel.maintain_size(status)
channel.add_player_blacklist(username)
channel.del_player_blacklist(username)
channel.get_player_blacklist()
channel.start_on_players_ready(status)
Ready
channel.is_start_on_players_ready()
Ready
channel.set_autostart_timer(status, secs)
channel.get_autostart_timer()
channel.is_autostart_timer()
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.
bot.get_username()
bot.get_password()
bot.set_verbose(status)
bot.is_verbose()
bot.set_logging(status)
config/logs/
bot.is_logging()
bot.log(message)
bot.set_default_message_log_length(length)
bot.get_default_message_log_length()
bot.set_personal_message_log_length(length)
bot.get_personal_message_log_length()
bot.set_authenticate(status)
bot.is_authenticate()
These methods can be used to change certain aspects of the functionality of the framework
bot.exit_handler()
bot.start()
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.
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 = [];
},
};
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);
},
};
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):
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]);
},
};
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):
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]);
},
};
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):
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]);
},
};
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")
def show_queue(self):
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):
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]);
},
};