├── .github └── FUNDING.yml ├── LICENSE ├── README.md ├── cogs ├── audio │ ├── application.yml │ └── audio.py ├── economy │ ├── economy.py │ └── games │ │ ├── blackjack.py │ │ ├── emoterace.py │ │ ├── resources │ │ └── cards │ │ │ ├── blackJoker.png │ │ │ ├── blueBack.png │ │ │ ├── club10.png │ │ │ ├── club2.png │ │ │ ├── club3.png │ │ │ ├── club4.png │ │ │ ├── club5.png │ │ │ ├── club6.png │ │ │ ├── club7.png │ │ │ ├── club8.png │ │ │ ├── club9.png │ │ │ ├── clubAce.png │ │ │ ├── clubJack.png │ │ │ ├── clubKing.png │ │ │ ├── clubQueen.png │ │ │ ├── diamond10.png │ │ │ ├── diamond2.png │ │ │ ├── diamond3.png │ │ │ ├── diamond4.png │ │ │ ├── diamond5.png │ │ │ ├── diamond6.png │ │ │ ├── diamond7.png │ │ │ ├── diamond8.png │ │ │ ├── diamond9.png │ │ │ ├── diamondAce.png │ │ │ ├── diamondJack.png │ │ │ ├── diamondKing.png │ │ │ ├── diamondQueen.png │ │ │ ├── heart10.png │ │ │ ├── heart2.png │ │ │ ├── heart3.png │ │ │ ├── heart4.png │ │ │ ├── heart5.png │ │ │ ├── heart6.png │ │ │ ├── heart7.png │ │ │ ├── heart8.png │ │ │ ├── heart9.png │ │ │ ├── heartAce.png │ │ │ ├── heartJack.png │ │ │ ├── heartKing.png │ │ │ ├── heartQueen.png │ │ │ ├── redBack.png │ │ │ ├── redJoker.png │ │ │ ├── spade10.png │ │ │ ├── spade2.png │ │ │ ├── spade3.png │ │ │ ├── spade4.png │ │ │ ├── spade5.png │ │ │ ├── spade6.png │ │ │ ├── spade7.png │ │ │ ├── spade8.png │ │ │ ├── spade9.png │ │ │ ├── spadeAce.png │ │ │ ├── spadeJack.png │ │ │ ├── spadeKing.png │ │ │ └── spadeQueen.png │ │ ├── slots.py │ │ ├── temp │ │ ├── blackjack_3.png │ │ ├── blackjack_31.png │ │ ├── blackjack_5.png │ │ ├── blackjack_50.png │ │ ├── blackjack_53.png │ │ ├── blackjack_75.png │ │ ├── blackjack_78.png │ │ ├── blackjack_81.png │ │ ├── blackjack_87.png │ │ ├── blackjack_91.png │ │ └── blackjack_93.png │ │ └── twentyfour.py ├── fun │ ├── fun.py │ ├── multiplayer.py │ └── resources │ │ ├── reactions │ │ └── owo │ │ │ ├── tenor (1).gif │ │ │ ├── tenor (10).gif │ │ │ ├── tenor (2).gif │ │ │ ├── tenor (3).gif │ │ │ ├── tenor (4).gif │ │ │ ├── tenor (5).gif │ │ │ ├── tenor (6).gif │ │ │ ├── tenor (7).gif │ │ │ ├── tenor (8).gif │ │ │ ├── tenor (80).gif │ │ │ ├── tenor (9).gif │ │ │ └── tenor.gif │ │ └── trivia │ │ ├── arts_and_literature.json │ │ ├── entertainment.json │ │ ├── food_and_drink.json │ │ ├── geography.json │ │ ├── history.json │ │ ├── language.json │ │ ├── mathematics.json │ │ ├── music.json │ │ ├── people_and_places.json │ │ ├── religion_and_mythology.json │ │ ├── science_and_nature.json │ │ ├── sport_and_leisure.json │ │ ├── tech_an_video_games.json │ │ ├── toys_and_games.json │ │ └── uncategorized.json ├── general │ ├── changelog.json │ └── general.py ├── osu │ ├── bancho_crawler.py │ ├── osu.py │ ├── osu_utils │ │ ├── chunks.py │ │ ├── database.py │ │ ├── drawing.py │ │ ├── droid_pyttanko.py │ │ ├── map_utils.py │ │ ├── owoAPI.py │ │ ├── owoCache.py │ │ ├── utils.py │ │ └── web_utils.py │ ├── replay_parser │ │ ├── enums.py │ │ └── replay.py │ └── updater.py ├── owner │ └── owner.py ├── settings │ └── settings.py ├── social │ ├── bgadmin.py │ ├── fonts │ │ ├── SourceSansPro-Regular.ttf │ │ ├── SourceSansPro-Semibold.ttf │ │ ├── Uni_Sans_Heavy.ttf │ │ ├── Uni_Sans_Thin.ttf │ │ ├── YasashisaAntique.ttf │ │ └── unicode.ttf │ ├── settings.json │ └── social.py ├── status │ └── status.py └── utility │ └── utility.py ├── config.json ├── database └── other │ └── special_lists.json ├── main.py ├── main_passive.py ├── other_scripts └── update_beatmaps.sh ├── tracker_bot.py └── utils ├── chat_formatting.py └── checks.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: Stevy 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Stevy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![osu profile](https://i.imgur.com/8fufXHA.png) 2 | 3 | This is the mostly-complete repo for the owo Discord osu! bot which you can invite [here](https://discord.com/oauth2/authorize?client_id=289066747443675143&scope=bot&permissions=305187840). As you look through this repo, please keep in mind that all of this code is written for *me* and not for anyone else, so the only consideration for code structure is that it's convenient and works for *me*. Here's a quick [FAQ section](#coding-faq) for you programmers or anyone who's considering trying to get this to work on your end. For a full list of commands, visit the website [here](http://owo-bot.xyz/). 4 | 5 | # Overview of Features 6 | 7 | **Firstly, if you're annoyed by the implicitly triggered "owo"-type commands, I'm with you - do `>funadmin prefixless` to disable them.** 8 | 9 | Secondly, if you haven't already, link your osu! account by doing `>osuset user "your username"`; if you have a space in your name, use quotes. The official server can provide **verification** for your account if you have your discord information on your osu! profile (settings section). If you wish to link your account to a private server, append the suffix `-(server name)` e.g. (`>osuset user "your username" -ripple`); things like `rx` are not needed in the server name when setting user. Do `>botinfo` to view supported private servers. 10 | 11 | ## Table of Contents 12 | - [Profile Commands](#profile-commands) 13 | - [Top Play Commands](#top-play-commands) 14 | - [Map Recommendations](#map-recommendations) 15 | - [Tracking](#tracking) 16 | - [Map Feed](#map-feed) 17 | - [Implicit Commands](#implicit-commands) 18 | - [Getting More Info w/ `>help`](#getting-more-info-w-help) 19 | 20 | ## Profile Commands 21 | Firstly, to view some basic profile information, there are four commands: `>osu` `>taiko` `>ctb` `>mania`. If no parameters are provided, they will display the information of the account you linked, otherwise, it will use your input as a username and find that user's info. Examples: `>osu Stevy`, `>taiko syaron105`, `>ctb AutoLs`, `>mania Jakads`. 22 | 23 | ![osu profile](https://i.imgur.com/pCWcxvI.png) 24 | ![taiko profile](https://i.imgur.com/baKLg8a.png) 25 | ![ctb profile](https://i.imgur.com/1Cx8wAp.png) 26 | ![mania profile](https://i.imgur.com/aewzgof.png) 27 | 28 | If you append `-d` to any one of those, you will get a _detailed_ profile. If you append `-s`, you will get some calculated statistics for the user using their top plays. Examples: `>osu -d "Stevy"`, `>osu -s "chocomint"`. 29 | 30 | ![detailed profile](https://i.imgur.com/Q3COkJj.png) 31 | ![stats profile](https://i.imgur.com/WMvdDob.png) 32 | 33 | [Return to Table of Contents](#table-of-contents) 34 | 35 | ## Top Play Commands 36 | 37 | Next, there are the `top` commands: `>osutop` `>taikotop` `>ctbtop` `>maniatop`. Input convention follows the "core" commands from above. This will display your top 5 plays for that gamemode. Example: `>osutop "chocomint"`. 38 | 39 | ![osutop](https://i.imgur.com/Oy7NK4a.png) 40 | 41 | The top command supports various types of sorting and filtering functions. By appending tags, you can sort by accuracy (`-acc`), max combo (`-c`), rank achieved (`-rk`), and score (`-sc`). You can filter by using tags like index (`-i #`) and mod (`-m (mods)`). Additionally, there is a no-choke option (`-nc`) that will calculate hypothetical no-choke plays for your entire top 100 - sorting and filters can be applied here as well. There is also a supporter feature (`-im`) that allows you to generate a score image of one of your plays. If you'd like to support, do `>support` or [visit the patreon page](https://www.patreon.com/stevy). Examples: `>osutop chocomint -nc`, `>osutop chocomint -im -i 3` 42 | 43 | ![no choke](https://i.imgur.com/9MTpepG.png) 44 | ![score image](https://i.imgur.com/LHqkMvw.png) 45 | 46 | For more information, use the `>help` command on the respective top command (e.g. `>help osutop`) in Discord or visit the [website](http://owo-bot.xyz/) for examples. 47 | 48 | [Return to Table of Contents](#table-of-contents) 49 | 50 | ## Map Recommendations 51 | 52 | The bot can give recommendations for any mode based on a user's top 15 plays and mods in the respective mode (e.g. `>recommend` or `>r`). If you think a recommendation is too easy, use the `-f` or farm parameter; the higher the number, the more farmy. If you don't like the mods it gives, you can specify by just writing the mod afterwards, like `HDDT`. If you want a specific ar, use the `-ar` tag. You can also use ranges, like `4-5`. However, it should be noted that for non-std recommendations, only the `-pp` and `-f` options work. For more information, visit the [website](http://owo-bot.xyz/). Example: `>r -f 10 -ar 10-10.4 HDDT -pp 300-350` (Farm rating = 10 (easy to farm), AR = 10-10.4, mods = HDDT, target pp = 300-350). 53 | 54 | ![rec image](https://i.imgur.com/MWcU8wF.png) 55 | 56 | [Return to Table of Contents](#table-of-contents) 57 | 58 | ## Tracking 59 | 60 | To track a user or users, type the command `>track add (username) (username2) ...`. The default mode and number tracked is `0` (std) and `50`. To specify the # of top plays to be notified about, append `-t #` to the command. To specify the modes to be tracked, append `-m (modes)` to your command; 0=std, 1=taiko, 2=ctb, 3=mania. e.g. `>track add -m 23 -t 75 Stevy` would track the top `75` plays for `Stevy` on that channel for modes `2` and `3`. You can also track certain countries and the number of players by appending `-c` and a [two-character country code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) or `global`, and specify the number of top players using `-p #`. The number of top plays and the modes are, again, defined by `-t` and `-m`, respectively. Please keep in mind that servers have a default track limit of 200 players, but tracking a country's top 30 players for 3 different gamemodes will only add 30 to your list! If you made a mistake in adding a user, simply use the add command again. 61 | 62 | If you want to completely overwrite with new options, use `-o`. If you want to remove that user or users from tracking, use `>track remove (username) (username2) ...`. If you want to clear all people on the server, do `>track remove -a`. If you want to clear a single channel, do `track remove -ch` in that channel. For more info, use `>help track add` or visit the [website](http://owo-bot.xyz/). 63 | 64 | [Return to Table of Contents](#table-of-contents) 65 | 66 | ## Map Feed 67 | 68 | The bot can track newly qualified, ranked, and loved maps for all gamemodes. To enable, pick a channel and do `>mapfeed`. By default, the bot will display all new maps that are either qualified, ranked, or loved and in all gamemodes. To filter what maps gets displayed, you can introduce filters such as excluded mappers (`-xmpr`) or least stars (`-ls`) to only get beatmap sets containing at least one map with a star value greater than what was specified. To view your settings, do `>mapfeed -info`. To remove a channel from the map feed, do `>mapfeed -rm`. For more information, visit the [website](http://owo-bot.xyz/). An example of a newly ranked map is shown below. 69 | 70 | ![map feed image](https://i.imgur.com/Xp3INzz.png) 71 | 72 | [Return to Table of Contents](#table-of-contents) 73 | 74 | ## Implicit Commands 75 | 76 | There are a few passive triggers for owo, mostly to do with osu links and screenshots. There is a 5 second cooldown per server when any of these are triggered. The way to disable all of these server-wide is `>osuadmin implicit`. To toggle, do the command again. Below are ways to selectively enable/disable different links. 77 | 78 | ### Beatmap Links 79 | 80 | If a beatmap linked from the official site is posted, owo will post that map's information, pp information, along with some download links. If it is a single beatmap, a graph (only accurate for std) will be displayed. If it's a beatmap set, the top 3 difficulties will be displayed. If you wanted to see how certain mods will effect the map's pp values, you can simply append +(mods) to the end of the link. This is very similar to the `>map` command. Examples: `https://osu.ppy.sh/beatmapsets/93523#osu/252238`, `https://osu.ppy.sh/beatmapsets/93523#osu/252238 +HDHR` 81 | 82 | ![beatmap image](https://i.imgur.com/7vsoqXB.png) 83 | ![beatmap_mod image](https://i.imgur.com/rlS1guk.png) 84 | 85 | [Return to Table of Contents](#table-of-contents) 86 | 87 | ### User Links 88 | 89 | The bot also detects user links and displays them in the same format as the basic profile commands. Example: `https://osu.ppy.sh/users/5053158`. 90 | 91 | ![user link image](https://i.imgur.com/FhvMpHU.png) 92 | 93 | ### Screenshots 94 | 95 | The bot is able to detect maps from screenshots (to varying degrees of accuracy...). If a top or recent play is detected, then it will provide some information of that play, otherwise, it will only be the map information. The screenshot must be from the official server or directly from the game (no modified filenames). Normally, screenshot files should follow the format `screenshot#.png`. 96 | 97 | ![screenshot image](https://i.imgur.com/nYYEBOm.png) 98 | 99 | ### Toggling Implicit/Passive Settings 100 | 101 | To toggle settings for link and screenshot detection, use the `>osuadmin` command and sub-commands. Toggling the `implicit` setting will enable/disable all link/screenshot detection (e.g. `>osuadmin implicit`). Sub-commands like `beatmapurl` will disable beatmap url detection. Other options are listed in the `osuadmin` stem command. To get an overview of your settings (not just for osu!), do `>overview`. Example: `>overview`. 102 | 103 | ![overview image](https://i.imgur.com/oa6xKP1.png) 104 | 105 | ## Getting More Info w/ `>help` 106 | 107 | As mentioned previously, if you want to explore more stuff about the bot, use the `>help` or `>h` command. If you are dealing with a nested command, you can do something like `>h track add`. You can also visit the [website](http://owo-bot.xyz/) which includes many examples. Example: `>h track add`. 108 | 109 | ![help image](https://i.imgur.com/H7daDP7.png) 110 | 111 | [Return to Table of Contents](#table-of-contents) 112 | 113 | # Coding FAQ 114 | 115 | ### Why are cogs mostly in a huge file and not separated? 116 | Writing in a single file is extremely convenient for me to apply hotfixes and reload the module. Hotfixing and reloading are far-preferred over restarting because the bot takes about 15 minutes to log in all shards due to Discord API rate limit restrictions. After fiddling around with `importlib` for several days, I haven't been successful in reloading files that aren't the one the cog is located in. If you have gotten this to work in Python/discord.py, then I'd love to know about it. 117 | 118 | ### What is with all this spaghetti code? 119 | Like you, my intensions aren't to write code that is unreadable. But when things get as complex as they do with new feature requests coming in every week, you just give into the mess while trying to implement things as fast as possible. So as I said up top, this code is for no one and is not meant to be read. To me, if it works, it works. 120 | 121 | ### Why are there so many unspecified try-catches? 122 | At some point, you just get tired of seeing errors in your console from bad user inputs. Doing this is akin to [this meme](https://i.imgur.com/A1X5zhR.png). 123 | 124 | ### Will you ever upload the databases you use? 125 | No, there is too much back-end going on and helping everyone get the database working will be a hassle. 126 | 127 | ### I have osu API questions, can you help? 128 | Yeah, of course! I'm open to any questions if people need help with the osu! API or programming questions in general. Although, after reading this code, I'm not so sure you'd want it! But if you still do, feel free to chat in the [Discord server](https://discord.gg/aNKde73). 129 | 130 | [Return to Table of Contents](#table-of-contents) 131 | -------------------------------------------------------------------------------- /cogs/audio/application.yml: -------------------------------------------------------------------------------- 1 | server: # REST and WS server 2 | port: 2500 3 | address: 0.0.0.0 4 | spring: 5 | main: 6 | banner-mode: log 7 | lavalink: 8 | server: 9 | password: "youshallnotpass" 10 | sources: 11 | youtube: true 12 | bandcamp: true 13 | soundcloud: true 14 | twitch: true 15 | vimeo: true 16 | mixer: true 17 | http: true 18 | local: false 19 | bufferDurationMs: 400 20 | youtubePlaylistLoadLimit: 6 # Number of pages at 100 each 21 | youtubeSearchEnabled: true 22 | soundcloudSearchEnabled: true 23 | gc-warnings: true 24 | 25 | metrics: 26 | prometheus: 27 | enabled: false 28 | endpoint: /metrics 29 | 30 | sentry: 31 | dsn: "" 32 | # tags: 33 | # some_key: some_value 34 | # another_key: another_value 35 | 36 | logging: 37 | file: 38 | max-history: 30 39 | max-size: 1GB 40 | path: ./logs/ 41 | 42 | level: 43 | root: INFO 44 | lavalink: INFO 45 | -------------------------------------------------------------------------------- /cogs/economy/games/blackjack.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import asyncio 4 | import discord 5 | import datetime 6 | import collections 7 | import motor.motor_asyncio 8 | from discord.ext import commands 9 | from discord.utils import get 10 | from PIL import Image 11 | 12 | from itertools import permutations, combinations, product, chain 13 | from pprint import pprint as pp 14 | from fractions import Fraction as F 15 | import random, ast, re 16 | import sys 17 | from itertools import zip_longest 18 | 19 | class Blackjack: 20 | def __init__(self, bot): 21 | self.bot = bot 22 | self.nums = list(range(2,15)) 23 | self.suits = list(range(1,5)) 24 | 25 | # cards 26 | self.drawn_cards = [] 27 | self.bot_hand = [] 28 | self.user_hand = [] 29 | 30 | # constant 31 | self.CARDS_FOLDER = os.path.join(os.getcwd(), 32 | 'cogs','economy','games','resources','cards') 33 | self.TEMP_FOLDER = os.path.join(os.getcwd(), 34 | 'cogs','economy','games','temp') 35 | 36 | async def start_game(self, ctx): 37 | def check_event(m): 38 | return any([x == m.content.lower() for x in ['h','hit','s','stay']]) and m.author == ctx.author 39 | 40 | user = ctx.message.author 41 | channel = ctx.message.channel 42 | 43 | self.user_cards = self.get_cards(num=2) 44 | 45 | self.bot_hand = self.get_cards(2) 46 | self.user_hand = self.get_cards(2) 47 | 48 | # draw cards 49 | user_cards_filepath = self._draw_cards(self.user_hand) 50 | cards_file, cards_url = self._get_discord_image_info(user_cards_filepath) 51 | 52 | # draw embed 53 | em = discord.Embed(description='', colour=user.colour) 54 | em.description = '**Ok {}, here are your cards: {}**\nWhat do you want to do? `hit(h)/stay(s)`'.format( 55 | user.mention, self.pretty_card(self.user_hand)) 56 | em.set_image(url=cards_url) 57 | em.set_footer(text='Current Total: {}'.format(self.calc_total(self.user_hand))) 58 | await ctx.send(embed=em, files=[cards_file]) 59 | 60 | try: 61 | response = await self.bot.wait_for('message', check=check_event, timeout=30) 62 | except asyncio.TimeoutError: 63 | response = None 64 | 65 | while response and any(res in response.content for res in ['h' or 'hit']) and self.calc_total(self.user_hand) <= 21: 66 | # print(any(res in response.content for res in ['h' or 'hit']) or self.calc_total(self.user_hand) < 21) 67 | card = self.get_cards(1) 68 | # await ctx.send('**New card: {}**'.format(self.pretty_card(card))) 69 | self.user_hand.extend(card) 70 | 71 | # draw cards 72 | user_cards_filepath = self._draw_cards(self.user_hand) 73 | cards_file, cards_url = self._get_discord_image_info(user_cards_filepath) 74 | 75 | # draw embed 76 | em = discord.Embed(description='', colour=user.colour) 77 | em.description = '**{}, your current cards are: {}**\nWhat do you want to do? `hit(h)/stay(s)`'.format( 78 | user.mention, self.pretty_card(self.user_hand)) 79 | em.set_image(url=cards_url) 80 | em.set_footer(text='Current Total: {}'.format(self.calc_total(self.user_hand))) 81 | await ctx.send(embed=em, files=[cards_file]) 82 | 83 | try: 84 | response = await self.bot.wait_for('message', check=check_event, timeout=30) 85 | except asyncio.TimeoutError: 86 | response = None 87 | 88 | max_limit = random.randint(13,16) 89 | while self.calc_total(self.bot_hand) < max_limit: 90 | self.bot_hand.extend(self.get_cards(1)) 91 | 92 | winner = self.is_winner() 93 | if winner is None: 94 | await ctx.send('**Tie! Both had {}**'.format(self.calc_total(self.user_hand))) 95 | return None 96 | elif self.is_winner(): 97 | await ctx.send('**Congrats, you won with `{}` vs `{}`**'.format( 98 | self.calc_total(self.user_hand), self.calc_total(self.bot_hand))) 99 | return True 100 | else: 101 | await ctx.send('**Sorry, you lost with `{}` vs `{}`**'.format( 102 | self.calc_total(self.user_hand), self.calc_total(self.bot_hand))) 103 | return False 104 | 105 | def _draw_cards(self, cards): 106 | # create a unique id for save file 107 | rand_id = random.randint(0, 100) 108 | 109 | for idx, card in enumerate(cards): 110 | card_image = self._get_card_image(card) 111 | if idx == 0: 112 | total_width = card_image.width * len(cards) 113 | total_height = card_image.height 114 | 115 | full_im = Image.new('RGBA', (total_width, total_height)) 116 | 117 | full_im.paste(card_image, (idx*card_image.width, 0), card_image) 118 | 119 | 120 | output_im_name = 'blackjack_{}.png'.format(rand_id) 121 | output_filepath = os.path.join(self.TEMP_FOLDER, output_im_name) 122 | 123 | full_im.save(output_filepath) 124 | 125 | return output_filepath 126 | 127 | 128 | def _get_card_image(self, card): 129 | suit = int(card[0]) 130 | if suit == 1: 131 | suit_str = 'club' 132 | elif suit == 2: 133 | suit_str = 'diamond' 134 | elif suit == 3: 135 | suit_str = 'heart' 136 | elif suit == 4: 137 | suit_str = 'spade' 138 | 139 | val = int(card[1]) 140 | if val == 11: 141 | val_str = 'Jack' 142 | elif val == 12: 143 | val_str = 'Queen' 144 | elif val == 13: 145 | val_str = 'King' 146 | elif val == 14: 147 | val_str = 'Ace' 148 | else: 149 | val_str = str(val) 150 | 151 | card_file = '{}{}.png'.format(suit_str, val_str) 152 | card_full_path = os.path.join(self.CARDS_FOLDER, card_file) 153 | 154 | card_image = Image.open(card_full_path) 155 | return card_image 156 | 157 | 158 | def _get_discord_image_info(self, card_path): 159 | discord_file = discord.File(card_path, filename="cards.png") 160 | url = 'attachment://' + "cards.png" 161 | 162 | return discord_file, url 163 | 164 | def is_winner(self): 165 | bot_val = self.calc_total(self.bot_hand) 166 | user_val = self.calc_total(self.user_hand) 167 | 168 | if bot_val > 21 and user_val > 21: 169 | return None 170 | elif bot_val > 21: 171 | return True 172 | elif user_val > 21: 173 | return False 174 | elif bot_val > user_val: 175 | return False 176 | elif bot_val < user_val: 177 | return True 178 | elif user_val == 21: 179 | return True 180 | 181 | def calc_total(self, hand): 182 | sum_hand = 0 183 | 184 | # sort by value so you handle aces at the end 185 | hand = sorted(hand, key=lambda tup: tup[1]) 186 | 187 | for suit, val in hand: 188 | if val == 14: 189 | if sum_hand + 11 > 21: 190 | val = 1 191 | else: 192 | val = 11 193 | 194 | elif val > 10: # if it's a royal 195 | val = 10 196 | 197 | sum_hand += val 198 | return sum_hand 199 | 200 | def get_cards(self, num = 1): 201 | counter = 0 202 | cards = [] 203 | 204 | while counter < num: 205 | new_card = (random.choice(self.suits), random.choice(self.nums)) 206 | if self.check_drawn(new_card): 207 | counter+=1 208 | self.drawn_cards.append(new_card) 209 | cards.append(new_card) 210 | return cards 211 | 212 | def check_drawn(self, card): 213 | if card in self.drawn_cards: 214 | return False 215 | return True 216 | 217 | def pretty_card(self, cards): 218 | card_suit = [":clubs:", ":diamonds:",":hearts:",":spades:"] 219 | str_cards = [] 220 | for card in cards: 221 | msg = '`{}`'.format(self._correct_card_num(card[1])) 222 | msg += card_suit[card[0]-1] 223 | str_cards.append(msg) 224 | 225 | return ', '.join(str_cards) 226 | 227 | def _correct_card_num(self, card_num): 228 | if card_num == '11': 229 | return 'J' 230 | elif card_num == '12': 231 | return 'Q' 232 | elif card_num == '13': 233 | return 'K' 234 | elif card_num == '14': 235 | return 'A' 236 | else: 237 | return str(card_num) 238 | -------------------------------------------------------------------------------- /cogs/economy/games/emoterace.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import asyncio 4 | import discord 5 | import datetime 6 | import collections 7 | import motor.motor_asyncio 8 | from discord.ext import commands 9 | from discord.utils import get 10 | 11 | from itertools import permutations, combinations, product, chain 12 | from pprint import pprint as pp 13 | from fractions import Fraction as F 14 | import random, ast, re 15 | import sys 16 | from itertools import zip_longest 17 | 18 | class EmoteRace: 19 | def __init__(self, bot): 20 | self.bot = bot 21 | 22 | async def start_game(self, ctx): 23 | 24 | 25 | def is_winner(self): 26 | pass 27 | 28 | def calc_total(self, hand): 29 | pass 30 | 31 | def get_cards(self, num = 1): 32 | pass 33 | 34 | def check_drawn(self, card): 35 | pass 36 | 37 | def pretty_card(self, cards): 38 | pass 39 | -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/blackJoker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/blackJoker.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/blueBack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/blueBack.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/club10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/club10.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/club2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/club2.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/club3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/club3.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/club4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/club4.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/club5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/club5.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/club6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/club6.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/club7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/club7.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/club8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/club8.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/club9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/club9.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/clubAce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/clubAce.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/clubJack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/clubJack.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/clubKing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/clubKing.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/clubQueen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/clubQueen.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/diamond10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/diamond10.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/diamond2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/diamond2.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/diamond3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/diamond3.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/diamond4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/diamond4.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/diamond5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/diamond5.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/diamond6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/diamond6.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/diamond7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/diamond7.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/diamond8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/diamond8.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/diamond9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/diamond9.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/diamondAce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/diamondAce.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/diamondJack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/diamondJack.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/diamondKing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/diamondKing.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/diamondQueen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/diamondQueen.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/heart10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/heart10.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/heart2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/heart2.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/heart3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/heart3.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/heart4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/heart4.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/heart5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/heart5.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/heart6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/heart6.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/heart7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/heart7.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/heart8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/heart8.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/heart9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/heart9.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/heartAce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/heartAce.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/heartJack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/heartJack.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/heartKing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/heartKing.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/heartQueen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/heartQueen.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/redBack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/redBack.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/redJoker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/redJoker.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/spade10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/spade10.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/spade2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/spade2.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/spade3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/spade3.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/spade4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/spade4.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/spade5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/spade5.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/spade6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/spade6.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/spade7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/spade7.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/spade8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/spade8.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/spade9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/spade9.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/spadeAce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/spadeAce.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/spadeJack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/spadeJack.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/spadeKing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/spadeKing.png -------------------------------------------------------------------------------- /cogs/economy/games/resources/cards/spadeQueen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/resources/cards/spadeQueen.png -------------------------------------------------------------------------------- /cogs/economy/games/slots.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import asyncio 4 | import discord 5 | import datetime 6 | import collections 7 | import numpy as np 8 | import motor.motor_asyncio 9 | 10 | from discord.ext import commands 11 | from discord.utils import get 12 | 13 | class Slots: 14 | def __init__(self, bot, bet): 15 | self.bot = bot 16 | self.bet = bet 17 | self.emotes_list = [ 18 | "<:owo:494203160718737418>", 19 | "<:ppcat:494205106569347111>", 20 | "<:easys:483759698919686175>", 21 | "<:expertpluss:483759702119940116>", 22 | "<:experts:483759702606479360>", 23 | "<:hards:483759702749085706>", 24 | "<:insanes:483759703512580128>", 25 | "<:normals:483759703579688972>", 26 | "<:easyt:483759702103162900>", 27 | "<:expertplust:483759702581313536>", 28 | "<:expertt:483759703508385792>", 29 | "<:hardt:483759703587946496>", 30 | "<:insanet:483759704019959848>", 31 | "<:normalt:483759704074485760>", 32 | "<:easyc:483759696877191183>", 33 | "<:expertplusc:483759700123320320>", 34 | "<:expertc:483759700374978580>", 35 | "<:hardc:483759702765731841>", 36 | "<:insanec:483759702854074378>", 37 | "<:normalc:483759703927816212>", 38 | "<:easym:483759698852577280>", 39 | "<:expertm:483759700589150208>", 40 | "<:expertplusm:483759700790476820>", 41 | "<:hardm:483759703617437696>", 42 | "<:insanem:483759703932010517>", 43 | "<:normalm:483759703957045254>", 44 | "<:rankingXH:462313722556186626>", 45 | "<:rankingX:462313722736672780>", 46 | "<:rankingS:462313719762911233>" 47 | ] 48 | self.line_of_interest = None # line of interest 49 | 50 | async def start_slots(self, ctx): 51 | total_payout = 0 52 | #trials = 2000 53 | #for n in range(trials): 54 | reels = [] 55 | for n in range(5): 56 | reels.append(self.create_reel()) 57 | slots = self.create_slot(reels) 58 | line = self.get_line_of_interest(reels) 59 | payout, payout_text = self.get_payout(line) 60 | #total_payout += payout 61 | em = self.create_embed(ctx, slots, payout, payout_text) 62 | 63 | #await ctx.send(f"Payout for {trials} trials: {total_payout/trials}") 64 | await ctx.send(embed = em) 65 | return payout 66 | 67 | def create_embed(self, ctx, slots, payout, payout_text): 68 | em = discord.Embed(colour=ctx.message.author.colour) 69 | em.add_field(name="Slots", value=slots, inline=True) 70 | 71 | payout_txt = "" 72 | payout_txt += "\n".join(payout_text) 73 | if self.bet > payout: 74 | net_type = "Lost" 75 | diff = self.bet - payout 76 | else: 77 | net_type = "Gained" 78 | diff = payout - self.bet 79 | 80 | payout_txt += f"\n-------------------\n**{payout}** (`{net_type} {diff}`)" 81 | em.add_field(name="Payout", value=payout_txt, inline=True) 82 | return em 83 | 84 | def create_reel(self): 85 | shuffled = sorted(self.emotes_list, key=lambda k: random.random()) 86 | return shuffled[0:3] 87 | 88 | def get_line_of_interest(self, reels): 89 | reels = np.transpose(reels) 90 | self.line_of_interest = reels[1] 91 | # print(self.line_of_interest) 92 | return self.line_of_interest 93 | 94 | def create_slot(self, reels): 95 | reels = np.transpose(reels) 96 | msg = "" # line_break + "\n" 97 | for i, row in enumerate(reels): 98 | if i == 1: 99 | msg += "▸" 100 | msg += "".join(row) 101 | msg += "\n" 102 | return msg 103 | 104 | def get_payout(self, line_of_interest): 105 | # define payout values 106 | payout_text = [] 107 | total_payout = 0 108 | for element in line_of_interest: 109 | amount = 0 110 | if "easy" in element: 111 | total_payout += (0/10) * self.bet 112 | if "normal" in element: 113 | # total_payout += (0/10) * self.bet 114 | amount = round((.5/10) * self.bet) 115 | total_payout += amount 116 | payout_text.append(f"**Normal diff** (`+{amount}`)") 117 | if "hard" in element: 118 | # total_payout += (0/10) * self.bet 119 | amount = round((.5/10) * self.bet) 120 | total_payout += amount 121 | payout_text.append(f"**Hard diff** (`+{amount}`)") 122 | if "insane" in element: 123 | amount = round((1/10) * self.bet) 124 | total_payout += amount 125 | payout_text.append(f"**Insane diff** (`+{amount}`)") 126 | 127 | if "expertplus" in element: 128 | amount = round((3/10) * self.bet) 129 | total_payout += amount 130 | payout_text.append(f"**Expert+ diff** (`+{amount}`)") 131 | elif "expert" in element: 132 | amount = round((2/10) * self.bet) 133 | total_payout += amount 134 | payout_text.append(f"**Expert diff** (`+{amount}`)") 135 | 136 | if "XH" in element: 137 | amount = round((5/10) * self.bet) 138 | total_payout += amount 139 | payout_text.append(f"**SSH Rank** (`+{amount}`)") 140 | elif "X" in element: 141 | amount = round((4/10) * self.bet) 142 | total_payout += amount 143 | payout_text.append(f"**SS Rank** (`+{amount}`)") 144 | 145 | if "owo" in element: 146 | amount = round((1.5) * self.bet) 147 | total_payout += amount 148 | payout_text.append(f"**owo** (`+{amount}`)") 149 | if "ppcat" in element: 150 | amount = round((1.25) * self.bet) 151 | total_payout += amount 152 | payout_text.append(f"**PP cat** (`+{amount}`)") 153 | 154 | # check if all the same mode 155 | for mode in ['s','c','t','m']: 156 | counter = 0 157 | for element in line_of_interest: 158 | parts = element.split(":") 159 | emote_name = parts[1] 160 | if emote_name.endswith(mode): 161 | counter += 1 162 | 163 | if counter == 5: 164 | amount = round(2 * self.bet) 165 | total_payout += amount 166 | payout_text.append(f"Same mode (`+{amount}`)") 167 | break 168 | 169 | # check if all the same 170 | check_element = line_of_interest[0] 171 | same_counter = 0 172 | for element in line_of_interest: 173 | if element == check_element: 174 | same_counter += 1 175 | 176 | if same_counter == 5: 177 | amount = self.bet * 20 178 | total_payout += amount 179 | payout_text.append(f"ALL SAME (`+{amount}`)") 180 | 181 | total_payout = round(total_payout) 182 | return total_payout, payout_text 183 | -------------------------------------------------------------------------------- /cogs/economy/games/temp/blackjack_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/temp/blackjack_3.png -------------------------------------------------------------------------------- /cogs/economy/games/temp/blackjack_31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/temp/blackjack_31.png -------------------------------------------------------------------------------- /cogs/economy/games/temp/blackjack_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/temp/blackjack_5.png -------------------------------------------------------------------------------- /cogs/economy/games/temp/blackjack_50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/temp/blackjack_50.png -------------------------------------------------------------------------------- /cogs/economy/games/temp/blackjack_53.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/temp/blackjack_53.png -------------------------------------------------------------------------------- /cogs/economy/games/temp/blackjack_75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/temp/blackjack_75.png -------------------------------------------------------------------------------- /cogs/economy/games/temp/blackjack_78.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/temp/blackjack_78.png -------------------------------------------------------------------------------- /cogs/economy/games/temp/blackjack_81.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/temp/blackjack_81.png -------------------------------------------------------------------------------- /cogs/economy/games/temp/blackjack_87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/temp/blackjack_87.png -------------------------------------------------------------------------------- /cogs/economy/games/temp/blackjack_91.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/temp/blackjack_91.png -------------------------------------------------------------------------------- /cogs/economy/games/temp/blackjack_93.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/economy/games/temp/blackjack_93.png -------------------------------------------------------------------------------- /cogs/economy/games/twentyfour.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import asyncio 4 | import discord 5 | import datetime 6 | import collections 7 | import motor.motor_asyncio 8 | from discord.ext import commands 9 | from discord.utils import get 10 | from PIL import Image 11 | 12 | from itertools import permutations, combinations, product, chain 13 | from pprint import pprint as pp 14 | from fractions import Fraction as F 15 | import random, ast, re 16 | import sys 17 | from itertools import zip_longest 18 | 19 | class TwentyFour: 20 | def __init__(self, bot, difficulty=1): 21 | self.digits = [] 22 | self.bot = bot 23 | self.difficulty = difficulty 24 | 25 | # constant 26 | self.CARDS_FOLDER = os.path.join(os.getcwd(), 27 | 'cogs','economy','games','resources','cards') 28 | self.TEMP_FOLDER = os.path.join(os.getcwd(), 29 | 'cogs','economy','games','temp') 30 | 31 | async def start_game(self, ctx): 32 | def check_event(m): 33 | return m.author == ctx.author 34 | 35 | user = ctx.message.author 36 | channel = ctx.message.channel 37 | 38 | self.digits = self.get_digits() 39 | answer = '' 40 | chk = False 41 | ans = False 42 | 43 | # draw embed 44 | fake_cards = self._get_cards(self.digits) 45 | cards_filepath = self._draw_cards(fake_cards) 46 | cards_file, cards_url = self._get_discord_image_info(cards_filepath) 47 | 48 | em = discord.Embed(description='', colour=user.colour) 49 | em.description = '**Ok {}, your numbers are: **'.format(user.mention) 50 | em.set_image(url=cards_url) 51 | await ctx.send(embed=em, files=[cards_file]) 52 | 53 | sol = await self.solve(self.digits) 54 | print(sol) 55 | 56 | # wait for user answer 57 | try: 58 | answer = await self.bot.wait_for('message', check=check_event, timeout=30) 59 | except asyncio.TimeoutError: 60 | answer = None 61 | if answer: 62 | answer = answer.content 63 | answer = answer.replace('x',"*").replace('X',"*") 64 | 65 | # check user solution 66 | chk = self.check(answer, self.digits) 67 | 68 | if not answer: 69 | await ctx.send('**Sorry {}, you ran out of time. One possible answer was: `{}` **'.format(user.mention, sol)) 70 | return False 71 | elif answer == '?' or answer.lower() == "pass": 72 | await ctx.send('**Give up? One possible answer was: `{}` **'.format(sol)) 73 | return False 74 | 75 | if not chk: 76 | if user: 77 | await ctx.send('**{}, your input is not valid. One possible answer was `{}`.**'.format(user.mention, sol)) 78 | else: 79 | await ctx.send('**Your input is not valid. One possible answer was `{}`**'.format(sol)) 80 | else: 81 | if '/' in answer: 82 | # Use Fractions for accuracy in divisions 83 | answer = ''.join( (('F(%s)' % char) if char in '123456789' else char) 84 | for char in answer ) 85 | 86 | if answer.lower() == "none" and sol == None: 87 | await ctx.send("**Congrats {}! You're correct!**".format(user.mention)) 88 | return True 89 | 90 | try: 91 | ans = eval(answer) 92 | except: 93 | ans = None 94 | 95 | if ans == 24: 96 | await ctx.send("**Congrats {}! You're correct!**".format(user.mention)) 97 | return True 98 | else: 99 | if sol == None: 100 | await ctx.send("**Nope, sorry {}. The correct answer was: `{}`**".format(user.mention, sol)) 101 | else: 102 | await ctx.send("**Nope, sorry {}. One possible answer was: `{}`**".format(user.mention, sol)) 103 | return 104 | 105 | def get_digits(self, num_digits=4): 106 | return [str(random.randint(1, 10)) for i in range(num_digits)] 107 | 108 | def check(self, answer, digits): 109 | if not answer: 110 | return False 111 | elif answer.lower() == "none": 112 | return True 113 | 114 | allowed = set('() +-*/\t'+''.join(digits)) 115 | ok = all(ch in allowed for ch in answer) and \ 116 | all(digits.count(dig) == answer.count(dig) for dig in set(digits)) \ 117 | and not re.search('\d\d', answer) 118 | if ok: 119 | try: 120 | ast.parse(answer) 121 | except: 122 | ok = False 123 | return ok 124 | 125 | async def solve(self, digits, target=24): 126 | digilen = len(digits) 127 | # length of an exp without brackets 128 | exprlen = 2 * digilen - 1 129 | # permute all the digits 130 | digiperm = sorted(set(permutations(digits))) 131 | # All the possible operator combinations 132 | opcomb = list(product('+-*/', repeat=digilen-1)) 133 | # All the bracket insertion points: 134 | brackets = ( [()] + [(x,y) 135 | for x in range(0, exprlen, 2) 136 | for y in range(x+4, exprlen+2, 2) 137 | if (x,y) != (0,exprlen+1)] 138 | + [(0, 3+1, 4+2, 7+3)] ) # double brackets case 139 | for d in digiperm: 140 | for ops in opcomb: 141 | if '/' in ops: 142 | d2 = [('F(%s)' % i) for i in d] # Use Fractions for accuracy 143 | else: 144 | d2 = d 145 | ex = list(chain.from_iterable(zip_longest(d2, ops, fillvalue=''))) 146 | for b in brackets: 147 | exp = ex[::] 148 | for insertpoint, bracket in zip(b, '()'*(len(b)//2)): 149 | exp.insert(insertpoint, bracket) 150 | txt = ''.join(exp) 151 | try: 152 | num = eval(txt) 153 | except ZeroDivisionError: 154 | continue 155 | if num == target: 156 | if '/' in ops: 157 | exp = [ (term if not term.startswith('F(') else term[2]) 158 | for term in exp ] 159 | ans = ' '.join(exp).rstrip() 160 | return ans 161 | return None 162 | 163 | def _draw_cards(self, cards): 164 | # create a unique id for save file 165 | rand_id = random.randint(0, 100) 166 | 167 | for idx, card in enumerate(cards): 168 | card_image = self._get_card_image(card) 169 | if idx == 0: 170 | total_width = card_image.width * len(cards) 171 | total_height = card_image.height 172 | 173 | full_im = Image.new('RGBA', (total_width, total_height)) 174 | 175 | full_im.paste(card_image, (idx*card_image.width, 0), card_image) 176 | 177 | 178 | output_im_name = 'blackjack_{}.png'.format(rand_id) 179 | output_filepath = os.path.join(self.TEMP_FOLDER, output_im_name) 180 | 181 | full_im.save(output_filepath) 182 | 183 | return output_filepath 184 | 185 | 186 | def _get_card_image(self, card): 187 | suit = int(card[0]) 188 | if suit == 1: 189 | suit_str = 'club' 190 | elif suit == 2: 191 | suit_str = 'diamond' 192 | elif suit == 3: 193 | suit_str = 'heart' 194 | elif suit == 4: 195 | suit_str = 'spade' 196 | 197 | val = int(card[1]) 198 | if val == 11: 199 | val_str = 'Jack' 200 | elif val == 12: 201 | val_str = 'Queen' 202 | elif val == 13: 203 | val_str = 'King' 204 | elif val == 14: 205 | val_str = 'Ace' 206 | else: 207 | val_str = str(val) 208 | 209 | card_file = '{}{}.png'.format(suit_str, val_str) 210 | card_full_path = os.path.join(self.CARDS_FOLDER, card_file) 211 | 212 | card_image = Image.open(card_full_path) 213 | return card_image 214 | 215 | 216 | def _get_discord_image_info(self, card_path): 217 | discord_file = discord.File(card_path, filename="cards.png") 218 | url = 'attachment://' + "cards.png" 219 | 220 | return discord_file, url 221 | 222 | 223 | def _get_cards(self, digits): 224 | cards = [] 225 | for digit in digits: 226 | rand_suit = random.randint(1, 4) 227 | card = (rand_suit, digit) 228 | 229 | while card in cards: 230 | rand_suit = random.randint(1, 4) 231 | card = (rand_suit, digit) 232 | 233 | cards.append(card) 234 | 235 | return cards 236 | -------------------------------------------------------------------------------- /cogs/fun/fun.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import aiohttp 4 | import asyncio 5 | import discord 6 | import datetime 7 | import collections 8 | from random import choice 9 | import motor.motor_asyncio 10 | from discord.ext import commands 11 | from discord.utils import get 12 | 13 | class Fun(commands.Cog): 14 | def __init__(self, bot): 15 | self.bot = bot 16 | self.server_settings = self.bot.db["fun"] 17 | 18 | self.prefix = self.bot.config['prefix'][0] 19 | self.all_users = self.bot.db["users"] 20 | 21 | client = motor.motor_asyncio.AsyncIOMotorClient() 22 | self.db = client['{}_fun'.format(self.bot.config['bot_name'])] 23 | self.markov = client['{}_markov'.format(self.bot.config['bot_name'])] 24 | 25 | # constants 26 | self.REACTION_FOLDER = os.path.join( 27 | os.getcwd(),'cogs','fun','resources','reactions') 28 | 29 | @commands.cooldown(1, 10, commands.BucketType.user) 30 | @commands.command(no_pm=True, pass_context=True) 31 | async def hug(self, ctx, user : discord.Member=None): 32 | """Hug people. 33 | 34 | [Example] 35 | + "" 36 | """ 37 | cmd_user = ctx.message.author 38 | if not user: 39 | user = cmd_user 40 | 41 | if cmd_user.id == user.id: 42 | desc = 'Hugging yourself, huh?...' 43 | else: 44 | desc = f'{cmd_user.mention} hugged {user.mention}!' 45 | 46 | react_filepath = self._get_reaction_in_folder('hug') 47 | file, url = self._get_reaction_info(react_filepath) 48 | 49 | em = discord.Embed(description=desc, colour=cmd_user.colour) 50 | em.set_image(url=url) 51 | await ctx.send(embed=em, files=[file]) 52 | 53 | def _get_reaction_in_folder(self, folder_name): 54 | react_folder = os.path.join(self.REACTION_FOLDER, folder_name) 55 | all_images = os.listdir(react_folder) 56 | 57 | rand_image_filepath = random.choice(all_images) 58 | full_filepath = os.path.join(react_folder, rand_image_filepath) 59 | return full_filepath 60 | 61 | def _get_reaction_info(self, filepath): 62 | discord_file = discord.File(filepath, 63 | filename="reaction.gif") 64 | url = 'attachment://' + "reaction.gif" 65 | 66 | return discord_file, url 67 | 68 | @commands.cooldown(1, 10, commands.BucketType.user) 69 | @commands.command(no_pm=True) 70 | async def triggered(self, ctx, user : discord.Member=None): 71 | """Become triggered. Because that's all we do these days. 72 | 73 | [Example] 74 | + "" 75 | """ 76 | cmd_user = ctx.message.author 77 | if not user: 78 | user = cmd_user 79 | 80 | if cmd_user.id == user.id: 81 | desc = 'Triggering... yourself?' 82 | else: 83 | desc = f'{cmd_user.mention} was triggered by {user.mention}!' 84 | 85 | react_filepath = self._get_reaction_in_folder('triggered') 86 | file, url = self._get_reaction_info(react_filepath) 87 | 88 | em = discord.Embed(description=desc, colour=cmd_user.colour) 89 | em.set_image(url=url) 90 | await ctx.send(embed=em, files=[file]) 91 | 92 | @commands.cooldown(1, 10, commands.BucketType.user) 93 | @commands.command(no_pm=True) 94 | async def kiss(self, ctx, user : discord.Member=None): 95 | """Kiss someone. We all know it's never going to happen irl. 96 | 97 | [Example] 98 | + "" 99 | """ 100 | cmd_user = ctx.message.author 101 | if not user: 102 | user = cmd_user 103 | 104 | if cmd_user.id == user.id: 105 | desc = 'Kissing yourself? Gross...' 106 | else: 107 | desc = f'{cmd_user.mention} kissed {user.mention}!' 108 | 109 | react_filepath = self._get_reaction_in_folder('kiss') 110 | file, url = self._get_reaction_info(react_filepath) 111 | 112 | em = discord.Embed(description=desc, colour=cmd_user.colour) 113 | em.set_image(url=url) 114 | await ctx.send(embed=em, files=[file]) 115 | 116 | @commands.cooldown(1, 10, commands.BucketType.user) 117 | @commands.command(no_pm=True) 118 | async def pout(self, ctx): 119 | """Make a pouting face. So sad. 120 | 121 | [Example] 122 | + "" 123 | """ 124 | cmd_user = ctx.message.author 125 | 126 | desc = '{} pouted...'.format(cmd_user.mention) 127 | 128 | react_filepath = self._get_reaction_in_folder('pout') 129 | file, url = self._get_reaction_info(react_filepath) 130 | 131 | em = discord.Embed(description=desc, colour=cmd_user.colour) 132 | em.set_image(url=url) 133 | await ctx.send(embed=em, files=[file]) 134 | 135 | @commands.cooldown(1, 10, commands.BucketType.user) 136 | @commands.command(no_pm=True) 137 | async def pat(self, ctx, user : discord.Member): 138 | """Pat pat. :3 139 | 140 | [Example] 141 | + "" 142 | """ 143 | cmd_user = ctx.message.author 144 | if not user: 145 | user = cmd_user 146 | 147 | 148 | if cmd_user.id == user.id: 149 | desc = 'Patting yourself, huh?...' 150 | else: 151 | desc = f'{cmd_user.mention} patted {user.mention}!' 152 | 153 | react_filepath = self._get_reaction_in_folder('pat') 154 | file, url = self._get_reaction_info(react_filepath) 155 | 156 | em = discord.Embed(description=desc, colour=cmd_user.colour) 157 | em.set_image(url=url) 158 | await ctx.send(embed=em, files=[file]) 159 | 160 | ''' 161 | @commands.cooldown(1, 10, commands.BucketType.user) 162 | @commands.command(no_pm=True) 163 | async def markov(self, ctx, start_text): 164 | """Generate a markov change based on a few texts. 165 | 166 | [Example] 167 | + is a 168 | """ 169 | pass''' 170 | 171 | @commands.cooldown(1, 10, commands.BucketType.user) 172 | @commands.command(no_pm=True, aliases = ["neko"]) 173 | async def cat(self, ctx): 174 | """Get a random picture of a cat! 175 | 176 | [Example] 177 | + 178 | """ 179 | user = ctx.message.author 180 | search = "https://nekos.life/api/v2/img/meow" 181 | try: 182 | async with aiohttp.ClientSession() as session: 183 | async with session.get(search) as r: 184 | result = await r.json() 185 | 186 | em = discord.Embed(colour=user.colour) 187 | em.set_image(url=result['url']) 188 | await ctx.send(embed = em) 189 | except: 190 | await ctx.send("**Error. Try again later.**") 191 | 192 | 193 | @commands.cooldown(1, 10, commands.BucketType.user) 194 | @commands.command(no_pm=True, aliases = ["doge","pupper"]) 195 | async def dog(self, ctx): 196 | """Get a random picture of a dog! 197 | 198 | [Example] 199 | + 200 | """ 201 | user = ctx.message.author 202 | search = "https://dog.ceo/api/breeds/image/random" 203 | try: 204 | async with aiohttp.ClientSession() as session: 205 | async with session.get(search) as r: 206 | result = await r.json() 207 | 208 | em = discord.Embed(colour=user.colour) 209 | em.set_image(url=result['message']) 210 | await ctx.send(embed = em) 211 | except: 212 | await ctx.send("**Error. Try again later.**") 213 | 214 | @commands.command(name="8ball", aliases=["8"]) 215 | async def _8ball(self, ctx, *, question : str): 216 | """Ask 8 ball a question. Question must end with a question mark. 217 | 218 | [Example] 219 | + Is owo a good bot? (the answer is no) 220 | """ 221 | 222 | responses = ["As I see it, yes", "It is certain", "It is decidedly so", "Most likely", "Outlook good", 223 | "Signs point to yes", "Without a doubt", "Yes", "Yes – definitely", "You may rely on it", "Reply hazy, try again", 224 | "Ask again later", "Better not tell you now", "Cannot predict now", "Concentrate and ask again", 225 | "Don't count on it", "My reply is no", "My sources say no", "Outlook not so good", "Very doubtful"] 226 | 227 | if question.endswith("?") and question != "?": 228 | await ctx.send("`" + choice(responses) + "`") 229 | else: 230 | await ctx.send("That doesn't look like a question.") 231 | 232 | 233 | @commands.cooldown(1, 10, commands.BucketType.user) 234 | @commands.command(no_pm=True) 235 | async def dance(self, ctx): 236 | """Do a dance! Better than you for sure. 237 | 238 | [Example] 239 | + 240 | """ 241 | cmd_user = ctx.message.author 242 | 243 | react_filepath = self._get_reaction_in_folder('dance') 244 | file, url = self._get_reaction_info(react_filepath) 245 | 246 | em = discord.Embed(description=desc, colour=cmd_user.colour) 247 | em.set_image(url=url) 248 | await ctx.send(embed=em, files=[file]) 249 | 250 | @commands.cooldown(1, 10, commands.BucketType.user) 251 | @commands.command(no_pm=True) 252 | async def insult(self, ctx, user:discord.Member): 253 | """Insult someone. That's not very nice! :c 254 | 255 | [Example] 256 | + "" 257 | """ 258 | cmd_user = ctx.message.author 259 | if not user: 260 | user = cmd_user 261 | 262 | 263 | if cmd_user.id == user.id: 264 | desc = 'Why are you insulting yourself?' 265 | else: 266 | desc = f'{cmd_user.mention} roasted {user.mention}!' 267 | 268 | react_filepath = self._get_reaction_in_folder('insult') 269 | file, url = self._get_reaction_info(react_filepath) 270 | 271 | em = discord.Embed(description=desc, colour=cmd_user.colour) 272 | em.set_image(url=url) 273 | await ctx.send(embed=em, files=[file]) 274 | 275 | 276 | @commands.cooldown(1, 10, commands.BucketType.user) 277 | @commands.command(no_pm=True) 278 | async def slap(self, ctx, user:discord.Member): 279 | """Slap someone. That must have hurt for both of you! 280 | 281 | [Example] 282 | + "" 283 | """ 284 | cmd_user = ctx.message.author 285 | if not user: 286 | user = cmd_user 287 | 288 | if cmd_user.id == user.id: 289 | desc = 'Why do you do this to yourself?' 290 | else: 291 | desc = f'{cmd_user.mention} slapped {user.mention}!' 292 | 293 | react_filepath = self._get_reaction_in_folder('slap') 294 | file, url = self._get_reaction_info(react_filepath) 295 | 296 | em = discord.Embed(description=desc, colour=cmd_user.colour) 297 | em.set_image(url=url) 298 | await ctx.send(embed=em, files=[file]) 299 | 300 | @commands.cooldown(1, 10, commands.BucketType.user) 301 | @commands.command(no_pm=True) 302 | async def smug(self, ctx): 303 | """Smug af. 304 | 305 | [Example] 306 | + 307 | """ 308 | cmd_user = ctx.message.author 309 | 310 | desc = f'{cmd_user.mention} is being smug!' 311 | 312 | react_filepath = self._get_reaction_in_folder('smug') 313 | file, url = self._get_reaction_info(react_filepath) 314 | 315 | em = discord.Embed(description=desc, colour=cmd_user.colour) 316 | em.set_image(url=url) 317 | await ctx.send(embed=em, files=[file]) 318 | 319 | 320 | @commands.cooldown(1, 60, commands.BucketType.user) 321 | @commands.command(no_pm=True) 322 | async def owo(self, ctx): 323 | """Does this really need an explanation at this point? 324 | 325 | [Example] 326 | + 327 | """ 328 | user = ctx.message.author 329 | db_user = await self.bot.get_cog('Social').get_user(user) 330 | new_owo_count = 0 331 | if db_user and "owo_count" not in db_user: 332 | new_owo_count = 1 333 | elif db_user: 334 | new_owo_count = db_user["owo_count"] + 1 335 | 336 | await ctx.send(f"**What's this?** | {user.name}, you've **owo**'d `{new_owo_count}` times.") 337 | 338 | await self.all_users.update_one({'user_id': user.id}, {'$set': 339 | {'owo_count':new_owo_count}}) 340 | 341 | @commands.cooldown(1, 10, commands.BucketType.user) 342 | @commands.command(no_pm=True) 343 | async def loss(self, ctx): 344 | """This is loss. 345 | 346 | [Options] 347 | Link (-l): Check if an image is loss. 348 | 349 | [Example] 350 | + 351 | """ 352 | cmd_user = ctx.message.author 353 | 354 | desc = f'{cmd_user.mention} Loss.' 355 | 356 | react_filepath = self._get_reaction_in_folder('loss') 357 | file, url = self._get_reaction_info(react_filepath) 358 | 359 | em = discord.Embed(description=desc, colour=cmd_user.colour) 360 | em.set_image(url=url) 361 | await ctx.send(embed=em, files=[file]) 362 | 363 | 364 | @commands.cooldown(1, 10, commands.BucketType.user) 365 | @commands.command(aliases = ['tsun']) 366 | async def tsundere(self,ctx): 367 | """B-baka! I died a bit on the inside after writing that. 368 | 369 | [Example] 370 | + 371 | """ 372 | user = ctx.message.author 373 | channel = ctx.message.channel 374 | server = user.guild 375 | 376 | phrases = [ 377 | "I-It's not that I like you or anything!", 378 | "B-Baka!", "N-No, it's not like I did it for you! I did it because I had free time, that's all!", 379 | "I like you, you idiot!", "I'm just here because I had nothing else to do!", "Are you stupid?", 380 | "Don't misunderstand, it's not that I like you or anything!", 381 | "T-Tch! S-Shut up!", "Can you be ANY MORE CLUELESS?", 382 | "I-I am not a tsundere, you b-baka!", "Don't get the wrong idea.", 383 | "I'm doing this p-purely for my own benefit. So d-don't misunderstand, you idiot!", 384 | 'Geez, stop pushing yourself! You\'re going to get yourself hurt one day, you idiot!' 385 | ] 386 | phrase = random.choice(phrases) 387 | cmd_user = ctx.message.author 388 | 389 | if cmd_user.id == user.id: 390 | desc = cmd_user.mention + ' ' + phrase 391 | 392 | react_filepath = self._get_reaction_in_folder('tsundere') 393 | file, url = self._get_reaction_info(react_filepath) 394 | 395 | em = discord.Embed(description=desc, colour=cmd_user.colour) 396 | em.set_image(url=url) 397 | await ctx.send(embed=em, files=[file]) 398 | 399 | 400 | @commands.cooldown(1, 30, commands.BucketType.user) 401 | @commands.command(no_pm=True, aliases=['easteregg', 'ee']) 402 | async def eastereggs(self, ctx): 403 | """Check how many easter eggs you've found! 404 | 405 | [Example] 406 | + 407 | """ 408 | user = ctx.message.author 409 | channel = ctx.message.channel 410 | 411 | db_user = await self.bot.get_cog('Social').get_user(user) 412 | ee_count = 0 413 | if db_user and "eastereggs_found" in db_user: 414 | ee_count = len(db_user["eastereggs_found"]) 415 | else: 416 | ee_count = 0 417 | 418 | await ctx.send(f"**{user.name}, you've found `{ee_count}` easter eggs!**") 419 | 420 | 421 | def setup(bot): 422 | n = Fun(bot) 423 | bot.add_cog(n) 424 | -------------------------------------------------------------------------------- /cogs/fun/multiplayer.py: -------------------------------------------------------------------------------- 1 | class Multiplayer: 2 | def __init__(self): 3 | pass 4 | 5 | -------------------------------------------------------------------------------- /cogs/fun/resources/reactions/owo/tenor (1).gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/fun/resources/reactions/owo/tenor (1).gif -------------------------------------------------------------------------------- /cogs/fun/resources/reactions/owo/tenor (10).gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/fun/resources/reactions/owo/tenor (10).gif -------------------------------------------------------------------------------- /cogs/fun/resources/reactions/owo/tenor (2).gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/fun/resources/reactions/owo/tenor (2).gif -------------------------------------------------------------------------------- /cogs/fun/resources/reactions/owo/tenor (3).gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/fun/resources/reactions/owo/tenor (3).gif -------------------------------------------------------------------------------- /cogs/fun/resources/reactions/owo/tenor (4).gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/fun/resources/reactions/owo/tenor (4).gif -------------------------------------------------------------------------------- /cogs/fun/resources/reactions/owo/tenor (5).gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/fun/resources/reactions/owo/tenor (5).gif -------------------------------------------------------------------------------- /cogs/fun/resources/reactions/owo/tenor (6).gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/fun/resources/reactions/owo/tenor (6).gif -------------------------------------------------------------------------------- /cogs/fun/resources/reactions/owo/tenor (7).gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/fun/resources/reactions/owo/tenor (7).gif -------------------------------------------------------------------------------- /cogs/fun/resources/reactions/owo/tenor (8).gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/fun/resources/reactions/owo/tenor (8).gif -------------------------------------------------------------------------------- /cogs/fun/resources/reactions/owo/tenor (80).gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/fun/resources/reactions/owo/tenor (80).gif -------------------------------------------------------------------------------- /cogs/fun/resources/reactions/owo/tenor (9).gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/fun/resources/reactions/owo/tenor (9).gif -------------------------------------------------------------------------------- /cogs/fun/resources/reactions/owo/tenor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AznStevy/owo-bot/f7651f3cb7d67ba4d93e8ffa96d7a1d3120b2bd8/cogs/fun/resources/reactions/owo/tenor.gif -------------------------------------------------------------------------------- /cogs/fun/resources/trivia/language.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"\"Entre nous\" means __________.\"", "answer":0, "answers":["Between ourselves"], "source":""}, 3 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"\"faux pas\" means ___________.\"", "answer":0, "answers":["Mistake"], "source":""}, 4 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"An adjective meaning 'pertaining to the sun.'", "answer":0, "answers":["Solar"], "source":""}, 5 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"From what Irish words is 'Dublin' derived?", "answer":0, "answers":["Dubh linn"], "source":""}, 6 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"From what language is the term 'finito'?", "answer":0, "answers":["Italian"], "source":""}, 7 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"From which language does the term 'Mayday' come?", "answer":0, "answers":["French"], "source":""}, 8 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"How do you say \"I Love You\" in German?\"", "answer":0, "answers":["Ich liebe Dich"], "source":""}, 9 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"Many Meanings: Fuel, vapor, flattulence, helium. What is it?", "answer":0, "answers":["Gas"], "source":""}, 10 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"Many Meanings: Fuel, vapor, flattulence, helium. What is it", "answer":0, "answers":["Gas"], "source":""}, 11 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"Mardi Gras is French for ___________.", "answer":0, "answers":["Fat tuesday"], "source":""}, 12 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"Merging the words 'melt' and 'weld' created which word?", "answer":0, "answers":["Meld"], "source":""}, 13 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"Multiple Meanings: Drinking utensils or sight-enhancers.", "answer":0, "answers":["Glasses"], "source":""}, 14 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"Multiple Meanings: Slamming your hands together quickly, or a venereal disease.", "answer":0, "answers":["Clap"], "source":""}, 15 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"Name the soda that is often confused with a drug.", "answer":0, "answers":["Coke"], "source":""}, 16 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"Other than Germany, whose official language is German?", "answer":0, "answers":["Austria"], "source":""}, 17 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"Subject, verb and object are parts of a _________.", "answer":0, "answers":["Sentence"], "source":""}, 18 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"The name Australia is derived from the Latin word \"australis\" which means _______.\"", "answer":0, "answers":["Southern"], "source":""}, 19 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"The name for this semi_precious stone comes from the Latin for \"sea water\"", "answer":0, "answers":["Aquamarine"], "source":""}, 20 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"The name for this semi-precious stone comes from the Latin for \"sea water\"", "answer":0, "answers":["Aquamarine"], "source":""}, 21 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"The Scots call it 'shinty' - what do Canadians and Americans call it?", "answer":0, "answers":["Hockey"], "source":""}, 22 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"The word rodent comes from the italian 'rodere', which means?", "answer":0, "answers":["Gnaw"], "source":""}, 23 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"This animal is found at the beginning of an (English) encyclopedia", "answer":0, "answers":["Aardvark"], "source":""}, 24 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What city's name is derived from the words 'dubh linn'?", "answer":0, "answers":["Dublin"], "source":""}, 25 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What do the initials 'VCR' stand for?", "answer":0, "answers":["Video Cassette Recorder"], "source":""}, 26 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What does 'A&W' (of root beer fame) stand for ?", "answer":0, "answers":["Allen & Wright"], "source":""}, 27 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What does 'alma mater' mean in English?", "answer":0, "answers":["Bountiful mother"], "source":""}, 28 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What does 'AOL' stand for?", "answer":0, "answers":["America Online"], "source":""}, 29 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What does 'majuba' mean?", "answer":0, "answers":["Place of rock pigeons"], "source":""}, 30 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What does 'shogun' mean in English?", "answer":0, "answers":["Military governor"], "source":""}, 31 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What does \"c'est la vie\" mean?\"", "answer":0, "answers":["That's life"], "source":""}, 32 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What does \"c'est la vie\" mean\"", "answer":0, "answers":["That's life"], "source":""}, 33 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What does N.A.S.A stand for?", "answer":0, "answers":["National Aeronautics and Space Administration"], "source":""}, 34 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What does S.O.S. stand for?", "answer":0, "answers":["Save Our Souls"], "source":""}, 35 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What does SOS stand for", "answer":0, "answers":["Save Our Souls"], "source":""}, 36 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What does the abbreviation N/A mean?", "answer":0, "answers":["Not applicable"], "source":""}, 37 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What does the acronym 'scuba' mean?", "answer":0, "answers":["Self Contained Underwater Breathing Apparatus"], "source":""}, 38 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What does the Greek root word 'chrom' mean?", "answer":0, "answers":["Color"], "source":""}, 39 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What does the Irish 'dubh linn' mean?", "answer":0, "answers":["Black pool"], "source":""}, 40 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What does the word 'karate' translate to in English?", "answer":0, "answers":["Open hand"], "source":""}, 41 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What is 'blackpool' in Irish?", "answer":0, "answers":["Dubh linn"], "source":""}, 42 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What is 'bountiful mother' in Latin?", "answer":0, "answers":["Alma mater"], "source":""}, 43 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What is 'military governor' in Japanese?", "answer":0, "answers":["Shogun"], "source":""}, 44 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What is the English word for 'fiesta'?", "answer":0, "answers":["Festival"], "source":""}, 45 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What is the first letter in the Greek alphabet?", "answer":0, "answers":["Alpha"], "source":""}, 46 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What is the Israeli 'knesset'?", "answer":0, "answers":["Parliament"], "source":""}, 47 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What is the language of Hungary?", "answer":0, "answers":["Magyar"], "source":""}, 48 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What is the last letter in the Greek alphabet?", "answer":0, "answers":["Omega"], "source":""}, 49 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What is the literal meaning of 'pince-nez'?", "answer":0, "answers":["Pinch nose"], "source":""}, 50 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What is the meaning of the Mercedes Benz motto 'Das beste oder nichts'?", "answer":0, "answers":["The best or nothing"], "source":""}, 51 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What is the official language of Austria?", "answer":0, "answers":["German"], "source":""}, 52 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What is the official language of Senegal?", "answer":0, "answers":["French"], "source":""}, 53 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What is the Old English word for 'sneeze'?", "answer":0, "answers":["Fneasan"], "source":""}, 54 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What is the only English word formed by the first three letters of the alphabet", "answer":0, "answers":["Cab"], "source":""}, 55 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What is the only English word formed by the first three letters of the alphabet?", "answer":0, "answers":["Cab"], "source":""}, 56 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What is the Spanish word for 'festival'?", "answer":0, "answers":["Fiesta"], "source":""}, 57 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What ONE word fits ____hood; ____hole; ____date.\"", "answer":0, "answers":["Man"], "source":""}, 58 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What ONE word fits ____stream; ____hill; _____pour.\"", "answer":0, "answers":["Down"], "source":""}, 59 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What ONE word fits? ____hood; ____hole; ____date.\"", "answer":0, "answers":["Man"], "source":""}, 60 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What ONE word fits? ____stream; ____hill; _____pour.\"", "answer":0, "answers":["Down"], "source":""}, 61 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What three letter word means the same as \"to ingest\"?", "answer":0, "answers":["Eat"], "source":""}, 62 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What three letter word means the same as \"to ingest\"", "answer":0, "answers":["Eat"], "source":""}, 63 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What two words make the word 'meld'?", "answer":0, "answers":["Melt and weld"], "source":""}, 64 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What was the language of ancient India?", "answer":0, "answers":["Sanskrit"], "source":""}, 65 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What word contains the combination of letters: \"xop\"?", "answer":0, "answers":["SaXOPhone"], "source":""}, 66 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"What word contains the combination of letters: \"xop\"", "answer":0, "answers":["Saxophone"], "source":""}, 67 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"Which is the only english word that contains all the vowels in alphabetical order?", "answer":0, "answers":["Facetious"], "source":""}, 68 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"Which two fruits are an anagram of each other?", "answer":0, "answers":["Lemon and melon"], "source":""}, 69 | {"category_id":"LANGUAGE", "lang":"en", "tags":["LANGUAGE"], "question":"Which word means \"profound boredom\" in both french and english?\"", "answer":0, "answers":["Ennui"], "source":""} 70 | ] 71 | -------------------------------------------------------------------------------- /cogs/fun/resources/trivia/mathematics.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"2 + 5 x 6 = ?", "answer":0, "answers":["32"], "source":""}, 3 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"2.7182 is the approximation for which variable used in logarithms?", "answer":0, "answers":["E"], "source":""}, 4 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"A line drawn from an angle of a triangle to the mid_point of the opposite side is a(n) _______.", "answer":0, "answers":["Median"], "source":""}, 5 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"A line drawn from an angle of a triangle to the mid-point of the opposite side is a(n) _______.", "answer":0, "answers":["Median"], "source":""}, 6 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"A triangle with three equal sides is called _______.", "answer":0, "answers":["Equilateral"], "source":""}, 7 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"A triangle with two equal sides is called __________.", "answer":0, "answers":["Isosceles"], "source":""}, 8 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"An angle greater than 180 degrees and less than 360 degrees is a(n) ________ angle.", "answer":0, "answers":["Reflex"], "source":""}, 9 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"An angle greater than 90 degrees and less than 180 degrees is said to be _________.", "answer":0, "answers":["Obtuse"], "source":""}, 10 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"An angle greater than 90 degrees is said to be _________.", "answer":0, "answers":["Obtuse"], "source":""}, 11 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"An integer that is greater than 1 and is divisible only by itself and 1 is known as a(n) _______.", "answer":0, "answers":["Prime"], "source":""}, 12 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"Approximately how many inches are there in one meter", "answer":0, "answers":["39"], "source":""}, 13 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"Approximately how many inches are there in one meter?", "answer":0, "answers":["Thirty nine"], "source":""}, 14 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"Arc, radius, and sector are parts of a(n) _________.", "answer":0, "answers":["Circle"], "source":""}, 15 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"Benoit Mandelbrot discovered what mathematical structures?", "answer":0, "answers":["Fractals"], "source":""}, 16 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"He is known as \"The Father of Geometry\".", "answer":0, "answers":["Euclid"], "source":""}, 17 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"How many corners are there in a cube", "answer":0, "answers":["Eight"], "source":""}, 18 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"How many corners are there in a cube?", "answer":0, "answers":["Eight"], "source":""}, 19 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"How many different letters are used in the roman numeral system?", "answer":0, "answers":["Seven"], "source":""}, 20 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"How many nickles are there in $2.25", "answer":0, "answers":["45"], "source":""}, 21 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"How many nickles are there in 2.25?", "answer":0, "answers":["Forty five"], "source":""}, 22 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"If you count from 1 to 100, how many 7's will you come across?", "answer":0, "answers":["20"], "source":""}, 23 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"If you cut through a solid sphere what shape will the flat area be", "answer":0, "answers":["Circle"], "source":""}, 24 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"If you cut through a solid sphere what shape will the flat area be?", "answer":0, "answers":["Circle"], "source":""}, 25 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"Name the number system which uses only the symbols 1 and 0.", "answer":0, "answers":["The binary system"], "source":""}, 26 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"Solve this: 10*3+2?", "answer":0, "answers":["32"], "source":""}, 27 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"The angles inside a square total _______ degrees.", "answer":0, "answers":["360"], "source":""}, 28 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"The first antiderivative of acceleration is:", "answer":0, "answers":["Velocity"], "source":""}, 29 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"The mathematical study of properties of lines, angels, etc., is ________.", "answer":0, "answers":["Geometry"], "source":""}, 30 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"The space occupied by a body is called its ______.", "answer":0, "answers":["Volume"], "source":""}, 31 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"The square root of 1 is?", "answer":0, "answers":["1"], "source":""}, 32 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"Two angles that total 180 degrees are called _______.", "answer":0, "answers":["Supplementary"], "source":""}, 33 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"What geometric shape has 4 equal sides", "answer":0, "answers":["Square"], "source":""}, 34 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"What geometric shape has 4 equal sides?", "answer":0, "answers":["Square"], "source":""}, 35 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"What is 65% of 60?", "answer":0, "answers":["39"], "source":""}, 36 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"What is next in the series 1 8 27 ?? 125 216?", "answer":0, "answers":["64"], "source":""}, 37 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"What is the maximum number of integer degrees in a reflex angle?", "answer":0, "answers":["359"], "source":""}, 38 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"What is the maximum number of integer degrees in an acute angle?", "answer":0, "answers":["89"], "source":""}, 39 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"What is the maximum number of integer degrees in an obtuse angle?", "answer":0, "answers":["179"], "source":""}, 40 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"What is the minimum number of integer degrees in a reflex angle?", "answer":0, "answers":["181"], "source":""}, 41 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"What is the minimum number of integer degrees in an acute angle?", "answer":0, "answers":["One"], "source":""}, 42 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"What is the minimum number of integer degrees in an obtuse angle?", "answer":0, "answers":["91"], "source":""}, 43 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"What is the name given to a curve that approaches a line, but never quite touches it?", "answer":0, "answers":["Asymptote"], "source":""}, 44 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"What is the name given to the number equal to 10 raised to the power of 100?", "answer":0, "answers":["A \"googol\""], "source":""}, 45 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"What is the only digit that has the same number of letters as its value?", "answer":0, "answers":["Four"], "source":""}, 46 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"What is the square root of -1 ?", "answer":0, "answers":["I"], "source":""}, 47 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"What is the square root of one quarter?", "answer":0, "answers":["One half"], "source":""}, 48 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"What is x to the power of zero equal to?", "answer":0, "answers":["One"], "source":""}, 49 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"Who invented logarithms ?", "answer":0, "answers":["John Napier"], "source":""}, 50 | {"category_id":"MATHEMATICS", "lang":"en", "tags":["MATHEMATICS"], "question":"Who proved Fermat's Last Theorem ?", "answer":0, "answers":["Andrew Wiles"], "source":""} 51 | ] 52 | -------------------------------------------------------------------------------- /cogs/general/changelog.json: -------------------------------------------------------------------------------- 1 | { 2 | "3.5.0": { 3 | "osu": [ 4 | "Improved caching system + decreased cooldowns", 5 | "Changed `-p` for play number to `-i` for index (starting from 1) (i.e. `>osutop -i 10`)", 6 | "Partial support for 8+ other private servers. Check in `>botinfo` command.", 7 | "New `>osu -d` and `>osu -s` commands for bancho", 8 | "New `-10` (condensed) / `-nc` (no-choke) option for top.", 9 | "New `>osutop` sorting and search commands. View using `>h osutop`.", 10 | "Commands like `>rs mania` will be now be `>rs -mania`.", 11 | "Map search feature. `>map -? \"search terms here\"`", 12 | "Map feed feature, `>mapfeed`.", 13 | "Improved pp calculation for non-std modes", 14 | "Changed beatmap download mirrors.", 15 | "New supporter feature that generates score image.", 16 | "Many more additions. Use the `>help (command)` function to see all options.", 17 | "Url and screenshot detection re-enabled.", 18 | "Added recomendations for other modes, albeit with limited selection. e.g. `>r -m 3`", 19 | "Added forced username option (`-u`) to ignore discord users.", 20 | "The `>track clear` command has been changed to `>track remove -a`." 21 | ], 22 | "general": [ 23 | "Added `>changelog` command.", 24 | "Updated `>userinfo` and `serverinfo` commands." 25 | ], 26 | "fun": [ 27 | "Added more images to reaction commands." 28 | ], 29 | "economy": [ 30 | "Fixed blackjack (`>blackjack`) and made card games image-based." 31 | ], 32 | "settings": [ 33 | "Updated `>overview` function to display current settings." 34 | ], 35 | "other": [ 36 | "Updated sharding scheme for bot and database", 37 | "TBA" 38 | ] 39 | } 40 | } -------------------------------------------------------------------------------- /cogs/osu/bancho_crawler.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import asyncio 3 | import re 4 | import json 5 | import time as time_obj 6 | import threading 7 | import motor.motor_asyncio 8 | from time import strftime, time, sleep 9 | from pymongo import MongoClient 10 | from multiprocessing import Process 11 | 12 | class BanchoBot(): 13 | def __init__(self, config): 14 | self.bancho_usr = config["API_KEYS"]["BANCHO"]['USERNAME'] 15 | self.bancho_pass = config["API_KEYS"]["BANCHO"]['PASSWORD'] 16 | self.msg_chan = '#osu' 17 | 18 | # Define conditions where the loop should restart automatically 19 | self.client = motor.motor_asyncio.AsyncIOMotorClient( 20 | port=config["database"]["primary"], 21 | connectTimeoutMS=5000, 22 | socketTimeoutMS=5000, 23 | serverSelectionTimeoutMS=5000) 24 | self.db = self.client['owo_database'] 25 | 26 | # sets are faster than lists 27 | self.online_set = set() 28 | self.track_set = set() 29 | 30 | # timer 31 | self.STALE_LIMIT = 5 32 | self.POLL_INTERVAL = 5 # seconds 33 | self.prev_online_num = 0 34 | self.prev_track_num = 0 35 | self.stale_counter = 0 36 | self.time = time() 37 | 38 | async def initialize_list(self): 39 | await self.create_db() 40 | 41 | # empty list 42 | await self.db.online.update_one({'type': 'userlist'}, 43 | {'$set':{"userlist": list(self.track_set)}}) 44 | await self.db.online.update_one({'type': 'onlinelist'}, 45 | {'$set':{"onlinelist": list(self.online_set)}}) 46 | 47 | async def create_db(self): 48 | test = await self.db.online.find_one({'type': 'userlist'}) 49 | if not test: 50 | await self.db.online.insert_one({'type': 'userlist', 'userlist': []}) 51 | 52 | test = await self.db.online.find_one({'type': 'onlinelist'}) # in case people aren't tracked 53 | if not test: 54 | await self.db.online.insert_one({'type': 'onlinelist', 'onlinelist': []}) 55 | 56 | async def add_online(self, username): 57 | 58 | # tests different variations of usernames 59 | test_usernames = [username] 60 | if "_" in username and username.find("_") != 0: 61 | test_usernames.append(username.replace("_", " ")) 62 | if " " in username: 63 | test_usernames.append(username.replace(" ", "_")) 64 | 65 | for username in test_usernames: 66 | if username not in self.online_set: 67 | self.online_set.add(username) 68 | print("Online {} | Online List {}".format(username, len(self.online_set))) 69 | 70 | if username not in self.track_set: 71 | self.track_set.add(username) 72 | print("Added {} | Tracking {}".format(username, len(self.track_set))) 73 | 74 | async def remove_online(self, username): 75 | 76 | test_usernames = [username] 77 | if "_" in username and username.find("_") != 0: 78 | test_usernames.append(username.replace("_", " ")) 79 | if " " in username: 80 | test_usernames.append(username.replace(" ", "_")) 81 | 82 | self.online_set.difference_update(test_usernames) 83 | print("Offline {} | Online List {}".format(username, len(self.online_set))) 84 | self.track_set.difference_update(test_usernames) 85 | print("Removed {} | Tracking {}".format(username, len(self.track_set))) 86 | 87 | """ 88 | self.online_set.discard(username) 89 | print("Offline {} | Online List {}".format(username, len(self.online_set))) 90 | self.track_set.discard(username) 91 | print("Removed {} | Tracking {}".format(username, len(self.track_set))) 92 | """ 93 | 94 | async def bancho(self): 95 | await self.initialize_list() 96 | self.stale_counter = 0 97 | self.prev_online_num = 0 98 | self.prev_track_num = 0 99 | 100 | network = 'irc.ppy.sh' 101 | port = 6667 102 | 103 | global irc 104 | irc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 105 | irc.connect((network, port)) 106 | irc.send(bytearray('PASS {}\r\n'.format(self.bancho_pass), encoding='utf-8')) 107 | irc.send(bytearray('NICK {}\r\n'.format(self.bancho_usr), encoding='utf-8')) 108 | irc.send(bytearray('USER {} {} {}\r\n'.format(self.bancho_usr, self.bancho_usr, self.bancho_usr), encoding='utf-8')) 109 | print('CONNECTED to BANCHO') 110 | 111 | # Joins the default channel initially---> 112 | irc.send(b'JOIN #osu\r\n') 113 | 114 | # Populate the channel list--------------------> 115 | irc.send(bytearray('LIST\r\n', encoding='utf-8')) 116 | 117 | while True: 118 | time_elapsed = time() - self.time 119 | # print(time_elapsed) 120 | if time_elapsed > self.POLL_INTERVAL: 121 | # check number of people 122 | num_online = len(self.online_set) 123 | num_track = len(self.track_set) 124 | 125 | # otherwise do these 126 | await self.database_poll() 127 | self.time = time() 128 | 129 | # check if connection is broken 130 | # print(num_online, self.prev_online_num, num_track, self.prev_track_num) 131 | if num_online == self.prev_online_num and num_track == self.prev_track_num: 132 | self.stale_counter += 1 133 | print("STALE COUNTER: ", self.stale_counter) 134 | else: 135 | self.stale_counter = 0 136 | 137 | if self.stale_counter >= self.STALE_LIMIT: 138 | print("RESTARTING CONNECTION") 139 | break 140 | self.prev_online_num = num_online 141 | self.prev_track_num = num_track 142 | 143 | await self.parse_messages() 144 | # await asyncio.sleep(0.1) 145 | 146 | #restart if broken 147 | try: 148 | irc.close() 149 | except: 150 | pass 151 | 152 | await asyncio.sleep(1) # arbitrary 153 | await self.bancho() 154 | 155 | async def database_poll(self): 156 | print("Updating Database") 157 | 158 | # send commands 159 | commands = await self.db.irc_commands.find_one({"type":"commands"}) 160 | if commands: 161 | try: 162 | commands = commands["commands"] 163 | for command in commands: 164 | try: 165 | irc.send(command.encode('utf-8')) 166 | print("SENT {}".format(command)) 167 | except: 168 | pass 169 | await self.db.irc_commands.update_one({"type":"commands"},{'$set':{"commands": []}}) 170 | except: 171 | pass 172 | 173 | # update list 174 | await self.db.online.update_one({'type': 'userlist'}, 175 | {'$set':{"userlist": list(self.track_set)}}) 176 | await self.db.online.update_one({'type': 'onlinelist'}, 177 | {'$set':{"onlinelist": list(self.online_set)}}) 178 | await asyncio.sleep(1) 179 | 180 | async def parse_messages(self): 181 | # process incoming 182 | try: 183 | data = irc.recv(4096) 184 | dats = data.decode('utf-8') 185 | # print(dats) 186 | a = dats.split('\n') 187 | except: 188 | return 189 | 190 | # print(a) 191 | 192 | for b in a: 193 | # print(b) 194 | for e in a: 195 | if 'No such nick' in e or 'End of /WHOIS' in e: 196 | try: 197 | c = e.split(':', maxsplit=2) 198 | # print(c) 199 | info = c[1].split() 200 | username = info[3] 201 | if 'No such nick' in e: 202 | # print('{} left game'.format(username)) 203 | await self.remove_online(username) 204 | # pass 205 | if 'End of /WHOIS' in e: 206 | if username not in self.online_set: 207 | self.online_set.add(username) 208 | print("Online {} | Online List {}".format(username, len(self.online_set))) 209 | except: 210 | pass 211 | 212 | if 'PING' in b: 213 | print("PONG") 214 | irc.send(b'PONG \r\n') 215 | 216 | if 'JOIN' in b: 217 | # for tracking 218 | try: 219 | c = b.split(':', maxsplit=2) 220 | info = c[1].split() 221 | msg_author = info[0].split('!')[0] 222 | msg_author = msg_author.replace('+','') 223 | await self.add_online(msg_author) 224 | except: 225 | pass 226 | # print('Error c', c) 227 | 228 | if 'QUIT' in b: 229 | try: 230 | c = b.split(':', maxsplit=2) 231 | info = c[1].split() 232 | msg_author = info[0].split('!')[0] 233 | if msg_author in self.online_set: 234 | irc.send('WHOIS {}\r\n'.format(msg_author).encode('utf-8')) 235 | print('Testing channel/server leave for {}'.format(msg_author)) 236 | except: 237 | pass 238 | 239 | 240 | if 'cho.ppy.sh 322 Stevy #' in dats: 241 | a = dats.split('\n') 242 | join_servers = [] 243 | for e in a: 244 | if 'cho.ppy.sh 322 Stevy #' in e: 245 | c = e.split(':', maxsplit=2) 246 | d = c[1].split(' ') 247 | join_servers.append(d[3]) 248 | 249 | for server in join_servers: 250 | irc.send('JOIN {}\r\n'.format(server).encode('utf-8')) 251 | 252 | if 'cho.ppy.sh 353' in dats and 'NAMES' in dats: 253 | """ 254 | This grabs all current uses in the channel. 255 | """ 256 | a = dats.split('\n') 257 | for e in a: 258 | if 'cho.ppy.sh 353' in e: 259 | try: 260 | # print(b) 261 | c = e.split(':') 262 | name_list = c[2].split() 263 | for name in name_list: 264 | await self.add_online(name.replace('+','')) 265 | except: 266 | pass 267 | 268 | if __name__ == '__main__': 269 | with open('config.json', 'rb') as f: 270 | config = json.load(f) 271 | 272 | loop = asyncio.get_event_loop() 273 | loop.run_until_complete(BanchoBot(config).bancho()) 274 | 275 | 276 | -------------------------------------------------------------------------------- /cogs/osu/osu_utils/chunks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | """oppai-chunks 3 | 4 | Moving-window difficulty calculation for osu beatmaps. 5 | Intended for mappers to check difficulty spikes/dips. 6 | 7 | Run in CLI as ./oppai-chunks.py 8 | Import oppai() and run oppai('path_to_beatmap') 9 | """ 10 | import codecs 11 | import json 12 | import sys 13 | import tempfile 14 | try: 15 | import pyoppai 16 | except: 17 | raise RuntimeError("""You need to install pyoppai before you 18 | can run this program. https://github.com/Francesco149/oppai 19 | Check here for how to install it.""") 20 | #import matplotlib.pyplot as plt 21 | 22 | 23 | def print_usage(): 24 | """Instructions on using the script 25 | """ 26 | print( 27 | "oppai-chunks.py\n" 28 | "Usage: ./oppai-chunks.py beatmap [window [step]]\n" 29 | "beatmap is a .osu file for a specific difficulty.\n" 30 | "window is a number indicating the window size in ms\n" 31 | "step is a number indicating the step size in ms\n" 32 | "You can unzip a .osz file to extract the .osu files.\n" 33 | ) 34 | 35 | 36 | class ParseError(Exception): 37 | """Given file doesn't fit expected .osu format""" 38 | pass 39 | 40 | 41 | def read(bmap): 42 | """Read in beatmap and split it into metadata and hitcircles 43 | Given a filename or the beatmap in one string or a list of lines, 44 | return two lists of lines - beatmap metadata and hit objects. 45 | Arguments: 46 | bmap {str, str list} -- filename (file.osu), or an already read 47 | beatmap as a single string (file.read) or lines (file.readlines) 48 | Returns: 49 | (str list, str list) -- (metadata, hitcircles) 50 | Raises: 51 | ParseError -- If [Hitobjects] not found (not an .osu file) 52 | """ 53 | if bmap[-4:] == '.osu': 54 | # filename 55 | with codecs.open(bmap, 'r', 'utf-8') as beatmap: 56 | map_lines = beatmap.readlines() 57 | else: 58 | try: 59 | # file.read 60 | map_lines = bmap.split('\n') 61 | except AttributeError: 62 | # file.readlines 63 | map_lines = bmap 64 | try: # Unicode life 65 | for i, line in enumerate(map_lines): 66 | map_lines[i] = line.decode() 67 | except AttributeError: 68 | # Already decoded 69 | pass 70 | index = -1 71 | for i, line in enumerate(map_lines): 72 | if line.startswith('[HitObjects]'): 73 | index = i 74 | break 75 | if index == -1: 76 | raise ParseError('Missing "[HitObjects]"') 77 | metadata = map_lines[:index] 78 | hitcircles = map_lines[index + 1:] 79 | return (metadata, hitcircles) 80 | 81 | 82 | def parse_meta(metadata): 83 | """Parse necessary metadata lines from the beatmap. 84 | Arguments: 85 | metadata {str list} -- Metadata section of beatmap 86 | Raises: 87 | ParseError -- For missing fields 88 | """ 89 | bm_info = {} 90 | bm_info['Title'] = [x for x in metadata if x.startswith('Title:')] 91 | bm_info['Artist'] = [x for x in metadata if x.startswith('Artist:')] 92 | bm_info['Mapper'] = [x for x in metadata if x.startswith('Creator:')] 93 | bm_info['Diff name'] = [x for x in metadata if x.startswith('Version:')] 94 | bm_info['HP'] = [x for x in metadata if x.startswith('HPDrainRate:')] 95 | bm_info['CS'] = [x for x in metadata if x.startswith('CircleSize:')] 96 | bm_info['OD'] = [x for x in metadata if x.startswith('OverallDifficulty:')] 97 | bm_info['AR'] = [x for x in metadata if x.startswith('ApproachRate:')] 98 | bm_info['SV'] = [x for x in metadata if x.startswith('SliderMultiplier:')] 99 | bm_info['TR'] = [x for x in metadata if x.startswith('SliderTickRate:')] 100 | if [] in bm_info.values(): 101 | missing = [x for x in bm_info if bm_info[x] == []] 102 | raise ParseError(', '.join(missing)) 103 | # Recover from using a list comprehension for everything 104 | bm_info = {x: bm_info[x][0] for x in bm_info} 105 | # The one line with a guaranteed position 106 | bm_info['format_ver'] = metadata[0] 107 | # Compose the necessary heading parts to keep oppai happy 108 | # and enable difficulty calculation 109 | bm_head = ''.join((bm_info['format_ver'], 110 | '[General]\r\n', 111 | '[Metadata]\r\n', 112 | bm_info['Title'], 113 | bm_info['Artist'], 114 | bm_info['Mapper'], 115 | bm_info['Diff name'], 116 | '[Difficulty]\r\n', 117 | bm_info['HP'], 118 | bm_info['CS'], 119 | bm_info['OD'], 120 | bm_info['AR'], 121 | bm_info['SV'], 122 | bm_info['TR'], 123 | '[TimingPoints]\r\n', 124 | '[HitObjects]\r\n')) 125 | return bm_head 126 | 127 | 128 | def chunks(bmap, mods=0, window_length=3000, step_size=500): 129 | """Open beatmap and process 130 | 131 | Runs oppai on chunks (default 30 sec windows) of the beatmap at regular 132 | intervals (default 5 second step). 133 | 134 | Arguments: 135 | bmap {} -- the lines of a beatmap file 136 | window_length {int} -- Window length in ms 137 | step_size {int} --- Step size in ms 138 | 139 | Returns: 140 | {list} -- A list of tuples for each chunk formatted as follows: 141 | (chunk start time (ms), overall stars, aim stars, speed stars) 142 | """ 143 | metadata, hitcircles = read(bmap) 144 | bm_head = parse_meta(metadata) 145 | results = [] # Array of (time, stars, aim stars, speed stars) tuples 146 | seek = 0 # Time in ms 147 | with tempfile.TemporaryDirectory() as tmpdir: 148 | while hitcircles: 149 | # Slice out window of beatmap 150 | try: 151 | window = [x for x in hitcircles 152 | if int(x.split(',')[2]) < seek + window_length] 153 | except IndexError: 154 | raise ParseError('Unexpected line in [HitObjects] section') 155 | if len(window) == 0: 156 | results.append({'time':seek, 'stars':0, 'speed_stars':0, 'aim_stars':0}) 157 | seek = seek + 5000 158 | hitcircles = [x for x in hitcircles 159 | if int(x.split(',')[2]) > seek] 160 | continue 161 | out = ''.join(window) 162 | with open(tmpdir + '/tmp.osu', 'w', encoding='utf-8') as tmp: 163 | tmp.write(bm_head + out) 164 | oppai_out = get_pyoppai(tmpdir + '/tmp.osu', mods=mods) 165 | oppai_out['time'] = seek 166 | results.append(oppai_out) 167 | 168 | # Step to next window 169 | seek = seek + step_size 170 | hitcircles = [x for x in hitcircles 171 | if int(x.split(',')[2]) > seek] 172 | return results 173 | 174 | def get_pyoppai(btmap_file, mods=0): # Pyoppai implementation rather than subprocess 175 | btmap = btmap_file 176 | ctx = pyoppai.new_ctx() 177 | b = pyoppai.new_beatmap(ctx) 178 | BUFSIZE = 2000000 179 | buf = pyoppai.new_buffer(BUFSIZE) 180 | pyoppai.parse(btmap, b, buf, BUFSIZE, True, 'data/osu/cache/') 181 | dctx = pyoppai.new_d_calc_ctx(ctx) 182 | pyoppai.apply_mods(b, mods) 183 | stars, aim, speed, _, _, _, _ = pyoppai.d_calc(dctx, b) 184 | cs, od, ar, hp = pyoppai.stats(b) 185 | combo = pyoppai.max_combo(b) 186 | acc, pp, aim_pp, speed_pp, acc_pp = pyoppai.pp_calc_acc(ctx, aim, speed, b, 100, 0, combo, 0) 187 | pyoppai_json = { 188 | 'version': pyoppai.version(b), 189 | 'title': pyoppai.title(b), 190 | 'artist': pyoppai.artist(b), 191 | 'creator': pyoppai.creator(b), 192 | 'combo': combo, 193 | 'misses': 0, 194 | 'max_combo': pyoppai.max_combo(b), 195 | 'mode': pyoppai.mode(b), 196 | 'num_objects': pyoppai.num_objects(b), 197 | 'num_circles': pyoppai.num_circles(b), 198 | 'num_sliders': pyoppai.num_sliders(b), 199 | 'num_spinners': pyoppai.num_spinners(b), 200 | 'stars': stars, 201 | 'aim_stars': aim, 202 | 'speed_stars': speed, 203 | 'pp': pp, 204 | 'aim_pp': aim_pp, 205 | 'speed_pp': speed_pp, 206 | 'acc': acc, 207 | 'cs': cs, 208 | 'od': od, 209 | 'ar': ar, 210 | 'hp': hp 211 | } 212 | return pyoppai_json 213 | 214 | def main(): 215 | """oppai-chunks from CLI 216 | 217 | Prints table of time|stars|aim|speed when run 218 | ./oppai-chunks.py beatmap.osu 219 | """ 220 | if len(sys.argv) > 4 or not sys.argv[1].endswith('.osu'): 221 | print_usage() 222 | sys.exit() 223 | 224 | print("Analyzing \"{}\"...".format(sys.argv[1])) 225 | try: 226 | results = oppai(sys.argv[1], *[int(x) for x in sys.argv[2:]]) 227 | except ValueError: 228 | print_usage() 229 | sys.exit() 230 | #static = get_pyoppai(sys.argv[1]) # To get the map title and version name 231 | #star_list, speed_list, aim_list, time_list = [], [], [], [] 232 | for chunk in results: 233 | #time_list.append(chunk['time']) 234 | #star_list.append(chunk['stars']) 235 | #aim_list.append(chunk['aim_stars']) 236 | #speed_list.append(chunk['speed_stars']) 237 | print("{}\t{}\t{}\t{}".format( 238 | chunk['time'], 239 | chunk['stars'], 240 | chunk['aim_stars'], 241 | chunk['speed_stars'])) 242 | #plt.plot(time_list, star_list) 243 | #plt.plot(time_list, aim_list) 244 | #plt.plot(time_list, speed_list) 245 | #plt.ylabel('Stars') 246 | #plt.xlabel('Times') 247 | #plt.title('{} [{}]'.format(static['title'], static['version'])) 248 | #plt.tight_layout() 249 | #plt.savefig('plot.png') 250 | 251 | if __name__ == '__main__': 252 | main() -------------------------------------------------------------------------------- /cogs/osu/osu_utils/database.py: -------------------------------------------------------------------------------- 1 | # Checks if user exists 2 | async def check_user_exists(user): 3 | find_user = await self.players.find_one({"user_id":str(user.id)}) 4 | if not find_user: 5 | return False 6 | return True 7 | 8 | def add_edit_user(discord_user_id, data): 9 | pass 10 | 11 | def add_edit_beatmap(discord_user_id, data): 12 | pass -------------------------------------------------------------------------------- /cogs/osu/osu_utils/owoCache.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import copy 4 | import pickle as pkl 5 | import motor.motor_asyncio 6 | from cogs.osu.osu_utils import map_utils 7 | 8 | class owoCache(object): 9 | 10 | def __init__(self, database): 11 | # cache basepath 12 | self.cache_folderpath = os.path.join('cogs', 'osu', 'cache') 13 | self.beatmap_parsed_folderpath = os.path.join( 14 | self.cache_folderpath, 'beatmap_parsed') 15 | self.create_folders() 16 | 17 | # initialize different cache databases 18 | self.beatmap_parsed = CacheBeatmapFile( 19 | database, 'cached_beatmap_parsed', 20 | self.beatmap_parsed_folderpath) 21 | 22 | self.beatmap = CacheBeatmap(database, 'cached_beatmap') 23 | self.beatmapset = CacheBeatmap(database, 'cached_beatmapset') 24 | self.beatmap_chunks = CacheBeatmap(database, 'cached_beatmap_chunks') 25 | self.beatmap_osu_file = CacheBeatmap(database, 'cached_beatmap_osu_file') 26 | 27 | self.leaderboard = Cache(database, 'cached_beatmap_leaderboard', 5*60) 28 | self.user = Cache(database, 'cached_user', 5*60) # 2*60 29 | self.user_best = Cache(database, 'cached_user_best', 2*60) # 2 * 60 30 | self.user_nc_best = Cache(database, 'cached_user_nc_best', 2*60) # 2 * 60 31 | self.user_recent = Cache(database, 'cached_user_recent', 60) # 1 * 60 32 | self.user_recent_activity = Cache(database, 'cached_user_recent_activity', 3*60) # 1 * 60 33 | self.user_stats = Cache(database, 'cached_user_stats', 7*24*60) # 1 * 60 34 | self.user_score = Cache(database, 'cached_user_score', 5*60) # 5 * 60 35 | 36 | 37 | def create_folders(self): 38 | folders = [ 39 | self.cache_folderpath, 40 | self.beatmap_parsed_folderpath 41 | ] 42 | 43 | for folder in folders: 44 | if not os.path.exists(folder): 45 | os.makedirs(folder) 46 | 47 | 48 | class Cache: 49 | def __init__(self, database, name, time): 50 | self.database = database 51 | self.name = name 52 | self.entries = self.database[self.name] 53 | self.time = time # in sections to expire 54 | 55 | async def get(self, query, force=False, include_time=False): 56 | # await self.entries.delete_many({}) # testing 57 | # db_query = self._get_db_query(query) 58 | # print(db_query) 59 | data = await self.entries.find_one(query) 60 | 61 | if data is None: 62 | return self._get_none_response(force, include_time) 63 | 64 | cache_valid = True 65 | elapsed_time = time.time() - float(data['cached_date']) # seconds 66 | if elapsed_time > self.time: 67 | cache_valid = False 68 | 69 | if force: 70 | if include_time: 71 | return data['data'], data['cached_date'], cache_valid 72 | else: 73 | return data['data'], cache_valid 74 | 75 | if not cache_valid: 76 | return self._get_none_response(force, include_time) 77 | 78 | # print('Using Cache') 79 | 80 | if include_time: 81 | return data['data'], data['cached_date'] 82 | else: 83 | return data['data'] 84 | 85 | def _get_none_response(self, force, include_time): 86 | if force: 87 | if include_time: 88 | return None, None, False 89 | else: 90 | return None, False 91 | else: 92 | if include_time: 93 | return None, None 94 | else: 95 | return None 96 | 97 | async def cache(self, identifiers, data): 98 | # print('Caching at time', time.time()) 99 | cache_obj = copy.deepcopy(identifiers) 100 | cache_obj['cached_date'] = time.time() 101 | cache_obj['data'] = data 102 | 103 | # print('To cache', cache_obj) 104 | db_query = self._get_db_query(identifiers) 105 | 106 | # print("Cached") 107 | """ 108 | await self.entries.replace_one( 109 | db_query, cache_obj, upsert=True)""" 110 | await self.entries.update_many( 111 | db_query, {"$set": cache_obj}, upsert=True) 112 | 113 | def _get_db_query(self, query): 114 | db_query = {} 115 | for key in query: 116 | db_query[key] = {"$eq": query[key]} 117 | 118 | return db_query 119 | 120 | 121 | class CacheBeatmap(Cache): 122 | def __init__(self, database, name): 123 | super().__init__(database, name, None) 124 | 125 | async def get(self, query, force=False, include_time=False): 126 | 127 | data = await self.entries.find_one(query) 128 | 129 | if data is None: 130 | return self._get_none_response(force, include_time) 131 | 132 | cache_valid = True 133 | elapsed_time = time.time() - float(data['cached_date']) # seconds 134 | if isinstance(data['data'], list): 135 | if 'approved' in data['data'][0]: 136 | if elapsed_time > self._beatmap_cache_timeout(data['data'][0]['approved']): 137 | cache_valid = False 138 | else: 139 | if elapsed_time > self._beatmap_cache_timeout(data['data'][0]['status']): 140 | cache_valid = False 141 | else: 142 | if 'approved' in data['data']: 143 | if elapsed_time > self._beatmap_cache_timeout(data['data']['approved']): 144 | cache_valid = False 145 | else: 146 | if elapsed_time > self._beatmap_cache_timeout(data['data']['status']): 147 | cache_valid = False 148 | 149 | if force: 150 | if include_time: 151 | return data['data'], data['cached_date'], cache_valid 152 | else: 153 | return data['data'], cache_valid 154 | 155 | if not cache_valid: 156 | return self._get_none_response(force, include_time) 157 | 158 | # print('Using Cache (Beatmap)') 159 | if include_time: 160 | return data['data'], data['cached_date'] 161 | else: 162 | return data['data'] 163 | 164 | def _beatmap_cache_timeout(self, status): 165 | status = int(self.handle_status(status)) 166 | # return 10 # 1 second for testing 167 | 168 | if status in [1, 2, 4]: 169 | return 6 * 30 * 24 * 3600 # 6 month for ranked 170 | elif status in [3]: 171 | return 24 * 3600 # 1 day for qualified 172 | elif status in [-2]: 173 | return 6 * 30 * 24 * 3600 # 6 month for graveyard 174 | else: 175 | return 5*60 # 5 minutes for WIP/Pending 176 | 177 | def handle_status(self, status): 178 | is_int = False 179 | try: 180 | status = int(status) 181 | is_int = True 182 | except ValueError: 183 | status = status.lower() 184 | 185 | if is_int: 186 | return status 187 | elif status == 'graveyard': # graveyard, red 188 | return -2 189 | elif status == 'work in progress' or status == 'wip': # WIP, purple 190 | return -1 191 | elif status == 'pending': # pending, blue 192 | return 0 193 | elif status == 'ranked': # ranked, bright green 194 | return 1 195 | elif status == 'approved': # approved, dark green 196 | return 2 197 | elif status == 'qualified': # qualified, turqoise 198 | return 3 199 | elif status == 'loved': # loved, pink 200 | return 4 201 | 202 | 203 | class CacheBeatmapFile(CacheBeatmap): 204 | def __init__(self, database, name, folderpath): 205 | super().__init__(database, name) 206 | self.folderpath = folderpath 207 | 208 | async def get(self, query, force=False, include_time=False): 209 | 210 | data = await self.entries.find_one(query) 211 | 212 | if data is None: 213 | return self._get_none_response(force, include_time) 214 | 215 | cache_valid = True 216 | elapsed_time = time.time() - float(data['cached_date']) # seconds 217 | if elapsed_time > self._beatmap_cache_timeout(data['data']['status']): 218 | cache_valid = False 219 | 220 | if force: 221 | if include_time: 222 | return data['data']['filepath'], data['cached_date'], cache_valid 223 | else: 224 | return data['data']['filepath'], cache_valid 225 | 226 | if not cache_valid: 227 | return self._get_none_response(force, include_time) 228 | 229 | # print('Using Cache (Beatmap)') 230 | if include_time: 231 | return data['data']['filepath'], data['cached_date'] 232 | else: 233 | return data['data']['filepath'] 234 | 235 | async def cache(self, identifiers, data, beatmap_info): 236 | # print('Caching Beatmap File') 237 | beatmap_id = str(beatmap_info['beatmap_id']) 238 | cache_obj = copy.deepcopy(identifiers) 239 | cache_obj['cached_date'] = float(time.time()) 240 | cache_obj['data'] = {} 241 | try: 242 | cache_obj['data']['status'] = int(beatmap_info['status']) 243 | except: 244 | cache_obj['data']['status'] = 1 # default to 1 cause i don't care 245 | cache_obj['data']['filepath'] = os.path.join( 246 | self.folderpath, '{}.pkl'.format(beatmap_id)) 247 | 248 | # print('Caching Time', time.time()) 249 | 250 | with open(cache_obj['data']['filepath'], 'wb') as pickle_file: 251 | pkl.dump(data, pickle_file) 252 | 253 | # print('To cache', cache_obj) 254 | db_query = self._get_db_query(identifiers) 255 | """ 256 | await self.entries.replace_one( 257 | db_query, cache_obj, upsert=True)""" 258 | await self.entries.update_many( 259 | db_query, {"$set": cache_obj}, upsert=True) -------------------------------------------------------------------------------- /cogs/osu/osu_utils/utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from operator import itemgetter 3 | from difflib import SequenceMatcher 4 | 5 | def get_similarity(a, b): 6 | return SequenceMatcher(None, a, b).ratio() 7 | 8 | def determine_range(value, range_of_value = 20): 9 | nearest_range = int(range_of_pp * round(float(pp)/range_of_pp)) 10 | 11 | upper_bound = 0 12 | lower_bound = 0 13 | if (nearest_range - value) > 0: 14 | lower_bound = nearest_range - range_of_value 15 | upper_bound = nearest_range - 1 16 | else: 17 | lower_bound = nearest_range 18 | upper_bound = nearest_range + range_of_value - 1 19 | 20 | return "{}_{}".format(lower_bound, upper_bound) 21 | 22 | def calc_time(total_sec, bpm, factor:float=1): 23 | m1, s1 = divmod(round(float(total_sec)/factor), 60) 24 | bpm1 = round(factor*float(bpm), 1) 25 | return (m1,s1,bpm1) 26 | 27 | def time_ago(time1, time2, shift = 0, abbr=False): 28 | time_diff = time1 - time2 29 | if shift != 0: 30 | time_diff += datetime.timedelta(hours=int(shift)) 31 | 32 | timeago = datetime.datetime(1,1,1) + time_diff 33 | time_limit = 0 34 | time_ago = "" 35 | if timeago.year-1 != 0: 36 | if abbr: 37 | time_ago += "{}Y".format(timeago.year-1) 38 | else: 39 | time_ago += "{} Year{} ".format(timeago.year-1, determine_plural(timeago.year-1)) 40 | time_limit = time_limit + 1 41 | if timeago.month-1 !=0: 42 | if abbr: 43 | time_ago += "{}M".format(timeago.month-1) 44 | else: 45 | time_ago += "{} Month{} ".format(timeago.month-1, determine_plural(timeago.month-1)) 46 | time_limit = time_limit + 1 47 | if timeago.day-1 !=0 and not time_limit == 2: 48 | if abbr: 49 | time_ago += "{}d".format(timeago.day-1) 50 | else: 51 | time_ago += "{} Day{} ".format(timeago.day-1, determine_plural(timeago.day-1)) 52 | time_limit = time_limit + 1 53 | if timeago.hour != 0 and not time_limit == 2: 54 | if abbr: 55 | time_ago += "{}h".format(timeago.hour) 56 | else: 57 | time_ago += "{} Hour{} ".format(timeago.hour, determine_plural(timeago.hour)) 58 | time_limit = time_limit + 1 59 | if timeago.minute != 0 and not time_limit == 2: 60 | if abbr: 61 | time_ago += "{}m".format(timeago.minute) 62 | else: 63 | time_ago += "{} Minute{} ".format(timeago.minute, determine_plural(timeago.minute)) 64 | time_limit = time_limit + 1 65 | if not time_limit == 2: 66 | if abbr: 67 | time_ago += "{}s".format(timeago.second) 68 | else: 69 | time_ago += "{} Second{} ".format(timeago.second, determine_plural(timeago.second)) 70 | return time_ago 71 | 72 | def determine_plural(number): 73 | if int(number) != 1: 74 | return 's' 75 | else: 76 | return '' 77 | 78 | def get_gamemode_text(gamemode:int): 79 | if gamemode == 1: 80 | gamemode_text = "Taiko" 81 | elif gamemode == 2: 82 | gamemode_text = "Catch the Beat!" 83 | elif gamemode == 3: 84 | gamemode_text = "osu! Mania" 85 | else: 86 | gamemode_text = "osu! Standard" 87 | return gamemode_text 88 | 89 | def get_gamemode_display(gamemode): 90 | if gamemode == "osu": 91 | gamemode_text = "osu! Standard" 92 | elif gamemode == "ctb": 93 | gamemode_text = "Catch the Beat!" 94 | elif gamemode == "mania": 95 | gamemode_text = "osu! Mania" 96 | elif gamemode == "taiko": 97 | gamemode_text = "Taiko" 98 | return gamemode_text 99 | 100 | def get_gamemode_number(gamemode:str): 101 | if gamemode == "taiko": 102 | gamemode_text = 1 103 | elif gamemode == "ctb": 104 | gamemode_text = 2 105 | elif gamemode == "mania": 106 | gamemode_text = 3 107 | elif gamemode == "osu": 108 | gamemode_text = 0 109 | else: 110 | gamemode_text = int(gamemode) 111 | 112 | return int(gamemode_text) 113 | 114 | def calculate_rank(beatmap, acc:float, mods): 115 | if acc == 100: 116 | rank = 'X' 117 | elif acc >= 93: 118 | if beatmap['count_miss'] == 0: 119 | rank = 'S' 120 | else: 121 | rank = 'A' 122 | elif acc >= 86: 123 | if beatmap['count_miss'] == 0: 124 | rank = 'A' 125 | else: 126 | rank = 'B' 127 | elif acc >= 81: 128 | if beatmap['count_miss'] == 0: 129 | rank = 'B' 130 | else: 131 | rank = 'C' 132 | else: 133 | rank = 'D' 134 | 135 | if ('S' in rank or 'X' in rank)and "HD" in mods: 136 | rank += 'H' 137 | 138 | return rank 139 | 140 | def calculate_acc(beatmap, gamemode): 141 | gamemode = int(gamemode) 142 | 143 | user_score = 0 144 | if gamemode == 0: 145 | total_unscale_score = float(beatmap['count_300']) 146 | total_unscale_score += float(beatmap['count_100']) 147 | total_unscale_score += float(beatmap['count_50']) 148 | total_unscale_score += float(beatmap['count_miss']) 149 | total_unscale_score *=300 150 | user_score = float(beatmap['count_300']) * 300.0 151 | user_score += float(beatmap['count_100']) * 100.0 152 | user_score += float(beatmap['count_50']) * 50.0 153 | elif gamemode == 1: 154 | total_unscale_score = float(beatmap['count_300']) 155 | total_unscale_score += float(beatmap['count_100']) 156 | total_unscale_score += float(beatmap['count_miss']) 157 | total_unscale_score *= 300 158 | user_score = float(beatmap['count_300']) * 1.0 159 | user_score += float(beatmap['count_100']) * 0.5 160 | user_score *= 300 161 | elif gamemode == 2: 162 | total_unscale_score = float(beatmap['count_300']) 163 | total_unscale_score += float(beatmap['count_100']) 164 | total_unscale_score += float(beatmap['count_50']) 165 | total_unscale_score += float(beatmap['count_miss']) 166 | total_unscale_score += float(beatmap['count_katu']) 167 | user_score = float(beatmap['count_300']) 168 | user_score += float(beatmap['count_100']) 169 | user_score += float(beatmap['count_50']) 170 | elif gamemode == 3: 171 | total_unscale_score = float(beatmap['count_300']) 172 | total_unscale_score += float(beatmap['count_geki']) 173 | total_unscale_score += float(beatmap['count_katu']) 174 | total_unscale_score += float(beatmap['count_100']) 175 | total_unscale_score += float(beatmap['count_50']) 176 | total_unscale_score += float(beatmap['count_miss']) 177 | total_unscale_score *=300 178 | user_score = float(beatmap['count_300']) * 300.0 179 | user_score += float(beatmap['count_geki']) * 300.0 180 | user_score += float(beatmap['count_katu']) * 200.0 181 | user_score += float(beatmap['count_100']) * 100.0 182 | user_score += float(beatmap['count_50']) * 50.0 183 | 184 | return (float(user_score)/float(total_unscale_score)) * 100.0 185 | 186 | def no_choke_acc(beatmap, gamemode:int): 187 | if gamemode == 0: 188 | total_unscale_score = float(beatmap['count_300']) 189 | total_unscale_score += float(beatmap['count_100']) 190 | total_unscale_score += float(beatmap['count_50']) 191 | total_unscale_score += float(beatmap['count_miss']) 192 | total_unscale_score *=300 193 | user_score = float(beatmap['count_300']) * 300.0 194 | user_score += (float(beatmap['count_100']) + float(beatmap['count_miss'])) * 100.0 195 | user_score += float(beatmap['count_50']) * 50.0 196 | elif gamemode == 1: 197 | total_unscale_score = float(beatmap['count_300']) 198 | total_unscale_score += float(beatmap['count_100']) 199 | total_unscale_score += float(beatmap['count_miss']) 200 | total_unscale_score *= 300 201 | user_score = float(beatmap['count_300']) * 1.0 202 | user_score += (float(beatmap['count_100']) + float(beatmap['count_miss'])) * 0.5 203 | user_score *= 300 204 | elif gamemode == 2: 205 | total_unscale_score = float(beatmap['count_300']) 206 | total_unscale_score += float(beatmap['count_100']) 207 | total_unscale_score += float(beatmap['count_50']) 208 | total_unscale_score += float(beatmap['count_miss']) 209 | total_unscale_score += float(beatmap['count_katu']) 210 | user_score = float(beatmap['count_300']) 211 | user_score += (float(beatmap['count_100']) + float(beatmap['count_miss'])) 212 | user_score += float(beatmap['count_50']) 213 | elif gamemode == 3: 214 | total_unscale_score = float(beatmap['count_300']) 215 | total_unscale_score += float(beatmap['count_geki']) 216 | total_unscale_score += float(beatmap['count_katu']) 217 | total_unscale_score += float(beatmap['count_100']) 218 | total_unscale_score += float(beatmap['count_50']) 219 | total_unscale_score += float(beatmap['count_miss']) 220 | total_unscale_score *=300 221 | user_score = float(beatmap['count_300']) * 300.0 222 | user_score += float(beatmap['count_geki']) * 300.0 223 | user_score += float(beatmap['count_katu']) * 200.0 224 | user_score += (float(beatmap['count_100']) + float(beatmap['count_miss'])) * 100.0 225 | user_score += float(beatmap['count_50']) * 50.0 226 | 227 | return (float(user_score)/float(total_unscale_score)) * 100.0 228 | 229 | # because you people just won't stop bothering me about it 230 | def fix_mods(mods:str): 231 | if mods == 'PFSOFLNCHTRXDTSDHRHDEZNF': 232 | return '? KEY' 233 | else: 234 | mods = mods.replace('DTHRHD', 'HDHRDT').replace('DTHD','HDDT').replace('HRHD', 'HDHR') 235 | if "PF" in mods and "SD" in mods: 236 | mods = mods.replace('SD', '') 237 | if "NC" in mods and "DT" in mods: 238 | mods = mods.replace('DT', '') 239 | 240 | return mods 241 | 242 | def fix_mod_list(mods_list): 243 | new_mod_list = [] 244 | if 'DT' in mods_list and 'NC' in mods_list: 245 | mods_list.remove('DT') 246 | if 'PF' in mods_list and 'SD' in mods_list: 247 | mods_list.remove('SD') 248 | 249 | if 'HD' in mods_list and 'DT' in mods_list and 'HR' in mods_list: 250 | new_mod_list.extend(['HD', 'HR', 'DT']) 251 | elif 'HD' in mods_list and 'NC' in mods_list and 'HR' in mods_list: 252 | new_mod_list.extend(['HD', 'HR', 'NC']) 253 | elif 'HD' in mods_list and 'HR' in mods_list: 254 | new_mod_list.extend(['HD', 'HR']) 255 | elif 'HD' in mods_list and 'DT' in mods_list: 256 | new_mod_list.extend(['HD', 'DT']) 257 | 258 | for mod in mods_list: 259 | if mod not in new_mod_list: 260 | new_mod_list.append(mod) 261 | 262 | return new_mod_list 263 | 264 | 265 | def str_to_mod(mod_str:str): 266 | mod_list = num_to_mod(mod_to_num(mod_str)) 267 | return fix_mod_list(mod_list) 268 | 269 | # gives a list of the ranked mods given a peppy number lol 270 | def num_to_mod(number): 271 | # print('UTILS numtomod', number) 272 | mods = [ 273 | 'NF','EZ','TD','HD','HR','SD','DT','RX','HT','NC','FL','Auto', 274 | 'SO','AP','PF','4K','5K','6K','7K','8K','FI','RDM','CI','TG', 275 | '9K','10K','1K','3K','2K','V2','MI' 276 | ] 277 | number = int(number) 278 | mod_list = [] 279 | 280 | for mod_idx in range(len(mods)-1, -1, -1): 281 | mod = mods[mod_idx] 282 | # print(mods[mod_idx], mod_idx, 2**mod_idx) 283 | if mod == 'NC': 284 | if number >= 576: 285 | number -= 576 286 | mod_list.append(mod) 287 | continue 288 | elif mod == 'PF': 289 | if number >= 16416: 290 | number -= 16416 291 | mod_list.append(mod) 292 | continue 293 | 294 | if number >= 2**mod_idx: 295 | number -= 2**mod_idx 296 | mod_list.append(mod) 297 | 298 | # print('UTIL NUM2MOD', mod_list) 299 | 300 | return mod_list 301 | 302 | def mod_to_num(input_mods:str): 303 | # Function checked. 304 | mods = [ 305 | 'NF','EZ','TD','HD','HR','SD','DT','RX','HT','NC','FL','Auto', 306 | 'SO','AP','PF','4K','5K','6K','7K','8K','FI','RDM','CI','TG', 307 | '9K','10K','1K','3K','2K','V2','MI' 308 | ] 309 | input_mods = input_mods.upper() 310 | total = 0 311 | 312 | # remove TD first becuase it interferes with HD/DT 313 | if 'TD' in input_mods: 314 | total += 4 315 | input_mods = input_mods.replace('TD', '') 316 | 317 | for mod_idx in range(len(mods)-1, -1, -1): 318 | if mods[mod_idx] in input_mods: 319 | if mods[mod_idx] == 'DT': 320 | total += 64 321 | elif mods[mod_idx] == 'NC': 322 | total += 576 323 | elif mods[mod_idx] == 'SD': 324 | total += 32 325 | elif mods[mod_idx] == 'PF': 326 | total += 16416 327 | else: 328 | total += 2**mod_idx 329 | input_mods = input_mods.replace(mods[mod_idx], '') 330 | 331 | # print('UTIL MOD2NUM', total) 332 | 333 | return int(total) 334 | 335 | 336 | def droid_mod_to_mod_list(droid_mods): 337 | """ 338 | Converts droid mod string to PC mod string. 339 | """ 340 | 341 | # Honestly not the best implementation, but the core is there 342 | final_mods = "" 343 | 344 | if "a" in droid_mods: 345 | final_mods += "at" 346 | if "x" in droid_mods: 347 | final_mods += "rx" 348 | if "p" in droid_mods: 349 | final_mods += "ap" 350 | if "e" in droid_mods: 351 | final_mods += "ez" 352 | if "n" in droid_mods: 353 | final_mods += "nf" 354 | if "r" in droid_mods: 355 | final_mods += "hr" 356 | if "h" in droid_mods: 357 | final_mods += "hd" 358 | if "i" in droid_mods: 359 | final_mods += "fl" 360 | if "d" in droid_mods: 361 | final_mods += "dt" 362 | if "c" in droid_mods: 363 | final_mods += "nc" 364 | if "t" in droid_mods: 365 | final_mods += "ht" 366 | if "s" in droid_mods: 367 | final_mods += "pr" 368 | if "m" in droid_mods: 369 | final_mods += "sc" 370 | if "b" in droid_mods: 371 | final_mods += "su" 372 | if "l" in droid_mods: 373 | final_mods += "re" 374 | if "f" in droid_mods: 375 | final_mods += "pf" 376 | if "u" in droid_mods: 377 | final_mods += "sd" 378 | if "v" in droid_mods: 379 | final_mods += "v2" 380 | 381 | return final_mods.upper() 382 | 383 | 384 | def num_to_droid_mod(mod_num): 385 | # print('UTILS mod num', mod_num) 386 | mod_list = num_to_mod(mod_num) 387 | mods = [ 388 | 'NF','EZ','HD','HR','SD','DT','RX','HT','NC','FL','AT', 'V2' 389 | ] 390 | mod_droid = [ 391 | 'n', 'e', 'h', 'r', 'u', 'd', 'x', 't', 'c', 'i', 'a', 'v' 392 | ] 393 | droid_str = '' 394 | for mod in mod_list: 395 | if mod.upper() in mods: 396 | mod_idx = mods.index(mod.upper()) 397 | droid_str += mod_droid[mod_idx] 398 | 399 | return droid_str 400 | 401 | 402 | def mode_to_num(mode_str): 403 | if 'std' in mode_str: 404 | return 0 405 | if 'osu' in mode_str: 406 | return 0 407 | elif 'taiko' in mode_str: 408 | return 1 409 | elif 'ctb' in mode_str: 410 | return 2 411 | elif 'fruit' in mode_str: 412 | return 2 413 | elif 'mania' in mode_str: 414 | return 3 415 | else: 416 | return 0 417 | 418 | def num_to_mode(mode_num): 419 | mode_num = int(mode_num) 420 | if mode_num == 0: 421 | return "std" 422 | elif mode_num == 1: 423 | return "taiko" 424 | elif mode_num == 2: 425 | return "ctb" 426 | elif mode_num == 3: 427 | return "mania" 428 | else: 429 | return None -------------------------------------------------------------------------------- /cogs/osu/osu_utils/web_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import urllib 4 | import random 5 | import aiohttp 6 | import asyncio 7 | import pyoppai 8 | import pyttanko 9 | import requests 10 | import datetime 11 | import aiofiles 12 | from bs4 import BeautifulSoup 13 | 14 | import matplotlib as mpl 15 | mpl.use('Agg') # for non gui 16 | from matplotlib import ticker 17 | import matplotlib.pyplot as plt 18 | import matplotlib.dates as mdates 19 | 20 | from utils.dataIO import dataIO, fileIO 21 | from cogs.osu.osu_utils.chunks import chunks 22 | 23 | from apiclient.discovery import build 24 | from apiclient.errors import HttpError 25 | from oauth2client.tools import argparser 26 | 27 | api_keys = fileIO("config.json", "load")["API_KEYS"] 28 | 29 | async def youtube_search(q, key, max_results=10, order="relevance"): 30 | youtube = build("youtube", "v3", developerKey=key) 31 | 32 | search_response = youtube.search().list( 33 | q=q, type="video", 34 | pageToken=None, 35 | order = order, 36 | part="id,snippet", 37 | maxResults=max_results, 38 | ).execute() 39 | 40 | videos = [] 41 | for search_result in search_response.get("items", []): 42 | if search_result["id"]["kind"] == "youtube#video": 43 | videos.append(search_result) 44 | return videos 45 | 46 | async def get_web(url, session=None, parser = 'html.parser'): 47 | if not session: 48 | async with aiohttp.ClientSession() as session: 49 | async with session.get(url) as resp: 50 | text = await resp.read() 51 | try: 52 | return BeautifulSoup(text.decode('utf-8'), parser) 53 | except: 54 | return BeautifulSoup(text, parser) 55 | else: 56 | async with session.get(url) as resp: 57 | text = await resp.read() 58 | try: 59 | return BeautifulSoup(text.decode('utf-8'), parser) 60 | except: 61 | return BeautifulSoup(text, parser) 62 | 63 | 64 | # asynchronously download the file 65 | async def download_file(url, filename): 66 | async with aiohttp.ClientSession() as session: 67 | async with session.get(url) as resp: 68 | if resp.status == 200: 69 | f = await aiofiles.open(filename, mode='wb') 70 | await f.write(await resp.read()) 71 | await f.close() 72 | 73 | 74 | async def get_REST(url): 75 | async with aiohttp.ClientSession() as session: 76 | async with session.get(url) as resp: 77 | return await resp.json() 78 | 79 | async def get_beatmap_listing(map_type=None): 80 | config_data = fileIO("config.json", "load") 81 | official = config_data["API_KEYS"]["OSU"]["OFFICIAL"] 82 | username = official["USERNAME"] 83 | password = official["PASSWORD"] 84 | 85 | payload = {"username": username, 86 | "password": password, 87 | "redirect": "index.php", 88 | "sid": "", 89 | "login": "Login"} 90 | 91 | async with aiohttp.ClientSession() as session: 92 | async with session.post('https://osu.ppy.sh/forum/ucp.php?mode=login', data = payload) as resp: 93 | text = await resp.read() 94 | 95 | query = "https://osu.ppy.sh/beatmapsets" 96 | if map_type and map_type!="None": 97 | query += f"?s={map_type}" 98 | 99 | async with session.get(query) as resp: 100 | text = await resp.read() 101 | soup = BeautifulSoup(text.decode('utf-8'), "lxml") 102 | script = soup.find("script", {"id": "json-beatmaps"}, type='application/json') 103 | web_data = json.loads(script.text) 104 | return web_data 105 | 106 | async def get_top_cc(pages = 1): 107 | """Get country codes for top 50""" 108 | target_base = 'https://osu.ppy.sh/rankings/osu/performance?country=' 109 | 110 | for i in range(pages): 111 | url = 'https://osu.ppy.sh/rankings/osu/country?page={}#jump-target'.format(i) 112 | soup = await get_web(url) 113 | a_tags = list(soup.findAll('a')) 114 | 115 | cc = [] 116 | for tag in a_tags: 117 | try: 118 | if target_base in tag['href']: 119 | # username tag.text.replace('\n','') 120 | cc.append(tag['href'].replace(target_base, '').upper()) 121 | except: 122 | pass 123 | return cc 124 | 125 | async def get_map_image(mapset_id): 126 | # also maintains length to 1500 127 | folder = f"cogs/osu/resources/beatmap_images" 128 | filename = f"{mapset_id}.jpg" 129 | file_path = f"{folder}/{filename}" 130 | 131 | # if it exists 132 | if os.path.exists(file_path): 133 | try: 134 | bg = Image.open(file_path) 135 | return bg, True 136 | except: 137 | pass 138 | 139 | # if it doesn't exist 140 | config_data = fileIO("config.json", "load") 141 | official = config_data["API_KEYS"]["OSU"]["OFFICIAL"] 142 | username = official["USERNAME"] 143 | password = official["PASSWORD"] 144 | 145 | payload = {"username": username, 146 | "password": password, 147 | "redirect": "index.php", 148 | "sid": "", 149 | "login": "Login"} 150 | 151 | async with aiohttp.ClientSession() as session: 152 | async with session.post('https://osu.ppy.sh/forum/ucp.php?mode=login', data = payload) as resp: 153 | text = await resp.read() 154 | try: 155 | print("Attempting Download") 156 | bg, bg_success = await download_map_image_to_folder(mapset_id, session = session) 157 | # bg = Image.open("cogs/osu/resources/triangles_map.jpg") 158 | return bg, bg_success 159 | # return bg 160 | except: 161 | bg = Image.open("../owo_v2/cogs/osu/resources/triangles_map.jpg") 162 | return bg, False 163 | 164 | async def download_map_image_to_folder(mapset_id, session=None, limit=8000): 165 | # get map url from site 166 | folder = f"cogs/osu/resources/beatmap_images" 167 | file_path = f"{folder}/{mapset_id}.jpg" 168 | 169 | bg = None 170 | bg_success = False 171 | bg_images = [] 172 | 173 | bg_images.append("https://assets.ppy.sh/beatmaps/{}/covers/card@2x.jpg?".format(mapset_id)) 174 | # bg_images.append("https://assets.ppy.sh/beatmaps/{}/covers/cover.jpg".format(mapset_id)) 175 | 176 | for bg_image in bg_images: 177 | async with session.get(bg_image) as r: 178 | image = await r.content.read() 179 | with open(file_path,'wb') as f: 180 | f.write(image) 181 | bg = Image.open(file_path) 182 | bg_success = True 183 | break 184 | await asyncio.sleep(1) 185 | 186 | # if too many 187 | path, dirs, files = next(os.walk(folder)) 188 | file_count = len(files) 189 | if file_count > limit: 190 | delete_num = file_count - limit 191 | for i in range(delete_num): 192 | file = random.choice(files) 193 | delete_file = f"{folder}/{file}" 194 | if delete_file != file_path: 195 | os.remove(delete_file) 196 | return bg, bg_success -------------------------------------------------------------------------------- /cogs/osu/replay_parser/enums.py: -------------------------------------------------------------------------------- 1 | 2 | from enum import Enum 3 | 4 | 5 | class GameMode(Enum): 6 | Standard = 0 7 | Taiko = 1 8 | CatchTheBeat = 2 9 | Osumania = 3 10 | 11 | 12 | class Mod(Enum): 13 | NoMod = 0 14 | NoFail = 1 15 | Easy = 2 16 | NoVideo = 4 17 | Hidden = 8 18 | HardRock = 16 19 | SuddenDeath = 32 20 | DoubleTime = 64 21 | Relax = 128 22 | HalfTime = 256 23 | Nightcore = 512 24 | Flashlight = 1024 25 | Autoplay = 2048 26 | SpunOut = 4096 27 | Autopilot = 8192 28 | Perfect = 16384 29 | Key4 = 32768 30 | Key5 = 65536 31 | Key6 = 131072 32 | Key7 = 262144 33 | Key8 = 524288 34 | keyMod = 1015808 35 | FadeIn = 1048576 36 | Random = 2097152 37 | LastMod = 4194304 38 | TargetPractice = 8388608 39 | Key9 = 16777216 40 | Coop = 33554432 41 | Key1 = 67108864 42 | Key3 = 134217728 43 | Key2 = 268435456 -------------------------------------------------------------------------------- /cogs/osu/replay_parser/replay.py: -------------------------------------------------------------------------------- 1 | from .enums import GameMode, Mod 2 | import lzma, struct, datetime 3 | 4 | 5 | class ReplayEvent(object): 6 | def __init__(self, time_since_previous_action, x, y, keys_pressed): 7 | self.time_since_previous_action = time_since_previous_action 8 | self.x = x 9 | self.y = y 10 | self.keys_pressed = keys_pressed 11 | 12 | 13 | class Replay(object): 14 | __BYTE = 1 15 | __SHORT = 2 16 | __INT = 4 17 | __LONG = 8 18 | 19 | #Order of field initilization matters. 20 | def __init__(self, replay_data): 21 | self.offset = 0 22 | self.game_mode = None 23 | self.game_version = None 24 | self.beatmap_hash = None 25 | self.player_name = None 26 | self.replay_hash = None 27 | self.number_300s = None 28 | self.number_100s = None 29 | self.number_50s = None 30 | self.gekis = None 31 | self.katus = None 32 | self.misses = None 33 | self.score = None 34 | self.max_combo = None 35 | self.is_perfect_combo = None 36 | self.mod_combination = None 37 | self.mod_int = None 38 | self.life_bar_graph = None 39 | self.timestamp = None 40 | self.play_data = None 41 | self.parse_replay_and_initialize_fields(replay_data) 42 | 43 | def parse_replay_and_initialize_fields(self, replay_data): 44 | self.parse_game_mode_and_version(replay_data) 45 | self.parse_beatmap_hash(replay_data) 46 | self.parse_player_name(replay_data) 47 | self.parse_replay_hash(replay_data) 48 | self.parse_score_stats(replay_data) 49 | self.parse_life_bar_graph(replay_data) 50 | self.parse_timestamp_and_replay_length(replay_data) 51 | self.parse_play_data(replay_data) 52 | 53 | def parse_game_mode_and_version(self, replay_data): 54 | format_specifier = "help', '>addbot', '>addbot', 'osu!', 'owo!','with Random things', 18 | 'Dedotated wam', 'with Circles!', 'òwó', 'o~o', '>~<', 19 | 'in {} Servers'.format(len(self.bot.guilds)), 20 | 'with {} Users'.format(str(len(set(self.bot.get_all_members())))), 21 | 'with baskets underwater', '>help', '>help' 22 | ] 23 | status = randint(0, len(statuses)-1) 24 | new_status = statuses[status] 25 | await self.bot.change_presence(activity=discord.Game(new_status)) 26 | await asyncio.sleep(60) 27 | 28 | ### ---------------------------- Setup ---------------------------------- ### 29 | def setup(bot): 30 | n = Status(bot) 31 | loop = asyncio.get_event_loop() 32 | loop.create_task(n.display_status()) 33 | bot.add_cog(n) -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "owner" : "never", 3 | "prefix" : [ 4 | ">" 5 | ], 6 | "shard_count": 1, 7 | "bot_name": "owo_osu_bot", 8 | "token" : "gonna", 9 | "client_id": "give you up", 10 | "description": "owo! - An osu! Discord bot by Stevy", 11 | "API_KEYS": { 12 | "WOLFRAM_API_KEY": "never", 13 | "YOUTUBE": "gonna", 14 | "OSU": { 15 | "OFFICIAL": { 16 | "KEY": "let you", 17 | "CLIENT_ID": "down", 18 | "CLIENT_SECRET": "never", 19 | "USERNAME": "gonna", 20 | "PASSWORD": "run around and" 21 | }, 22 | "DROID": { 23 | "KEY" : "desert you" 24 | }, 25 | "BEATCONNECT": { 26 | "KEY": "never gonna make" 27 | } 28 | }, 29 | "BANCHO": { 30 | "USERNAME": "you cry", 31 | "PASSWORD": "never gonna say" 32 | }, 33 | "PATREON": { 34 | "CLIENT_ID": "goodbye", 35 | "CLIENT_SECRET": "never gonna tell", 36 | "ACCESS_TOKEN": "a lie", 37 | "REFRESH_TOKEN": "and hurt you" 38 | } 39 | }, 40 | "settings":{ 41 | "production": true, 42 | "osu": { 43 | "cache" : true 44 | } 45 | }, 46 | "database": { 47 | "primary": 27017 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /database/other/special_lists.json: -------------------------------------------------------------------------------- 1 | { 2 | "whitelist": { 3 | "servers": [ 4 | ], 5 | "users": [ 6 | ] 7 | }, 8 | "blacklist": { 9 | "servers": [], 10 | "users": [] 11 | } 12 | } -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sys 4 | import time 5 | import json 6 | import copy 7 | import inspect 8 | import logging 9 | import logging.handlers 10 | import asyncio 11 | import pathlib 12 | import textwrap 13 | import datetime 14 | import warnings 15 | import operator 16 | import importlib 17 | import traceback 18 | from io import TextIOWrapper 19 | from threading import Thread 20 | from utils.dataIO import fileIO 21 | from utils.donor_utils import Patreon 22 | 23 | import uvloop 24 | # import pymongo 25 | import motor.motor_asyncio 26 | 27 | import discord 28 | from discord.ext import commands 29 | 30 | from cogs.osu.updater import Updater 31 | 32 | class Bot(commands.AutoShardedBot): 33 | def __init__(self, **kwargs): 34 | # specify intents 35 | intents = discord.Intents.default() 36 | # intents = discord.Intents.all() 37 | #intents.presence = True 38 | intents.members = True 39 | 40 | self.start_time = datetime.datetime.utcnow() 41 | 42 | super().__init__( 43 | command_prefix=self.get_server_prefixes, 44 | description=kwargs.pop('description'), 45 | shard_count=kwargs.pop('shard_count'), 46 | chunk_guilds_at_startup=False, 47 | intents=intents 48 | ) 49 | self.help_formatter = HelpFormatter() 50 | self.config = kwargs['config'] 51 | self.logger = set_logger(self) 52 | self.settings = None 53 | 54 | # load database 55 | self.db_client = motor.motor_asyncio.AsyncIOMotorClient( 56 | port=self.config['database']['primary'], 57 | connectTimeoutMS=5000, 58 | socketTimeoutMS=5000, 59 | serverSelectionTimeoutMS=5000) 60 | 61 | self.db = self.db_client[str(self.config['bot_name'])] # overall database, not collection 62 | self.disabled_commands = self.db["disabled"] 63 | self.prefix_settings = self.db["prefix"] # settings for the cog 64 | 65 | # patreons 66 | self.patreon = Patreon(self.config['API_KEYS']['PATREON']) 67 | 68 | # load cogs 69 | self.loop.create_task(self.load_cogs()) 70 | 71 | # special lists 72 | special_lists = fileIO( 73 | os.path.join(os.getcwd(), 'database','other','special_lists.json'), "load") 74 | self.blacklist = special_lists['blacklist'] 75 | self.whitelist = special_lists['whitelist'] 76 | 77 | # server settings 78 | self.cached_settings = {} 79 | 80 | # passive commands, separate bot 81 | self.is_passive_bot = False 82 | self.is_production = self.config['settings']['production'] 83 | 84 | 85 | async def get_server_prefixes(self, bot, message, 86 | one_prefix=False, prefix_list=False): 87 | """ 88 | Obtains prefix based on server preference. 89 | """ 90 | prefix = await self._get_prefix(message) 91 | if one_prefix: 92 | return prefix[0] 93 | elif prefix_list: 94 | return prefix 95 | return commands.when_mentioned_or(*prefix)(bot, message) 96 | 97 | 98 | async def _get_prefix(self, message): 99 | """ 100 | Get prefixes for the server 101 | """ 102 | server = message.guild 103 | default_prefixes = self.config['prefix'] 104 | try: 105 | server_info = await self.prefix_settings.find_one({"server_id":str(server.id)}) 106 | prefixes = server_info["prefix"] 107 | except: 108 | prefixes = None 109 | 110 | if not prefixes or prefixes == []: 111 | prefix = default_prefixes 112 | else: 113 | prefix = prefixes 114 | return prefix 115 | 116 | 117 | async def load_cogs(self): 118 | """ 119 | Loads all cogs in folder. 120 | """ 121 | ignore_list = ['streams','games','audio','updater'] 122 | await self.wait_until_ready() 123 | # await asyncio.sleep(1) # sleep for 3 minutes 124 | 125 | loaded_cogs = [] 126 | failed_cogs = [] 127 | cog_directory = pathlib.Path('./cogs') 128 | for cog_name in cog_directory.iterdir(): 129 | cog_name = cog_name.name 130 | if "__" not in cog_name and cog_name not in ignore_list: 131 | success = False 132 | while not success: 133 | try: 134 | self.load_extension(f'cogs.{cog_name}.{cog_name}') 135 | success = True 136 | except Exception as e: 137 | print(traceback.format_exc()) 138 | # failed_cogs.append(cog_name) 139 | # self.logger.exception(e) 140 | await asyncio.sleep(5) 141 | 142 | 143 | async def on_ready(self): 144 | # self.settings = await self.application_info() 145 | print(f'All {len(self.shards.keys())} shards loaded.') 146 | elapsed_time = str(datetime.datetime.utcnow() - self.start_time) 147 | print(f'Took {elapsed_time} seconds.') 148 | 149 | 150 | async def on_message(self, message): 151 | server = message.guild 152 | user = message.author 153 | 154 | # check if dm 155 | if not server: 156 | return 157 | 158 | # handle whitelisting/blacklisting 159 | if server.id in self.blacklist['servers']: 160 | return 161 | if user.id in self.blacklist['users']: 162 | return 163 | if (self.whitelist['servers'] and \ 164 | server.id not in self.whitelist['servers']) and \ 165 | (self.whitelist['users'] and \ 166 | user.id not in self.whitelist['users']): 167 | return 168 | 169 | # also check if not on blacklist 170 | await self.process_commands(message) 171 | 172 | 173 | def escape_mass_mentions(self, text): 174 | return self._escape(text, mass_mentions=True) 175 | 176 | 177 | def _escape(self, text, *, mass_mentions=False, formatting=False): 178 | if mass_mentions: 179 | text = text.replace("@everyone", "@\u200beveryone") 180 | text = text.replace("@here", "@\u200bhere") 181 | if formatting: 182 | text = (text.replace("`", "\\`") 183 | .replace("*", "\\*") 184 | .replace("_", "\\_") 185 | .replace("~", "\\~")) 186 | return text 187 | 188 | 189 | async def get_setting(self, server, db_name): 190 | if not server: 191 | return None 192 | 193 | db_name = self._check_db_name(db_name) 194 | db = self.db[db_name] 195 | 196 | try: 197 | settings = self.cached_settings[str(server.id)][db_name] 198 | except: 199 | settings = await db.find_one({"server_id":str(server.id)}) 200 | await self.cache_setting(server, db_name, settings) 201 | 202 | return settings 203 | 204 | 205 | async def cache_setting(self, server, db_name, settings): 206 | if str(server.id) not in self.cached_settings.keys(): 207 | self.cached_settings[str(server.id)] = {} 208 | if db_name not in self.cached_settings[str(server.id)].keys(): 209 | self.cached_settings[str(server.id)][db_name] = settings 210 | 211 | 212 | def _check_db_name(self, db_name): 213 | db_name = db_name.lower() 214 | db_names = ["global", "general", "audio", 215 | "osu", "economy", "fun", "leveler", 216 | "streams", "utility", "prefix", "disabled"] 217 | 218 | # double check 219 | if not any([x == db_name for x in db_names]): 220 | return None 221 | return db_name 222 | 223 | 224 | async def process_commands(self, message): 225 | server = message.guild 226 | channel = message.channel 227 | # test if server has disabled the command, has to do this for every message 228 | server_disabled_cmd = await self.get_setting(server, 'disabled') 229 | 230 | # print('Server disabled commands', server_disabled_cmd) 231 | # if not there, then run normally 232 | try: 233 | if not server_disabled_cmd: 234 | return await super().process_commands(message) 235 | except: 236 | return 237 | 238 | ctx = await self.get_context(message) 239 | if not ctx: 240 | return 241 | command = None 242 | if not ctx.command and not ctx.invoked_subcommand: 243 | return 244 | elif ctx.invoked_subcommand: 245 | # print('SUB: ', ctx.invoked_subcommand) 246 | command = str(ctx.invoked_subcommand) 247 | else: 248 | # print('CMD: ', ctx.command) 249 | command = str(ctx.command) 250 | 251 | split_cmd = str(ctx.command.module).split(".") 252 | group = str(split_cmd[2]) 253 | 254 | # check if group disabled channel 255 | if str(channel.id) in server_disabled_cmd.keys() and \ 256 | "group" in server_disabled_cmd[str(channel.id)].keys() and \ 257 | group in server_disabled_cmd[str(channel.id)]["group"]: 258 | return await channel.send(f":x: **The `{group}` command group is disabled!**", delete_after=5) 259 | # check if command disabled in channel 260 | if str(channel.id) in server_disabled_cmd.keys() and \ 261 | "commands" in server_disabled_cmd[str(channel.id)].keys() and \ 262 | command in server_disabled_cmd[str(channel.id)]["commands"]: 263 | return await channel.send(f":x: **The `{command}` command is disabled!**", delete_after=5) 264 | 265 | # otherwise, run command normally 266 | await super().process_commands(message) 267 | 268 | 269 | async def send_cmd_help(self, ctx): 270 | help_em = await self.help_formatter.help_format(ctx, ctx.command) 271 | await ctx.send(embed = help_em) 272 | 273 | 274 | async def on_command_error(self, ctx, error): 275 | channel = ctx.message.channel 276 | user = ctx.message.author 277 | 278 | if isinstance(error, commands.errors.MissingRequiredArgument): 279 | await self.send_cmd_help(ctx) 280 | elif isinstance(error, commands.errors.BadArgument): 281 | await self.send_cmd_help(ctx) 282 | elif isinstance(error, commands.errors.CommandInvokeError): 283 | self.logger.exception("Exception in command '{}'".format( 284 | ctx.command.qualified_name), exc_info=error.original) 285 | oneliner = "Error in command '{}' - {}: {}".format( 286 | ctx.command.qualified_name, type(error.original).__name__, 287 | str(error.original)) 288 | await ctx.send(f'`{oneliner}`') 289 | elif isinstance(error, discord.HTTPException): 290 | await ctx.send("I need the `Embed links` permission " 291 | "to send this") 292 | elif isinstance(error, commands.errors.CheckFailure): 293 | pass 294 | elif isinstance(error, commands.errors.CommandNotFound): 295 | pass 296 | elif isinstance(error, commands.errors.NoPrivateMessage): 297 | pass 298 | elif isinstance(error, commands.errors.CommandOnCooldown): 299 | await ctx.send("{} needs to cool down! Try again in {:.2f}s".format( 300 | user.mention, float(error.retry_after)), delete_after=5) 301 | else: 302 | self.logger.exception(type(error).__name__, exc_info=error) 303 | 304 | 305 | # -----------------------------Menu Systems --------------------------------- 306 | async def menu(self, ctx, embed_list, 307 | files=[], message:discord.Message=None, page=0, timeout: int=30): 308 | def react_check(r, u): 309 | return u == ctx.author and r.message.id == message.id and str(r.emoji) in expected 310 | 311 | expected = ["➡", "⬅"] 312 | numbs = { 313 | "end": ":track_next:", 314 | "next": "➡", 315 | "back": "⬅", 316 | "first": ":track_previous:", 317 | "exit": "❌" 318 | } 319 | 320 | embed = embed_list[page] 321 | 322 | if not message: 323 | message =\ 324 | await ctx.send(embed=embed, files=files) 325 | if len(embed_list)>1: 326 | await message.add_reaction("⬅") 327 | await message.add_reaction("➡") 328 | else: 329 | await message.edit(embed=embed, files=files) 330 | 331 | try: 332 | react = await self.wait_for('reaction_add', 333 | check=react_check, timeout=timeout 334 | ) 335 | except asyncio.TimeoutError: 336 | try: 337 | await message.clear_reactions() 338 | except discord.Forbidden: # cannot remove all reactions 339 | for emote in expected: 340 | await message.remove_reaction(emote, self.user) 341 | return None 342 | 343 | if react is None: 344 | try: 345 | try: 346 | await message.clear_reactions() 347 | except: 348 | await message.remove_reaction("⬅", self.user) 349 | await message.remove_reaction("➡", self.user) 350 | except: 351 | pass 352 | return None 353 | reacts = {v: k for k, v in numbs.items()} 354 | react = reacts[react[0].emoji] 355 | if react == "next": 356 | page += 1 357 | next_page = page % len(embed_list) 358 | try: 359 | await message.remove_reaction("➡", ctx.message.author) 360 | except: 361 | pass 362 | return await self.menu(ctx, embed_list, message=message, 363 | page=next_page, timeout=timeout) 364 | elif react == "back": 365 | page -= 1 366 | next_page = page % len(embed_list) 367 | try: 368 | await message.remove_reaction("⬅", ctx.message.author) 369 | except: 370 | pass 371 | return await self.menu(ctx, embed_list, message=message, 372 | page=next_page, timeout=timeout) 373 | 374 | 375 | class HelpFormatter(commands.MinimalHelpCommand): 376 | def __init__(self, *args, **kwargs): 377 | super().__init__(*args, **kwargs) 378 | 379 | async def help_format(self, ctx, command): 380 | try: 381 | self.ctx = ctx 382 | self.command = command 383 | user = ctx.message.author 384 | embed = discord.Embed(colour=user.colour) 385 | cmd_name = self.command.name 386 | except: 387 | return 388 | 389 | desc = "" 390 | # print(self.command.description, self.command.cog_name, self.command.__dict__) 391 | # we need a padding of ~80 or so 392 | """ 393 | if isinstance(self.command, commands.Group): 394 | description = self.command.description 395 | if description: 396 | # portion 397 | desc += '{}\n'.format(description)""" 398 | 399 | # get subcommands 400 | cmd_subcommands = [] 401 | if hasattr(self.command, 'commands'): 402 | for cmd in self.command.commands: 403 | cmd_subcommands.append(cmd) 404 | 405 | if isinstance(self.command, commands.Group): 406 | max_width = 70 407 | else: 408 | max_width = 40 409 | 410 | if isinstance(self.command, commands.Command): 411 | # 412 | signature = self.get_command_signature(self.ctx, self.command) 413 | if len(signature) < max_width: 414 | white_space = max_width - len(signature) 415 | else: 416 | white_space = len(signature) 417 | desc += '```{}{}```\n'.format(signature, " "*white_space) 418 | 419 | # section 420 | if self.command.help: 421 | help_text = self.command.help 422 | desc += '{}'.format(self.command.help) 423 | 424 | # end it here if it's just a regular command 425 | if not cmd_subcommands: 426 | embed.description = await self.pretty_format(desc, cmd_name, ctx=ctx) 427 | return embed 428 | 429 | # if there are subcommands, the append them to the end 430 | desc_limit = 40 431 | desc += "```" 432 | for sub_command in cmd_subcommands: 433 | cmd_help = sub_command.help.split("\n") 434 | cmd_help = cmd_help[0] # doesn't grab any options/examples 435 | cmd_help = textwrap.fill(cmd_help, desc_limit) 436 | cmd_help = cmd_help.split('\n') 437 | for num, line in enumerate(cmd_help): 438 | if num == 0: 439 | desc += "{:<15}{:<25}\n".format(sub_command.name, line) 440 | else: 441 | desc += "{:<15}{:<25}\n".format("", line) 442 | desc += "```" 443 | embed.description = await self.pretty_format(desc, cmd_name, ctx=ctx) 444 | return embed 445 | 446 | def get_command_signature(self, ctx, command): 447 | return '{0.prefix}{1.qualified_name} {1.signature}'.format(ctx, command) 448 | 449 | async def pretty_format(self, description, cmd_name, ctx = None): 450 | # replace <> stuff 451 | if ctx: 452 | description = description.replace("", "{}".format(ctx.message.author.name)) 453 | else: 454 | description = description.replace("", "Stevy") 455 | 456 | if ctx.invoked_subcommand: 457 | # print(ctx.command, "/", cmd_name) 458 | description = description.replace( 459 | "", "{}".format(ctx.command)) 460 | else: 461 | description = description.replace("", cmd_name) 462 | description = description.replace(" ", "") 463 | description = description.replace("", 464 | ">botinfo for list of supported servers.") 465 | description = description.replace("+", ">") 466 | 467 | # make everything after [Example] code text 468 | look_for = "[Example]" 469 | find_ex = description.find(look_for) 470 | if find_ex != -1: 471 | description = list(description) 472 | description.insert(find_ex + len(look_for) + 1, '`') 473 | description = ''.join(description) 474 | description += '`' 475 | 476 | find_block = [m.start() for m in re.finditer('```', description)] 477 | description = list(description) 478 | new_desc = "" 479 | ind = 0 480 | for ch in description: 481 | if ind < find_block[0] or ind > find_block[1]: 482 | if ch == "[": 483 | new_desc += "**[" 484 | elif ch == "]": 485 | new_desc += "]**" 486 | elif ch == "(": 487 | new_desc += "`(" 488 | elif ch == ")": 489 | new_desc += ")`" 490 | else: 491 | new_desc += ch 492 | else: 493 | new_desc += ch 494 | ind+=1 495 | 496 | # do things on the word level 497 | word_split = new_desc.split(' ') 498 | final_desc = [] 499 | for word in word_split: 500 | if '-' in word and '(' not in word and 'Example' not in word: 501 | final_desc.append('{}'.format(word)) 502 | else: 503 | final_desc.append('{}'.format(word)) 504 | new_desc = ' '.join(final_desc) 505 | 506 | return new_desc 507 | 508 | 509 | def set_logger(bot): 510 | logger = logging.getLogger("bot") 511 | logger.setLevel(logging.INFO) 512 | 513 | log_format = logging.Formatter( 514 | '%(asctime)s %(levelname)s %(module)s %(funcName)s %(lineno)d: ' 515 | '%(message)s', 516 | datefmt="[%d/%m/%Y %H:%M]") 517 | 518 | stdout_handler = logging.StreamHandler(sys.stdout) 519 | stdout_handler.setFormatter(log_format) 520 | stdout_handler.setLevel(logging.INFO) 521 | logger.setLevel(logging.INFO) 522 | 523 | fhandler = logging.handlers.RotatingFileHandler( 524 | filename='log/bot.log', encoding='utf-8', mode='a', 525 | maxBytes=10**7, backupCount=5) 526 | fhandler.setFormatter(log_format) 527 | 528 | logger.addHandler(fhandler) 529 | logger.addHandler(stdout_handler) 530 | 531 | dpy_logger = logging.getLogger("discord") 532 | dpy_logger.setLevel(logging.WARNING) 533 | handler = logging.FileHandler( 534 | filename='log/discord.log', encoding='utf-8', mode='a') 535 | handler.setFormatter(logging.Formatter( 536 | '%(asctime)s %(levelname)s %(module)s %(funcName)s %(lineno)d: ' 537 | '%(message)s', 538 | datefmt="[%d/%m/%Y %H:%M]")) 539 | dpy_logger.addHandler(handler) 540 | 541 | return logger 542 | 543 | 544 | def start_bot(config): 545 | bot = Bot(config=config, description=config['description'], 546 | shard_count=config["shard_count"]) 547 | bot.run(config['token']) 548 | 549 | if __name__ == '__main__': 550 | warnings.filterwarnings("ignore") 551 | config = json.loads(open('config.json').read()) 552 | 553 | asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) 554 | start_bot(config) -------------------------------------------------------------------------------- /main_passive.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sys 4 | import time 5 | import json 6 | import copy 7 | import inspect 8 | import logging 9 | import logging.handlers 10 | import asyncio 11 | import pathlib 12 | import textwrap 13 | import datetime 14 | import warnings 15 | import operator 16 | import importlib 17 | import traceback 18 | from io import TextIOWrapper 19 | from threading import Thread 20 | from utils.dataIO import fileIO 21 | from utils.donor_utils import Patreon 22 | 23 | import uvloop 24 | # import pymongo 25 | import motor.motor_asyncio 26 | 27 | import discord 28 | from discord.ext import commands 29 | 30 | from cogs.osu.updater import Updater 31 | 32 | class Bot(commands.AutoShardedBot): 33 | def __init__(self, **kwargs): 34 | # specify intents 35 | intents = discord.Intents.default() 36 | # intents = discord.Intents.all() 37 | #intents.presence = True 38 | intents.members = True 39 | 40 | self.start_time = datetime.datetime.utcnow() 41 | 42 | super().__init__( 43 | command_prefix=self.get_server_prefixes, 44 | description=kwargs.pop('description'), 45 | shard_count=kwargs.pop('shard_count'), 46 | chunk_guilds_at_startup=False, 47 | intents=intents 48 | ) 49 | self.help_formatter = HelpFormatter() 50 | self.config = kwargs['config'] 51 | self.logger = set_logger(self) 52 | self.settings = None 53 | 54 | # load database 55 | self.db_client = motor.motor_asyncio.AsyncIOMotorClient( 56 | port=self.config['database']['primary'], 57 | connectTimeoutMS=5000, 58 | socketTimeoutMS=5000, 59 | serverSelectionTimeoutMS=5000) 60 | 61 | self.db = self.db_client[str(self.config['bot_name'])] # overall database, not collection 62 | self.disabled_commands = self.db["disabled"] 63 | self.prefix_settings = self.db["prefix"] # settings for the cog 64 | 65 | # patreons 66 | self.patreon = Patreon(self.config['API_KEYS']['PATREON']) 67 | 68 | # load cogs 69 | self.loop.create_task(self.load_cogs()) 70 | 71 | # special lists 72 | special_lists = fileIO( 73 | os.path.join(os.getcwd(), 'database','other','special_lists.json'), "load") 74 | self.blacklist = special_lists['blacklist'] 75 | self.whitelist = special_lists['whitelist'] 76 | 77 | # passive commands, separate bot 78 | self.is_passive_bot = True 79 | 80 | 81 | async def get_server_prefixes(self, bot, message, 82 | one_prefix=False, prefix_list=False): 83 | """ 84 | Obtains prefix based on server preference. 85 | """ 86 | prefix = await self._get_prefix(message) 87 | if one_prefix: 88 | return prefix[0] 89 | elif prefix_list: 90 | return prefix 91 | return commands.when_mentioned_or(*prefix)(bot, message) 92 | 93 | 94 | async def _get_prefix(self, message): 95 | """ 96 | Get prefixes for the server 97 | """ 98 | server = message.guild 99 | default_prefixes = self.config['prefix'] 100 | try: 101 | server_info = await self.prefix_settings.find_one({"server_id":str(server.id)}) 102 | prefixes = server_info["prefix"] 103 | except: 104 | prefixes = None 105 | 106 | if not prefixes or prefixes == []: 107 | prefix = default_prefixes 108 | else: 109 | prefix = prefixes 110 | return prefix 111 | 112 | 113 | async def load_cogs(self): 114 | """ 115 | Loads all cogs in folder. 116 | """ 117 | load_list = ['osu'] 118 | await self.wait_until_ready() 119 | # await asyncio.sleep(.1) 120 | loaded_cogs = [] 121 | failed_cogs = [] 122 | cog_directory = pathlib.Path('./cogs') 123 | for cog_name in cog_directory.iterdir(): 124 | cog_name = cog_name.name 125 | if "__" not in cog_name and cog_name in load_list: 126 | success = False 127 | while not success: 128 | try: 129 | self.load_extension(f'cogs.{cog_name}.{cog_name}') 130 | success = True 131 | except Exception as e: 132 | print(traceback.format_exc()) 133 | # failed_cogs.append(cog_name) 134 | # self.logger.exception(e) 135 | await asyncio.sleep(5) 136 | 137 | 138 | async def on_ready(self): 139 | # self.settings = await self.application_info() 140 | print(f'All {len(self.shards.keys())} passive shards loaded.') 141 | elapsed_time = str(datetime.datetime.utcnow() - self.start_time) 142 | print(f'Took {elapsed_time} seconds.') 143 | 144 | 145 | async def on_message(self, message): 146 | return 147 | 148 | 149 | def escape_mass_mentions(self, text): 150 | return self._escape(text, mass_mentions=True) 151 | 152 | 153 | def _escape(self, text, *, mass_mentions=False, formatting=False): 154 | if mass_mentions: 155 | text = text.replace("@everyone", "@\u200beveryone") 156 | text = text.replace("@here", "@\u200bhere") 157 | if formatting: 158 | text = (text.replace("`", "\\`") 159 | .replace("*", "\\*") 160 | .replace("_", "\\_") 161 | .replace("~", "\\~")) 162 | return text 163 | 164 | 165 | async def get_setting(self, server, db_name): 166 | if not server: 167 | return None 168 | 169 | db_name = self._check_db_name(db_name) 170 | db = self.db[db_name] 171 | settings = await db.find_one({"server_id":str(server.id)}) 172 | return settings 173 | 174 | 175 | def _check_db_name(self, db_name): 176 | db_name = db_name.lower() 177 | db_names = ["global", "general", "audio", 178 | "osu", "economy", "fun", "leveler", 179 | "streams", "utility", "prefix", "disabled"] 180 | 181 | # double check 182 | if not any([x == db_name for x in db_names]): 183 | return None 184 | return db_name 185 | 186 | 187 | async def process_commands(self, message): 188 | return 189 | 190 | 191 | async def send_cmd_help(self, ctx): 192 | return 193 | 194 | 195 | async def on_command_error(self, ctx, error): 196 | return 197 | 198 | 199 | # -----------------------------Menu Systems --------------------------------- 200 | async def menu(self, ctx, embed_list, 201 | files=[], message:discord.Message=None, page=0, timeout: int=30): 202 | def react_check(r, u): 203 | return u == ctx.author and r.message.id == message.id and str(r.emoji) in expected 204 | 205 | expected = ["➡", "⬅"] 206 | numbs = { 207 | "end": ":track_next:", 208 | "next": "➡", 209 | "back": "⬅", 210 | "first": ":track_previous:", 211 | "exit": "❌" 212 | } 213 | 214 | embed = embed_list[page] 215 | 216 | if not message: 217 | message =\ 218 | await ctx.send(embed=embed, files=files) 219 | if len(embed_list)>1: 220 | await message.add_reaction("⬅") 221 | await message.add_reaction("➡") 222 | else: 223 | await message.edit(embed=embed, files=files) 224 | 225 | try: 226 | react = await self.wait_for('reaction_add', 227 | check=react_check, timeout=timeout 228 | ) 229 | except asyncio.TimeoutError: 230 | try: 231 | await message.clear_reactions() 232 | except discord.Forbidden: # cannot remove all reactions 233 | for emote in expected: 234 | await message.remove_reaction(emote, self.user) 235 | return None 236 | 237 | if react is None: 238 | try: 239 | try: 240 | await message.clear_reactions() 241 | except: 242 | await message.remove_reaction("⬅", self.user) 243 | await message.remove_reaction("➡", self.user) 244 | except: 245 | pass 246 | return None 247 | reacts = {v: k for k, v in numbs.items()} 248 | react = reacts[react[0].emoji] 249 | if react == "next": 250 | page += 1 251 | next_page = page % len(embed_list) 252 | try: 253 | await message.remove_reaction("➡", ctx.message.author) 254 | except: 255 | pass 256 | return await self.menu(ctx, embed_list, message=message, 257 | page=next_page, timeout=timeout) 258 | elif react == "back": 259 | page -= 1 260 | next_page = page % len(embed_list) 261 | try: 262 | await message.remove_reaction("⬅", ctx.message.author) 263 | except: 264 | pass 265 | return await self.menu(ctx, embed_list, message=message, 266 | page=next_page, timeout=timeout) 267 | 268 | 269 | class HelpFormatter(commands.MinimalHelpCommand): 270 | def __init__(self, *args, **kwargs): 271 | super().__init__(*args, **kwargs) 272 | 273 | async def help_format(self, ctx, command): 274 | try: 275 | self.ctx = ctx 276 | self.command = command 277 | user = ctx.message.author 278 | embed = discord.Embed(colour=user.colour) 279 | cmd_name = self.command.name 280 | except: 281 | return 282 | 283 | desc = "" 284 | # print(self.command.description, self.command.cog_name, self.command.__dict__) 285 | # we need a padding of ~80 or so 286 | """ 287 | if isinstance(self.command, commands.Group): 288 | description = self.command.description 289 | if description: 290 | # portion 291 | desc += '{}\n'.format(description)""" 292 | 293 | # get subcommands 294 | cmd_subcommands = [] 295 | if hasattr(self.command, 'commands'): 296 | for cmd in self.command.commands: 297 | cmd_subcommands.append(cmd) 298 | 299 | if isinstance(self.command, commands.Group): 300 | max_width = 70 301 | else: 302 | max_width = 40 303 | 304 | if isinstance(self.command, commands.Command): 305 | # 306 | signature = self.get_command_signature(self.ctx, self.command) 307 | if len(signature) < max_width: 308 | white_space = max_width - len(signature) 309 | else: 310 | white_space = len(signature) 311 | desc += '```{}{}```\n'.format(signature, " "*white_space) 312 | 313 | # section 314 | if self.command.help: 315 | help_text = self.command.help 316 | desc += '{}'.format(self.command.help) 317 | 318 | # end it here if it's just a regular command 319 | if not cmd_subcommands: 320 | embed.description = await self.pretty_format(desc, cmd_name, ctx=ctx) 321 | return embed 322 | 323 | # if there are subcommands, the append them to the end 324 | desc_limit = 40 325 | desc += "```" 326 | for sub_command in cmd_subcommands: 327 | cmd_help = sub_command.help.split("\n") 328 | cmd_help = cmd_help[0] # doesn't grab any options/examples 329 | cmd_help = textwrap.fill(cmd_help, desc_limit) 330 | cmd_help = cmd_help.split('\n') 331 | for num, line in enumerate(cmd_help): 332 | if num == 0: 333 | desc += "{:<15}{:<25}\n".format(sub_command.name, line) 334 | else: 335 | desc += "{:<15}{:<25}\n".format("", line) 336 | desc += "```" 337 | embed.description = await self.pretty_format(desc, cmd_name, ctx=ctx) 338 | return embed 339 | 340 | def get_command_signature(self, ctx, command): 341 | return '{0.prefix}{1.qualified_name} {1.signature}'.format(ctx, command) 342 | 343 | async def pretty_format(self, description, cmd_name, ctx = None): 344 | # replace <> stuff 345 | if ctx: 346 | description = description.replace("", "{}".format(ctx.message.author.name)) 347 | else: 348 | description = description.replace("", "Stevy") 349 | 350 | if ctx.invoked_subcommand: 351 | # print(ctx.command, "/", cmd_name) 352 | description = description.replace( 353 | "", "{}".format(ctx.command)) 354 | else: 355 | description = description.replace("", cmd_name) 356 | description = description.replace(" ", "") 357 | description = description.replace("", 358 | ">botinfo for list of supported servers.") 359 | description = description.replace("+", ">") 360 | 361 | # make everything after [Example] code text 362 | look_for = "[Example]" 363 | find_ex = description.find(look_for) 364 | if find_ex != -1: 365 | description = list(description) 366 | description.insert(find_ex + len(look_for) + 1, '`') 367 | description = ''.join(description) 368 | description += '`' 369 | 370 | find_block = [m.start() for m in re.finditer('```', description)] 371 | description = list(description) 372 | new_desc = "" 373 | ind = 0 374 | for ch in description: 375 | if ind < find_block[0] or ind > find_block[1]: 376 | if ch == "[": 377 | new_desc += "**[" 378 | elif ch == "]": 379 | new_desc += "]**" 380 | elif ch == "(": 381 | new_desc += "`(" 382 | elif ch == ")": 383 | new_desc += ")`" 384 | else: 385 | new_desc += ch 386 | else: 387 | new_desc += ch 388 | ind+=1 389 | 390 | # do things on the word level 391 | word_split = new_desc.split(' ') 392 | final_desc = [] 393 | for word in word_split: 394 | if '-' in word and '(' not in word and 'Example' not in word: 395 | final_desc.append('{}'.format(word)) 396 | else: 397 | final_desc.append('{}'.format(word)) 398 | new_desc = ' '.join(final_desc) 399 | 400 | return new_desc 401 | 402 | 403 | def set_logger(bot): 404 | logger = logging.getLogger("bot") 405 | logger.setLevel(logging.INFO) 406 | 407 | log_format = logging.Formatter( 408 | '%(asctime)s %(levelname)s %(module)s %(funcName)s %(lineno)d: ' 409 | '%(message)s', 410 | datefmt="[%d/%m/%Y %H:%M]") 411 | 412 | stdout_handler = logging.StreamHandler(sys.stdout) 413 | stdout_handler.setFormatter(log_format) 414 | stdout_handler.setLevel(logging.INFO) 415 | logger.setLevel(logging.INFO) 416 | 417 | fhandler = logging.handlers.RotatingFileHandler( 418 | filename='log/bot.log', encoding='utf-8', mode='a', 419 | maxBytes=10**7, backupCount=5) 420 | fhandler.setFormatter(log_format) 421 | 422 | logger.addHandler(fhandler) 423 | logger.addHandler(stdout_handler) 424 | 425 | dpy_logger = logging.getLogger("discord") 426 | dpy_logger.setLevel(logging.WARNING) 427 | handler = logging.FileHandler( 428 | filename='log/discord.log', encoding='utf-8', mode='a') 429 | handler.setFormatter(logging.Formatter( 430 | '%(asctime)s %(levelname)s %(module)s %(funcName)s %(lineno)d: ' 431 | '%(message)s', 432 | datefmt="[%d/%m/%Y %H:%M]")) 433 | dpy_logger.addHandler(handler) 434 | 435 | return logger 436 | 437 | 438 | def start_bot(config): 439 | bot = Bot(config=config, description=config['description'], 440 | shard_count=config["shard_count"]) 441 | bot.run(config['token']) 442 | 443 | if __name__ == '__main__': 444 | warnings.filterwarnings("ignore") 445 | config = json.loads(open('config.json').read()) 446 | 447 | asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) 448 | start_bot(config) -------------------------------------------------------------------------------- /other_scripts/update_beatmaps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | bot_dir="" 3 | filename="" 4 | filename_zip="${filename}.tar.bz2" 5 | urlname="https://data.ppy.sh/${filename_zip}" 6 | echo "Downloading file from ${urlname}" 7 | 8 | outputdir="$HOME/${bot_dir}/other_scripts" 9 | full_zip_output_path="${outputdir}/${filename_zip}" 10 | full_folder_output_path="${outputdir}/${filename}/" 11 | echo "Zip file: ${full_zip_output_path}" 12 | echo "Folder: ${full_folder_output_path}" 13 | wget "${urlname}" -P "${outputdir}" 14 | tar -xvjf "${full_zip_output_path}" 15 | rsync -av "${full_folder_output_path}" "$HOME/${bot_dir}/cogs/osu/beatmaps" 16 | rm "${full_zip_output_path}" 17 | rm -r "${full_folder_output_path}" -------------------------------------------------------------------------------- /tracker_bot.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import time 4 | import json 5 | import copy 6 | import inspect 7 | import logging 8 | import logging.handlers 9 | import asyncio 10 | import pathlib 11 | import textwrap 12 | import datetime 13 | import warnings 14 | import operator 15 | import importlib 16 | import traceback 17 | from io import TextIOWrapper 18 | from threading import Thread 19 | from utils.dataIO import fileIO 20 | 21 | import uvloop 22 | import motor.motor_asyncio 23 | 24 | import discord 25 | from discord.ext import commands 26 | 27 | from cogs.osu.updater import Updater 28 | 29 | class TrackerBot(commands.AutoShardedBot): 30 | def __init__(self, **kwargs): 31 | # specify intents 32 | intents = discord.Intents.default() 33 | intents.members = True 34 | 35 | self.start_time = time.time() 36 | 37 | super().__init__( 38 | command_prefix='###', 39 | description=kwargs.pop('description'), 40 | shard_count=kwargs.pop('shard_count'), 41 | chunk_guilds_at_startup=False, 42 | intents=intents 43 | ) 44 | 45 | self.config = kwargs['config'] 46 | self.logger = set_logger(self) 47 | self.settings = None 48 | 49 | # load database 50 | self.db_client = motor.motor_asyncio.AsyncIOMotorClient( 51 | port=self.config['database']['primary'], 52 | connectTimeoutMS=5000, 53 | socketTimeoutMS=5000, 54 | serverSelectionTimeoutMS=5000) 55 | self.db = self.db_client[str(self.config['bot_name'])] 56 | 57 | # load cogs 58 | self.loop.create_task(self.load_updater()) 59 | 60 | async def load_updater(self): 61 | await self.wait_until_ready() 62 | self.load_extension(f'cogs.osu.updater') 63 | 64 | async def on_ready(self): 65 | # self.settings = await self.application_info() 66 | print(f'All {len(self.shards.keys())} tracking shards loaded.') 67 | elapsed_time = str(time.time() - self.start_time) 68 | print(f'Took {elapsed_time} seconds.') 69 | 70 | async def on_message(self, message): 71 | return 72 | 73 | async def process_commands(self, message): 74 | return 75 | 76 | async def on_command_error(self, ctx, error): 77 | return 78 | 79 | def set_logger(bot): 80 | logger = logging.getLogger("bot") 81 | logger.setLevel(logging.INFO) 82 | 83 | log_format = logging.Formatter( 84 | '%(asctime)s %(levelname)s %(module)s %(funcName)s %(lineno)d: ' 85 | '%(message)s', 86 | datefmt="[%d/%m/%Y %H:%M]") 87 | 88 | stdout_handler = logging.StreamHandler(sys.stdout) 89 | stdout_handler.setFormatter(log_format) 90 | stdout_handler.setLevel(logging.INFO) 91 | logger.setLevel(logging.INFO) 92 | 93 | fhandler = logging.handlers.RotatingFileHandler( 94 | filename='log/bot.log', encoding='utf-8', mode='a', 95 | maxBytes=10**7, backupCount=5) 96 | fhandler.setFormatter(log_format) 97 | 98 | logger.addHandler(fhandler) 99 | logger.addHandler(stdout_handler) 100 | 101 | dpy_logger = logging.getLogger("discord") 102 | dpy_logger.setLevel(logging.WARNING) 103 | handler = logging.FileHandler( 104 | filename='log/discord.log', encoding='utf-8', mode='a') 105 | handler.setFormatter(logging.Formatter( 106 | '%(asctime)s %(levelname)s %(module)s %(funcName)s %(lineno)d: ' 107 | '%(message)s', 108 | datefmt="[%d/%m/%Y %H:%M]")) 109 | dpy_logger.addHandler(handler) 110 | 111 | return logger 112 | 113 | 114 | def start_bot(config): 115 | bot = TrackerBot( 116 | config=config, 117 | description=config['description'], 118 | shard_count=config["shard_count"]) 119 | bot.run(config['token']) 120 | 121 | if __name__ == '__main__': 122 | warnings.filterwarnings("ignore") 123 | config = json.loads(open('config.json').read()) 124 | 125 | asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) 126 | start_bot(config) -------------------------------------------------------------------------------- /utils/chat_formatting.py: -------------------------------------------------------------------------------- 1 | def error(text): 2 | return "\N{NO ENTRY SIGN} {}".format(text) 3 | 4 | 5 | def warning(text): 6 | return "\N{WARNING SIGN} {}".format(text) 7 | 8 | 9 | def info(text): 10 | return "\N{INFORMATION SOURCE} {}".format(text) 11 | 12 | 13 | def question(text): 14 | return "\N{BLACK QUESTION MARK ORNAMENT} {}".format(text) 15 | 16 | 17 | def bold(text): 18 | return "**{}**".format(text) 19 | 20 | 21 | def box(text, lang=""): 22 | ret = "```{}\n{}\n```".format(lang, text) 23 | return ret 24 | 25 | 26 | def inline(text): 27 | return "`{}`".format(text) 28 | 29 | 30 | def italics(text): 31 | return "*{}*".format(text) 32 | 33 | 34 | def pagify(text, delims=["\n"], *, escape=True, shorten_by=8, 35 | page_length=2000): 36 | """DOES NOT RESPECT MARKDOWN BOXES OR INLINE CODE""" 37 | in_text = text 38 | if escape: 39 | num_mentions = text.count("@here") + text.count("@everyone") 40 | shorten_by += num_mentions 41 | page_length -= shorten_by 42 | while len(in_text) > page_length: 43 | closest_delim = max([in_text.rfind(d, 0, page_length) 44 | for d in delims]) 45 | closest_delim = closest_delim if closest_delim != -1 else page_length 46 | if escape: 47 | to_send = escape_mass_mentions(in_text[:closest_delim]) 48 | else: 49 | to_send = in_text[:closest_delim] 50 | yield to_send 51 | in_text = in_text[closest_delim:] 52 | 53 | if escape: 54 | yield escape_mass_mentions(in_text) 55 | else: 56 | yield in_text 57 | 58 | 59 | def strikethrough(text): 60 | return "~~{}~~".format(text) 61 | 62 | 63 | def underline(text): 64 | return "__{}__".format(text) 65 | 66 | 67 | def escape(text, *, mass_mentions=False, formatting=False): 68 | if mass_mentions: 69 | text = text.replace("@everyone", "@\u200beveryone") 70 | text = text.replace("@here", "@\u200bhere") 71 | if formatting: 72 | text = (text.replace("`", "\\`") 73 | .replace("*", "\\*") 74 | .replace("_", "\\_") 75 | .replace("~", "\\~")) 76 | return text 77 | 78 | 79 | def escape_mass_mentions(text): 80 | return escape(text, mass_mentions=True) 81 | -------------------------------------------------------------------------------- /utils/checks.py: -------------------------------------------------------------------------------- 1 | from discord.ext import commands 2 | 3 | # The permission system of the bot is based on a "just works" basis 4 | # You have permissions and the bot has permissions. If you meet the permissions 5 | # required to execute the command (and the bot does as well) then it goes through 6 | # and you can execute the command. 7 | # Certain permissions signify if the person is a moderator (Manage Server) or an 8 | # admin (Administrator). Having these signify certain bypasses. 9 | # Of course, the owner will always be able to execute commands. 10 | 11 | async def check_permissions(ctx, perms, *, check=all): 12 | is_owner = await ctx.bot.is_owner(ctx.author) 13 | if is_owner: 14 | return True 15 | 16 | resolved = ctx.channel.permissions_for(ctx.author) 17 | return check(getattr(resolved, name, None) == value for name, value in perms.items()) 18 | 19 | def has_permissions(*, check=all, **perms): 20 | async def pred(ctx): 21 | return await check_permissions(ctx, perms, check=check) 22 | return commands.check(pred) 23 | 24 | async def check_guild_permissions(ctx, perms, *, check=all): 25 | is_owner = await ctx.bot.is_owner(ctx.author) 26 | if is_owner: 27 | return True 28 | 29 | if ctx.guild is None: 30 | return False 31 | 32 | resolved = ctx.author.guild_permissions 33 | return check(getattr(resolved, name, None) == value for name, value in perms.items()) 34 | 35 | def has_guild_permissions(*, check=all, **perms): 36 | async def pred(ctx): 37 | return await check_guild_permissions(ctx, perms, check=check) 38 | return commands.check(pred) 39 | 40 | # These do not take channel overrides into account 41 | 42 | def is_mod(): 43 | async def pred(ctx): 44 | return await check_guild_permissions(ctx, {'manage_guild': True}) 45 | return commands.check(pred) 46 | 47 | def is_admin(): 48 | async def pred(ctx): 49 | return await check_guild_permissions(ctx, {'administrator': True}) 50 | return commands.check(pred) 51 | 52 | def mod_or_permissions(**perms): 53 | perms['manage_guild'] = True 54 | async def predicate(ctx): 55 | return await check_guild_permissions(ctx, perms, check=any) 56 | return commands.check(predicate) 57 | 58 | def admin_or_permissions(**perms): 59 | perms['administrator'] = True 60 | async def predicate(ctx): 61 | return await check_guild_permissions(ctx, perms, check=any) 62 | return commands.check(predicate) 63 | 64 | def is_in_guilds(*guild_ids): 65 | def predicate(ctx): 66 | guild = ctx.guild 67 | if guild is None: 68 | return False 69 | return guild.id in guild_ids 70 | return commands.check(predicate) 71 | 72 | def is_lounge_cpp(): 73 | return is_in_guilds(145079846832308224) --------------------------------------------------------------------------------