├── r6 ├── data │ ├── bg │ │ ├── ash.jpg │ │ ├── sledge.jpg │ │ ├── twitch.jpg │ │ ├── thatcher.jpg │ │ └── thermite.jpg │ └── fonts │ │ ├── RobotoBold.ttf │ │ └── RobotoRegular.ttf ├── __init__.py ├── info.json └── converters.py ├── simleague ├── data │ ├── font_bold.ttf │ ├── Seven-Segment.ttf │ └── LeagueSpartan-Bold.otf ├── functions.py ├── abc.py ├── __init__.py ├── info.json ├── teamset.py └── stats.py ├── crypto ├── __init__.py └── info.json ├── f1 ├── __init__.py └── info.json ├── palette ├── __init__.py ├── data │ └── fonts │ │ └── RobotoRegular.ttf ├── info.json ├── converters.py └── palette.py ├── trigger ├── __init__.py ├── info.json └── objects.py ├── apitools ├── __init__.py ├── info.json └── apitools.py ├── cashdrop ├── __init__.py └── info.json ├── jsk ├── __init__.py ├── info.json └── jishaku_cog.py ├── giveaways ├── __init__.py ├── info.json └── menu.py ├── stickbugged ├── __init__.py ├── info.json ├── converters.py └── stickbugged.py ├── voicetracker ├── __init__.py ├── info.json └── voicetracker.py ├── userinfo ├── __init__.py └── info.json ├── emailverify ├── __init__.py └── info.json ├── permchecker ├── __init__.py ├── info.json └── permchecker.py ├── rolehistory ├── __init__.py ├── info.json └── rolehistory.py ├── threadbumper ├── __init__.py ├── info.json └── threadbumper.py ├── tips ├── __init__.py ├── info.json └── tips.py ├── dminvites ├── __init__.py ├── info.json └── dminvites.py ├── pyproject.toml ├── forward ├── __init__.py ├── info.json └── forward.py ├── commandstats ├── __init__.py ├── info.json └── menus.py ├── joinmessage ├── __init__.py ├── info.json └── joinmessage.py ├── redditpost ├── __init__.py └── info.json ├── news ├── __init__.py ├── info.json ├── menus.py └── news.py ├── snipe ├── __init__.py ├── info.json └── menus.py ├── covid ├── __init__.py ├── info.json └── menus.py ├── antispam ├── __init__.py └── info.json ├── botlistspost ├── __init__.py ├── info.json └── botlistspost.py ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── workflows │ └── black.yml └── FUNDING.yml ├── unbelievaboat ├── __init__.py ├── abc.py ├── info.json ├── functions.py ├── checks.py └── wallet.py ├── mod ├── __init__.py ├── info.json └── _tagscript.py ├── faceit ├── __init__.py ├── info.json ├── converters.py └── funcs.py ├── tiktokreposter ├── __init__.py └── info.json ├── info.json ├── highlight ├── __init__.py └── info.json ├── dankmemer ├── __init__.py ├── info.json ├── converters.py └── README.md ├── Makefile ├── LICENSE ├── make.bat ├── .pre-commit-config.yaml ├── .gitignore └── .utils └── utils.py /r6/data/bg/ash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaree/flare-cogs/HEAD/r6/data/bg/ash.jpg -------------------------------------------------------------------------------- /r6/data/bg/sledge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaree/flare-cogs/HEAD/r6/data/bg/sledge.jpg -------------------------------------------------------------------------------- /r6/data/bg/twitch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaree/flare-cogs/HEAD/r6/data/bg/twitch.jpg -------------------------------------------------------------------------------- /r6/data/bg/thatcher.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaree/flare-cogs/HEAD/r6/data/bg/thatcher.jpg -------------------------------------------------------------------------------- /r6/data/bg/thermite.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaree/flare-cogs/HEAD/r6/data/bg/thermite.jpg -------------------------------------------------------------------------------- /r6/data/fonts/RobotoBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaree/flare-cogs/HEAD/r6/data/fonts/RobotoBold.ttf -------------------------------------------------------------------------------- /simleague/data/font_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaree/flare-cogs/HEAD/simleague/data/font_bold.ttf -------------------------------------------------------------------------------- /r6/data/fonts/RobotoRegular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaree/flare-cogs/HEAD/r6/data/fonts/RobotoRegular.ttf -------------------------------------------------------------------------------- /simleague/data/Seven-Segment.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaree/flare-cogs/HEAD/simleague/data/Seven-Segment.ttf -------------------------------------------------------------------------------- /crypto/__init__.py: -------------------------------------------------------------------------------- 1 | from .crypto import Crypto 2 | 3 | 4 | async def setup(bot): 5 | await bot.add_cog(Crypto(bot)) 6 | -------------------------------------------------------------------------------- /f1/__init__.py: -------------------------------------------------------------------------------- 1 | from .f1 import F1 2 | 3 | 4 | async def setup(bot): 5 | cog = F1(bot) 6 | await bot.add_cog(cog) 7 | -------------------------------------------------------------------------------- /palette/__init__.py: -------------------------------------------------------------------------------- 1 | from .palette import Palette 2 | 3 | 4 | async def setup(bot): 5 | await bot.add_cog(Palette(bot)) 6 | -------------------------------------------------------------------------------- /palette/data/fonts/RobotoRegular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaree/flare-cogs/HEAD/palette/data/fonts/RobotoRegular.ttf -------------------------------------------------------------------------------- /simleague/data/LeagueSpartan-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaree/flare-cogs/HEAD/simleague/data/LeagueSpartan-Bold.otf -------------------------------------------------------------------------------- /trigger/__init__.py: -------------------------------------------------------------------------------- 1 | from .trigger import Trigger 2 | 3 | 4 | async def setup(bot): 5 | await bot.add_cog(Trigger(bot)) 6 | -------------------------------------------------------------------------------- /apitools/__init__.py: -------------------------------------------------------------------------------- 1 | from .apitools import ApiTools 2 | 3 | 4 | async def setup(bot): 5 | await bot.add_cog(ApiTools(bot)) 6 | -------------------------------------------------------------------------------- /cashdrop/__init__.py: -------------------------------------------------------------------------------- 1 | from .cashdrop import Cashdrop 2 | 3 | 4 | async def setup(bot): 5 | await bot.add_cog(Cashdrop(bot)) 6 | -------------------------------------------------------------------------------- /jsk/__init__.py: -------------------------------------------------------------------------------- 1 | from .jishaku_cog import Jishaku 2 | 3 | 4 | async def setup(bot): 5 | await bot.add_cog(Jishaku(bot=bot)) 6 | -------------------------------------------------------------------------------- /giveaways/__init__.py: -------------------------------------------------------------------------------- 1 | from .giveaways import Giveaways 2 | 3 | 4 | async def setup(bot): 5 | await bot.add_cog(Giveaways(bot)) 6 | -------------------------------------------------------------------------------- /stickbugged/__init__.py: -------------------------------------------------------------------------------- 1 | from .stickbugged import StickBugged 2 | 3 | 4 | async def setup(bot): 5 | await bot.add_cog(StickBugged(bot)) 6 | -------------------------------------------------------------------------------- /voicetracker/__init__.py: -------------------------------------------------------------------------------- 1 | from .voicetracker import VoiceTracker 2 | 3 | 4 | async def setup(bot): 5 | await bot.add_cog(VoiceTracker(bot)) 6 | -------------------------------------------------------------------------------- /userinfo/__init__.py: -------------------------------------------------------------------------------- 1 | from .userinfo import setup 2 | 3 | __red_end_user_data_statement__ = "This cog does not persistently store data about users." 4 | -------------------------------------------------------------------------------- /emailverify/__init__.py: -------------------------------------------------------------------------------- 1 | from .emailverify import EmailVerify 2 | 3 | 4 | async def setup(bot): 5 | cog = EmailVerify(bot) 6 | await bot.add_cog(cog) 7 | -------------------------------------------------------------------------------- /permchecker/__init__.py: -------------------------------------------------------------------------------- 1 | from .permchecker import PermChecker 2 | 3 | 4 | async def setup(bot): 5 | cog = PermChecker(bot) 6 | await bot.add_cog(cog) 7 | -------------------------------------------------------------------------------- /rolehistory/__init__.py: -------------------------------------------------------------------------------- 1 | from .rolehistory import RoleHistory 2 | 3 | 4 | async def setup(bot): 5 | cog = RoleHistory(bot) 6 | await cog.initalize() 7 | await bot.add_cog(cog) 8 | -------------------------------------------------------------------------------- /threadbumper/__init__.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from redbot.core.errors import CogLoadError 3 | 4 | from .threadbumper import ThreadBumper 5 | 6 | 7 | async def setup(bot): 8 | await bot.add_cog(ThreadBumper(bot)) 9 | -------------------------------------------------------------------------------- /tips/__init__.py: -------------------------------------------------------------------------------- 1 | from redbot.core.bot import Red 2 | 3 | from .tips import Tips 4 | 5 | 6 | async def setup(bot: Red) -> None: 7 | cog = Tips(bot) 8 | await bot.add_cog(cog) 9 | await cog.initialize() 10 | -------------------------------------------------------------------------------- /dminvites/__init__.py: -------------------------------------------------------------------------------- 1 | from .dminvites import DmInvite 2 | 3 | __red_end_user_data_statement__ = "This cog does not persistently store data about users." 4 | 5 | 6 | async def setup(bot): 7 | await bot.add_cog(DmInvite(bot)) 8 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.isort] 2 | profile = 'black' 3 | line_length = 99 4 | combine_as_imports = true 5 | 6 | [tool.black] 7 | line-length = 99 8 | target-version = ['py38'] 9 | include = '\.pyi?$' 10 | -------------------------------------------------------------------------------- /forward/__init__.py: -------------------------------------------------------------------------------- 1 | from .forward import Forward 2 | 3 | __red_end_user_data_statement__ = "This cog does not persistently store data about users." 4 | 5 | 6 | async def setup(bot): 7 | n = Forward(bot) 8 | await bot.add_cog(n) 9 | -------------------------------------------------------------------------------- /commandstats/__init__.py: -------------------------------------------------------------------------------- 1 | from .commandstats import CommandStats 2 | 3 | __red_end_user_data_statement__ = "This cog does not persistently store data about users." 4 | 5 | 6 | async def setup(bot): 7 | await bot.add_cog(CommandStats(bot)) 8 | -------------------------------------------------------------------------------- /joinmessage/__init__.py: -------------------------------------------------------------------------------- 1 | from .joinmessage import JoinMessage 2 | 3 | __red_end_user_data_statement__ = "This cog does not persistently store data about users." 4 | 5 | 6 | async def setup(bot): 7 | await bot.add_cog(JoinMessage(bot)) 8 | -------------------------------------------------------------------------------- /redditpost/__init__.py: -------------------------------------------------------------------------------- 1 | from .redditpost import RedditPost 2 | 3 | __red_end_user_data_statement__ = "This cog does not persistently store data about users." 4 | 5 | 6 | async def setup(bot): 7 | cog = RedditPost(bot) 8 | await bot.add_cog(cog) 9 | -------------------------------------------------------------------------------- /news/__init__.py: -------------------------------------------------------------------------------- 1 | from .news import News 2 | 3 | __red_end_user_data_statement__ = "This cog does not persistently store data about users." 4 | 5 | 6 | async def setup(bot): 7 | n = News(bot) 8 | await bot.add_cog(n) 9 | await n.initalize() 10 | -------------------------------------------------------------------------------- /snipe/__init__.py: -------------------------------------------------------------------------------- 1 | from .snipe import Snipe 2 | 3 | __red_end_user_data_statement__ = "This cog does not persistently store data about users." 4 | 5 | 6 | async def setup(bot): 7 | n = Snipe(bot) 8 | await n.init() 9 | await bot.add_cog(n) 10 | -------------------------------------------------------------------------------- /covid/__init__.py: -------------------------------------------------------------------------------- 1 | from .covid import Covid 2 | 3 | __red_end_user_data_statement__ = "This cog does not persistently store data about users." 4 | 5 | 6 | async def setup(bot): 7 | n = Covid(bot) 8 | await bot.add_cog(n) 9 | await n.initalize() 10 | -------------------------------------------------------------------------------- /antispam/__init__.py: -------------------------------------------------------------------------------- 1 | from .antispam import AntiSpam 2 | 3 | __red_end_user_data_statement__ = "This cog does not persistently store data about users." 4 | 5 | 6 | async def setup(bot): 7 | cog = AntiSpam(bot) 8 | await cog.init() 9 | await bot.add_cog(cog) 10 | -------------------------------------------------------------------------------- /simleague/functions.py: -------------------------------------------------------------------------------- 1 | WEATHER = [ 2 | "rainy", 3 | "thunderstorms", 4 | "sunny", 5 | "dusk", 6 | "dawn", 7 | "night", 8 | "snowy", 9 | "hazy rain", 10 | "windy", 11 | "partly cloudy", 12 | "overcast", 13 | "cloudy", 14 | ] 15 | -------------------------------------------------------------------------------- /botlistspost/__init__.py: -------------------------------------------------------------------------------- 1 | from .botlistspost import BotListsPost 2 | 3 | __red_end_user_data_statement__ = "This cog does not persistently store data about users." 4 | 5 | 6 | async def setup(bot): 7 | cog = BotListsPost(bot) 8 | await cog.init() 9 | await bot.add_cog(cog) 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Help improve cogs by filing a bug report 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Output of [p]help CogName and [p]debuginfo** 14 | -------------------------------------------------------------------------------- /unbelievaboat/__init__.py: -------------------------------------------------------------------------------- 1 | from .unbelievaboat import Unbelievaboat 2 | 3 | __red_end_user_data_statement__ = ( 4 | "This cog stores data attached to a users ID for intent of showing a balance.\n" 5 | "It does not store user data.\n" 6 | "This cog supports data removal requests." 7 | ) 8 | 9 | 10 | async def setup(bot): 11 | await bot.add_cog(Unbelievaboat(bot)) 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE] [COG]" 5 | labels: enhancement 6 | assignees: flaree 7 | 8 | --- 9 | 10 | **Describe the feature you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Additional context** 14 | Add any other context or screenshots about the feature request here. 15 | -------------------------------------------------------------------------------- /simleague/abc.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | 3 | from redbot.core import Config 4 | from redbot.core.bot import Red 5 | 6 | 7 | class MixinMeta(ABC): 8 | """Base class for well behaved type hint detection with composite class. 9 | 10 | Basically, to keep developers sane when not all attributes are defined in each mixin. 11 | """ 12 | 13 | def __init__(self, *_args): 14 | self.config: Config 15 | self.bot: Red 16 | -------------------------------------------------------------------------------- /unbelievaboat/abc.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | 3 | from redbot.core import Config 4 | from redbot.core.bot import Red 5 | 6 | 7 | class MixinMeta(ABC): 8 | """Base class for well behaved type hint detection with composite class. 9 | 10 | Basically, to keep developers sane when not all attributes are defined in each mixin. 11 | """ 12 | 13 | def __init__(self, *_args): 14 | self.config: Config 15 | self.bot: Red 16 | -------------------------------------------------------------------------------- /.github/workflows/black.yml: -------------------------------------------------------------------------------- 1 | name: black 2 | 3 | on: [push, pull_request] 4 | 5 | 6 | jobs: 7 | black: 8 | name: Style Check with Black 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - uses: actions/setup-python@v1 13 | with: 14 | python_version: "3.8" 15 | - run: "python -m pip install black" 16 | name: Install black 17 | - run: "python -m black -l 99 --check ." 18 | name: Style checking with black 19 | -------------------------------------------------------------------------------- /mod/__init__.py: -------------------------------------------------------------------------------- 1 | from .mod import Mod 2 | 3 | __red_end_user_data_statement__ = ( 4 | "This cog stores user data to actively maintain server moderation.\n" 5 | "It will not respect data deletion by end users as the data kept is the minimum " 6 | "needed for operation of an anti-abuse measure, nor can end users request " 7 | "their data from this cog since it only stores a discord ID.\n" 8 | ) 9 | 10 | 11 | async def setup(bot): 12 | cog = Mod(bot) 13 | await bot.add_cog(cog) 14 | -------------------------------------------------------------------------------- /rolehistory/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Role History. A way to visualize the history of a user's roles.", 6 | "name": "RoleHistory", 7 | "disabled": false, 8 | "short": "Visualize the history of a user's roles.", 9 | "description": "Visualize the history of a user's roles.", 10 | "tags": [ 11 | "roles", 12 | "history", 13 | "rolehistory" 14 | ], 15 | "min_bot_version": "3.5.0" 16 | } 17 | -------------------------------------------------------------------------------- /trigger/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Basic trigger cog, respond to keywords.", 6 | "name": "trigger", 7 | "disabled": false, 8 | "short": "Allow for the creation of triggers to respond to keywords in messages.", 9 | "description": "Allow for the creation of triggers to respond to keywords in messages.", 10 | "tags": [ 11 | "trigger" 12 | ], 13 | "min_bot_version": "3.5.0", 14 | "hidden": false 15 | } 16 | -------------------------------------------------------------------------------- /threadbumper/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Bump threads to keep them alive forever.", 6 | "name": "ThreadBumper", 7 | "disabled": false, 8 | "hidden": false, 9 | "short": "Bump threads to keep them alive forever.", 10 | "description": "Bump threads to keep them alive forever.", 11 | "tags": [ 12 | "threads", 13 | "bump", 14 | "thread bumping" 15 | ], 16 | "min_bot_version": "3.5.0" 17 | } 18 | -------------------------------------------------------------------------------- /r6/__init__.py: -------------------------------------------------------------------------------- 1 | from .r6 import R6 2 | 3 | __red_end_user_data_statement__ = ( 4 | "This cog stores data provided by uses for the purpose of displaying a users statistics.\n" 5 | "It does not store user data which was not provided through a command and users may remove their own content without the use of a data removal request.\n" 6 | "This cog supports data removal requests." 7 | ) 8 | 9 | 10 | async def setup(bot): 11 | cog = R6(bot) 12 | await cog.initalize() 13 | await bot.add_cog(cog) 14 | -------------------------------------------------------------------------------- /faceit/__init__.py: -------------------------------------------------------------------------------- 1 | from .faceit import Faceit 2 | 3 | __red_end_user_data_statement__ = ( 4 | "This cog stores data provided by uses for the purpose of displaying a users statistics.\n" 5 | "It does not store user data which was not provided through a command and users may remove their own content without the use of a data removal request.\n" 6 | "This cog supports data removal requests." 7 | ) 8 | 9 | 10 | async def setup(bot): 11 | cog = Faceit(bot) 12 | await bot.add_cog(cog) 13 | await cog.initalize() 14 | -------------------------------------------------------------------------------- /apitools/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "This cog will perform basic GET/POST requests.", 6 | "name": "ApiTools", 7 | "disabled": false, 8 | "short": "Simple GET/POST requests via the bot.", 9 | "description": "Simple GET/POST requests via the bot.", 10 | "tags": [ 11 | "http", 12 | "api", 13 | "api requests", 14 | "get", 15 | "post" 16 | ], 17 | "hidden": false, 18 | "min_bot_version": "3.5.0" 19 | } 20 | -------------------------------------------------------------------------------- /tips/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Append a message to the beginning of X bot messages randomly\nThis cog relies on the core code of SmileySend by Jakub Kuczys (Jackenmen)\n[p]help tips.\n", 6 | "name": "Tips", 7 | "disabled": true, 8 | "short": "Append a message to a bot message.", 9 | "description": "Randomly apply a tip message to a bot message randomly.", 10 | "tags": [ 11 | "tips" 12 | ], 13 | "min_bot_version": "3.5.0" 14 | } 15 | -------------------------------------------------------------------------------- /tiktokreposter/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from .tiktokreposter import TikTokReposter 4 | 5 | try: 6 | from redbot.core.errors import CogLoadError 7 | except ImportError: 8 | CogLoadError = RuntimeError 9 | 10 | 11 | async def setup(bot): 12 | # check if ffmpeg is installed 13 | if os.system("ffmpeg -version") != 0: 14 | raise CogLoadError("FFmpeg is not installed. Please install it before running this cog.") 15 | 16 | cog = TikTokReposter(bot) 17 | await cog.initialize() 18 | await bot.add_cog(cog) 19 | -------------------------------------------------------------------------------- /f1/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Data related to Formula 1.", 6 | "name": "F1", 7 | "disabled": false, 8 | "short": "Data related to F1.", 9 | "description": "F1 data, races, drivers, constructors etc.", 10 | "tags": [ 11 | "F1", 12 | "formula one", 13 | "racing", 14 | "forumla", 15 | "one" 16 | ], 17 | "requirements": [ 18 | "tabulate" 19 | ], 20 | "min_bot_version": "3.5.0", 21 | "hidden": false 22 | } 23 | -------------------------------------------------------------------------------- /covid/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Covid-19 Information. Use [p]help covid for more information.", 6 | "name": "Covid", 7 | "disabled": false, 8 | "short": "Show Covid-19 (Novel Coronavirus) Statistics.", 9 | "description": "List stats of Covid-19 (Novel Coronavirus), global or countrywise!", 10 | "tags": [ 11 | "covid", 12 | "coronavirus" 13 | ], 14 | "requirements": [ 15 | "validators" 16 | ], 17 | "min_bot_version": "3.5.0" 18 | } 19 | -------------------------------------------------------------------------------- /info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare (flare#0001)" 4 | ], 5 | "install_msg": "Thanks for adding my repo! Should you encounter any issues, they can be posted in the support-flare-cogs channel on the Red 3rd Party server or you can post an issue on the GitHub.", 6 | "name": "Flare's Cogs", 7 | "short": "Assortment of cogs written by flare.", 8 | "description": "Assortment of cogs written by flare.", 9 | "tags": [ 10 | "unbelievaboat", 11 | "dankmemer", 12 | "rainbow6", 13 | "reddit" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /snipe/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Snipe command, show the last deleted message in a channel. ( Sniped messaged are not persistent across reloads or restarts. )", 6 | "name": "Snipe", 7 | "disabled": false, 8 | "short": "Snipe the last message deleted in a channel.", 9 | "description": "Snipe command converted to Red, get the last message deleted in a channel.", 10 | "tags": [ 11 | "snipe" 12 | ], 13 | "min_bot_version": "3.5.0", 14 | "hidden": false 15 | } 16 | -------------------------------------------------------------------------------- /faceit/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Faceit Stats - Use [p]help faceitset for information on setting up.", 6 | "name": "Faceit", 7 | "disabled": false, 8 | "short": "List stats of faceit users, game history or specific games and even ongoing games!", 9 | "description": "List stats of faceit users, game history or specific games and even ongoing games!", 10 | "tags": [ 11 | "faceit", 12 | "csgo", 13 | "games" 14 | ], 15 | "min_bot_version": "3.5.0" 16 | } 17 | -------------------------------------------------------------------------------- /botlistspost/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "This cog posts bot data to various bot lists. This does not post to top.gg. Check out [p]botlistpost to set the API keys required for this cog up.", 6 | "name": "BotListsPost", 7 | "disabled": false, 8 | "short": "Post server count to various discord bot lists.", 9 | "description": "Post server count to various discord bot lists.", 10 | "tags": [ 11 | "botlist" 12 | ], 13 | "hidden": false, 14 | "min_bot_version": "3.5.0" 15 | } 16 | -------------------------------------------------------------------------------- /crypto/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)", 4 | "Flame", 5 | "TrustyJAID" 6 | ], 7 | "install_msg": "Thanks for installing crypto, use [p]help crypto for more information..", 8 | "name": "Crypto", 9 | "disabled": false, 10 | "short": "Buy and sell cyrptocurrencies with Red economy.", 11 | "description": "Buy and sell cryptocurrencies with Red economy.", 12 | "tags": [ 13 | "crypto" 14 | ], 15 | "requirements": [ 16 | "tabulate" 17 | ], 18 | "min_bot_version": "3.5.0" 19 | } 20 | -------------------------------------------------------------------------------- /news/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "News Cog. Use [p]help news for more information.", 6 | "name": "Covid", 7 | "disabled": false, 8 | "hidden": false, 9 | "short": "Grab the latest headlines, nationally or globally.", 10 | "description": "Grab breaking headline around the world!", 11 | "tags": [ 12 | "news", 13 | "information" 14 | ], 15 | "requirements": [ 16 | "validators", 17 | "iso8601" 18 | ], 19 | "min_bot_version": "3.5.0" 20 | } 21 | -------------------------------------------------------------------------------- /highlight/__init__.py: -------------------------------------------------------------------------------- 1 | from .highlight import Highlight 2 | 3 | __red_end_user_data_statement__ = ( 4 | "This cog stores data provided by uses for the purpose of notifying a user when data they provide is said.\n" 5 | "It does not store user data which was not provided through a command and users may remove their own content without the use of a data removal request.\n" 6 | "This cog will support data removal requests." # TODO: Add removal 7 | ) 8 | 9 | 10 | async def setup(bot): 11 | cog = Highlight(bot) 12 | await cog.initalize() 13 | await bot.add_cog(cog) 14 | -------------------------------------------------------------------------------- /highlight/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Highlight certain words to be notified of when said.", 6 | "name": "Highlight", 7 | "disabled": false, 8 | "short": "Highlight certain words to be notified of if said in a certain channel.", 9 | "description": "Highlight certain words to be notified of if said in a certain channel.", 10 | "tags": [ 11 | "highlight" 12 | ], 13 | "requirements": [ 14 | "tabulate" 15 | ], 16 | "min_bot_version": "3.5.0", 17 | "hidden": false 18 | } 19 | -------------------------------------------------------------------------------- /voicetracker/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Track the voice chat of yourself or those in your server.", 6 | "name": "VoiceTracker", 7 | "hidden": false, 8 | "short": "Track the voice chat of yourself or those in your server.", 9 | "description": "Track the voice chat of yourself or those in your server.", 10 | "tags": [ 11 | "voice tracking", 12 | "voice chat", 13 | "voice", 14 | "tracking", 15 | "voice chat" 16 | ], 17 | "min_bot_version": "3.5.0" 18 | } 19 | -------------------------------------------------------------------------------- /stickbugged/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Get stick bugged lol. This cog may be usage intensive and not recommender for smaller hosts. This cog required ffmpeg to be installed, it will error otherwise.", 6 | "name": "StickBugged", 7 | "disabled": false, 8 | "short": "Get stickbugged.", 9 | "description": "Get stickbugged.", 10 | "tags": [ 11 | "stickbugged" 12 | ], 13 | "requirements": [ 14 | "get-stick-bugged-lol" 15 | ], 16 | "min_bot_version": "3.5.0", 17 | "hidden": false 18 | } 19 | -------------------------------------------------------------------------------- /r6/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Thanks for this installing the R6 cog. This cog comes bundled with a font file and some background images.", 6 | "name": "R6", 7 | "disabled": false, 8 | "short": "Show R6 Statistics.", 9 | "description": "List R6 Statistics from seasons, individual operators, all operators and more!", 10 | "tags": [ 11 | "r6", 12 | "stats", 13 | "operators" 14 | ], 15 | "requirements": [ 16 | "pillow", 17 | "r6statsapi" 18 | ], 19 | "min_bot_version": "3.5.0" 20 | } 21 | -------------------------------------------------------------------------------- /antispam/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "This cog will blacklist any user who spams X amount of commands in Y seconds and will be blacklisted for Z length. All is configurable using [p]antispamset", 6 | "name": "AntiSpam", 7 | "disabled": false, 8 | "short": "Block users who spam commands for a defined period of time.", 9 | "description": "Block users who spam commands for a defined period of time.", 10 | "tags": [ 11 | "blacklist", 12 | "antispam" 13 | ], 14 | "min_bot_version": "3.5.0", 15 | "hidden": false 16 | } 17 | -------------------------------------------------------------------------------- /unbelievaboat/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Unbelievaboat economy commands converted for Red use.", 6 | "name": "Unbelievaboat", 7 | "disabled": false, 8 | "short": "Unbelievaboat economy commands converted for Red use.", 9 | "description": "Unbelievaboat economy commands converted for Red use..", 10 | "tags": [ 11 | "economy", 12 | "unbelievaboat", 13 | "rob", 14 | "work" 15 | ], 16 | "requirements": [ 17 | "tabulate" 18 | ], 19 | "min_bot_version": "3.5.0", 20 | "hidden": false 21 | } 22 | -------------------------------------------------------------------------------- /mod/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "This cog subclasses core Mod. It may be finicky so be warned. This cog implements the features of Red#5532 until it is merged into core Red.", 6 | "name": "Mod", 7 | "disabled": false, 8 | "short": "Mod with custom messages.", 9 | "description": "Core mod with the inclusion of custom messages for banning, kicking and unbanning.", 10 | "tags": [ 11 | "mod" 12 | ], 13 | "requirements": [ 14 | "AdvancedTagScriptEngine" 15 | ], 16 | "min_bot_version": "3.5.0", 17 | "hidden": false 18 | } 19 | -------------------------------------------------------------------------------- /emailverify/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Thanks for installing crypto, use [p]help EmailVerify for more information..", 6 | "name": "VerifyEmail", 7 | "disabled": false, 8 | "short": "Secure your server by making users verify their account with an email.", 9 | "description": "Secure your server by making users verify their account with an email.", 10 | "tags": [ 11 | "email verification", 12 | "email", 13 | "verification" 14 | ], 15 | "requirements": [ 16 | "aiosmtplib" 17 | ], 18 | "min_bot_version": "3.5.0" 19 | } 20 | -------------------------------------------------------------------------------- /dminvites/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Respond to server invites sent to the bot. To configure the permissions, use the built in `[p]inviteset perms` command if using the bot supplied URL.", 6 | "name": "DmInvites", 7 | "disabled": false, 8 | "short": "Respond with the bot invite link when DM'd a server invite link.", 9 | "description": "Respond with the bots invite link if the bot recieves a message containing a server invite.", 10 | "tags": [ 11 | "dminvite", 12 | "invites" 13 | ], 14 | "min_bot_version": "3.5.0", 15 | "hidden": false 16 | } 17 | -------------------------------------------------------------------------------- /jsk/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "This cog ports the Jishaku module to Red. No support is given for this cog, nor am I responsibile for anything that can go wrong as from you misuing the tools. Note that some of these may be useless on Red. [p]help Jishaku or [p]help jsk", 6 | "name": "Jsk", 7 | "hidden": false, 8 | "short": "Jishaku ported to Red.", 9 | "description": "Jishaku ported to Red.", 10 | "tags": [ 11 | "jsk" 12 | ], 13 | "requirements": [ 14 | "git+https://github.com/flaree/jishaku" 15 | ], 16 | "min_bot_version": "3.5.0" 17 | } 18 | -------------------------------------------------------------------------------- /jsk/jishaku_cog.py: -------------------------------------------------------------------------------- 1 | import jishaku 2 | from jishaku.cog import OPTIONAL_FEATURES, STANDARD_FEATURES 3 | 4 | jishaku.Flags.RETAIN = True 5 | jishaku.Flags.NO_DM_TRACEBACK = True 6 | jishaku.Flags.FORCE_PAGINATOR = True 7 | 8 | 9 | class Jishaku(*STANDARD_FEATURES, *OPTIONAL_FEATURES): 10 | """Jishaku ported to Red""" 11 | 12 | __version__ = "0.0.2, Jishaku {}".format(jishaku.__version__) 13 | __author__ = "flare#0001, Gorialis" 14 | 15 | def format_help_for_context(self, ctx): 16 | pre_processed = super().format_help_for_context(ctx) 17 | return f"{pre_processed}\nCog Version: {self.__version__}\nAuthor: {self.__author__}" 18 | -------------------------------------------------------------------------------- /redditpost/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Reddit Autoposting of new content. This has the option to use webhooks too if the bot has manage webhook permissions.", 6 | "name": "RedditPost", 7 | "disabled": false, 8 | "short": "Reddit Autoposting of new content.", 9 | "description": "Reddit Autoposting of new content.", 10 | "tags": [ 11 | "reddit", 12 | "autoposting" 13 | ], 14 | "requirements": [ 15 | "validators", 16 | "tabulate", 17 | "asyncpraw" 18 | ], 19 | "min_bot_version": "3.5.0", 20 | "hidden": false 21 | } 22 | -------------------------------------------------------------------------------- /joinmessage/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare (flare#0001)" 4 | ], 5 | "install_msg": "Send a message to a guilds general chat, system channel or first available channel. This features multi language support. If a language is missing, contact flare#0001 or feel free to create a PR/issue.", 6 | "name": "JoinMessage", 7 | "disabled": false, 8 | "short": "Send a message to a guilds general chat, system channel or first available channel.", 9 | "description": "Send a message to a guilds general chat, system channel or first available channel.", 10 | "tags": [ 11 | "Join Message" 12 | ], 13 | "min_bot_version": "3.5.0", 14 | "hidden": false 15 | } 16 | -------------------------------------------------------------------------------- /.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: flaree 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 | -------------------------------------------------------------------------------- /permchecker/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "This cog is a basic permission checker, used to check if a user/role/channel has a set permission.", 6 | "name": "PermChecker", 7 | "disabled": false, 8 | "short": "This cog is a basic permission checker, used to check if a user/role/channel has a set permission.", 9 | "description": "This cog is a basic permission checker, used to check if a user/role/channel has a set permission.", 10 | "tags": [ 11 | "permissions", 12 | "checker", 13 | "permissionchecker", 14 | "permchecker", 15 | "perm", 16 | "perms" 17 | ], 18 | "min_bot_version": "3.5.0" 19 | } 20 | -------------------------------------------------------------------------------- /cashdrop/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Random credits drop, where people can answer questions or pick them up!", 6 | "name": "CashDrop", 7 | "disabled": false, 8 | "short": "Random credits drop, where people can answer questions or pick them up!", 9 | "description": "Random credits drop, where people can answer questions or pick them up!", 10 | "tags": [ 11 | "maths", 12 | "drop", 13 | "credits", 14 | "questions", 15 | "random", 16 | "random-drop", 17 | "random-credits", 18 | "random-questions", 19 | "economy" 20 | ], 21 | "min_bot_version": "3.5.0", 22 | "hidden": false 23 | } 24 | -------------------------------------------------------------------------------- /giveaways/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Giveaway cog. Currently not fully finished, report any bugs in the 3rd party cog server.", 6 | "name": "Giveaways", 7 | "disabled": false, 8 | "short": "Giveaway cog with features such duration or end timing, multipliers, role only acces, bank integration etc.", 9 | "description": "Giveaway cog with features such duration or end timing, multipliers, role only acces, bank integration etc.", 10 | "tags": [ 11 | "giveaway", 12 | "giveaways", 13 | "raffle" 14 | ], 15 | "requirements": [ 16 | "dateparser" 17 | ], 18 | "min_bot_version": "3.5.0", 19 | "hidden": false 20 | } 21 | -------------------------------------------------------------------------------- /userinfo/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Userinfo with badges and bank information. If you wish for the badge emojis to work, you must have you bot added to the friends of Martine server. You can request it to be added here: https://discord.gg/eKxTEsF\nYou can also use your own emojis using `[p]uinfoset setemoji`, you can reset to default with `[p]uinfoset clear`.", 6 | "name": "Userinfo", 7 | "hidden": false, 8 | "short": "Userinfo with user badges and economy details.", 9 | "description": "Show a users normal userinfo + their badges and shared servers and bank stuff.", 10 | "tags": [ 11 | "userinfo" 12 | ], 13 | "min_bot_version": "3.5.3" 14 | } 15 | -------------------------------------------------------------------------------- /palette/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)", 4 | "Kuro" 5 | ], 6 | "install_msg": "Thanks for this installing the Palette cog. This cog comes bundled with a font file.", 7 | "name": "Palette", 8 | "disabled": false, 9 | "short": "Show colour palette of images, avatars etc.", 10 | "description": "Show colour palette of images, avatars, emojis etc", 11 | "tags": [ 12 | "colours", 13 | "palette", 14 | "colour", 15 | "color", 16 | "image", 17 | "avatar", 18 | "emoji" 19 | ], 20 | "requirements": [ 21 | "pillow", 22 | "colorgram.py==1.2.0", 23 | "tabulate" 24 | ], 25 | "min_bot_version": "3.5.0" 26 | } 27 | -------------------------------------------------------------------------------- /simleague/__init__.py: -------------------------------------------------------------------------------- 1 | from .simleague import SimLeague 2 | 3 | __end_user_data_statement__ = ( 4 | "This cog stores a discord User id for when a user wishes to be notified on an action.\n" 5 | "The user can turn this notification off without the use of a data removal request.\n" 6 | "This cog supports data removal requests." 7 | ) 8 | 9 | try: 10 | from redbot.core.errors import CogLoadError 11 | except ImportError: 12 | CogLoadError = RuntimeError 13 | 14 | 15 | async def setup(bot): 16 | if "Leveler" not in bot.cogs: 17 | raise CogLoadError( 18 | "A mongodb instance and leveler by fixator/aikaterna is **REQUIRED** for this cog to function." 19 | ) 20 | cog = SimLeague(bot) 21 | await bot.add_cog(cog) 22 | -------------------------------------------------------------------------------- /forward/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)", 4 | "Aikaterna" 5 | ], 6 | "install_msg": "Forward messages sent to the bot to the bot owner or in a specified channel. Bare in mind that this cog is set to ignore messages from the bot owner.\nThis cog forwards all media too, and if set to resend bot messages opens it up to receiving NSFW pics. ", 7 | "name": "Forward", 8 | "disabled": false, 9 | "short": "Forward messages sent to the bot to the bot owner or in a specified channel.", 10 | "description": "Forward messages sent to the bot to the bot owner or in a specified channel.", 11 | "tags": [ 12 | "forward", 13 | "forwarding" 14 | ], 15 | "hidden": false, 16 | "min_bot_version": "3.5.0" 17 | } 18 | -------------------------------------------------------------------------------- /unbelievaboat/functions.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | def roll(): 5 | roll = random.randint(1, 20) 6 | if roll == 1: 7 | return 0.005 8 | if roll > 1 and roll <= 6: 9 | return 0.03 10 | if roll > 6 and roll <= 8: 11 | return 0.10 12 | if roll > 8 and roll <= 10: 13 | return 0.20 14 | if roll > 10 and roll <= 13: 15 | return 0.25 16 | if roll > 13 and roll <= 16: 17 | return 0.4 18 | if roll > 16 and roll <= 17: 19 | return 0.655 20 | if roll > 17 and roll <= 19: 21 | return 0.8 22 | if roll == 20: 23 | return 0.85 24 | 25 | 26 | def chunks(l, n): 27 | """Yield successive n-sized chunks from l.""" 28 | for i in range(0, len(l), n): 29 | yield l[i : i + n] 30 | -------------------------------------------------------------------------------- /commandstats/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Command Statistics, Check out commands used in a guild, globally or in the current session(while the cog is loaded). Note to developers: This cog allows you to record 3rd party events using the `on_commandstats_action_v2` listener.\nThis command will save the data to config every 5 minutes to prevent loss of data.", 6 | "name": "CommandStats", 7 | "disabled": false, 8 | "short": "Track all commands used.", 9 | "description": "Track all commands used globally, guild wise and during the current session.", 10 | "tags": [ 11 | "commandstats" 12 | ], 13 | "requirements": [ 14 | "tabulate", 15 | "pandas" 16 | ], 17 | "min_bot_version": "3.5.0", 18 | "hidden": false 19 | } 20 | -------------------------------------------------------------------------------- /dankmemer/__init__.py: -------------------------------------------------------------------------------- 1 | from asyncio import create_task 2 | 3 | from .dankmemer import DankMemer 4 | 5 | __red_end_user_data_statement__ = "This cog does not persistently store data about users." 6 | 7 | 8 | async def setup_after_ready(bot): 9 | await bot.wait_until_red_ready() 10 | cog = DankMemer(bot) 11 | await cog.initalize() 12 | for name, command in cog.all_commands.items(): 13 | if not command.parent: 14 | if bot.get_command(name): 15 | command.name = f"dm{command.name}" 16 | for alias in command.aliases: 17 | if bot.get_command(alias): 18 | command.aliases[command.aliases.index(alias)] = f"dm{alias}" 19 | await bot.add_cog(cog) 20 | 21 | 22 | # Thanks Fixator for the changing name code. 23 | 24 | 25 | async def setup(bot): 26 | create_task(setup_after_ready(bot)) 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # thanks zeph 2 | PYTHON ?= python3.8 3 | DIFF := $(shell git diff --name-only --staged "*.py" "*.pyi") 4 | ifeq ($(DIFF),) 5 | DIFF := $(shell git ls-files "*.py" "*.pyi") 6 | endif 7 | 8 | installreqs: 9 | $(PYTHON) -m pip install --upgrade autoflake isort black 10 | lint: 11 | $(PYTHON) -m flake8 --count --select=E9,F7,F82 --show-source $(DIFF) 12 | stylecheck: 13 | $(PYTHON) -m autoflake --check --imports aiohttp,discord,redbot $(DIFF) 14 | $(PYTHON) -m isort --check-only $(DIFF) 15 | $(PYTHON) -m black --check $(DIFF) 16 | reformat: 17 | $(PYTHON) -m autoflake --in-place --imports=aiohttp,discord,redbot $(DIFF) 18 | $(PYTHON) -m isort $(DIFF) 19 | $(PYTHON) -m black $(DIFF) 20 | reformatblack: 21 | $(PYTHON) -m black $(DIFF) 22 | 23 | # Translations 24 | gettext: 25 | $(PYTHON) -m redgettext --command-docstrings --verbose --recursive redbot --exclude-files "redbot/pytest/**/*" 26 | -------------------------------------------------------------------------------- /simleague/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)", 4 | "Stevy" 5 | ], 6 | "install_msg": "Simulation League based on leveler statistics. This cog **REQUIRES** a mongodb instance and leveler running. This cog wouldn't function without the help/assistance of leveler.", 7 | "name": "SimLeague", 8 | "disabled": false, 9 | "short": "Football simulation league for discord - utilizing leveler as a way to determine performances.", 10 | "description": "Simulation League for discord based on reds leveler level.", 11 | "tags": [ 12 | "sim league" 13 | ], 14 | "requirements": [ 15 | "tabulate", 16 | "motor", 17 | "pillow", 18 | "validators" 19 | ], 20 | "required_cogs": { 21 | "leveler": "https://github.com/fixator10/Fixator10-Cogs" 22 | }, 23 | "min_bot_version": "3.5.0", 24 | "hidden": true 25 | } 26 | -------------------------------------------------------------------------------- /tiktokreposter/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "Thank you for installing TikTokReposter! Please make sure to run `[p]tiktokset` to set up the cog.\nThis cog requires yt-dlp to be installed, please ensure its up to date, this cog also requires ffmpeg which must be installed seperately.\nThis cog downloads each video and then transcodes them before uploading to discord, this can be IO heavy at times.", 6 | "name": "TikTokReposter", 7 | "disabled": false, 8 | "short": "Repost TikToks from TikTok to Discord!", 9 | "description": "Repost TikToks from TikTok to Discord automatically or manually!", 10 | "tags": [ 11 | "tiktok", 12 | "repost", 13 | "video", 14 | "media" 15 | ], 16 | "requirements": [ 17 | "yt-dlp[default]" 18 | ], 19 | "min_bot_version": "3.5.0", 20 | "hidden": false 21 | } 22 | -------------------------------------------------------------------------------- /faceit/converters.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from discord.ext.commands.converter import Converter 4 | 5 | MENTION_REGEX = re.compile(r"<@!?([0-9]+)>") 6 | ID_REGEX = re.compile(r"[0-9]{17,}") 7 | 8 | 9 | class StrUser(Converter): 10 | """This is a class to convert mentions/ids to users or return a string.""" 11 | 12 | async def convert(self, ctx, argument): 13 | mentions = MENTION_REGEX.finditer(argument) 14 | ids = ID_REGEX.finditer(argument) 15 | users = [] 16 | if mentions: 17 | for mention in mentions: 18 | user = ctx.bot.get_user(int(mention.group(1))) 19 | if user: 20 | users.append(user) 21 | if not users and ids: 22 | for possible_id in ids: 23 | user = ctx.bot.get_user(int(possible_id.group(0))) 24 | if user: 25 | users.append(user) 26 | if not users: 27 | return argument 28 | return users[0] 29 | -------------------------------------------------------------------------------- /dankmemer/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": [ 3 | "flare(flare#0001)" 4 | ], 5 | "install_msg": "This cog requires the use of an imgen instance. Check [p]dankmemersetup for information, alternatively check our the readme in the DankMemer folder on the repo (The hosting of your own instance is not supported outside of the readme file. You are on your own for issues that may arise or questions). If you wish to host your own instance, you can use the inbuilt command to change url.\nThis cog features over 95 commands, some may conflict.\nThis cog may feature some commands that may be found offensive, they were left included as they're part of the imgen. Disable those commands as you wish.", 6 | "name": "DankMemer", 7 | "disabled": false, 8 | "short": "DankMemer image generator.", 9 | "description": "DankMemer's image generation commands.", 10 | "tags": [ 11 | "memes", 12 | "image gen" 13 | ], 14 | "requirements": [ 15 | "validators" 16 | ], 17 | "min_bot_version": "3.4.99", 18 | "hidden": true 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 - Present Jamie 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 | -------------------------------------------------------------------------------- /r6/converters.py: -------------------------------------------------------------------------------- 1 | import r6statsapi 2 | from redbot.core.commands import BadArgument, Context 3 | 4 | PLATFORMS = { 5 | "psn": r6statsapi.Platform.psn, 6 | "ps4": r6statsapi.Platform.psn, 7 | "ps": r6statsapi.Platform.psn, 8 | "xbl": r6statsapi.Platform.xbox, 9 | "xbox": r6statsapi.Platform.xbox, 10 | "uplay": r6statsapi.Platform.uplay, 11 | "pc": r6statsapi.Platform.uplay, 12 | } 13 | 14 | REGIONS = { 15 | "all": r6statsapi.Regions.all, 16 | "na": r6statsapi.Regions.ncsa, 17 | "eu": r6statsapi.Regions.emea, 18 | "asia": r6statsapi.Regions.apac, 19 | "us": r6statsapi.Regions.ncsa, 20 | "as": r6statsapi.Regions.apac, 21 | "europe": r6statsapi.Regions.emea, 22 | } 23 | 24 | 25 | class PlatformConverter: 26 | @classmethod 27 | async def convert(cls, ctx: Context, argument: str): 28 | if argument.lower() in PLATFORMS: 29 | return PLATFORMS[argument.lower()] 30 | raise BadArgument("Platform isn't found, please specify either psn, xbox or pc.") 31 | 32 | 33 | class RegionConverter: 34 | @classmethod 35 | async def convert(cls, ctx: Context, argument: str): 36 | if argument.lower() in REGIONS: 37 | return REGIONS[argument.lower()] 38 | raise BadArgument("Region not found, please specify either na, eu or asia.") 39 | -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | if [%1] == [] goto help 4 | 5 | REM This allows us to expand variables at execution 6 | setlocal ENABLEDELAYEDEXPANSION 7 | 8 | REM This will set DIFF as a list of staged files 9 | set DIFF= 10 | for /F "tokens=* USEBACKQ" %%A in (`git diff --name-only --staged "*.py" "*.pyi"`) do ( 11 | set DIFF=!DIFF! %%A 12 | ) 13 | 14 | REM This will set DIFF as a list of files tracked by git 15 | if [!DIFF!]==[] ( 16 | set DIFF= 17 | for /F "tokens=* USEBACKQ" %%A in (`git ls-files "*.py" "*.pyi"`) do ( 18 | set DIFF=!DIFF! %%A 19 | ) 20 | ) 21 | 22 | goto %1 23 | 24 | :reformat 25 | autoflake --in-place --imports=aiohttp,discord,redbot !DIFF! || goto :eof 26 | isort !DIFF! || goto :eof 27 | black !DIFF! 28 | goto :eof 29 | 30 | :stylecheck 31 | autoflake --check --imports aiohttp,discord,redbot !DIFF! || goto :eof 32 | isort --check-only !DIFF! || goto :eof 33 | black --check !DIFF! 34 | goto :eof 35 | 36 | :reformatblack 37 | black !DIFF! 38 | goto :eof 39 | 40 | 41 | :help 42 | echo Usage: 43 | echo make ^ 44 | echo. 45 | echo Commands: 46 | echo reformat Reformat all .py files being tracked by git. 47 | echo stylecheck Check which tracked .py files need reformatting. 48 | echo newenv Create or replace this project's virtual environment. 49 | echo syncenv Sync this project's virtual environment to Red's latest 50 | echo dependencies. 51 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_language_version: 2 | python: python3.11 3 | fail_fast: false 4 | ci: 5 | skip: [readmegen] 6 | autoupdate_schedule: quarterly 7 | repos: 8 | - repo: https://github.com/pre-commit/pre-commit-hooks 9 | rev: v6.0.0 10 | hooks: 11 | - id: check-yaml 12 | - id: end-of-file-fixer 13 | - id: trailing-whitespace 14 | - id: check-builtin-literals 15 | - id: check-ast 16 | - id: check-docstring-first 17 | - id: check-json 18 | - id: detect-private-key 19 | - id: check-toml 20 | - id: pretty-format-json 21 | args: 22 | - "--autofix" 23 | - "--indent=4" 24 | - "--no-sort-keys" 25 | - id: requirements-txt-fixer 26 | - id: trailing-whitespace 27 | args: [--markdown-linebreak-ext=md] 28 | 29 | - repo: https://github.com/psf/black-pre-commit-mirror 30 | rev: '25.9.0' 31 | hooks: 32 | - id: black 33 | - repo: https://github.com/Pierre-Sassoulas/black-disable-checker 34 | rev: 'v1.1.3' 35 | hooks: 36 | - id: black-disable-checker 37 | - repo: https://github.com/pycqa/isort 38 | rev: '6.1.0' 39 | hooks: 40 | - id: isort 41 | - repo: local 42 | hooks: 43 | - id: readmegen 44 | name: readmegen 45 | description: >- 46 | Script to automatically generate readme.md 47 | entry: python ./.utils/utils.py 48 | language: python 49 | additional_dependencies: 50 | - babel~=2.9.0 51 | - tabulate~=0.8.9 52 | always_run: true 53 | pass_filenames: false 54 | require_serial: true 55 | verbose: true 56 | -------------------------------------------------------------------------------- /unbelievaboat/checks.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from redbot.core import bank, commands 3 | 4 | 5 | def check_global_setting_admin(): 6 | async def predicate(ctx): 7 | author = ctx.author 8 | if await bank.is_global(): 9 | return await ctx.bot.is_owner(author) 10 | 11 | if not isinstance(ctx.channel, discord.abc.GuildChannel): 12 | return False 13 | if await ctx.bot.is_owner(author): 14 | return True 15 | if author == ctx.guild.owner: 16 | return True 17 | if ctx.channel.permissions_for(author).manage_guild: 18 | return True 19 | admin_roles = set(await ctx.bot.get_admin_role_ids(ctx.guild.id)) 20 | for role in author.roles: 21 | if role.id in admin_roles: 22 | return True 23 | 24 | return commands.check(predicate) 25 | 26 | 27 | def wallet_disabled_check(): 28 | async def predicate(ctx): 29 | if await bank.is_global(): 30 | return await ctx.bot.get_cog("Unbelievaboat").config.disable_wallet() 31 | if ctx.guild is None: 32 | return False 33 | return await ctx.bot.get_cog("Unbelievaboat").config.guild(ctx.guild).disable_wallet() 34 | 35 | return commands.check(predicate) 36 | 37 | 38 | def roulette_disabled_check(): 39 | async def predicate(ctx): 40 | if await bank.is_global(): 41 | return await ctx.bot.get_cog("Unbelievaboat").config.roulette_toggle() 42 | if ctx.guild is None: 43 | return False 44 | return await ctx.bot.get_cog("Unbelievaboat").config.guild(ctx.guild).roulette_toggle() 45 | 46 | return commands.check(predicate) 47 | -------------------------------------------------------------------------------- /mod/_tagscript.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, List 2 | 3 | import TagScriptEngine as tse 4 | from redbot.core import commands 5 | from redbot.core.utils.chat_formatting import humanize_number 6 | 7 | kick_message: str = "Done. That felt good." 8 | ban_message: str = "Done. That felt good." 9 | tempban_message: str = "Done. Enough chaos for now." 10 | unban_message: str = "Unbanned the user from this server." 11 | 12 | TAGSCRIPT_LIMIT: int = 10_000 13 | 14 | blocks: List[tse.Block] = [ 15 | tse.LooseVariableGetterBlock(), 16 | tse.AssignmentBlock(), 17 | tse.EmbedBlock(), 18 | ] 19 | 20 | tagscript_engine: tse.Interpreter = tse.Interpreter(blocks) 21 | 22 | 23 | def process_tagscript(content: str, seed_variables: Dict[str, tse.Adapter] = {}) -> Dict[str, Any]: 24 | output: tse.Response = tagscript_engine.process(content, seed_variables) 25 | kwargs: Dict[str, Any] = {} 26 | if output.body: 27 | kwargs["content"] = output.body[:2000] 28 | if embed := output.actions.get("embed"): 29 | kwargs["embed"] = embed 30 | return kwargs 31 | 32 | 33 | def validate_tagscript(tagscript: str) -> bool: 34 | length = len(tagscript) 35 | if length > TAGSCRIPT_LIMIT: 36 | raise TagCharacterLimitReached(TAGSCRIPT_LIMIT, length) 37 | return True 38 | 39 | 40 | class TagError(Exception): 41 | """Base exception class.""" 42 | 43 | 44 | class TagCharacterLimitReached(TagError): 45 | """Taised when the Tagscript character limit is reached.""" 46 | 47 | def __init__(self, limit: int, length: int): 48 | super().__init__( 49 | f"Tagscript cannot be longer than {humanize_number(limit)} (**{humanize_number(length)}**)." 50 | ) 51 | 52 | 53 | class TagScriptConverter(commands.Converter[str]): 54 | async def convert(self, ctx: commands.Context, argument: str) -> str: 55 | try: 56 | validate_tagscript(argument) 57 | except TagError as e: 58 | raise commands.BadArgument(str(e)) 59 | return argument 60 | -------------------------------------------------------------------------------- /palette/converters.py: -------------------------------------------------------------------------------- 1 | # Taken and modified from Trusty's NotSoBot cog. 2 | import re 3 | 4 | import discord 5 | from redbot.core.commands import BadArgument, Converter 6 | 7 | IMAGE_LINKS = re.compile(r"(https?:\/\/[^\"\'\s]*\.(?:png|jpg|jpeg|gif)(\?size=[0-9]*)?)") 8 | EMOJI_REGEX = re.compile(r"(<(a)?:[a-zA-Z0-9\_]+:([0-9]+)>)") 9 | MENTION_REGEX = re.compile(r"<@!?([0-9]+)>") 10 | ID_REGEX = re.compile(r"[0-9]{17,}") 11 | 12 | 13 | class ImageFinder(Converter): 14 | """This is a class to convert notsobots image searching capabilities into a more general 15 | converter class.""" 16 | 17 | async def convert(self, ctx, argument): 18 | mentions = MENTION_REGEX.finditer(argument) 19 | matches = IMAGE_LINKS.finditer(argument) 20 | emojis = EMOJI_REGEX.finditer(argument) 21 | ids = ID_REGEX.finditer(argument) 22 | urls = [] 23 | if matches: 24 | # If it's invalid it will be handled by Palette.get_img later 25 | urls.extend(match.string for match in matches) 26 | if emojis: 27 | for emoji in emojis: 28 | partial_emoji = discord.PartialEmoji.from_str(emoji.group(1)) 29 | if partial_emoji.is_custom_emoji(): 30 | urls.append(partial_emoji.url) 31 | # else: 32 | # # Default emoji 33 | # try: 34 | # """https://github.com/glasnt/emojificate/blob/master/emojificate/filter.py""" 35 | # cdn_fmt = "https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/72x72/{codepoint:x}.png" 36 | # urls.append(cdn_fmt.format(codepoint=ord(str(emoji)))) 37 | # except TypeError: 38 | # continue 39 | if mentions: 40 | for mention in mentions: 41 | if user := ctx.guild.get_member(int(mention.group(1))): 42 | urls.append(str(user.display_avatar)) 43 | if not urls and ids: 44 | for possible_id in ids: 45 | if user := ctx.guild.get_member(int(possible_id.group(0))): 46 | urls.append(str(user.display_avatar)) 47 | if not urls and ctx.guild: 48 | if user := ctx.guild.get_member_named(argument): 49 | urls.append(str(user.display_avatar)) 50 | if not urls: 51 | raise BadArgument("No images found.") 52 | return urls[0] 53 | -------------------------------------------------------------------------------- /threadbumper/threadbumper.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import discord 4 | from discord.ext import tasks 5 | from redbot.core import Config, commands 6 | 7 | log = logging.getLogger("red.flare.threadbumper") 8 | 9 | 10 | class ThreadBumper(commands.Cog): 11 | __version__ = "0.0.1" 12 | __author__ = "flare#0001" 13 | 14 | def format_help_for_context(self, ctx): 15 | """Thanks Sinbad.""" 16 | pre_processed = super().format_help_for_context(ctx) 17 | return f"{pre_processed}\nCog Version: {self.__version__}\nAuthor: {self.__author__}" 18 | 19 | async def red_get_data_for_user(self, *, user_id: int): 20 | # this cog does not store any data 21 | return {} 22 | 23 | async def red_delete_data_for_user(self, *, requester, user_id: int) -> None: 24 | # this cog does not store any data 25 | pass 26 | 27 | @tasks.loop(hours=12) 28 | async def bump_threads(self): 29 | config = await self.config.all_guilds() 30 | for guild_id, guild_data in config.items(): 31 | guild = self.bot.get_guild(guild_id) 32 | if guild is None: 33 | continue 34 | 35 | for thread_id in guild_data["threads"]: 36 | thread = guild.get_thread(thread_id) 37 | if thread is None: 38 | continue 39 | await thread.edit(archived=False, auto_archive_duration=60) 40 | await thread.edit(archived=False, auto_archive_duration=1440) 41 | log.debug(f"Thread {thread.id} was bumped") 42 | 43 | def __init__(self, bot): 44 | self.bot = bot 45 | self.config = Config.get_conf(self, identifier=1398467138476, force_registration=True) 46 | self.config.register_guild(threads=[]) 47 | self.bump_threads.start() 48 | 49 | def cog_unload(self): 50 | self.bump_threads.cancel() 51 | 52 | @commands.command() 53 | @commands.bot_has_permissions(manage_threads=True) 54 | async def keepalive(self, ctx, thread: discord.Thread): 55 | """ 56 | Sends a ping to the thread to keep it alive. 57 | """ 58 | async with self.config.guild(ctx.guild).threads() as threads: 59 | if thread.id in threads: 60 | threads.remove(thread.id) 61 | await ctx.send( 62 | f"{thread.mention} under {thread.parent.mention} is no longer being bumped." 63 | ) 64 | else: 65 | threads.append(thread.id) 66 | await ctx.send( 67 | f"{thread.mention} under {thread.parent.mention} is now being bumped." 68 | ) 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | donttouch/ 2 | utils/ 3 | .idea/ 4 | */__pycache__/ 5 | test/ 6 | workspace.code-workspace 7 | .vscode/settings.json 8 | venv/ 9 | .debris/ 10 | rainbow6/r6.png 11 | foco/ 12 | wcrp/ 13 | privatecogs/ 14 | mrp/ 15 | sampservers/ 16 | .vscode/launch.json 17 | epl/ 18 | piper/ 19 | hungergames/ 20 | lastfm/ 21 | simleague/schedule.py 22 | .isort.cfg 23 | # Byte-compiled / optimized / DLL files 24 | __pycache__/ 25 | *.py[cod] 26 | *$py.class 27 | 28 | # C extensions 29 | *.so 30 | 31 | # Distribution / packaging 32 | .Python 33 | build/ 34 | develop-eggs/ 35 | dist/ 36 | downloads/ 37 | eggs/ 38 | .eggs/ 39 | lib/ 40 | lib64/ 41 | parts/ 42 | sdist/ 43 | var/ 44 | wheels/ 45 | pip-wheel-metadata/ 46 | share/python-wheels/ 47 | *.egg-info/ 48 | .installed.cfg 49 | *.egg 50 | MANIFEST 51 | 52 | # PyInstaller 53 | # Usually these files are written by a python script from a template 54 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 55 | *.manifest 56 | *.spec 57 | 58 | # Installer logs 59 | pip-log.txt 60 | pip-delete-this-directory.txt 61 | 62 | # Unit test / coverage reports 63 | htmlcov/ 64 | .tox/ 65 | .nox/ 66 | .coverage 67 | .coverage.* 68 | .cache 69 | nosetests.xml 70 | coverage.xml 71 | *.cover 72 | *.py,cover 73 | .hypothesis/ 74 | .pytest_cache/ 75 | 76 | # Translations 77 | *.mo 78 | 79 | # Django stuff: 80 | *.log 81 | local_settings.py 82 | db.sqlite3 83 | db.sqlite3-journal 84 | 85 | # Flask stuff: 86 | instance/ 87 | .webassets-cache 88 | 89 | # Scrapy stuff: 90 | .scrapy 91 | 92 | # Sphinx documentation 93 | docs/_build/ 94 | 95 | # PyBuilder 96 | target/ 97 | 98 | # Jupyter Notebook 99 | .ipynb_checkpoints 100 | 101 | # IPython 102 | profile_default/ 103 | ipython_config.py 104 | 105 | # pyenv 106 | .python-version 107 | 108 | # pipenv 109 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 110 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 111 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 112 | # install all needed dependencies. 113 | #Pipfile.lock 114 | 115 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 116 | __pypackages__/ 117 | 118 | # Celery stuff 119 | celerybeat-schedule 120 | celerybeat.pid 121 | 122 | # SageMath parsed files 123 | *.sage.py 124 | 125 | # Environments 126 | .env 127 | .venv 128 | env/ 129 | venv/ 130 | ENV/ 131 | env.bak/ 132 | venv.bak/ 133 | 134 | # Spyder project settings 135 | .spyderproject 136 | .spyproject 137 | 138 | # Rope project settings 139 | .ropeproject 140 | 141 | # mkdocs documentation 142 | /site 143 | 144 | # mypy 145 | .mypy_cache/ 146 | .dmypy.json 147 | dmypy.json 148 | 149 | # Pyre type checker 150 | .pyre/ 151 | -------------------------------------------------------------------------------- /dankmemer/converters.py: -------------------------------------------------------------------------------- 1 | # Taken and modified from Trustys NotSoBot cog. 2 | 3 | import re 4 | 5 | from discord.ext.commands.converter import Converter 6 | from discord.ext.commands.errors import BadArgument 7 | 8 | IMAGE_LINKS = re.compile(r"(https?:\/\/[^\"\'\s]*\.(?:png|jpg|jpeg|gif|png|svg)(\?size=[0-9]*)?)") 9 | MENTION_REGEX = re.compile(r"<@!?([0-9]+)>") 10 | ID_REGEX = re.compile(r"[0-9]{17,}") 11 | 12 | 13 | class ImageFinder(Converter): 14 | """This is a class to convert notsobots image searching capabilities into a more general 15 | converter class.""" 16 | 17 | async def convert(self, ctx, argument): 18 | attachments = ctx.message.attachments 19 | mentions = MENTION_REGEX.finditer(argument) 20 | matches = IMAGE_LINKS.finditer(argument) 21 | ids = ID_REGEX.finditer(argument) 22 | urls = [] 23 | if matches: 24 | for match in matches: 25 | urls.append(match.group(1)) 26 | if mentions: 27 | for mention in mentions: 28 | user = ctx.guild.get_member(int(mention.group(1))) 29 | if user is not None: 30 | if user.is_avatar_animated(): 31 | url = IMAGE_LINKS.search( 32 | str(user.display_avatar.replace(static_format="gif").url) 33 | ) 34 | else: 35 | url = IMAGE_LINKS.search( 36 | str(user.display_avatar.replace(static_format="png").url) 37 | ) 38 | urls.append(url.group(1)) 39 | if not urls and ids: 40 | for possible_id in ids: 41 | user = ctx.guild.get_member(int(possible_id.group(0))) 42 | if user: 43 | if user.is_avatar_animated(): 44 | url = IMAGE_LINKS.search( 45 | str(user.display_avatar.replace(static_format="gif").url) 46 | ) 47 | else: 48 | url = IMAGE_LINKS.search( 49 | str(user.display_avatar.replace(static_format="png").url) 50 | ) 51 | urls.append(url.group(1)) 52 | if attachments: 53 | for attachment in attachments: 54 | urls.append(attachment.url) 55 | 56 | if not urls and ctx.guild: 57 | user = ctx.guild.get_member_named(argument) 58 | if not user: 59 | raise BadArgument("No images provided.") 60 | if user.is_avatar_animated(): 61 | url = user.display_avatar.replace(static_format="gif").url 62 | else: 63 | url = user.display_avatar.replace(static_format="png").url 64 | urls.append(url) 65 | if not urls: 66 | raise BadArgument("No images provided.") 67 | return urls[0] 68 | -------------------------------------------------------------------------------- /permchecker/permchecker.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from redbot.core import commands 3 | from redbot.core.utils.chat_formatting import pagify 4 | from redbot.core.utils.menus import DEFAULT_CONTROLS, menu 5 | 6 | perms = [x[0] for x in discord.Permissions.all()] 7 | 8 | 9 | class PermChecker(commands.Cog): 10 | __version__ = "0.1.0" 11 | 12 | def format_help_for_context(self, ctx): 13 | pre_processed = super().format_help_for_context(ctx) 14 | return f"{pre_processed}\nCog Version: {self.__version__}" 15 | 16 | def __init__(self, bot): 17 | self.bot = bot 18 | 19 | @commands.hybrid_group() 20 | async def permcheck(self, ctx): 21 | """Check permissions""" 22 | 23 | @permcheck.command(name="user") 24 | async def permcheck_user(self, ctx, user: discord.Member, permission: str): 25 | """Check if a user has a permission""" 26 | if permission not in perms: 27 | await ctx.send(f"Invalid permission, valid permissions are:\n{', '.join(perms)}") 28 | return 29 | msg = "```diff\nRoles with permission:\n" 30 | found = False 31 | for role in user.roles: 32 | if getattr(role.permissions, permission): 33 | found = True 34 | msg += f"+ {role.name}\n" 35 | msg += "```" 36 | if not found: 37 | msg = "```diff\nNo roles with permission```" 38 | await ctx.send(msg) 39 | 40 | @permcheck.command(name="channel") 41 | async def permcheck_channel(self, ctx, permission: str): 42 | """Check if a channel has a permission""" 43 | if permission not in perms: 44 | await ctx.send(f"Invalid permission, valid permissions are:\n{', '.join(perms)}") 45 | return 46 | msg = "```diff\nRoles with permission:\n" 47 | found = False 48 | for channel in ctx.guild.channels: 49 | for obj in channel.overwrites: 50 | if getattr(channel.overwrites[obj], permission): 51 | found = True 52 | msg += f"+ {channel.name} - {obj.name}\n" 53 | msg += "```" 54 | if not found: 55 | msg = "```diff\nNo roles with permission```" 56 | await ctx.send(msg) 57 | 58 | @permcheck.command(name="role") 59 | async def permcheck_role(self, ctx, permission: str): 60 | """Check if a role has a permission""" 61 | if permission not in perms: 62 | await ctx.send(f"Invalid permission, valid permissions are:\n{', '.join(perms)}") 63 | return 64 | msg = "```diff\nRoles with permission:\n" 65 | found = False 66 | for role in ctx.guild.roles: 67 | if getattr(role.permissions, permission): 68 | found = True 69 | msg += f"+ {role.name}\n" 70 | msg += "```" 71 | if not found: 72 | msg = "```diff\nNo roles with permission```" 73 | await ctx.send(msg) 74 | -------------------------------------------------------------------------------- /giveaways/menu.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import discord 4 | from discord.ui import Button, Modal, TextInput, View 5 | 6 | from .objects import AlreadyEnteredError, GiveawayEnterError, GiveawayExecError 7 | 8 | log = logging.getLogger("red.flare.giveaways") 9 | 10 | 11 | class GiveawayView(View): 12 | def __init__(self, cog): 13 | super().__init__(timeout=None) 14 | self.cog = cog 15 | 16 | 17 | BUTTON_STYLE = { 18 | "blurple": discord.ButtonStyle.primary, 19 | "grey": discord.ButtonStyle.secondary, 20 | "green": discord.ButtonStyle.success, 21 | "red": discord.ButtonStyle.danger, 22 | "gray": discord.ButtonStyle.secondary, 23 | } 24 | 25 | 26 | class GiveawayButton(Button): 27 | def __init__( 28 | self, 29 | label: str, 30 | style: str, 31 | emoji, 32 | cog, 33 | id, 34 | update=False, 35 | ): 36 | super().__init__( 37 | label=label, style=BUTTON_STYLE[style], emoji=emoji, custom_id=f"giveaway_button:{id}" 38 | ) 39 | self.default_label = label 40 | self.update = update 41 | self.cog = cog 42 | 43 | async def callback(self, interaction: discord.Interaction): 44 | if interaction.message.id in self.cog.giveaways: 45 | giveaway = self.cog.giveaways[interaction.message.id] 46 | await interaction.response.defer() 47 | try: 48 | await giveaway.add_entrant( 49 | interaction.user, bot=self.cog.bot, session=self.cog.session, cog=self.cog 50 | ) 51 | except GiveawayEnterError as e: 52 | await interaction.followup.send(e.message, ephemeral=True) 53 | return 54 | except GiveawayExecError as e: 55 | log.exception("Error while adding user to giveaway", exc_info=e) 56 | return 57 | except AlreadyEnteredError: 58 | await interaction.followup.send( 59 | "You are already in the giveaway.", ephemeral=True 60 | ) 61 | return 62 | await self.update_entrant(giveaway, interaction) 63 | await interaction.followup.send( 64 | f"You have been entered into the giveaway for {giveaway.prize}.", 65 | ephemeral=True, 66 | ) 67 | await self.update_label(giveaway, interaction) 68 | 69 | async def update_entrant(self, giveaway, interaction): 70 | await self.cog.config.custom( 71 | "giveaways", interaction.guild_id, interaction.message.id 72 | ).entrants.set(self.cog.giveaways[interaction.message.id].entrants) 73 | 74 | async def update_label(self, giveaway, interaction): 75 | if self.update: 76 | if len(giveaway.entrants) >= 1: 77 | self.label = f"{self.default_label} ({len(giveaway.entrants)})" 78 | if len(giveaway.entrants) == 0: 79 | self.label = self.default_label 80 | await interaction.message.edit(view=self.view) 81 | -------------------------------------------------------------------------------- /stickbugged/converters.py: -------------------------------------------------------------------------------- 1 | # Taken and modified from Trustys NotSoBot cog. 2 | 3 | import re 4 | 5 | from discord.ext.commands.converter import Converter 6 | from discord.ext.commands.errors import BadArgument 7 | 8 | IMAGE_LINKS = re.compile(r"(https?:\/\/[^\"\'\s]*\.(?:png|jpg|jpeg|gif|png|svg)(\?size=[0-9]*)?)") 9 | EMOJI_REGEX = re.compile(r"(<(a)?:[a-zA-Z0-9\_]+:([0-9]+)>)") 10 | MENTION_REGEX = re.compile(r"<@!?([0-9]+)>") 11 | ID_REGEX = re.compile(r"[0-9]{17,}") 12 | 13 | 14 | class ImageFinder(Converter): 15 | """This is a class to convert notsobots image searching capabilities into a more general 16 | converter class.""" 17 | 18 | async def convert(self, ctx, argument): 19 | attachments = ctx.message.attachments 20 | mentions = MENTION_REGEX.finditer(argument) 21 | matches = IMAGE_LINKS.finditer(argument) 22 | emojis = EMOJI_REGEX.finditer(argument) 23 | ids = ID_REGEX.finditer(argument) 24 | urls = [] 25 | if matches: 26 | for match in matches: 27 | urls.append(match.group(1)) 28 | if emojis: 29 | for emoji in emojis: 30 | ext = "gif" if emoji.group(2) else "png" 31 | url = "https://cdn.discordapp.com/emojis/{id}.{ext}?v=1".format( 32 | id=emoji.group(3), ext=ext 33 | ) 34 | urls.append(url) 35 | if mentions: 36 | for mention in mentions: 37 | user = ctx.guild.get_member(int(mention.group(1))) 38 | if user is not None: 39 | url = IMAGE_LINKS.search( 40 | str(user.display_avatar.replace(size=512, static_format="png").url) 41 | ) 42 | urls.append(url.group(1)) 43 | if not urls and ids: 44 | for possible_id in ids: 45 | user = ctx.guild.get_member(int(possible_id.group(0))) 46 | if user: 47 | url = IMAGE_LINKS.search( 48 | str(user.display_avatar.replace(size=512, static_format="png").url) 49 | ) 50 | urls.append(url.group(1)) 51 | if attachments: 52 | for attachment in attachments: 53 | urls.append(attachment.url) 54 | 55 | if not urls and ctx.guild: 56 | user = ctx.guild.get_member_named(argument) 57 | if user: 58 | url = IMAGE_LINKS.search( 59 | str(user.display_avatar.replace(size=512, static_format="png").url) 60 | ) 61 | urls.append(url) 62 | if not urls: 63 | raise BadArgument("No images found.") 64 | return urls[0] 65 | 66 | async def search_for_images(self, ctx): 67 | urls = [] 68 | async for message in ctx.channel.history(limit=10): 69 | if message.attachments: 70 | for attachment in message.attachments: 71 | urls.append(attachment.url) 72 | match = IMAGE_LINKS.match(message.content) 73 | if match: 74 | urls.append(match.group(1)) 75 | if not urls: 76 | raise BadArgument("No Images found in recent history.") 77 | return urls[0] 78 | -------------------------------------------------------------------------------- /faceit/funcs.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | 3 | import discord 4 | from redbot.core import commands 5 | from redbot.core.utils.menus import menu 6 | 7 | 8 | async def match_info( 9 | ctx: commands.Context, 10 | pages: list, 11 | controls: dict, 12 | message: discord.Message, 13 | page: int, 14 | timeout: float, 15 | emoji: str, 16 | ): 17 | perms = message.channel.permissions_for(ctx.me) 18 | if perms.manage_messages: # Can manage messages, so remove react 19 | with contextlib.suppress(discord.NotFound): 20 | await message.remove_reaction(emoji, ctx.author) 21 | command = ctx.bot.get_command("faceit match") 22 | await ctx.send("Click the X on the match menu to return to the menu before.", delete_after=20) 23 | await ctx.invoke(command, match_id=message.embeds[0].to_dict()["fields"][0]["value"]) 24 | return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout) 25 | 26 | 27 | async def account_stats( 28 | ctx: commands.Context, 29 | pages: list, 30 | controls: dict, 31 | message: discord.Message, 32 | page: int, 33 | timeout: float, 34 | emoji: str, 35 | ): 36 | perms = message.channel.permissions_for(ctx.me) 37 | if perms.manage_messages: # Can manage messages, so remove react 38 | with contextlib.suppress(discord.NotFound): 39 | await message.remove_reaction(emoji, ctx.author) 40 | command = ctx.bot.get_command("faceit stats") 41 | await ctx.send( 42 | "Click the X on the in-depth statistics to return to the menu before.", delete_after=20 43 | ) 44 | embed = message.embeds[0].to_dict() 45 | await ctx.invoke( 46 | command, 47 | game=message.embeds[0].to_dict()["fields"][3]["name"].lower(), 48 | user=embed["author"]["name"], 49 | ) 50 | return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout) 51 | 52 | 53 | async def account_matches( 54 | ctx: commands.Context, 55 | pages: list, 56 | controls: dict, 57 | message: discord.Message, 58 | page: int, 59 | timeout: float, 60 | emoji: str, 61 | ): 62 | perms = message.channel.permissions_for(ctx.me) 63 | if perms.manage_messages: # Can manage messages, so remove react 64 | with contextlib.suppress(discord.NotFound): 65 | await message.remove_reaction(emoji, ctx.author) 66 | command = ctx.bot.get_command("faceit matches") 67 | await ctx.send( 68 | "Click the X on the in-depth stat statistics to return to the menu before.", 69 | delete_after=20, 70 | ) 71 | embed = message.embeds[0].to_dict() 72 | await ctx.invoke(command, user=embed["author"]["name"]) 73 | return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout) 74 | 75 | 76 | async def account_ongoing( 77 | ctx: commands.Context, 78 | pages: list, 79 | controls: dict, 80 | message: discord.Message, 81 | page: int, 82 | timeout: float, 83 | emoji: str, 84 | ): 85 | perms = message.channel.permissions_for(ctx.me) 86 | if perms.manage_messages: # Can manage messages, so remove react 87 | with contextlib.suppress(discord.NotFound): 88 | await message.remove_reaction(emoji, ctx.author) 89 | command = ctx.bot.get_command("faceit ongoing") 90 | embed = message.embeds[0].to_dict() 91 | await ctx.invoke(command, user=embed["author"]["name"]) 92 | return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout) 93 | -------------------------------------------------------------------------------- /stickbugged/stickbugged.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import functools 3 | import logging 4 | import os 5 | from io import BytesIO 6 | from typing import Optional 7 | 8 | import aiohttp 9 | import discord 10 | from gsbl.stick_bug import StickBug 11 | from PIL import Image 12 | from redbot.core import commands 13 | from redbot.core.data_manager import cog_data_path 14 | 15 | from .converters import ImageFinder 16 | 17 | log = logging.getLogger("red.flare.stick") 18 | 19 | 20 | class StickBugged(commands.Cog): 21 | __version__ = "0.0.1" 22 | __author__ = "flare#0001" 23 | 24 | def format_help_for_context(self, ctx): 25 | """Thanks Sinbad.""" 26 | pre_processed = super().format_help_for_context(ctx) 27 | return f"{pre_processed}\nCog Version: {self.__version__}\nAuthor: {self.__author__}" 28 | 29 | def __init__(self, bot) -> None: 30 | self.bot = bot 31 | self._stickbug = StickBug() 32 | 33 | def blocking(self, io, id): 34 | io = Image.open(io) 35 | self._stickbug.image = io 36 | 37 | self._stickbug.video_resolution = max(min(1280, io.width), 128), max( 38 | min(720, io.height), 72 39 | ) 40 | self._stickbug.lsd_scale = 0.35 41 | video = self._stickbug.video 42 | video.write_videofile( 43 | str(cog_data_path(self)) + f"/{id}stick.mp4", 44 | threads=1, 45 | preset="superfast", 46 | verbose=False, 47 | logger=None, 48 | temp_audiofile=str(cog_data_path(self) / f"{id}stick.mp3"), 49 | ) 50 | video.close() 51 | return 52 | 53 | @commands.max_concurrency(1, commands.BucketType.default) 54 | @commands.command(aliases=["stickbug", "stickbugged"]) 55 | async def stick(self, ctx, images: Optional[ImageFinder]): 56 | """get stick bugged lol""" 57 | if images is None: 58 | images = await ImageFinder().search_for_images(ctx) 59 | if not images: 60 | return await ctx.send_help() 61 | image = images 62 | async with ctx.typing(): 63 | io = BytesIO() 64 | if isinstance(image, discord.Asset): 65 | await image.save(io, seek_begin=True) 66 | else: 67 | async with aiohttp.ClientSession() as session: 68 | async with session.get(str(image)) as resp: 69 | if resp.status != 200: 70 | return await ctx.send("The picture returned an unknown status code.") 71 | io.write(await resp.read()) 72 | io.seek(0) 73 | await asyncio.sleep(0.2) 74 | fake_task = functools.partial(self.blocking, io=io, id=ctx.message.id) 75 | task = self.bot.loop.run_in_executor(None, fake_task) 76 | try: 77 | video_file = await asyncio.wait_for(task, timeout=300) 78 | except asyncio.TimeoutError as e: 79 | log.error("Timeout creating stickbug video", exc_info=e) 80 | return await ctx.send("Timeout creating stickbug video.") 81 | except Exception: 82 | log.exception("Error sending stick bugged video") 83 | return await ctx.send( 84 | "An error occured during the creation of the stick bugged video" 85 | ) 86 | fp = cog_data_path(self) / f"{ctx.message.id}stick.mp4" 87 | file = discord.File(str(fp), filename="stick.mp4") 88 | try: 89 | await ctx.send(files=[file]) 90 | except Exception as e: 91 | log.error("Error sending stick bugged video", exc_info=e) 92 | try: 93 | os.remove(fp) 94 | except Exception as e: 95 | log.error("Error deleting stick bugged video", exc_info=e) 96 | -------------------------------------------------------------------------------- /trigger/objects.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import random 3 | import re 4 | 5 | 6 | class TriggerObject: 7 | def __init__(self, **kwargs) -> None: 8 | self.trigger_name = kwargs.get("name") 9 | self.trigger = kwargs.get("trigger", None) 10 | self.responses = kwargs.get("responses", None) 11 | self.owner = kwargs.get("owner", None) 12 | self.guild = kwargs.get("guild", None) 13 | self.cooldown = kwargs.get("cooldown", 0) 14 | self.timestamp = None 15 | self.uses = kwargs.get("uses", 0) 16 | self.toggle = kwargs.get("toggle", False) 17 | self.case_sensitive = kwargs.get("case_sensitive", True) 18 | self.word_boundary = kwargs.get("word_boundary", False) 19 | self.embed_search = kwargs.get("embed_search", False) 20 | self.pattern = None 21 | 22 | def check(self, message): 23 | if not self.toggle: 24 | return False 25 | 26 | trigger = self.trigger 27 | content = message.content 28 | if not self.case_sensitive: 29 | trigger = trigger.lower() 30 | content = content.lower() 31 | 32 | if self.cooldown > 0: 33 | if self.timestamp is None: 34 | self.timestamp = datetime.datetime.now(tz=datetime.timezone.utc) 35 | else: 36 | now = datetime.datetime.now(tz=datetime.timezone.utc) 37 | diff = now - self.timestamp 38 | if diff.total_seconds() < self.cooldown: 39 | return False 40 | else: 41 | self.timestamp = now 42 | 43 | if self.word_boundary: 44 | if self.pattern is None: 45 | self.pattern = re.compile(rf"\b{re.escape(self.trigger.lower())}\b", flags=re.I) 46 | if self.pattern.search(content): 47 | return True 48 | elif trigger in content: 49 | return True 50 | elif self.embed_search: 51 | embeds = message.embeds 52 | if len(embeds) > 0: 53 | embed_dict_list = [] 54 | for embed in embeds: 55 | embed_dict_list.append(embed.to_dict()) 56 | if self.pattern is None: 57 | self.pattern = re.compile(rf"{re.escape(self.trigger.lower())}", flags=re.I) 58 | if self.pattern.search(str(embed_dict_list)): 59 | return True 60 | return False 61 | 62 | async def respond(self, message): 63 | response = random.choice(self.responses) 64 | self.uses += 1 65 | self.timestamp = datetime.datetime.now(tz=datetime.timezone.utc) 66 | objects = { 67 | "user": message.author, 68 | "uses": self.uses, 69 | "channel": message.channel, 70 | "guild": message.guild, 71 | "message": message, 72 | "trigger": self.trigger_name, 73 | } 74 | resp = self.transform_message(response, objects) 75 | await message.channel.send(resp) 76 | 77 | def __repr__(self) -> str: 78 | return f"" 79 | 80 | # https://github.com/Cog-Creators/Red-DiscordBot/blob/V3/develop/redbot/cogs/customcom/customcom.py#L824 81 | @staticmethod 82 | def transform_parameter(result, objects) -> str: 83 | """ 84 | For security reasons only specific objects are allowed 85 | Internals are ignored 86 | """ 87 | raw_result = "{" + result + "}" 88 | if result in objects: 89 | return str(objects[result]) 90 | try: 91 | first, second = result.split(".") 92 | except ValueError: 93 | return raw_result 94 | if first in objects and not second.startswith("_"): 95 | first = objects[first] 96 | else: 97 | return raw_result 98 | return str(getattr(first, second, raw_result)) 99 | 100 | def transform_message(self, message, objects): 101 | results = re.findall(r"{([^}]+)\}", message) 102 | for result in results: 103 | param = self.transform_parameter(result, objects) 104 | message = message.replace("{" + result + "}", param) 105 | return message 106 | -------------------------------------------------------------------------------- /apitools/apitools.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import aiohttp 4 | import discord 5 | from redbot.core import commands 6 | from redbot.core.utils.chat_formatting import box 7 | 8 | 9 | class ApiTools(commands.Cog): 10 | """API tool to get/post data.""" 11 | 12 | __version__ = "0.0.3" 13 | __author__ = "flare" 14 | 15 | def format_help_for_context(self, ctx): 16 | """Thanks Sinbad.""" 17 | pre_processed = super().format_help_for_context(ctx) 18 | return f"{pre_processed}\nCog Version: {self.__version__}\nAuthor: {self.__author__}" 19 | 20 | def __init__(self, bot): 21 | self.bot = bot 22 | self.session = aiohttp.ClientSession() 23 | 24 | def cog_unload(self): 25 | self.bot.loop.create_task(self.session.close()) 26 | 27 | async def req(self, get_or_post, url, headers={}, data={}): 28 | reqmethod = self.session.get if get_or_post == "get" else self.session.post 29 | async with reqmethod(url, headers=headers, data=json.dumps(data)) as req: 30 | data = await req.text() 31 | status = req.status 32 | try: 33 | parsed = json.loads(data) 34 | except json.JSONDecodeError: 35 | parsed = data 36 | return parsed, status 37 | 38 | @commands.group() 39 | @commands.is_owner() 40 | async def apitools(self, ctx): 41 | """Group for API tools.""" 42 | 43 | @apitools.command(name="get") 44 | async def _get(self, ctx, url, *, headers=None): 45 | """Send a HTTP Get request.""" 46 | if headers is not None: 47 | try: 48 | headers = json.loads(headers) 49 | except json.JSONDecodeError: 50 | return await ctx.send( 51 | "The headers you provided are invalid. Please provide them in JSON/Dictionary format." 52 | ) 53 | else: 54 | headers = {} 55 | try: 56 | data, status = await self.req("get", url, headers=headers) 57 | except Exception: 58 | return await ctx.send( 59 | "An error occured while trying to post your request. Ensure the URL is correct etcetra." 60 | ) 61 | color = discord.Color.green() if status == 200 else discord.Color.red() 62 | msg = json.dumps(data, indent=4, sort_keys=True)[:2030] 63 | if len(msg) > 2029: 64 | msg += "\n..." 65 | embed = discord.Embed( 66 | title=f"Results for **GET** {url}", 67 | color=color, 68 | description=box(msg, lang="json"), 69 | ) 70 | embed.add_field(name="Status Code", value=status) 71 | await ctx.send(embed=embed) 72 | 73 | @apitools.command() 74 | async def post(self, ctx, url, *, headers_and_data=None): 75 | """Send a HTTP POST request.""" 76 | if headers_and_data is not None: 77 | try: 78 | headers_and_data = json.loads(headers_and_data) 79 | headers = headers_and_data.get("Headers", {}) or headers_and_data.get( 80 | "headers", {} 81 | ) 82 | data = headers_and_data.get("Data", {}) or headers_and_data.get("data", {}) 83 | except json.JSONDecodeError: 84 | return await ctx.send( 85 | 'The data you provided are invalid. Please provide them in JSON/Dictionary format.\nExample: {"headers": {"Authorization": "token"}, "data": {"name": "flare"}}' 86 | ) 87 | else: 88 | headers = {} 89 | data = {} 90 | try: 91 | data, status = await self.req("post", url, headers=headers, data=data) 92 | except Exception: 93 | return await ctx.send( 94 | "An error occured while trying to post your request. Ensure the URL is correct etcetra." 95 | ) 96 | color = discord.Color.green() if status == 200 else discord.Color.red() 97 | msg = json.dumps(data, indent=4)[:2030] 98 | if len(msg) > 2029: 99 | msg += "\n..." 100 | embed = discord.Embed( 101 | title=f"Results for **POST** {url}", 102 | color=color, 103 | description=box(msg, lang="json"), 104 | ) 105 | embed.add_field(name="Status Code", value=status) 106 | await ctx.send(embed=embed) 107 | -------------------------------------------------------------------------------- /dankmemer/README.md: -------------------------------------------------------------------------------- 1 | # Information on self hosting an imgen instance. 2 | 3 | Imgen is the name given to the image generator that powers Dank Memers image manipulation services. This service is open source and is found on their repo. Do the digging yourself to find out. This will have the basic steps on how to host an instance. Nothing indept, but enough to get going. 4 | 5 | --- 6 | 7 | Start by first making a venv. Making a venv is highly recommended. You can host imgen inside the same venv you're hosting your redbot instance on OR you can create a new venv and setup imgen there. It's completely your choice. 8 | [Read this if you don't know how to create a venv](https://docs.discord.red/en/stable/install_linux_mac.html#creating-venv-linux). 9 | 10 | Before you activate your venv, make sure you installed ImageMagik, if you didn't please check out [this guide](https://docs.wand-py.org/en/0.4.1/guide/install.html) and install that, you will also need rethinkdb from [here](https://rethinkdb.com/docs/install/) depending on your linux distribution or docker, whichever you prefer. 11 | Also install `redis-server` for your linux distribution. If you're using Ubuntu 18.04 or above, you can follow [this guide](https://www.digitalocean.com/community/tutorials/how-to-install-and-secure-redis-on-ubuntu-18-04). 12 | 13 | Now that you have created venv, activate it with: 14 | ``` 15 | source ~/path/to/venv/activate 16 | ``` 17 | 18 | Once you're inside venv, start by cloning the dank memer imgen repo and finishing the setup: 19 | ``` 20 | git clone https://github.com/DankMemer/imgen 21 | cd imgen/ 22 | 23 | # now copy the config.json 24 | 25 | cp -rf example-config.json config.json 26 | 27 | # open config.json and fill in the necessary credentials and save it. 28 | # Now to install the requirements 29 | 30 | python -m pip install --upgrade pip 31 | pip install gunicorn 32 | pip install -r requirements.txt 33 | ``` 34 | 35 | Now that is done, `rethinkdb` requires a database named "imgen" and inside that it must have two tables named "keys" and "applications" 36 | You can create them by following this commands in the order one by one: 37 | ```py 38 | #this opens a python3 interpreter 39 | python 40 | 41 | from rethinkdb import r 42 | r.connect('localhost', 28015).repl() 43 | r.db_create("imgen").run() 44 | r.db('imgen').table_create('keys').run() 45 | r.db('imgen').table_create('applications').run() 46 | exit() 47 | ``` 48 | 49 | If you're just hosting for that machine (i.e. hosting locally), the next step does not apply. 50 | 51 | - Change the address in the `start.sh` script from `127.0.0.1` to `0.0.0.0` 52 | 53 | Now before starting the start script, go to [Discord Developers](https://discord.com/developers) portal, then go to your Bot Application -> OAuth2 section and add the following redirect in the Redirects field: 54 | ``` 55 | http://PUBLIC_IP:65535/callback 56 | 57 | ## replace `PUBLIC_IP` with your host's Public IP. 58 | ## you can find your host's `PUBLIC_IP` with following command in your terminal: 59 | wget -qO- ifconfig.me 60 | ``` 61 | Scroll below on that OAuth2 page, choose any of the scopes you desire and click on Save button. 62 | 63 | Start the imgen server using the start script provided: 64 | ``` 65 | ./start.sh 66 | ``` 67 | 68 | At this point, you can visit `http://PUBLIC_IP:65535` to see if the imgen server is running successfully or not. If it's working, go to Admin panel there, generate a new key and copy it then come to Discord and do the following command with your Red bot in a private channel: 69 | ``` 70 | [p]set api imgen authorization 71 | ``` 72 | where `[p]` is your bot's prefix and replace `` with the newly generated imgen key you just copied. 73 | 74 | Now, download the dankmemer cog and load it and do: 75 | ``` 76 | #first read the instructions listed at: 77 | [p]dankmemersetup 78 | 79 | [p]dmurl http://PUBLIC_IP:65535/api 80 | ``` 81 | 82 | Now do `[p]help DankMemer` to see if your bot lists all the commands this cog has. 83 | 84 | NOTE: If you're hosting your Red bot instance on Amazon AWS, you need to enable traffic from ports 80, 443 and 65535 from your AWS console -> Security Groups and you also need to enable those ports from your system/VPS firewall otherwise you won't be able to access your Public IP. Google it if you don't know how to do it. 85 | 86 | ## Yes, this guide isn't extremely detailed. If you want to host it then I expect you to know what you're doing. What I listed was some information not seen on their repo. 87 | -------------------------------------------------------------------------------- /rolehistory/rolehistory.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | import discord 4 | from discord.utils import format_dt, utcnow 5 | from redbot.core import Config, commands 6 | from redbot.core.utils.chat_formatting import pagify 7 | from redbot.core.utils.menus import DEFAULT_CONTROLS, menu 8 | 9 | 10 | class RoleHistory(commands.Cog): 11 | __version__ = "0.1.0" 12 | 13 | def format_help_for_context(self, ctx): 14 | pre_processed = super().format_help_for_context(ctx) 15 | return f"{pre_processed}\nCog Version: {self.__version__}" 16 | 17 | def __init__(self, bot): 18 | self.bot = bot 19 | self.config = Config.get_conf(self, identifier=95932766180343808) 20 | default_member = {"history": [], "toggle": False} 21 | default_guild = {"toggle": False} 22 | self.config.register_member(**default_member) 23 | self.config.register_guild(**default_guild) 24 | self.guild_cache = {} 25 | self.toggle_cache = {} 26 | 27 | async def initalize(self): 28 | await self.bot.wait_until_ready() 29 | self.guild_cache = await self.config.all_guilds() 30 | self.toggle_cache = await self.config.all_members() 31 | 32 | @commands.Cog.listener() 33 | async def on_member_update(self, before, after): 34 | guild = before.guild 35 | if before.guild.id not in self.guild_cache: 36 | return 37 | if not self.guild_cache[before.guild.id]["toggle"]: 38 | return 39 | if guild.id not in self.toggle_cache: 40 | return 41 | if not self.toggle_cache[guild.id].get(before.id, {}).get("toggle", False): 42 | return 43 | if before.roles != after.roles: 44 | roles = [] 45 | if len(before.roles) > len(after.roles): 46 | roles.extend( 47 | (role.id, role.name, "removed", utcnow().timestamp()) 48 | for role in before.roles 49 | if role not in after.roles 50 | ) 51 | elif len(before.roles) < len(after.roles): 52 | roles.extend( 53 | (role.id, role.name, "added", utcnow().timestamp()) 54 | for role in after.roles 55 | if role not in before.roles 56 | ) 57 | if roles: 58 | async with self.config.member(before).history() as history: 59 | for role in roles: 60 | history.append(role) 61 | 62 | @commands.hybrid_group() 63 | async def rolehistory(self, ctx): 64 | """Role History Commands""" 65 | 66 | @rolehistory.command() 67 | async def toggle(self, ctx): 68 | """Toggle Role History""" 69 | guild_toggle = await self.config.guild(ctx.guild).toggle() 70 | if not guild_toggle: 71 | return await ctx.send("Role History is disabled for this guild", ephemeral=True) 72 | toggle = await self.config.member(ctx.author).toggle() 73 | await self.config.member(ctx.author).toggle.set(not toggle) 74 | await ctx.send(f"Role History is now {not toggle}", ephemeral=True) 75 | self.toggle_cache = await self.config.all_members() 76 | 77 | @rolehistory.command() 78 | async def show(self, ctx): 79 | """Show Role History""" 80 | history = await self.config.member(ctx.author).history() 81 | if not history: 82 | return await ctx.send("You have no role history", ephemeral=True) 83 | minus = "\\-" 84 | msg = "".join( 85 | f"{'+' if role[2] == 'added' else minus} {role[1]} | {format_dt(datetime.fromtimestamp(role[3]), style='F')}\n" 86 | for role in history 87 | ) 88 | embeds = [] 89 | for i, page in enumerate(pagify(msg, page_length=1000)): 90 | embed = discord.Embed(title=f"{ctx.author}'s Role History", description=page) 91 | embed.set_footer(text=f"Page {i}") 92 | embeds.append(embed) 93 | if len(embeds) == 1: 94 | return await ctx.send(embed=embeds[0]) 95 | await menu(ctx, embeds, DEFAULT_CONTROLS) 96 | 97 | @rolehistory.command() 98 | @commands.admin_or_permissions(manage_roles=True) 99 | async def guildtoggle(self, ctx): 100 | """Toggle Role History for the guild""" 101 | toggle = await self.config.guild(ctx.guild).toggle() 102 | await self.config.guild(ctx.guild).toggle.set(not toggle) 103 | await ctx.send(f"Role History is now {not toggle}", ephemeral=True) 104 | self.guild_cache = await self.config.all_guilds() 105 | -------------------------------------------------------------------------------- /snipe/menus.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import functools 3 | from typing import List, Union 4 | 5 | import discord 6 | from redbot.core import commands 7 | from redbot.core.utils.menus import start_adding_reactions 8 | from redbot.core.utils.predicates import ReactionPredicate 9 | 10 | 11 | # Code taken from Red-DiscordBot repo, adapted to support replies. 12 | async def menu( 13 | ctx: commands.Context, 14 | pages: Union[List[str], List[discord.Embed]], 15 | controls: dict, 16 | message: discord.Message = None, 17 | page: int = 0, 18 | timeout: float = 30.0, 19 | **kwargs, 20 | ): 21 | """ 22 | An emoji-based menu 23 | .. note:: All pages should be of the same type 24 | .. note:: All functions for handling what a particular emoji does 25 | should be coroutines (i.e. :code:`async def`). Additionally, 26 | they must take all of the parameters of this function, in 27 | addition to a string representing the emoji reacted with. 28 | This parameter should be the last one, and none of the 29 | parameters in the handling functions are optional 30 | Parameters 31 | ---------- 32 | ctx: commands.Context 33 | The command context 34 | pages: `list` of `str` or `discord.Embed` 35 | The pages of the menu. 36 | controls: dict 37 | A mapping of emoji to the function which handles the action for the 38 | emoji. 39 | message: discord.Message 40 | The message representing the menu. Usually :code:`None` when first opening 41 | the menu 42 | page: int 43 | The current page number of the menu 44 | timeout: float 45 | The time (in seconds) to wait for a reaction 46 | Raises 47 | ------ 48 | RuntimeError 49 | If either of the notes above are violated 50 | """ 51 | if not isinstance(pages[0], (discord.Embed, str)): 52 | raise RuntimeError("Pages must be of type discord.Embed or str") 53 | if not all(isinstance(x, discord.Embed) for x in pages) and not all( 54 | isinstance(x, str) for x in pages 55 | ): 56 | raise RuntimeError("All pages must be of the same type") 57 | for key, value in controls.items(): 58 | maybe_coro = value 59 | if isinstance(value, functools.partial): 60 | maybe_coro = value.func 61 | if not asyncio.iscoroutinefunction(maybe_coro): 62 | raise RuntimeError("Function must be a coroutine") 63 | current_page = pages[page] 64 | 65 | if not message: 66 | if isinstance(current_page, discord.Embed): 67 | message = await ctx.send(embed=current_page, **kwargs) 68 | else: 69 | message = await ctx.send(current_page, **kwargs) 70 | # Don't wait for reactions to be added (GH-1797) 71 | # noinspection PyAsyncCall 72 | start_adding_reactions(message, controls.keys()) 73 | else: 74 | try: 75 | if isinstance(current_page, discord.Embed): 76 | await message.edit(embed=current_page, **kwargs) 77 | else: 78 | await message.edit(content=current_page, **kwargs) 79 | except discord.NotFound: 80 | return 81 | 82 | try: 83 | predicates = ReactionPredicate.with_emojis(tuple(controls.keys()), message, ctx.author) 84 | tasks = [ 85 | asyncio.ensure_future(ctx.bot.wait_for("reaction_add", check=predicates)), 86 | asyncio.ensure_future(ctx.bot.wait_for("reaction_remove", check=predicates)), 87 | ] 88 | done, pending = await asyncio.wait( 89 | tasks, timeout=timeout, return_when=asyncio.FIRST_COMPLETED 90 | ) 91 | for task in pending: 92 | task.cancel() 93 | 94 | if len(done) == 0: 95 | raise asyncio.TimeoutError() 96 | react, user = done.pop().result() 97 | except asyncio.TimeoutError: 98 | if not ctx.me: 99 | return 100 | try: 101 | if message.channel.permissions_for(ctx.me).manage_messages: 102 | await message.clear_reactions() 103 | else: 104 | raise RuntimeError 105 | except (discord.Forbidden, RuntimeError): # cannot remove all reactions 106 | for key in controls: 107 | try: 108 | await message.remove_reaction(key, ctx.bot.user) 109 | except discord.Forbidden: 110 | return 111 | except discord.HTTPException: 112 | pass 113 | except discord.NotFound: 114 | return 115 | else: 116 | return await controls[react.emoji]( 117 | ctx, pages, controls, message, page, timeout, react.emoji 118 | ) 119 | -------------------------------------------------------------------------------- /dminvites/dminvites.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from redbot.core import Config, commands 3 | from redbot.core.utils.common_filters import INVITE_URL_RE 4 | 5 | 6 | class DmInvite(commands.Cog): 7 | """Respond to invites send in DMs.""" 8 | 9 | __version__ = "0.0.4" 10 | 11 | def format_help_for_context(self, ctx): 12 | """Thanks Sinbad.""" 13 | pre_processed = super().format_help_for_context(ctx) 14 | return f"{pre_processed}\nCog Version: {self.__version__}" 15 | 16 | def __init__(self, bot): 17 | self.bot = bot 18 | self.config = Config.get_conf(self, 1398467138476, force_registration=True) 19 | self.config.register_global( 20 | toggle=True, 21 | message="I've detected a discord server invite. If you'd like to invite me please click the link below.\n{link}", 22 | embed=False, 23 | ) 24 | 25 | async def red_get_data_for_user(self, *, user_id: int): 26 | # this cog does not story any data 27 | return {} 28 | 29 | async def red_delete_data_for_user(self, *, requester, user_id: int) -> None: 30 | # this cog does not story any data 31 | pass 32 | 33 | async def invite_url(self): 34 | app_info = await self.bot.application_info() 35 | perms = await self.bot._config.invite_perm() 36 | permissions = discord.Permissions(perms) 37 | return discord.utils.oauth_url(app_info.id, permissions=permissions) 38 | 39 | @commands.group() 40 | @commands.is_owner() 41 | async def dminvite(self, ctx): 42 | """Group Commands for DM Invites.""" 43 | 44 | @dminvite.command() 45 | @commands.is_owner() 46 | async def settings(self, ctx): 47 | """DM Invite Settings.""" 48 | embed = discord.Embed(title="DM Invite Settings", color=discord.Color.red()) 49 | embed.add_field( 50 | name="Tracking Invites", value="Yes" if await self.config.toggle() else "No" 51 | ) 52 | embed.add_field(name="Embeds", value="Yes" if await self.config.embed() else "No") 53 | msg = await self.config.message() 54 | embed.add_field(name="Message", value=msg) 55 | embed.add_field(name="Permissions Value", value=await self.bot._config.invite_perm()) 56 | if "{link}" in msg: 57 | embed.add_field(name="Link", value=f"[Click Here]({await self.invite_url()})") 58 | await ctx.send(embed=embed) 59 | 60 | @dminvite.command() 61 | @commands.is_owner() 62 | async def toggle(self, ctx, toggle: bool = None): 63 | """Turn DM responding on/off.""" 64 | toggle = toggle or await self.config.toggle() 65 | if toggle: 66 | await self.config.toggle.set(False) 67 | await ctx.send(f"{ctx.me.name} will no longer auto-respond to invites sent in DMs.") 68 | 69 | else: 70 | await self.config.toggle.set(True) 71 | await ctx.send(f"{ctx.me.name} will auto-respond to invites sent in DMs.") 72 | 73 | @dminvite.command() 74 | @commands.is_owner() 75 | async def embeds(self, ctx, toggle: bool = None): 76 | """Toggle whether the message is an embed or not.""" 77 | toggle = toggle or await self.config.embed() 78 | if toggle: 79 | await self.config.embed.set(False) 80 | await ctx.send("Responses will no longer be sent as an embed.") 81 | else: 82 | await self.config.embed.set(True) 83 | await ctx.send( 84 | "Responses will now be sent as an embed. You can now use other markdown such as link masking etc." 85 | ) 86 | 87 | @dminvite.command() 88 | @commands.is_owner() 89 | async def message(self, ctx, *, message: str): 90 | """Set the message that the bot will respond with. 91 | 92 | **Available Parameters**: 93 | {link} - return the bots oauth url with the permissions you've set with the core inviteset. 94 | """ 95 | await self.config.message.set(message) 96 | await ctx.tick() 97 | 98 | @commands.Cog.listener() 99 | async def on_message(self, message): 100 | if message.guild: 101 | return 102 | if message.author.bot: 103 | return 104 | if await self.config.toggle(): 105 | if link_res := INVITE_URL_RE.findall(message.content): 106 | msg = await self.config.message() 107 | if "{link}" in msg: 108 | msg = msg.format(link=await self.invite_url()) 109 | if await self.config.embed(): 110 | embed = discord.Embed(color=discord.Color.red(), description=msg) 111 | await message.author.send(embed=embed) 112 | return 113 | await message.author.send(msg) 114 | -------------------------------------------------------------------------------- /news/menus.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | from typing import Any, Dict, Iterable, Optional 3 | 4 | import discord 5 | import iso8601 6 | import validators 7 | from redbot.core import commands 8 | from redbot.vendored.discord.ext import menus 9 | 10 | 11 | class GenericMenu(menus.MenuPages, inherit_buttons=False): 12 | def __init__( 13 | self, 14 | source: menus.PageSource, 15 | cog: Optional[commands.Cog] = None, 16 | ctx=None, 17 | clear_reactions_after: bool = True, 18 | delete_message_after: bool = False, 19 | add_reactions: bool = True, 20 | using_custom_emoji: bool = False, 21 | using_embeds: bool = False, 22 | keyword_to_reaction_mapping: Dict[str, str] = None, 23 | timeout: int = 180, 24 | message: discord.Message = None, 25 | **kwargs: Any, 26 | ) -> None: 27 | self.cog = cog 28 | self.ctx = ctx 29 | super().__init__( 30 | source, 31 | clear_reactions_after=clear_reactions_after, 32 | delete_message_after=delete_message_after, 33 | check_embeds=using_embeds, 34 | timeout=timeout, 35 | message=message, 36 | **kwargs, 37 | ) 38 | 39 | def reaction_check(self, payload): 40 | """The function that is used to check whether the payload should be processed. 41 | This is passed to :meth:`discord.ext.commands.Bot.wait_for `. 42 | There should be no reason to override this function for most users. 43 | Parameters 44 | ------------ 45 | payload: :class:`discord.RawReactionActionEvent` 46 | The payload to check. 47 | Returns 48 | --------- 49 | :class:`bool` 50 | Whether the payload should be processed. 51 | """ 52 | if payload.message_id != self.message.id: 53 | return False 54 | if payload.user_id not in (*self.bot.owner_ids, self._author_id): 55 | return False 56 | 57 | return payload.emoji in self.buttons 58 | 59 | def _skip_single_arrows(self): 60 | max_pages = self._source.get_max_pages() 61 | if max_pages is None: 62 | return True 63 | return max_pages == 1 64 | 65 | def _skip_double_triangle_buttons(self): 66 | max_pages = self._source.get_max_pages() 67 | if max_pages is None: 68 | return True 69 | return max_pages <= 2 70 | 71 | # left 72 | @menus.button( 73 | "\N{BLACK LEFT-POINTING TRIANGLE}", position=menus.First(1), skip_if=_skip_single_arrows 74 | ) 75 | async def prev(self, payload: discord.RawReactionActionEvent): 76 | if self.current_page == 0: 77 | await self.show_page(self._source.get_max_pages() - 1) 78 | else: 79 | await self.show_checked_page(self.current_page - 1) 80 | 81 | @menus.button("\N{CROSS MARK}", position=menus.First(2)) 82 | async def stop_pages_default(self, payload: discord.RawReactionActionEvent) -> None: 83 | self.stop() 84 | with contextlib.suppress(discord.NotFound): 85 | await self.message.delete() 86 | 87 | @menus.button( 88 | "\N{BLACK RIGHT-POINTING TRIANGLE}", position=menus.First(2), skip_if=_skip_single_arrows 89 | ) 90 | async def next(self, payload: discord.RawReactionActionEvent): 91 | if self.current_page == self._source.get_max_pages() - 1: 92 | await self.show_page(0) 93 | else: 94 | await self.show_checked_page(self.current_page + 1) 95 | 96 | @menus.button( 97 | "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f", 98 | position=menus.First(0), 99 | skip_if=_skip_double_triangle_buttons, 100 | ) 101 | async def go_to_first_page(self, payload: discord.RawReactionActionEvent): 102 | """go to the first page""" 103 | await self.show_page(0) 104 | 105 | @menus.button( 106 | "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f", 107 | position=menus.Last(1), 108 | skip_if=_skip_double_triangle_buttons, 109 | ) 110 | async def go_to_last_page(self, payload: discord.RawReactionActionEvent): 111 | """go to the last page""" 112 | # The call here is safe because it's guarded by skip_if 113 | await self.show_page(self._source.get_max_pages() - 1) 114 | 115 | 116 | class ArticleFormat(menus.ListPageSource): 117 | def __init__(self, entries: Iterable[str]): 118 | super().__init__(entries, per_page=1) 119 | 120 | async def format_page(self, menu: GenericMenu, article) -> str: 121 | embed = discord.Embed( 122 | title=article["title"], 123 | color=await menu.ctx.embed_colour(), 124 | description=f"\n{article['description']}", 125 | timestamp=iso8601.parse_date(article["publishedAt"]), 126 | url=article["url"], 127 | ) 128 | if article["urlToImage"] is not None and validators.url(article["urlToImage"]): 129 | embed.set_image(url=article["urlToImage"]) 130 | embed.set_author(name=f"{article['author']} - {article['source']['name']}") 131 | embed.set_footer(text=f"Article {menu.current_page + 1 }/{menu._source.get_max_pages()}") 132 | return embed 133 | -------------------------------------------------------------------------------- /news/news.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | from redbot.core import commands 3 | 4 | from .menus import ArticleFormat, GenericMenu 5 | 6 | 7 | class News(commands.Cog): 8 | """News Cog.""" 9 | 10 | __version__ = "0.0.3" 11 | __author__ = "flare#0001" 12 | 13 | def format_help_for_context(self, ctx): 14 | """Thanks Sinbad.""" 15 | pre_processed = super().format_help_for_context(ctx) 16 | return f"{pre_processed}\nCog Version: {self.__version__}\nAuthor: {self.__author__}" 17 | 18 | def __init__(self, bot): 19 | self.bot = bot 20 | self.api = ( 21 | "https://newsapi.org/v2/{}?{}&sortBy=publishedAt{}&apiKey={}&page=1&pageSize=15{}" 22 | ) 23 | self.session = aiohttp.ClientSession() 24 | self.newsapikey = None 25 | 26 | async def red_get_data_for_user(self, *, user_id: int): 27 | # this cog does not story any data 28 | return {} 29 | 30 | async def red_delete_data_for_user(self, *, requester, user_id: int) -> None: 31 | # this cog does not story any data 32 | pass 33 | 34 | async def initalize(self): 35 | token = await self.bot.get_shared_api_tokens("newsapi") 36 | self.newsapikey = token.get("key", None) 37 | 38 | @commands.Cog.listener() 39 | async def on_red_api_tokens_update(self, service_name, api_tokens): 40 | if service_name == "newsapi": 41 | self.newsapikey = api_tokens.get("key", None) 42 | 43 | def cog_unload(self): 44 | self.bot.loop.create_task(self.session.close()) 45 | 46 | async def get(self, url): 47 | async with self.session.get(url) as response: 48 | data = await response.json() 49 | if response.status != 200: 50 | return {"failed": data["message"]} 51 | try: 52 | return data 53 | except aiohttp.ServerTimeoutError: 54 | return { 55 | "failed": "Their appears to be an issue with the API. Please try again later." 56 | } 57 | 58 | @commands.group() 59 | async def news(self, ctx): 60 | """Group Command for News.""" 61 | 62 | @commands.command() 63 | async def newssetup(self, ctx): 64 | """Instructions on how to setup news related APIs.""" 65 | msg = f"**News API Setup**\n**1**. Visit https://newsapi.org and register for an API.\n**2**. Use the following command: {ctx.prefix}set api newsapi key \n**3**. Reload the cog if it doesnt work immediately." 66 | 67 | await ctx.maybe_send_embed(msg) 68 | 69 | @news.command(hidden=True) 70 | async def countrycodes(self, ctx): 71 | """Countries supported by the News Cog.""" 72 | await ctx.send( 73 | "Valid country codes are:\nae ar at au be bg br ca ch cn co cu cz de eg fr gb gr hk hu id ie il in it jp kr lt lv ma mx my ng nl no nz ph pl pt ro rs ru sa se sg si sk th tr tw ua us ve za" 74 | ) 75 | 76 | @news.command() 77 | async def top(self, ctx, countrycode: str, *, query: str = None): 78 | """ 79 | Top News from a Country - County must be 2-letter ISO 3166-1 code. Supports querys to search news. 80 | 81 | Check [p]news countrycodes for a list of all possible country codes supported. 82 | """ 83 | async with ctx.typing(): 84 | data = await self.get( 85 | self.api.format( 86 | "top-headlines", 87 | f"q={query}" if query is not None else "", 88 | f"&country={countrycode}", 89 | self.newsapikey, 90 | "", 91 | ) 92 | ) 93 | 94 | if data.get("failed") is not None: 95 | return await ctx.send(data.get("failed")) 96 | if data["totalResults"] == 0: 97 | return await ctx.send( 98 | f"No results found, ensure you're looking up the correct country code. Check {ctx.prefix}countrycodes for a list. Alternatively, your query may be returning no results." 99 | ) 100 | 101 | await GenericMenu( 102 | source=ArticleFormat(data["articles"][:15]), 103 | ctx=ctx, 104 | ).start( 105 | ctx=ctx, 106 | wait=False, 107 | ) 108 | 109 | @news.command(name="global") 110 | async def global_all(self, ctx, *, query: str = None): 111 | """News from around the World. 112 | 113 | Not considered top-headlines. May be unreliable, unknown etc. 114 | """ 115 | async with ctx.typing(): 116 | data = await self.get( 117 | self.api.format("everything", f"q={query}", "", self.newsapikey, "") 118 | ) 119 | 120 | if data.get("failed") is not None: 121 | return await ctx.send(data.get("failed")) 122 | if data["totalResults"] == 0: 123 | return await ctx.send("No results found.") 124 | await GenericMenu( 125 | source=ArticleFormat(data["articles"]), 126 | ctx=ctx, 127 | ).start( 128 | ctx=ctx, 129 | wait=False, 130 | ) 131 | 132 | @news.command() 133 | async def topglobal(self, ctx, *, query: str): 134 | """Top Headlines from around the world.""" 135 | async with ctx.typing(): 136 | data = await self.get( 137 | self.api.format("top-headlines", f"q={query}", "", self.newsapikey, "") 138 | ) 139 | 140 | if data.get("failed") is not None: 141 | return await ctx.send(data.get("failed")) 142 | if data["totalResults"] == 0: 143 | return await ctx.send("No results found.") 144 | await GenericMenu( 145 | source=ArticleFormat(data["articles"]), 146 | ctx=ctx, 147 | ).start( 148 | ctx=ctx, 149 | wait=False, 150 | ) 151 | -------------------------------------------------------------------------------- /palette/palette.py: -------------------------------------------------------------------------------- 1 | from io import BytesIO 2 | from typing import Optional 3 | 4 | import aiohttp 5 | import colorgram 6 | import discord 7 | from PIL import Image, ImageDraw, ImageFont 8 | from redbot.core import commands 9 | from redbot.core.data_manager import bundled_data_path 10 | from redbot.core.utils.chat_formatting import box 11 | from tabulate import tabulate 12 | 13 | from .converters import ImageFinder 14 | 15 | 16 | class Palette(commands.Cog): 17 | """ 18 | This is a collection of commands that are used to show colour palettes. 19 | """ 20 | 21 | __version__ = "0.1.1" 22 | __author__ = "flare(flare#0001) and Kuro" 23 | 24 | def format_help_for_context(self, ctx): 25 | pre_processed = super().format_help_for_context(ctx) 26 | return f"{pre_processed}\nCog Version: {self.__version__}\nAuthor: {self.__author__}" 27 | 28 | def __init__(self, bot): 29 | self.bot = bot 30 | self.session = aiohttp.ClientSession() 31 | 32 | async def cog_unload(self): 33 | await self.session.close() 34 | 35 | def rgb_to_hex(self, rgb): 36 | return "#%02x%02x%02x" % rgb 37 | 38 | async def get_img(self, url): 39 | async with self.session.get(url) as resp: 40 | if resp.status == 404: 41 | return { 42 | "error": "Server not found, ensure the correct URL is setup and is reachable. " 43 | } 44 | if not str(resp.headers.get("Content-Type")).startswith("image/"): 45 | return {"error": "Invalid image."} 46 | if resp.status in [200, 201]: 47 | file = await resp.read() 48 | file = BytesIO(file) 49 | file.seek(0) 50 | return file 51 | return {"error": resp.status} 52 | 53 | @commands.max_concurrency(1, commands.BucketType.user) 54 | @commands.command() 55 | async def palette( 56 | self, 57 | ctx, 58 | image: Optional[ImageFinder] = None, 59 | amount: Optional[commands.Range[int, 1, 50]] = 10, 60 | detailed: Optional[bool] = False, 61 | sort: Optional[bool] = False, 62 | ): 63 | """Get the colour palette of an image. 64 | 65 | **Arguments** 66 | - `[image]` The image to get the palette from. If not provided, the author's avatar will be used. You can also provide an attachment. 67 | - `[amount]` The amount of colours to get. Must be between 1 and 50. Defaults to 10. 68 | - `[detailed]` Whether to show the colours in a detailed format (with rgb and hex). Defaults to False. 69 | - `[sort]` Whether to sort the colours by rgb. Defaults to False. 70 | """ 71 | if not image: 72 | image = str(ctx.author.display_avatar) 73 | if attachments := ctx.message.attachments: 74 | valid_attachments = [a for a in attachments if a.content_type.startswith("image/")] 75 | if valid_attachments: 76 | image = valid_attachments[0].url 77 | 78 | async with ctx.typing(): 79 | img = await self.get_img(image) 80 | if isinstance(img, dict): 81 | return await ctx.send(img["error"]) 82 | 83 | colors, file = await self.bot.loop.run_in_executor( 84 | None, self.create_palette, img, amount, detailed, sort 85 | ) 86 | 87 | if not detailed: 88 | return await ctx.send(file=file) 89 | 90 | table = [] 91 | for i, color in enumerate(colors, start=1): 92 | row = [f"{color.rgb.r}, {color.rgb.g}, {color.rgb.b}", self.rgb_to_hex(color.rgb)] 93 | if len(colors) > 1: 94 | row.insert(0, str(i)) 95 | table.append(row) 96 | headers = ["#", "RGB", "Hex"] if len(colors) > 1 else ["RGB", "Hex"] 97 | 98 | embed = discord.Embed( 99 | color=await ctx.embed_color(), 100 | title="Colour Palette", 101 | description=box(tabulate(table, headers), lang="css"), 102 | ) 103 | embed.set_thumbnail(url=image) 104 | embed.set_image(url=f"attachment://{file.filename}") 105 | await ctx.send( 106 | embed=embed, 107 | file=file, 108 | reference=ctx.message.to_reference(fail_if_not_exists=False), 109 | mention_author=False, 110 | ) 111 | 112 | def create_palette(self, fp: BytesIO, amount: int, detailed: bool, sort: bool): 113 | colors = colorgram.extract(fp, amount) 114 | if sort: 115 | colors.sort(key=lambda c: c.rgb) 116 | 117 | dimensions = (500 * len(colors), 500) if detailed else (100 * len(colors), 100) 118 | final = Image.new("RGBA", dimensions) 119 | a = ImageDraw.Draw(final) 120 | start = 0 121 | if detailed: 122 | font_file = f"{bundled_data_path(self)}/fonts/RobotoRegular.ttf" 123 | name_fnt = ImageFont.truetype(font_file, 69, encoding="utf-8") 124 | for i, color in enumerate(colors, start=1): 125 | a.rectangle( 126 | [(start, 0), (start + dimensions[1], 431 if detailed else 100)], fill=color.rgb 127 | ) 128 | if detailed: 129 | # Bold text effect 130 | offsets = ((0, 0), (1, 0), (0, 1), (1, 1)) 131 | for xo, yo in offsets: 132 | a.text( 133 | (start + dimensions[1] // 2 + xo, 499 + yo), 134 | str(i), 135 | fill=(255, 255, 255, 255), 136 | font=name_fnt, 137 | anchor="mb", 138 | ) 139 | start = start + dimensions[1] 140 | final = final.resize((500 * len(colors), 500), resample=Image.Resampling.LANCZOS) 141 | fileObj = BytesIO() 142 | final.save(fileObj, "png") 143 | fileObj.name = "palette.png" 144 | fileObj.seek(0) 145 | return colors, discord.File(fileObj) 146 | -------------------------------------------------------------------------------- /simleague/teamset.py: -------------------------------------------------------------------------------- 1 | import discord 2 | import validators 3 | from redbot.core import checks, commands 4 | 5 | from .abc import MixinMeta 6 | 7 | 8 | class TeamsetMixin(MixinMeta): 9 | """Teamset Settings""" 10 | 11 | @checks.admin_or_permissions(manage_guild=True) 12 | @commands.group(autohelp=True) 13 | async def teamset(self, ctx): 14 | """Team Settings.""" 15 | 16 | @teamset.command() 17 | async def role(self, ctx, team: str, *, role: discord.Role): 18 | """Set a teams role.""" 19 | async with self.config.guild(ctx.guild).teams() as teams: 20 | if team not in teams: 21 | return await ctx.send("Not a valid team.") 22 | teams[team]["role"] = role.id 23 | await ctx.tick() 24 | 25 | @teamset.command() 26 | async def stadium(self, ctx, team: str, *, stadium: str): 27 | """Set a teams stadium.""" 28 | async with self.config.guild(ctx.guild).teams() as teams: 29 | if team not in teams: 30 | return await ctx.send("Not a valid team.") 31 | teams[team]["stadium"] = stadium 32 | await ctx.tick() 33 | 34 | @teamset.command() 35 | async def logo(self, ctx, team: str, *, logo: str): 36 | """Set a teams logo.""" 37 | if not validators.url(logo): 38 | await ctx.send("This doesn't seem to be a valid URL.") 39 | if not logo.endswith(".png"): 40 | await ctx.send("URL must be a png.") 41 | async with self.config.guild(ctx.guild).teams() as teams: 42 | if team not in teams: 43 | return await ctx.send("Not a valid team.") 44 | teams[team]["logo"] = logo 45 | await ctx.tick() 46 | 47 | @teamset.command(hidden=True) 48 | async def bonus(self, ctx, team: str, *, amount: int): 49 | """Set a teams bonus multiplier.""" 50 | async with self.config.guild(ctx.guild).teams() as teams: 51 | if team not in teams: 52 | return await ctx.send("Not a valid team.") 53 | teams[team]["bonus"] = amount 54 | await ctx.tick() 55 | 56 | @teamset.command(usage=" ") 57 | async def name(self, ctx, team: str, *, newname: str): 58 | """Set a teams name. Try keep names to one word if possible.""" 59 | async with self.config.guild(ctx.guild).teams() as teams: 60 | if team not in teams: 61 | return await ctx.send("Not a valid team.") 62 | teams[newname] = teams[team] 63 | if teams[team]["role"] is not None: 64 | role = ctx.guild.get_role(teams[team]["role"]) 65 | await role.edit(name=newname) 66 | del teams[team] 67 | async with self.config.guild(ctx.guild).standings() as teams: 68 | teams[newname] = teams[team] 69 | del teams[team] 70 | await ctx.tick() 71 | 72 | @teamset.command() 73 | async def fullname(self, ctx, team: str, *, fullname: str): 74 | """Set a teams full name.""" 75 | async with self.config.guild(ctx.guild).teams() as teams: 76 | if team not in teams: 77 | return await ctx.send("Not a valid team.") 78 | teams[team]["fullname"] = fullname 79 | await ctx.tick() 80 | 81 | @teamset.command() 82 | async def captain(self, ctx, team: str, captain: discord.Member): 83 | """Set a teams captain.""" 84 | async with self.config.guild(ctx.guild).teams() as teams: 85 | if team not in teams: 86 | return await ctx.send("Not a valid team.") 87 | if captain.id not in teams[team]["members"]: 88 | return await ctx.send("He is not a member of that team.") 89 | teams[team]["captain"] = {} 90 | teams[team]["captain"] = {str(captain.id): captain.name} 91 | 92 | await ctx.tick() 93 | 94 | @teamset.group(autohelp=True) 95 | async def kits(self, ctx): 96 | """Kit Settings.""" 97 | 98 | @kits.command() 99 | async def home(self, ctx, team: str, *, kiturl: str): 100 | """Set a teams home kit.""" 101 | if not validators.url(kiturl): 102 | await ctx.send("This doesn't seem to be a valid URL.") 103 | if not kiturl.endswith(".png"): 104 | await ctx.send("URL must be a png.") 105 | async with self.config.guild(ctx.guild).teams() as teams: 106 | if team not in teams: 107 | return await ctx.send("Not a valid team.") 108 | teams[team]["kits"]["home"] = kiturl 109 | await ctx.tick() 110 | 111 | @kits.command() 112 | async def away(self, ctx, team: str, *, kiturl: str): 113 | """Set a teams away kit.""" 114 | if not validators.url(kiturl): 115 | await ctx.send("This doesn't seem to be a valid URL.") 116 | return 117 | if not kiturl.endswith(".png"): 118 | await ctx.send("URL must be a png.") 119 | async with self.config.guild(ctx.guild).teams() as teams: 120 | if team not in teams: 121 | return await ctx.send("Not a valid team.") 122 | teams[team]["kits"]["away"] = kiturl 123 | await ctx.tick() 124 | 125 | @kits.command() 126 | async def third(self, ctx, team: str, *, kiturl: str): 127 | """Set a teams third kit.""" 128 | if not validators.url(kiturl): 129 | await ctx.send("This doesn't seem to be a valid URL.") 130 | if not kiturl.endswith(".png"): 131 | await ctx.send("URL must be a png.") 132 | async with self.config.guild(ctx.guild).teams() as teams: 133 | if team not in teams: 134 | return await ctx.send("Not a valid team.") 135 | teams[team]["kits"]["third"] = kiturl 136 | await ctx.tick() 137 | 138 | @teamset.command(name="transfer") 139 | async def _transfer(self, ctx, team1, player1: discord.Member, team2, player2: discord.Member): 140 | """Transfer two players.""" 141 | if not await self.config.guild(ctx.guild).transferwindow(): 142 | return await ctx.send("The transfer window is currently closed.") 143 | await self.transfer(ctx, ctx.guild, team1, player1, team2, player2) 144 | await ctx.tick() 145 | 146 | @teamset.command(name="sign") 147 | async def _sign(self, ctx, team1, player1: discord.Member, player2: discord.Member): 148 | """Release a player and sign a free agent.""" 149 | if not await self.config.guild(ctx.guild).transferwindow(): 150 | return await ctx.send("The transfer window is currently closed.") 151 | await self.sign(ctx, ctx.guild, team1, player1, player2) 152 | await ctx.tick() 153 | 154 | @teamset.command(name="delete") 155 | async def _delete(self, ctx, *, team): 156 | """Delete a team.""" 157 | await self.team_delete(ctx, team) 158 | -------------------------------------------------------------------------------- /joinmessage/joinmessage.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import discord 4 | from redbot.core import Config, commands 5 | 6 | log = logging.getLogger("red.flare.joinmessage") 7 | 8 | CHANNELS = [ 9 | "general", 10 | "general-chat", 11 | "основной", 12 | "основной-чат", 13 | "generell", 14 | "generell-chatt", 15 | "כללי", 16 | "צ'אט-כללי", 17 | "allgemein", 18 | "generale", 19 | "général", 20 | "općenito", 21 | "bendra", 22 | "általános", 23 | "algemeen", 24 | "generelt", 25 | "geral", 26 | "informații generale", 27 | "ogólny", 28 | "yleinen", 29 | "allmänt", 30 | "allmän-chat", 31 | "chung", 32 | "genel", 33 | "obecné", 34 | "obično", 35 | "Генерален чат", 36 | "общи", 37 | "загальний", 38 | "ทั่วไป", 39 | "常规", 40 | ] 41 | 42 | 43 | class JoinMessage(commands.Cog): 44 | """Send a message on guild join.""" 45 | 46 | __version__ = "0.1.0" 47 | __author__ = "flare#0001" 48 | 49 | def format_help_for_context(self, ctx): 50 | """Thanks Sinbad.""" 51 | pre_processed = super().format_help_for_context(ctx) 52 | return f"{pre_processed}\nCog Version: {self.__version__}\nAuthor: {self.__author__}" 53 | 54 | async def red_get_data_for_user(self, *, user_id: int): 55 | # this cog does not story any data 56 | return {} 57 | 58 | async def red_delete_data_for_user(self, *, requester, user_id: int) -> None: 59 | # this cog does not story any data 60 | pass 61 | 62 | def __init__(self, bot): 63 | self.bot = bot 64 | self.config = Config.get_conf(self, identifier=1398467138476, force_registration=True) 65 | self.config.register_global(message=None, toggle=False, embed=False, image=None) 66 | self.config.register_guild(notified=False) 67 | 68 | @commands.Cog.listener() 69 | async def on_guild_join(self, guild): 70 | if not await self.config.toggle(): 71 | return 72 | if await self.config.guild(guild).notified(): 73 | return 74 | msg = await self.config.message() 75 | if msg is None: 76 | log.info("No message setup, please set one up via the joinmessage message command.") 77 | return 78 | channel = ( 79 | discord.utils.find(lambda x: x.name in CHANNELS, guild.text_channels) 80 | or guild.system_channel 81 | or next( 82 | (x for x in guild.text_channels if x.permissions_for(guild.me).send_messages), None 83 | ) 84 | ) 85 | if channel is None: 86 | log.debug("Couldn't find a channel to send join message in %s", guild) 87 | return 88 | if not channel.permissions_for(guild.me).send_messages: 89 | return 90 | if await self.config.embed() and channel.permissions_for(guild.me).embed_links: 91 | embed = discord.Embed( 92 | title=f"Thanks for inviting {guild.me.name}!", 93 | description=msg, 94 | colour=await self.bot.get_embed_colour(location=channel), 95 | ) 96 | img = await self.config.image() 97 | if img is not None: 98 | embed.set_image(url=img) 99 | await channel.send(embed=embed) 100 | else: 101 | await channel.send(msg) 102 | await self.config.guild(guild).notified.set(True) 103 | log.debug("Guild welcome message sent in %s", guild) 104 | 105 | @commands.group() 106 | @commands.is_owner() 107 | async def joinmessage(self, ctx): 108 | """Options for sending messages on server join.""" 109 | 110 | @joinmessage.command(usage="type") 111 | async def toggle(self, ctx, _type: bool = None): 112 | """Toggle server join messages on or off.""" 113 | if _type is None: 114 | _type = not await self.config.toggle() 115 | await self.config.toggle.set(_type) 116 | if _type: 117 | await ctx.send("Server join messages have been enabled.") 118 | return 119 | await ctx.send("Server join messages have been disabled.") 120 | 121 | @joinmessage.command(usage="type") 122 | async def embed(self, ctx, _type: bool = None): 123 | """Toggle sending of embed or not.""" 124 | if _type is None: 125 | _type = not await self.config.embed() 126 | await self.config.embed.set(_type) 127 | if _type: 128 | await ctx.send("Server join messages will now be sent as an embed.") 129 | return 130 | await ctx.send("Server join messages will be sent in raw text.") 131 | 132 | @joinmessage.command() 133 | async def raw(self, ctx): 134 | """Send the configured message with markdown escaped.""" 135 | msg = await self.config.message() 136 | if msg is None: 137 | await ctx.send( 138 | f"You do not have a message configured. Configure one using `{ctx.clean_prefix}joinmessage message`." 139 | ) 140 | return 141 | raw = discord.utils.escape_markdown(msg) 142 | await ctx.send(f"```{raw}```") 143 | 144 | @joinmessage.command() 145 | async def image(self, ctx, url: str = None): 146 | """Set image to be used when using embeds.""" 147 | if url is None: 148 | await self.config.image.set(None) 149 | else: 150 | await self.config.image.set(url) 151 | await ctx.tick() 152 | 153 | @joinmessage.command() 154 | async def message(self, ctx, *, message: str = None): 155 | """Set the message to be sent on join. 156 | 157 | Sending no message will show the current message or help menu if 158 | none is set. 159 | """ 160 | if message is None: 161 | msg = await self.config.message() 162 | if msg is None: 163 | await ctx.send_help() 164 | return 165 | await ctx.send(f"Your current message being sent is:\n{msg}") 166 | return 167 | await self.config.message.set(message) 168 | await ctx.send(f"Your message will be sent as:\n{message}") 169 | 170 | @joinmessage.command() 171 | async def test(self, ctx): 172 | """Test your joinmessage.""" 173 | msg = await self.config.message() 174 | if msg is None: 175 | log.info("No message setup, please set one up via the joinmessage message command.") 176 | return 177 | channel, guild = ctx.channel, ctx.guild 178 | if await self.config.embed() and channel.permissions_for(guild.me).embed_links: 179 | embed = discord.Embed( 180 | title=f"Thanks for inviting {guild.me.name}!", 181 | description=msg, 182 | colour=await self.bot.get_embed_colour(location=channel), 183 | ) 184 | img = await self.config.image() 185 | if img is not None: 186 | embed.set_image(url=img) 187 | await channel.send(embed=embed) 188 | else: 189 | await channel.send(msg) 190 | -------------------------------------------------------------------------------- /commandstats/menus.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import datetime 3 | from typing import Any, Dict, Iterable, Optional 4 | 5 | import discord 6 | import tabulate 7 | from redbot.core import commands 8 | from redbot.core.utils.chat_formatting import box, escape, humanize_number 9 | from redbot.vendored.discord.ext import menus 10 | 11 | 12 | class GenericMenu(menus.MenuPages, inherit_buttons=False): 13 | def __init__( 14 | self, 15 | source: menus.PageSource, 16 | cog: Optional[commands.Cog] = None, 17 | title: Optional[str] = None, 18 | _type: Optional[str] = None, 19 | ctx=None, 20 | timestamp: Optional[datetime.datetime] = None, 21 | clear_reactions_after: bool = True, 22 | delete_message_after: bool = False, 23 | add_reactions: bool = True, 24 | using_custom_emoji: bool = False, 25 | using_embeds: bool = False, 26 | keyword_to_reaction_mapping: Dict[str, str] = None, 27 | timeout: int = 180, 28 | message: discord.Message = None, 29 | **kwargs: Any, 30 | ) -> None: 31 | self.cog = cog 32 | self.title = title 33 | self._type = _type 34 | self.timestamp = timestamp 35 | self.ctx = ctx 36 | super().__init__( 37 | source, 38 | clear_reactions_after=clear_reactions_after, 39 | delete_message_after=delete_message_after, 40 | check_embeds=using_embeds, 41 | timeout=timeout, 42 | message=message, 43 | **kwargs, 44 | ) 45 | 46 | def reaction_check(self, payload): 47 | """The function that is used to check whether the payload should be processed. 48 | This is passed to :meth:`discord.ext.commands.Bot.wait_for `. 49 | There should be no reason to override this function for most users. 50 | Parameters 51 | ------------ 52 | payload: :class:`discord.RawReactionActionEvent` 53 | The payload to check. 54 | Returns 55 | --------- 56 | :class:`bool` 57 | Whether the payload should be processed. 58 | """ 59 | if payload.message_id != self.message.id: 60 | return False 61 | if payload.user_id not in (*self.bot.owner_ids, self._author_id): 62 | return False 63 | 64 | return payload.emoji in self.buttons 65 | 66 | def _skip_single_arrows(self): 67 | max_pages = self._source.get_max_pages() 68 | if max_pages is None: 69 | return True 70 | return max_pages == 1 71 | 72 | def _skip_double_triangle_buttons(self): 73 | max_pages = self._source.get_max_pages() 74 | if max_pages is None: 75 | return True 76 | return max_pages <= 2 77 | 78 | # left 79 | @menus.button( 80 | "\N{BLACK LEFT-POINTING TRIANGLE}", position=menus.First(1), skip_if=_skip_single_arrows 81 | ) 82 | async def prev(self, payload: discord.RawReactionActionEvent): 83 | if self.current_page == 0: 84 | await self.show_page(self._source.get_max_pages() - 1) 85 | else: 86 | await self.show_checked_page(self.current_page - 1) 87 | 88 | @menus.button("\N{CROSS MARK}", position=menus.First(2)) 89 | async def stop_pages_default(self, payload: discord.RawReactionActionEvent) -> None: 90 | self.stop() 91 | with contextlib.suppress(discord.NotFound): 92 | await self.message.delete() 93 | 94 | @menus.button( 95 | "\N{BLACK RIGHT-POINTING TRIANGLE}", position=menus.First(2), skip_if=_skip_single_arrows 96 | ) 97 | async def next(self, payload: discord.RawReactionActionEvent): 98 | if self.current_page == self._source.get_max_pages() - 1: 99 | await self.show_page(0) 100 | else: 101 | await self.show_checked_page(self.current_page + 1) 102 | 103 | @menus.button( 104 | "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f", 105 | position=menus.First(0), 106 | skip_if=_skip_double_triangle_buttons, 107 | ) 108 | async def go_to_first_page(self, payload: discord.RawReactionActionEvent): 109 | """go to the first page""" 110 | await self.show_page(0) 111 | 112 | @menus.button( 113 | "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f", 114 | position=menus.Last(1), 115 | skip_if=_skip_double_triangle_buttons, 116 | ) 117 | async def go_to_last_page(self, payload: discord.RawReactionActionEvent): 118 | """go to the last page""" 119 | # The call here is safe because it's guarded by skip_if 120 | await self.show_page(self._source.get_max_pages() - 1) 121 | 122 | 123 | class EmbedFormat(menus.ListPageSource): 124 | def __init__(self, entries: Iterable[str]): 125 | super().__init__(entries, per_page=1) 126 | 127 | async def format_page(self, menu: GenericMenu, data) -> str: 128 | stats = list(data) 129 | embed = discord.Embed( 130 | title=menu.title, 131 | colour=await menu.ctx.embed_color(), 132 | description=box( 133 | tabulate.tabulate(stats, headers=[menu._type, "Times Used"]), lang="prolog" 134 | ), 135 | ) 136 | if menu.timestamp is not None: 137 | embed.set_footer(text="Recording commands since") 138 | embed.timestamp = menu.timestamp 139 | else: 140 | embed.set_footer( 141 | text="Page {page}/{amount}".format( 142 | page=menu.current_page + 1, amount=menu._source.get_max_pages() 143 | ) 144 | ) 145 | return embed 146 | 147 | 148 | class LeaderboardSource(menus.ListPageSource): 149 | def __init__(self, entries): 150 | super().__init__(entries, per_page=10) 151 | 152 | async def format_page(self, menu: GenericMenu, entries): 153 | bot = menu.ctx.bot 154 | position = (menu.current_page * self.per_page) + 1 155 | bal_len = len(humanize_number(entries[0][1])) 156 | pound_len = len(str(position + 9)) 157 | header = "{pound:{pound_len}}{score:{bal_len}}{name:2}\n".format( 158 | pound="#", 159 | name=("Name"), 160 | score=("Score"), 161 | bal_len=bal_len + 6, 162 | pound_len=pound_len + 3, 163 | ) 164 | msg = "" 165 | for i, data in enumerate(entries, start=position): 166 | try: 167 | server = bot.get_guild(int(data[0])).name 168 | except AttributeError: 169 | server = "" 170 | name = escape(server, formatting=True) 171 | 172 | balance = data[1] 173 | balance = humanize_number(balance) 174 | msg += f"{humanize_number(i)}. {balance: <{bal_len + 5}} {name}\n" 175 | 176 | bank_name = "Guild Command Leaderboard." 177 | page = discord.Embed( 178 | title=("{}").format(bank_name), 179 | color=await menu.ctx.embed_color(), 180 | description="{}\n{} ".format(box(header, lang="prolog"), box(msg, lang="md")), 181 | ) 182 | page.set_footer(text=f"Page {menu.current_page + 1}/{self.get_max_pages()}") 183 | 184 | return page 185 | -------------------------------------------------------------------------------- /.utils/utils.py: -------------------------------------------------------------------------------- 1 | # Full credits goto Trusty, https://github.com/TrustyJAID/Trusty-cogs/blob/master/.utils/utils.py 2 | 3 | import glob 4 | import json 5 | import logging 6 | import os 7 | import re 8 | import string 9 | from dataclasses import dataclass, field 10 | from pathlib import Path 11 | from typing import List, Mapping, Optional 12 | 13 | import tabulate 14 | from babel.lists import format_list as babel_list 15 | 16 | DEFAULT_INFO = { 17 | "author": [], 18 | "install_msg": "", 19 | "name": "", 20 | "disabled": False, 21 | "short": "", 22 | "description": "", 23 | "tags": [], 24 | "requirements": [], 25 | "hidden": False, 26 | } 27 | 28 | logging.basicConfig(filename="scripts.log", level=logging.INFO) 29 | log = logging.getLogger(__file__) 30 | 31 | ROOT = Path(__file__).parents[1] 32 | 33 | VER_REG = re.compile(r"\_\_version\_\_ = \"(\d+\.\d+\.\d+)", flags=re.I) 34 | 35 | DEFAULT_AUTHOR = ["flare#0001"] 36 | 37 | 38 | HEADER = """# Flare-Cogs - (flare#0001) 39 | # No longer actively maintained, use at your own risk. 40 |

41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |

52 | 53 | # Contact 54 | You can contact me in the Red 3rd party server in #support_flare-cogs 55 | 56 |
57 | 58 | --- 59 | 60 | 61 | # Installation 62 | `[p]repo add flare-cogs https://github.com/flaree/Flare-Cogs` 63 | 64 | `[p]cog install flare-cogs ` 65 | 66 | --- 67 | {body} 68 | --- 69 | 70 | 71 | 72 | --- 73 | ## Cogs in other repos. 74 | --- 75 | | Name | Description 76 | | --- | --- | 77 | | Pokecord | Pokecord - found @ [flaree/pokecord-red](https://github.com/flaree/pokecord-red) | 78 | | LastFM | LastFM stats - port of Miso Bot - found @ [flaree/lastfm-red](https://github.com/flaree/lastfm-red) | 79 | 80 | 81 | --- 82 | """ 83 | 84 | 85 | @dataclass 86 | class InfoJson: 87 | author: List[str] 88 | description: Optional[str] = "" 89 | install_msg: Optional[str] = "Thanks for installing" 90 | short: Optional[str] = "" 91 | name: Optional[str] = "" 92 | min_bot_version: Optional[str] = "3.3.0" 93 | max_bot_version: Optional[str] = "0.0.0" 94 | hidden: Optional[bool] = False 95 | disabled: Optional[bool] = False 96 | required_cogs: Mapping = field(default_factory=dict) 97 | requirements: List[str] = field(default_factory=list) 98 | tags: List[str] = field(default_factory=list) 99 | type: Optional[str] = "COG" 100 | permissions: List[str] = field(default_factory=list) 101 | min_python_version: Optional[List[int]] = field(default_factory=lambda: [3, 8, 0]) 102 | end_user_data_statement: str = ( 103 | "This cog does not persistently store data or metadata about users." 104 | ) 105 | 106 | @classmethod 107 | def from_json(cls, data: dict): 108 | min_bot_version = "3.1.8" 109 | required_cogs: Mapping = {} 110 | author = data.get("author", []) 111 | description = data.get("description", "") 112 | install_msg = data.get("install_msg", "Thanks for installing") 113 | short = data.get("short", "Thanks for installing") 114 | if "bot_version" in data: 115 | min_bot_version = data["bot_version"] 116 | if isinstance(min_bot_version, list): 117 | min_bot_version = ".".join(str(i) for i in data["bot_version"]) 118 | if "min_bot_version" in data: 119 | min_bot_version = data["min_bot_version"] 120 | # min_bot_version = "3.3.0" 121 | max_bot_version = data.get("max_bot_version", "0.0.0") 122 | name = data.get("name", "") 123 | if "required_cogs" in data: 124 | if isinstance(data["required_cogs"], list): 125 | required_cogs = {} 126 | else: 127 | required_cogs = data["required_cogs"] 128 | requirements = data.get("requirements", []) 129 | tags = data.get("tags", []) 130 | hidden = data.get("hidden", False) 131 | disabled = data.get("disabled", False) 132 | type = data.get("type", "COG") 133 | permissions = data.get("permissions", []) 134 | min_python_version = data.get("min_python_version", []) 135 | end_user_data_statement = data.get( 136 | "end_user_data_statement", 137 | "This cog does not persistently store data or metadata about users.", 138 | ) 139 | 140 | return cls( 141 | author, 142 | description, 143 | install_msg, 144 | short, 145 | name, 146 | min_bot_version, 147 | max_bot_version, 148 | hidden, 149 | disabled, 150 | required_cogs, 151 | requirements, 152 | tags, 153 | type, 154 | permissions, 155 | min_python_version, 156 | end_user_data_statement, 157 | ) 158 | 159 | 160 | def save_json(folder, data): 161 | with open(folder, "w") as newfile: 162 | json.dump(data, newfile, indent=4, sort_keys=True, separators=(",", " : ")) 163 | 164 | 165 | def makereadme(): 166 | """Generate README.md from info about all cogs""" 167 | table_data = [] 168 | for folder in sorted(os.listdir(ROOT)): 169 | if folder.startswith(".") or folder.startswith("_"): 170 | continue 171 | _version = "" 172 | info = None 173 | for file in glob.glob(f"{ROOT}/{folder}/*"): 174 | if not file.endswith(".py") and not file.endswith("json"): 175 | continue 176 | if file.endswith("info.json"): 177 | try: 178 | with open(file, encoding="utf-8") as infile: 179 | data = json.loads(infile.read()) 180 | info = InfoJson.from_json(data) 181 | except Exception: 182 | log.exception(f"Error reading info.json {file}") 183 | if _version == "": 184 | with open(file, encoding="utf-8") as infile: 185 | data = infile.read() 186 | maybe_version = VER_REG.search(data) 187 | if maybe_version: 188 | _version = maybe_version.group(1) 189 | if info and not info.disabled and not info.hidden: 190 | to_append = [info.name.strip(), _version.strip()] 191 | description = f"
{info.short}{info.description if info.description != info.short else ''}
" 192 | to_append.append(description.strip()) 193 | to_append.append(babel_list(info.author, style="standard").strip()) 194 | table_data.append(to_append) 195 | 196 | body = tabulate.tabulate( 197 | table_data, 198 | headers=["Name", "Status/Version", "Description (Click to see full status)", "Authors"], 199 | # headers=["Name", "Version", "Description (Click to see full info)", "Author(s)"], 200 | tablefmt="github", 201 | ) 202 | file_content = HEADER.format(body=body) 203 | with open(f"{ROOT}/README.md", "r") as outfile: 204 | remove = string.punctuation + string.whitespace 205 | mapping = {ord(c): None for c in remove} 206 | if outfile.read().rstrip().translate(mapping) == file_content.rstrip().translate(mapping): 207 | return 1 208 | with open(f"{ROOT}/README.md", "w") as outfile: 209 | outfile.write(file_content) 210 | return 1 211 | 212 | 213 | if __name__ == "__main__": 214 | makereadme() 215 | -------------------------------------------------------------------------------- /covid/menus.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import datetime 3 | from typing import Any, Dict, Iterable, Optional 4 | 5 | import discord 6 | import validators 7 | from redbot.core import commands 8 | from redbot.core.utils.chat_formatting import humanize_number 9 | from redbot.vendored.discord.ext import menus 10 | 11 | 12 | class GenericMenu(menus.MenuPages, inherit_buttons=False): 13 | def __init__( 14 | self, 15 | source: menus.PageSource, 16 | cog: Optional[commands.Cog] = None, 17 | ctx=None, 18 | type: Optional[str] = None, 19 | timestamp: Optional[datetime.datetime] = None, 20 | clear_reactions_after: bool = True, 21 | delete_message_after: bool = False, 22 | add_reactions: bool = True, 23 | using_custom_emoji: bool = False, 24 | using_embeds: bool = False, 25 | keyword_to_reaction_mapping: Dict[str, str] = None, 26 | timeout: int = 180, 27 | message: discord.Message = None, 28 | **kwargs: Any, 29 | ) -> None: 30 | self.cog = cog 31 | self.ctx = ctx 32 | self.type = type 33 | super().__init__( 34 | source, 35 | clear_reactions_after=clear_reactions_after, 36 | delete_message_after=delete_message_after, 37 | check_embeds=using_embeds, 38 | timeout=timeout, 39 | message=message, 40 | **kwargs, 41 | ) 42 | 43 | def reaction_check(self, payload): 44 | """The function that is used to check whether the payload should be processed. 45 | This is passed to :meth:`discord.ext.commands.Bot.wait_for `. 46 | There should be no reason to override this function for most users. 47 | Parameters 48 | ------------ 49 | payload: :class:`discord.RawReactionActionEvent` 50 | The payload to check. 51 | Returns 52 | --------- 53 | :class:`bool` 54 | Whether the payload should be processed. 55 | """ 56 | if payload.message_id != self.message.id: 57 | return False 58 | if payload.user_id not in (*self.bot.owner_ids, self._author_id): 59 | return False 60 | 61 | return payload.emoji in self.buttons 62 | 63 | def _skip_single_arrows(self): 64 | max_pages = self._source.get_max_pages() 65 | if max_pages is None: 66 | return True 67 | return max_pages == 1 68 | 69 | def _skip_double_triangle_buttons(self): 70 | max_pages = self._source.get_max_pages() 71 | if max_pages is None: 72 | return True 73 | return max_pages <= 2 74 | 75 | # left 76 | @menus.button( 77 | "\N{BLACK LEFT-POINTING TRIANGLE}", position=menus.First(1), skip_if=_skip_single_arrows 78 | ) 79 | async def prev(self, payload: discord.RawReactionActionEvent): 80 | if self.current_page == 0: 81 | await self.show_page(self._source.get_max_pages() - 1) 82 | else: 83 | await self.show_checked_page(self.current_page - 1) 84 | 85 | @menus.button("\N{CROSS MARK}", position=menus.First(2)) 86 | async def stop_pages_default(self, payload: discord.RawReactionActionEvent) -> None: 87 | self.stop() 88 | with contextlib.suppress(discord.NotFound): 89 | await self.message.delete() 90 | 91 | @menus.button( 92 | "\N{BLACK RIGHT-POINTING TRIANGLE}", position=menus.First(2), skip_if=_skip_single_arrows 93 | ) 94 | async def next(self, payload: discord.RawReactionActionEvent): 95 | if self.current_page == self._source.get_max_pages() - 1: 96 | await self.show_page(0) 97 | else: 98 | await self.show_checked_page(self.current_page + 1) 99 | 100 | @menus.button( 101 | "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f", 102 | position=menus.First(0), 103 | skip_if=_skip_double_triangle_buttons, 104 | ) 105 | async def go_to_first_page(self, payload: discord.RawReactionActionEvent): 106 | """go to the first page""" 107 | await self.show_page(0) 108 | 109 | @menus.button( 110 | "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f", 111 | position=menus.Last(1), 112 | skip_if=_skip_double_triangle_buttons, 113 | ) 114 | async def go_to_last_page(self, payload: discord.RawReactionActionEvent): 115 | """go to the last page""" 116 | # The call here is safe because it's guarded by skip_if 117 | await self.show_page(self._source.get_max_pages() - 1) 118 | 119 | 120 | class ArticleFormat(menus.ListPageSource): 121 | def __init__(self, entries: Iterable[str]): 122 | super().__init__(entries, per_page=1) 123 | 124 | async def format_page(self, menu: GenericMenu, data) -> str: 125 | embed = discord.Embed( 126 | title=data["title"], 127 | color=await menu.ctx.embed_colour(), 128 | description=f"[Click Here for Full data]({data['url']})\n\n{data['description']}", 129 | timestamp=datetime.datetime.fromisoformat(data["publishedAt"].replace("Z", "")), 130 | ) 131 | if data["urlToImage"] is not None and validators.url(data["urlToImage"]): 132 | embed.set_image(url=data["urlToImage"]) 133 | embed.set_author(name=f"{data['author']} - {data['source']['name']}") 134 | embed.set_footer(text=f"Article {menu.current_page + 1 }/{menu._source.get_max_pages()}") 135 | return embed 136 | 137 | 138 | class CovidMenu(menus.ListPageSource): 139 | def __init__(self, entries: Iterable[str]): 140 | super().__init__(entries, per_page=1) 141 | 142 | async def format_page(self, menu: GenericMenu, country) -> str: 143 | embed = discord.Embed( 144 | color=await menu.ctx.embed_colour(), 145 | title="Covid-19 | {} Statistics".format(country["country"]), 146 | timestamp=datetime.datetime.utcfromtimestamp(country["updated"] / 1000), 147 | ) 148 | embed.set_thumbnail(url=country["countryInfo"]["flag"]) 149 | embed.add_field(name="Cases", value=humanize_number(country["cases"])) 150 | embed.add_field(name="Deaths", value=humanize_number(country["deaths"])) 151 | embed.add_field(name="Recovered", value=humanize_number(country["recovered"])) 152 | embed.add_field(name=f"Cases {menu.type}", value=humanize_number(country["todayCases"])) 153 | embed.add_field(name=f"Deaths {menu.type}", value=humanize_number(country["todayDeaths"])) 154 | embed.add_field( 155 | name=f"Recovered {menu.type}", value=humanize_number(country["todayRecovered"]) 156 | ) 157 | embed.add_field(name="Critical", value=humanize_number(country["critical"])) 158 | embed.add_field(name="Active", value=humanize_number(country["active"])) 159 | embed.add_field(name="Total Tests", value=humanize_number(country["tests"])) 160 | embed.set_footer(text=f"Page {menu.current_page + 1 }/{menu._source.get_max_pages()}") 161 | return embed 162 | 163 | 164 | class CovidStateMenu(menus.ListPageSource): 165 | def __init__(self, entries: Iterable[str]): 166 | super().__init__(entries, per_page=1) 167 | 168 | async def format_page(self, menu: GenericMenu, state) -> str: 169 | embed = discord.Embed( 170 | color=await menu.ctx.embed_colour(), 171 | title="Covid-19 | USA | {} Statistics".format(state["state"]), 172 | ) 173 | embed.add_field(name="Cases", value=humanize_number(state["cases"])) 174 | embed.add_field(name="Deaths", value=humanize_number(state["deaths"])) 175 | embed.add_field(name=f"Cases {menu.type}", value=humanize_number(state["todayCases"])) 176 | embed.add_field(name=f"Deaths {menu.type}", value=humanize_number(state["todayDeaths"])) 177 | embed.add_field(name=f"Active {menu.type}", value=humanize_number(state["active"])) 178 | embed.add_field(name="Total Tests", value=humanize_number(state["tests"])) 179 | embed.set_footer(text=f"Page {menu.current_page + 1 }/{menu._source.get_max_pages()}") 180 | return embed 181 | -------------------------------------------------------------------------------- /tips/tips.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import functools 3 | import logging 4 | import random 5 | 6 | import discord 7 | from redbot.core import commands 8 | from redbot.core.config import Config 9 | from redbot.core.utils.menus import DEFAULT_CONTROLS, menu 10 | 11 | log = logging.getLogger("red.flare.tips") 12 | 13 | real_send = commands.Context.send 14 | 15 | 16 | def task_done_callback(task: asyncio.Task): 17 | """Properly handle and log errors in the startup task.""" 18 | try: 19 | task.result() 20 | except asyncio.CancelledError: 21 | pass 22 | except Exception as error: 23 | log.exception("Failed to initialize the cog.", exc_info=error) 24 | 25 | 26 | def chunks(l, n): 27 | """Yield successive n-sized chunks from l.""" 28 | for i in range(0, len(l), n): 29 | yield l[i : i + n] 30 | 31 | 32 | # from: https://docs.python.org/3/library/stdtypes.html#str.format_map 33 | class Default(dict): 34 | """Used with str.format_map to avoid KeyErrors on bad tips.""" 35 | 36 | def __missing__(self, key): 37 | # returns missing keys as '{key}' 38 | return f"{{{key}}}" 39 | 40 | 41 | # Thanks Jack for smileysend 42 | @functools.wraps(real_send) 43 | async def send(self, content=None, **kwargs): 44 | content = str(content) if content is not None else None 45 | cog = self.bot.get_cog("Tips") 46 | if (cog).usercache.get(self.author.id, {}).get("toggle", True) and random.randint( 47 | 1, cog.chance 48 | ) == 1: 49 | tips = cog.message_cache or ["No tips configured."] 50 | tip_msg = random.choice(tips).replace("{prefix}", self.clean_prefix) 51 | new_content = cog.tip_format.format_map( 52 | Default( 53 | content=content or "", 54 | tip_msg=tip_msg, 55 | prefix=self.clean_prefix, 56 | ) 57 | ) 58 | 59 | if len(new_content) <= 2000: 60 | content = new_content 61 | return await real_send(self, content, **kwargs) 62 | 63 | 64 | class Tips(commands.Cog): 65 | """Tips - Credit to Jackenmen""" 66 | 67 | __version__ = "0.0.3" 68 | 69 | def format_help_for_context(self, ctx): 70 | pre_processed = super().format_help_for_context(ctx) 71 | return f"{pre_processed}\nCog Version: {self.__version__}" 72 | 73 | def __init__(self, bot) -> None: 74 | self.bot = bot 75 | self.config = Config.get_conf(self, 176070082584248320, force_registration=True) 76 | self.config.register_global( 77 | tips=["Add tips by using `{prefix}tips add-tip`."], 78 | chance=50, 79 | tip_format="{tip_msg}\nYou can turn these tips off by typing `{prefix}tips off`\n\n{content}", 80 | ) 81 | self.config.register_user(toggle=True) 82 | 83 | setattr(commands.Context, "send", send) 84 | self.message_cache = [] 85 | self.chance = 50 86 | self.tip_format = "" 87 | self.usercache = {} 88 | 89 | self.initialize_task = asyncio.create_task(self.initialize()) 90 | self.initialize_task.add_done_callback(task_done_callback) 91 | 92 | async def initialize(self) -> None: 93 | await self.generate_cache() 94 | 95 | async def generate_cache(self): 96 | data = await self.config.all() 97 | self.message_cache = data["tips"] 98 | self.chance = data["chance"] 99 | self.tip_format = data["tip_format"] 100 | self.usercache = await self.config.all_users() 101 | 102 | def cog_unload(self) -> None: 103 | setattr(commands.Context, "send", real_send) 104 | 105 | @commands.group(invoke_without_command=True) 106 | async def tips(self, ctx: commands.Context, toggle: bool) -> None: 107 | """ 108 | Toggle and setup tips. 109 | 110 | Run `[p]tips off` to disable tips. 111 | """ 112 | await self.config.user(ctx.author).toggle.set(toggle) 113 | await ctx.tick() 114 | await self.generate_cache() 115 | 116 | @commands.is_owner() 117 | @tips.command() 118 | async def chance(self, ctx, chance: int): 119 | """ 120 | Chance for a tip to show. 121 | 122 | Default is 50 123 | """ 124 | if chance <= 1: 125 | return await ctx.send("Chance must be greater than 1") 126 | await self.config.chance.set(chance) 127 | await self.generate_cache() 128 | await ctx.tick() 129 | 130 | @commands.is_owner() 131 | @tips.command(name="add-tip", aliases=["add", "addtip", "create"]) 132 | async def add_tip(self, ctx, *, tip: str): 133 | """ 134 | Add a tip message. 135 | 136 | Append `{prefix}` to have it formatted with prefix on send. 137 | """ 138 | async with self.config.tips() as replies: 139 | if tip in replies: 140 | return await ctx.send("That is already a response.") 141 | replies.append(tip) 142 | ind = replies.index(tip) 143 | await ctx.send(f"Your tip has been added and is tip ID #{ind}") 144 | await self.generate_cache() 145 | 146 | @commands.is_owner() 147 | @tips.command(name="del-tip", aliases=["del", "deltip", "delete"]) 148 | async def del_tips(self, ctx, *, id: int): 149 | """Delete a custom tip.""" 150 | async with self.config.tips() as replies: 151 | if not replies: 152 | return await ctx.send("No custom tips are configured.") 153 | if id > len(replies): 154 | return await ctx.send("Invalid ID.") 155 | replies.pop(id) 156 | await ctx.send("Your tip has been removed") 157 | await self.generate_cache() 158 | 159 | @commands.is_owner() 160 | @tips.command(name="list-tips", aliases=["list", "listtips"]) 161 | async def list_tips( 162 | self, 163 | ctx, 164 | ): 165 | """List custom tips.""" 166 | async with self.config.tips() as replies: 167 | if not replies: 168 | return await ctx.send("No tips have been configured.") 169 | a = chunks(replies, 10) 170 | embeds = [] 171 | i = 0 172 | for item in a: 173 | items = [] 174 | for strings in item: 175 | items.append(f"**Reply {i}**: {strings}") 176 | i += 1 177 | embed = discord.Embed( 178 | colour=await self.bot.get_embed_colour(ctx.channel), 179 | description="\n".join(items), 180 | ) 181 | embeds.append(embed) 182 | if len(embeds) == 1: 183 | await ctx.send(embed=embeds[0]) 184 | else: 185 | await menu(ctx, embeds, DEFAULT_CONTROLS) 186 | 187 | @commands.is_owner() 188 | @tips.command(name="format") 189 | async def format( 190 | self, 191 | ctx: commands.Context, 192 | *, 193 | formatting=None, 194 | ): 195 | """ 196 | Set the format for tip messages. 197 | 198 | Variables: 199 | `tip_msg` - the tip 200 | `content` - the original message content 201 | `prefix` - the invocation prefix 202 | 203 | Default value: 204 | `{tip_msg}\\nYou can turn these tips off by typing `{prefix}tips off`\\n\\n{content}` 205 | """ 206 | if formatting: 207 | await self.config.tip_format.set(formatting) 208 | await ctx.channel.send( 209 | f"The tip format has been set to:\n{formatting}" 210 | ) # intentionally uses ctx.channel to avoid tips being triggered 211 | else: 212 | await self.config.tip_format.clear() 213 | await ctx.channel.send("The tip format has been reset to the default.") 214 | await self.generate_cache() 215 | content = "This is example content of a message with a tip." 216 | tips = self.message_cache or ["No tips configured."] 217 | tip_msg = random.choice(tips).format(prefix=ctx.clean_prefix) 218 | await ctx.channel.send( 219 | self.tip_format.format(content=content, tip_msg=tip_msg, prefix=ctx.clean_prefix) 220 | ) 221 | -------------------------------------------------------------------------------- /forward/forward.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from redbot.core import Config, checks, commands 3 | from redbot.core.utils.chat_formatting import humanize_list 4 | 5 | 6 | class Forward(commands.Cog): 7 | """Forward messages sent to the bot to the bot owner or in a specified channel.""" 8 | 9 | __version__ = "1.2.9" 10 | 11 | def format_help_for_context(self, ctx): 12 | pre_processed = super().format_help_for_context(ctx) 13 | return f"{pre_processed}\nCog Version: {self.__version__}" 14 | 15 | def __init__(self, bot): 16 | self.bot = bot 17 | 18 | self.config = Config.get_conf(self, 1398467138476, force_registration=True) 19 | default_global = {"toggles": {"botmessages": False}, "destination": None, "blacklist": []} 20 | self.config.register_global(**default_global) 21 | 22 | async def red_get_data_for_user(self, *, user_id: int): 23 | # this cog does not story any data 24 | return {} 25 | 26 | async def red_delete_data_for_user(self, *, requester, user_id: int) -> None: 27 | # this cog does not story any data 28 | pass 29 | 30 | async def _destination(self, msg: str = None, embed: discord.Embed = None): 31 | await self.bot.wait_until_ready() 32 | channel = await self.config.destination() 33 | channel = self.bot.get_channel(channel) 34 | if channel is None: 35 | await self.bot.send_to_owners(msg, embed=embed) 36 | else: 37 | await channel.send(msg, embed=embed) 38 | 39 | @staticmethod 40 | def _append_attachements(message: discord.Message, embeds: list): 41 | attachments_urls = [] 42 | for attachment in message.attachments: 43 | if any(attachment.filename.endswith(imageext) for imageext in ["jpg", "png", "gif"]): 44 | if embeds[0].image: 45 | embed = discord.Embed() 46 | embed.set_image(url=attachment.url) 47 | embeds.append(embed) 48 | else: 49 | embeds[0].set_image(url=attachment.url) 50 | else: 51 | attachments_urls.append(f"[{attachment.filename}]({attachment.url})") 52 | if attachments_urls: 53 | embeds[0].add_field(name="Attachments", value="\n".join(attachments_urls)) 54 | return embeds 55 | 56 | @commands.Cog.listener() 57 | async def on_message_without_command(self, message): 58 | if message.guild is not None: 59 | return 60 | recipient = message.channel.recipient 61 | if recipient is None: 62 | chan = self.bot.get_channel(message.channel.id) 63 | if chan is None: 64 | chan = await self.bot.fetch_channel(message.channel.id) 65 | if not isinstance(chan, discord.DMChannel): 66 | return 67 | recipient = chan.recipient 68 | if recipient.id in self.bot.owner_ids: 69 | return 70 | if not await self.bot.allowed_by_whitelist_blacklist(message.author): 71 | return 72 | if message.author.id in await self.config.blacklist(): 73 | return 74 | msg = "" 75 | if message.author == self.bot.user: 76 | async with self.config.toggles() as toggle: 77 | if not toggle["botmessages"]: 78 | return 79 | msg = f"Sent PM to {recipient} (`{recipient.id}`)" 80 | if message.embeds: 81 | msg += f"\n**Message Content**: {message.content}" 82 | embeds = [ 83 | discord.Embed.from_dict( 84 | {**message.embeds[0].to_dict(), "timestamp": str(message.created_at)} 85 | ) 86 | ] 87 | else: 88 | embeds = [discord.Embed(description=message.content)] 89 | embeds[0].set_author( 90 | name=f"{message.author} | {message.author.id}", 91 | icon_url=message.author.display_avatar, 92 | ) 93 | embeds = self._append_attachements(message, embeds) 94 | embeds[-1].timestamp = message.created_at 95 | else: 96 | embeds = [discord.Embed(description=message.content)] 97 | embeds[0].set_author( 98 | name=f"{message.author} | {message.author.id}", 99 | icon_url=message.author.display_avatar.url, 100 | ) 101 | embeds = self._append_attachements(message, embeds) 102 | embeds[-1].timestamp = message.created_at 103 | for embed in embeds: 104 | await self._destination(msg=msg, embed=embed) 105 | 106 | @checks.is_owner() 107 | @commands.group() 108 | async def forwardset(self, ctx): 109 | """Forwarding commands.""" 110 | 111 | @forwardset.command(aliases=["botmessage"]) 112 | async def botmsg(self, ctx, type: bool = None): 113 | """Set whether to send notifications when the bot sends a message. 114 | 115 | Type must be a valid bool. 116 | """ 117 | async with self.config.toggles() as toggles: 118 | if type is None: 119 | type = not toggles.get("botmessages") 120 | if type: 121 | toggles["botmessages"] = True 122 | await ctx.send("Bot message notifications have been enabled.") 123 | else: 124 | toggles["botmessages"] = False 125 | await ctx.send("Bot message notifications have been disabled.") 126 | 127 | @forwardset.command() 128 | async def channel(self, ctx, channel: discord.TextChannel = None): 129 | """Set if you want to receive notifications in a channel instead of your DMs. 130 | 131 | Leave blank if you want to set back to your DMs. 132 | """ 133 | data = ( 134 | {"msg": "Notifications will be sent in your DMs.", "config": None} 135 | if channel is None 136 | else {"msg": f"Notifications will be sent in {channel.mention}.", "config": channel.id} 137 | ) 138 | await self.config.destination.set(data["config"]) 139 | await ctx.send(data["msg"]) 140 | 141 | @forwardset.command(aliases=["bl"]) 142 | async def blacklist(self, ctx: commands.Context, user_id: int = None): 143 | """Blacklist receiving messages from a user.""" 144 | if not user_id: 145 | e = discord.Embed( 146 | color=await ctx.embed_color(), 147 | title="Forward Blacklist", 148 | description=humanize_list(await self.config.blacklist()), 149 | ) 150 | await ctx.send(embed=e) 151 | else: 152 | if user_id in await self.config.blacklist(): 153 | await ctx.send("This user is already blacklisted.") 154 | return 155 | async with self.config.blacklist() as b: 156 | b.append(user_id) 157 | await ctx.tick() 158 | 159 | @forwardset.command(aliases=["unbl"]) 160 | async def unblacklist(self, ctx: commands.Context, user_id: int): 161 | """Remove a user from the blacklist.""" 162 | if user_id not in await self.config.blacklist(): 163 | await ctx.send("This user is not in the blacklist.") 164 | return 165 | async with self.config.blacklist() as b: 166 | index = b.index(user_id) 167 | b.pop(index) 168 | await ctx.tick() 169 | 170 | @commands.command() 171 | @commands.guild_only() 172 | @checks.guildowner() 173 | async def pm(self, ctx, user: discord.Member, *, message: str): 174 | """PMs a person. 175 | 176 | Separate version of [p]dm but allows for guild owners. This only works for users in the 177 | guild. 178 | """ 179 | em = discord.Embed(colour=discord.Colour.red(), description=message) 180 | 181 | if ctx.bot.user.display_avatar: 182 | em.set_author( 183 | name=f"Message from {ctx.author} | {ctx.author.id}", 184 | icon_url=ctx.bot.user.display_avatar, 185 | ) 186 | else: 187 | em.set_author(name=f"Message from {ctx.author} | {ctx.author.id}") 188 | 189 | try: 190 | await user.send(embed=em) 191 | except discord.Forbidden: 192 | await ctx.send( 193 | "Oops. I couldn't deliver your message to {}. They most likely have me blocked or DMs closed!" 194 | ) 195 | await ctx.send(f"Message delivered to {user}") 196 | -------------------------------------------------------------------------------- /unbelievaboat/wallet.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | import discord 4 | from redbot.core import bank, commands 5 | from redbot.core.errors import BalanceTooHigh 6 | from redbot.core.utils.chat_formatting import box, humanize_number 7 | from redbot.core.utils.menus import DEFAULT_CONTROLS, menu 8 | 9 | from .abc import MixinMeta 10 | from .checks import check_global_setting_admin, roulette_disabled_check, wallet_disabled_check 11 | 12 | 13 | class Wallet(MixinMeta): 14 | """Wallet Commands.""" 15 | 16 | async def walletdisabledcheck(self, ctx): 17 | if await bank.is_global(): 18 | return not await self.config.disable_wallet() 19 | return not await self.config.guild(ctx.guild).disable_wallet() 20 | 21 | async def walletdeposit(self, ctx, user, amount): 22 | conf = await self.configglobalcheckuser(user) 23 | main_conf = await self.configglobalcheck(ctx) 24 | wallet = await conf.wallet() 25 | max_bal = await main_conf.wallet_max() 26 | amount = wallet + amount 27 | if amount <= max_bal: 28 | await conf.wallet.set(amount) 29 | else: 30 | await conf.wallet.set(max_bal) 31 | raise ValueError 32 | 33 | async def walletremove(self, user, amount): 34 | conf = await self.configglobalcheckuser(user) 35 | wallet = await conf.wallet() 36 | if amount < wallet: 37 | await conf.wallet.set(wallet - amount) 38 | else: 39 | await conf.wallet.set(0) 40 | 41 | async def walletwithdraw(self, user, amount): 42 | conf = await self.configglobalcheckuser(user) 43 | wallet = await conf.wallet() 44 | if amount < wallet: 45 | await conf.wallet.set(wallet - amount) 46 | else: 47 | raise ValueError 48 | 49 | async def walletset(self, user, amount): 50 | conf = await self.configglobalcheckuser(user) 51 | await conf.wallet.set(amount) 52 | 53 | async def bankdeposit(self, ctx, user, amount): 54 | conf = await self.configglobalcheckuser(user) 55 | wallet = await conf.wallet() 56 | deposit = abs(amount) 57 | if deposit > wallet: 58 | return await ctx.send("You have insufficent funds to complete this deposit.") 59 | try: 60 | await bank.deposit_credits(user, deposit) 61 | msg = f"You have succesfully deposited {deposit} {await bank.get_currency_name(ctx.guild)} into your bank account." 62 | except BalanceTooHigh as e: 63 | deposit = e.max_balance - await bank.get_balance(user) 64 | await bank.deposit_credits(user, deposit) 65 | msg = f"Your transaction was limited to {deposit} {e.currency_name} as your bank account has reached the max balance." 66 | await self.walletset(user, wallet - deposit) 67 | return await ctx.send(msg) 68 | 69 | async def walletbalance(self, user): 70 | conf = await self.configglobalcheckuser(user) 71 | return await conf.wallet() 72 | 73 | async def bankwithdraw(self, ctx, user, amount): 74 | conf = await self.configglobalcheckuser(user) 75 | mainconf = await self.configglobalcheck(ctx) 76 | max_bal = await mainconf.wallet_max() 77 | wallet = await conf.wallet() 78 | try: 79 | if wallet + amount > max_bal: 80 | return await ctx.send( 81 | f"You have attempted to withdraw more cash than the maximum balance allows. The maximum balance is {humanize_number(max_bal)} {await bank.get_currency_name(ctx.guild)}." 82 | ) 83 | await bank.withdraw_credits(user, amount) 84 | await self.walletset(user, wallet + amount) 85 | return await ctx.send( 86 | f"You have succesfully withdrawn {humanize_number(amount)} {await bank.get_currency_name(ctx.guild)} from your bank account." 87 | ) 88 | except ValueError: 89 | return await ctx.send("You have insufficent funds to complete this withdrawal.") 90 | 91 | @commands.group() 92 | @wallet_disabled_check() 93 | @commands.guild_only() 94 | async def wallet(self, ctx): 95 | """Wallet commands.""" 96 | 97 | @wallet.command() 98 | @commands.guild_only() 99 | async def balance(self, ctx, user: discord.Member = None): 100 | """Show the user's wallet balance. 101 | 102 | Defaults to yours. 103 | """ 104 | if user is None: 105 | user = ctx.author 106 | balance = await self.walletbalance(user) 107 | currency = await bank.get_currency_name(ctx.guild) 108 | await ctx.send( 109 | f"{user.display_name}'s wallet balance is {humanize_number(balance)} {currency}" 110 | ) 111 | 112 | @wallet.command() 113 | @commands.guild_only() 114 | async def leaderboard(self, ctx, top: int = 10): 115 | """Print the wallet leaderboard.""" 116 | if top < 1: 117 | top = 10 118 | guild = ctx.guild 119 | if await bank.is_global(): 120 | raw_accounts = await self.config.all_users() 121 | if guild is not None: 122 | tmp = raw_accounts.copy() 123 | for acc in tmp: 124 | if not guild.get_member(acc): 125 | del raw_accounts[acc] 126 | else: 127 | raw_accounts = await self.config.all_members(guild) 128 | walletlist = sorted(raw_accounts.items(), key=lambda x: x[1]["wallet"], reverse=True)[:top] 129 | try: 130 | bal_len = len(str(walletlist[0][1]["wallet"])) 131 | 132 | except IndexError: 133 | return await ctx.send("There are no users with a wallet balance.") 134 | pound_len = len(str(len(walletlist))) 135 | header = "{pound:{pound_len}}{score:{bal_len}}{name:2}\n".format( 136 | pound="#", name="Name", score="Score", bal_len=bal_len + 6, pound_len=pound_len + 3 137 | ) 138 | highscores = [] 139 | pos = 1 140 | temp_msg = header 141 | for acc in walletlist: 142 | try: 143 | name = guild.get_member(acc[0]).display_name 144 | except AttributeError: 145 | user_id = f"({acc[0]})" if await ctx.bot.is_owner(ctx.author) else "" 146 | name = f"{user_id}" 147 | balance = acc[1]["wallet"] 148 | 149 | if acc[0] != ctx.author.id: 150 | temp_msg += f"{pos}. {balance: <{bal_len + 5}} {name}\n" 151 | 152 | else: 153 | temp_msg += f"{pos}. {balance: <{bal_len + 5}} <<{ctx.author.display_name}>>\n" 154 | if pos % 10 == 0: 155 | highscores.append(box(temp_msg, lang="md")) 156 | temp_msg = header 157 | pos += 1 158 | 159 | if temp_msg != header: 160 | highscores.append(box(temp_msg, lang="md")) 161 | 162 | if highscores: 163 | await menu(ctx, highscores, DEFAULT_CONTROLS) 164 | 165 | @wallet_disabled_check() 166 | @check_global_setting_admin() 167 | @commands.guild_only() 168 | @wallet.command(name="set") 169 | async def _walletset(self, ctx, user: discord.Member, amount: int): 170 | """Set a users wallet balance.""" 171 | conf = await self.configglobalcheck(ctx) 172 | maxw = await conf.wallet_max() 173 | if amount > maxw: 174 | return await ctx.send( 175 | f"{user.display_name}'s wallet balance cannot rise above {humanize_number(maxw)} {await bank.get_currency_name(ctx.guild)}." 176 | ) 177 | await self.walletset(user, amount) 178 | await ctx.send( 179 | f"{ctx.author.display_name} has set {user.display_name}'s wallet balance to {humanize_number(amount)} {await bank.get_currency_name(ctx.guild)}." 180 | ) 181 | 182 | @commands.command() 183 | @wallet_disabled_check() 184 | @commands.guild_only() 185 | @commands.cooldown(1, 5, commands.BucketType.user) 186 | async def deposit(self, ctx, amount: Union[int, str]): 187 | """Deposit cash from your wallet to your bank.""" 188 | cdcheck = await self.cdcheck(ctx, "depositcd") 189 | if isinstance(cdcheck, tuple): 190 | embed = await self.cdnotice(ctx.author, cdcheck[1], "deposit") 191 | return await ctx.send(embed=embed) 192 | if isinstance(amount, str): 193 | if amount != "all": 194 | return await ctx.send("You must provide a valid number or the string `all`.") 195 | amount = await self.walletbalance(ctx.author) 196 | await self.bankdeposit(ctx, ctx.author, amount) 197 | 198 | @commands.command() 199 | @wallet_disabled_check() 200 | @commands.guild_only() 201 | @commands.cooldown(1, 5, commands.BucketType.user) 202 | async def withdraw(self, ctx, amount: int): 203 | """Withdraw cash from your bank to your wallet.""" 204 | cdcheck = await self.cdcheck(ctx, "withdrawcd") 205 | if isinstance(cdcheck, tuple): 206 | embed = await self.cdnotice(ctx.author, cdcheck[1], "withdraw") 207 | return await ctx.send(embed=embed) 208 | await self.bankwithdraw(ctx, ctx.author, amount) 209 | -------------------------------------------------------------------------------- /voicetracker/voicetracker.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | from collections import defaultdict 4 | from datetime import datetime, timezone 5 | from math import ceil 6 | 7 | import discord 8 | from redbot.core import Config, commands 9 | from redbot.core.utils.chat_formatting import humanize_timedelta, pagify 10 | from redbot.core.utils.menus import DEFAULT_CONTROLS, menu 11 | 12 | log = logging.getLogger("red.flare.voicetracker") 13 | 14 | 15 | class VoiceTracker(commands.Cog): 16 | __version__ = "0.0.2" 17 | __author__ = "flare#0001" 18 | 19 | def format_help_for_context(self, ctx): 20 | """Thanks Sinbad.""" 21 | pre_processed = super().format_help_for_context(ctx) 22 | return f"{pre_processed}\nCog Version: {self.__version__}\nCog Author: {self.__author__}" 23 | 24 | def __init__(self, bot): 25 | self.bot = bot 26 | self.tracking = {} 27 | self.config = Config.get_conf(self, identifier=95932766180343808) 28 | default_guild = {"voice_channels": {}, "enabled": False, "tracking": []} 29 | default_member = {"enabled": False} 30 | self.config.register_guild(**default_guild) 31 | self.config.register_member(**default_member) 32 | self.config_guild_cache = {} 33 | self.config_member_cache = {} 34 | self.update_cache_dict = defaultdict(dict) 35 | self.bg_loop_task = asyncio.create_task(self.init()) 36 | 37 | async def init(self): 38 | await self.bot.wait_until_ready() 39 | await self.update_cache() 40 | while True: 41 | try: 42 | await self.save_config() 43 | await asyncio.sleep(60) 44 | except Exception as exc: 45 | log.error("Exception in bg_loop: ", exc_info=exc) 46 | self.bg_loop_task.cancel() 47 | 48 | def cog_unload(self): 49 | asyncio.create_task(self.save_config()) 50 | self.bg_loop_task.cancel() 51 | 52 | async def update_cache(self): 53 | self.config_guild_cache = await self.config.all_guilds() 54 | self.config_member_cache = await self.config.all_members() 55 | 56 | async def save_config(self): 57 | for server, data in self.update_cache_dict.items(): 58 | async with self.config.guild_from_id(server).voice_channels() as voice_channels: 59 | for channel in data: 60 | if channel not in voice_channels: 61 | voice_channels[channel] = data[channel] 62 | else: 63 | for user, time in data[channel].items(): 64 | if user in voice_channels[channel]: 65 | voice_channels[channel][user] += time 66 | else: 67 | voice_channels[channel] = {user: time} 68 | self.update_cache_dict[server][channel] = {} 69 | 70 | @commands.Cog.listener() 71 | async def on_voice_state_update(self, member, before, after): 72 | channel = after.channel or before.channel 73 | if ( 74 | self.config_guild_cache.get(member.guild.id, {}).get("enabled", False) is False 75 | or channel.id 76 | not in self.config_guild_cache.get(member.guild.id, {}).get("tracking", []) 77 | ) and ( 78 | self.config_member_cache.get(member.guild.id, {}) 79 | .get(member.id, {}) 80 | .get("enabled", False) 81 | is False 82 | ): 83 | return 84 | if before.channel is None and after.channel is not None: 85 | self.tracking[member.id] = { 86 | "channel": after.channel.id, 87 | "time": datetime.now(tz=timezone.utc), 88 | } 89 | elif before.channel is not None and after.channel is None: 90 | if member.id in self.tracking: 91 | time = datetime.now(tz=timezone.utc) - self.tracking[member.id]["time"] 92 | seconds = time.total_seconds() 93 | if ( 94 | self.update_cache_dict[member.guild.id] 95 | .get(before.channel.id, {}) 96 | .get(member.id) 97 | is None 98 | ): 99 | self.update_cache_dict[member.guild.id] = { 100 | before.channel.id: {member.id: seconds} 101 | } 102 | else: 103 | self.update_cache_dict[member.guild.id][before.channel.id][ 104 | member.id 105 | ] += seconds 106 | del self.tracking[member.id] 107 | elif before.channel is not None: 108 | if member.id in self.tracking: 109 | time = datetime.now(tz=timezone.utc) - self.tracking[member.id]["time"] 110 | seconds = time.total_seconds() 111 | if ( 112 | self.update_cache_dict[member.guild.id] 113 | .get(before.channel.id, {}) 114 | .get(member.id) 115 | is None 116 | ): 117 | self.update_cache_dict[member.guild.id] = { 118 | before.channel.id: {member.id: seconds} 119 | } 120 | else: 121 | self.update_cache_dict[member.guild.id][before.channel.id][ 122 | member.id 123 | ] += seconds 124 | del self.tracking[member.id] 125 | self.tracking[member.id] = { 126 | "channel": after.channel.id, 127 | "time": datetime.now(tz=timezone.utc), 128 | } 129 | 130 | @commands.group(aliases=["vc"]) 131 | @commands.guild_only() 132 | async def voicetracker(self, ctx): 133 | """Voice Tracker""" 134 | 135 | @voicetracker.command(name="toggle") 136 | async def vc_toggle(self, ctx): 137 | """Enable/Disable Voice Tracker""" 138 | value = not await self.config.member(ctx.author).enabled() 139 | if value: 140 | await ctx.send("You have enabled your voice tracker.") 141 | await self.config.member(ctx.author).enabled.set(True) 142 | else: 143 | await ctx.send("You have disabled your voice tracker.") 144 | await self.config.member(ctx.author).enabled.set(False) 145 | await self.update_cache() 146 | 147 | @commands.group() 148 | @commands.guild_only() 149 | @commands.admin_or_permissions(manage_guild=True) 150 | async def vcset(self, ctx): 151 | """Voice Tracker Settings""" 152 | 153 | @vcset.command(name="toggle") 154 | async def vcset_toggle(self, ctx): 155 | """Enable/Disable Server Voice Tracker""" 156 | value = not await self.config.guild(ctx.guild).enabled() 157 | if value: 158 | await ctx.send("You have enabled your voice tracker for this server.") 159 | await self.config.guild(ctx.guild).enabled.set(True) 160 | else: 161 | await ctx.send("You have disabled the voice tracker for this server.") 162 | await self.config.guild(ctx.guild).enabled.set(False) 163 | await self.update_cache() 164 | 165 | @vcset.command(name="track", aliases=["add"]) 166 | async def vcset_track(self, ctx, *, channel: discord.VoiceChannel): 167 | """Add a voice channel to track""" 168 | async with self.config.guild(ctx.guild).tracking() as voice_channels: 169 | if channel.id in voice_channels: 170 | await ctx.send(f"Removed {channel.mention} from tracking stats.") 171 | voice_channels.remove(channel.id) 172 | else: 173 | await ctx.send(f"Added {channel.mention} to tracking stats.") 174 | voice_channels.append(channel.id) 175 | await self.update_cache() 176 | 177 | @voicetracker.command(name="stats") 178 | async def vc_stats(self, ctx, user: discord.Member = None): 179 | """Voice Tracker Stats""" 180 | user = user or ctx.author 181 | await self.save_config() 182 | data = await self.config.guild(ctx.guild).voice_channels() 183 | 184 | msg = "" 185 | userid = str(user.id) 186 | for channel in data: 187 | if userid in data[channel]: 188 | channel_obj = ctx.guild.get_channel(int(channel)) 189 | msg += f"{channel_obj.mention if channel_obj else 'Deleted Channel'} - {humanize_timedelta(seconds=ceil(data[channel][userid]))}\n" 190 | if msg == "": 191 | msg = f"No data found. Ensure you've toggled tracking on via `{ctx.prefix}vc toggle`." 192 | lst = list(pagify(msg, delims=["\n"])) 193 | embeds = [] 194 | for i, page in enumerate(lst): 195 | embed = discord.Embed( 196 | title=f"Voice Tracker Stats for {user.display_name}", 197 | description=page, 198 | colour=user.colour, 199 | ) 200 | embed.set_footer(text=f"Page {i+1}/{len(lst)}") 201 | embeds.append(embed) 202 | 203 | if len(embeds) == 1: 204 | await ctx.send(embed=embeds[0]) 205 | else: 206 | await menu(ctx, embeds, DEFAULT_CONTROLS) 207 | -------------------------------------------------------------------------------- /botlistspost/botlistspost.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import contextlib 3 | import json 4 | import logging 5 | from typing import Dict 6 | 7 | import aiohttp 8 | import discord 9 | from redbot.core import Config, commands 10 | from redbot.core.utils.chat_formatting import humanize_list, pagify 11 | from redbot.core.utils.menus import DEFAULT_CONTROLS, menu 12 | 13 | BOTBLOCK = "https://botblock.org/api" 14 | BSDC = "https://api.server-discord.com/v2/bots/{BOTID}/stats" 15 | 16 | log = logging.getLogger("red.flare.botlistpost") 17 | 18 | 19 | class BotListsPost(commands.Cog): 20 | """Post data to bot lists. For DBL use Predas cog""" 21 | 22 | __version__ = "0.0.6" 23 | 24 | def format_help_for_context(self, ctx): 25 | """Thanks Sinbad.""" 26 | pre_processed = super().format_help_for_context(ctx) 27 | return f"{pre_processed}\nCog Version: {self.__version__}" 28 | 29 | def __init__(self, bot): 30 | self.bot = bot 31 | self.config = Config.get_conf(self, identifier=95932766180343808, force_registration=True) 32 | self.config.register_global(lists={}, version=1) 33 | self._session = aiohttp.ClientSession() 34 | self.bsdctoken = None 35 | self.post_stats_task = self.bot.loop.create_task(self.post_stats()) 36 | 37 | async def red_get_data_for_user(self, *, user_id: int): 38 | # this cog does not story any data 39 | return {} 40 | 41 | async def red_delete_data_for_user(self, *, requester, user_id: int) -> None: 42 | # this cog does not story any data 43 | pass 44 | 45 | async def init(self): 46 | sd = await self.bot.get_shared_api_tokens("serverdiscord") 47 | self.bsdctoken = sd.get("authorization") 48 | if await self.config.version() < 2: 49 | await self.bot.send_to_owners( 50 | "Hi, the current cog has been redesigned to use bot block. If you dont wish to use this to manage more lists and save me time then feel free to uninstall. Current setup sites will no longer work (except server-discord) and will need to be readded." 51 | ) 52 | await self.config.version.set(2) 53 | 54 | @commands.Cog.listener() 55 | async def on_red_api_tokens_update(self, service_name, api_tokens): 56 | if service_name == "serverdiscord": 57 | self.bsdctoken = {"Authorization": api_tokens.get("authorization")} 58 | 59 | def cog_unload(self): 60 | self.bot.loop.create_task(self._session.close()) 61 | if self.post_stats_task: 62 | self.post_stats_task.cancel() 63 | 64 | async def post_stats(self): 65 | await self.bot.wait_until_ready() 66 | await self.init() 67 | botid = str(self.bot.user.id) 68 | while True: 69 | success = [] 70 | failed = [] 71 | serverc = len(self.bot.guilds) 72 | shardc = self.bot.shard_count 73 | conf = await self.config.lists() 74 | if conf: 75 | conf["server_count"] = serverc 76 | conf["shard_count"] = shardc 77 | conf["bot_id"] = botid 78 | 79 | async with self._session.post( 80 | BOTBLOCK + "/count", 81 | headers={"Content-Type": "application/json"}, 82 | data=json.dumps(conf), 83 | ) as r: 84 | if r.status == 200: 85 | data = await r.json() 86 | if data.get("success"): 87 | success = list(data["success"].keys()) 88 | if data.get("failure"): 89 | for _list in data["failure"]: 90 | failed.append(f"{_list} ({data['failure'][_list][0]})") 91 | else: 92 | print(await r.json()) 93 | failed.append(f"BotBlock ({r.status})") 94 | 95 | if self.bsdctoken is not None: 96 | async with self._session.post( 97 | BSDC.format(BOTID=botid), 98 | headers={"Authorization": f"SDC {self.bsdctoken}"}, 99 | data={"servers": serverc, "guilds": shardc}, 100 | ) as resp: 101 | resp = await resp.json() 102 | if resp.get("status"): 103 | success.append("Server-Discord bot list") 104 | else: 105 | failed.append(f"Server-Discord bot list ({resp.get('error')})") 106 | if failed: 107 | log.info(f"Unable to post data to {humanize_list(failed)}.") 108 | if success: 109 | log.info(f"Successfully posted servercount to {humanize_list(success)}.") 110 | await asyncio.sleep(1800) 111 | 112 | async def get_lists(self) -> Dict: 113 | async with self._session.get(BOTBLOCK + "/lists") as r: 114 | if r.status == 200: 115 | return await r.json() 116 | return {"error": r.status} 117 | 118 | @commands.is_owner() 119 | @commands.command() 120 | async def botlistpost(self, ctx): 121 | """Setup for botlistposting""" 122 | msg = ( 123 | "This cog currently supports every bot list on [BotBlock](https://botblock.org) along with" 124 | " [Server-Discord](https://docs.server-discord.com/)." 125 | "To set this cog up for Server-Discord, please use the following command:\n" 126 | f"`{ctx.clean_prefix}set api serverdiscord authorization `\n" 127 | f"Otherwise, you need to type `{ctx.clean_prefix}botlist add `\n" 128 | f"You can find a list of allowed lists via `{ctx.clean_prefix}botlist list`" 129 | ) 130 | await ctx.maybe_send_embed(msg) 131 | 132 | @commands.is_owner() 133 | @commands.group() 134 | async def botlist(self, ctx): 135 | """Bot list posting setup""" 136 | 137 | @botlist.command(name="list") 138 | async def _list(self, ctx): 139 | """Return all available botlists.""" 140 | lists = await self.get_lists() 141 | if lists.get("error"): 142 | return await ctx.send( 143 | f"An error occured retrieving the lists. Error Code: {lists['error']}" 144 | ) 145 | msg = "".join(f"[{_list}]({lists[_list]['url']})\n" for _list in lists) 146 | embeds = [] 147 | for page in pagify(msg, page_length=1024): 148 | embed = discord.Embed( 149 | title="Bot Lists", description=page, colour=await ctx.embed_colour() 150 | ) 151 | embeds.append(embed) 152 | await menu(ctx, embeds, DEFAULT_CONTROLS) 153 | 154 | @botlist.command(name="add") 155 | async def _add(self, ctx, site: str, *, token: str): 156 | """Add a botlist to post stats to.""" 157 | with contextlib.suppress(discord.NotFound): 158 | await ctx.message.delete() 159 | lists = await self.get_lists() 160 | if lists.get("error"): 161 | return await ctx.send( 162 | f"An error occured retrieving the lists. Error Code: {lists['error']}" 163 | ) 164 | if site not in lists: 165 | return await ctx.send( 166 | f"Your list doesn't appear to exist. Ensure its named after how it appears in `{ctx.clean_prefix}botlist list`" 167 | ) 168 | async with self.config.lists() as config: 169 | if site in config: 170 | config[site] = token 171 | await ctx.send(f"{site} token has been updated.") 172 | else: 173 | config[site] = token 174 | await ctx.send(f"{site} has been added to the list.") 175 | 176 | @botlist.command(name="delete") 177 | async def _delete(self, ctx, site: str): 178 | """Remove a botlist from your setup lists..""" 179 | lists = await self.get_lists() 180 | if lists.get("error"): 181 | return await ctx.send( 182 | f"An error occured retrieving the lists. Error Code: {lists['error']}" 183 | ) 184 | if site not in lists: 185 | return await ctx.send( 186 | f"Your list doesn't appear to exist. Ensure its named after how it appears in `{ctx.clean_prefix}botlist list`" 187 | ) 188 | async with self.config.lists() as config: 189 | if site in config: 190 | del config[site] 191 | await ctx.send(f"{site} has been removed") 192 | else: 193 | await ctx.send(f"{site} doesnt exist in your setup bot lists.") 194 | 195 | @botlist.command() 196 | async def available(self, ctx): 197 | """List current setup botlists which are having stats posted to them.""" 198 | conf = await self.config.lists() 199 | if not conf: 200 | return await ctx.send("You don't have any lists setup.") 201 | msg = "".join(f"{_list}\n" for _list in conf) 202 | embeds = [] 203 | for page in pagify(msg, page_length=1024): 204 | embed = discord.Embed( 205 | title="Bot Lists Setup", description=page, colour=await ctx.embed_colour() 206 | ) 207 | embeds.append(embed) 208 | await menu(ctx, embeds, DEFAULT_CONTROLS) 209 | -------------------------------------------------------------------------------- /simleague/stats.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from redbot.core import commands 3 | 4 | from .abc import MixinMeta 5 | 6 | 7 | class StatsMixin(MixinMeta): 8 | """Stats Settings""" 9 | 10 | @commands.group(invoke_without_command=True) 11 | async def stats(self, ctx, user: discord.Member = None): 12 | """Sim League Statistics.""" 13 | if user is not None: 14 | stats = await self.config.guild(ctx.guild).stats() 15 | userid = str(user.id) 16 | pens = stats["penalties"].get(userid) 17 | statistics = [ 18 | stats["goals"].get(userid), 19 | stats["assists"].get(userid), 20 | stats["yellows"].get(userid), 21 | stats["reds"].get(userid), 22 | stats["motm"].get(userid), 23 | pens.get("missed") if pens else None, 24 | pens.get("scored") if pens else None, 25 | ] 26 | headers = [ 27 | "goals", 28 | "assists", 29 | "yellows", 30 | "reds", 31 | "motms", 32 | "penalties missed", 33 | "penalties scored", 34 | ] 35 | embed = discord.Embed( 36 | color=ctx.author.color, title="Statistics for {}".format(user.display_name) 37 | ) 38 | for i, stat in enumerate(statistics): 39 | if stat is not None: 40 | embed.add_field(name=headers[i].title(), value=stat) 41 | else: 42 | embed.add_field(name=headers[i].title(), value="0") 43 | await ctx.send(embed=embed) 44 | 45 | else: 46 | await ctx.send_help() 47 | stats = await self.config.guild(ctx.guild).stats() 48 | goalscorer = sorted(stats["goals"], key=stats["goals"].get, reverse=True) 49 | assists = sorted(stats["assists"], key=stats["assists"].get, reverse=True) 50 | yellows = sorted(stats["yellows"], key=stats["yellows"].get, reverse=True) 51 | reds = sorted(stats["reds"], key=stats["reds"].get, reverse=True) 52 | motms = sorted(stats["motm"], key=stats["motm"].get, reverse=True) 53 | cleansheets = sorted(stats["cleansheets"], key=stats["cleansheets"].get, reverse=True) 54 | penscored = sorted( 55 | stats["penalties"], key=lambda x: stats["penalties"][x]["scored"], reverse=True 56 | ) 57 | penmissed = sorted( 58 | stats["penalties"], key=lambda x: stats["penalties"][x]["missed"], reverse=True 59 | ) 60 | msg = "" 61 | msg += "**Top Goalscorer**: {}\n".format(await self.statsmention(ctx, goalscorer)) 62 | msg += "**Most Assists**: {}\n".format(await self.statsmention(ctx, assists)) 63 | msg += "**Most Yellow Cards**: {}\n".format(await self.statsmention(ctx, yellows)) 64 | msg += "**Most Red Cards**: {}\n".format(await self.statsmention(ctx, reds)) 65 | msg += "**Penalties Scored**: {}\n".format(await self.statsmention(ctx, penscored)) 66 | msg += "**Penalties Missed**: {}\n".format(await self.statsmention(ctx, penmissed)) 67 | msg += "**MOTMs**: {}\n".format(await self.statsmention(ctx, motms)) 68 | msg += "**Cleansheets**: {}\n".format(cleansheets[0] if cleansheets else "None") 69 | await ctx.maybe_send_embed(msg) 70 | 71 | async def statsmention(self, ctx, stats): 72 | if stats: 73 | user = ctx.guild.get_member(int(stats[0])) 74 | if not user: 75 | return "Invalid User {}".format(stats[0]) 76 | return user.mention 77 | else: 78 | return "None" 79 | 80 | @stats.command(name="goals", alias=["topscorer", "topscorers"]) 81 | async def _goals(self, ctx): 82 | """Players with the most goals.""" 83 | stats = await self.config.guild(ctx.guild).stats() 84 | stats = stats["goals"] 85 | if stats: 86 | a = [] 87 | for k in sorted(stats, key=stats.get, reverse=True): 88 | user = self.bot.get_user(int(k)) 89 | a.append(f"{user.mention if user else 'Invalid User {}'.format(k)} - {stats[k]}") 90 | embed = discord.Embed( 91 | title="Top Scorers", description="\n".join(a[:10]), colour=0xFF0000 92 | ) 93 | await ctx.send(embed=embed) 94 | else: 95 | await ctx.send("No stats available.") 96 | 97 | @stats.command(aliases=["yellowcards"]) 98 | async def yellows(self, ctx): 99 | """Players with the most yellow cards.""" 100 | stats = await self.config.guild(ctx.guild).stats() 101 | stats = stats["yellows"] 102 | if stats: 103 | a = [] 104 | for k in sorted(stats, key=stats.get, reverse=True): 105 | user = self.bot.get_user(int(k)) 106 | a.append(f"{user.mention if user else 'Invalid User {}'.format(k)} - {stats[k]}") 107 | embed = discord.Embed( 108 | title="Most Yellow Cards", description="\n".join(a[:10]), colour=0xFF0000 109 | ) 110 | await ctx.send(embed=embed) 111 | else: 112 | await ctx.send("No stats available.") 113 | 114 | @stats.command(alies=["redcards"]) 115 | async def reds(self, ctx): 116 | """Players with the most red cards.""" 117 | stats = await self.config.guild(ctx.guild).stats() 118 | stats = stats["reds"] 119 | if stats: 120 | a = [] 121 | for k in sorted(stats, key=stats.get, reverse=True): 122 | user = self.bot.get_user(int(k)) 123 | a.append(f"{user.mention if user else 'Invalid User {}'.format(k)} - {stats[k]}") 124 | embed = discord.Embed( 125 | title="Most Red Cards", description="\n".join(a[:10]), colour=0xFF0000 126 | ) 127 | await ctx.send(embed=embed) 128 | else: 129 | await ctx.send("No stats available.") 130 | 131 | @stats.command(alies=["motms"]) 132 | async def motm(self, ctx): 133 | """Players with the most MOTMs.""" 134 | stats = await self.config.guild(ctx.guild).stats() 135 | stats = stats["motm"] 136 | if stats: 137 | a = [] 138 | for k in sorted(stats, key=stats.get, reverse=True): 139 | user = self.bot.get_user(int(k)) 140 | a.append(f"{user.mention if user else 'Invalid User {}'.format(k)} - {stats[k]}") 141 | embed = discord.Embed( 142 | title="Most MOTMs", description="\n".join(a[:10]), colour=0xFF0000 143 | ) 144 | await ctx.send(embed=embed) 145 | else: 146 | await ctx.send("No stats available.") 147 | 148 | @stats.command(name="cleansheets") 149 | async def _cleansheets(self, ctx): 150 | """Teams with the most cleansheets.""" 151 | stats = await self.config.guild(ctx.guild).stats() 152 | stats = stats["cleansheets"] 153 | if stats: 154 | a = [f"{k} - {stats[k]}" for k in sorted(stats, key=stats.get, reverse=True)[:10]] 155 | 156 | embed = discord.Embed( 157 | title="Most Cleansheets", description="\n".join(a), colour=0xFF0000 158 | ) 159 | await ctx.send(embed=embed) 160 | else: 161 | await ctx.send("No stats available.") 162 | 163 | @stats.command() 164 | async def penalties(self, ctx): 165 | """Penalties scored and missed statistics.""" 166 | stats = await self.config.guild(ctx.guild).stats() 167 | stats = stats["penalties"] 168 | if stats: 169 | a = [] 170 | b = [] 171 | for k in sorted(stats, key=lambda x: stats[x]["scored"], reverse=True)[:10]: 172 | user = self.bot.get_user(int(k)) 173 | a.append( 174 | f"{user.mention if user else 'Invalid User {}'.format(k)} - {stats[k]['scored']}" 175 | ) 176 | for k in sorted(stats, key=lambda x: stats[x]["missed"], reverse=True)[:10]: 177 | user = self.bot.get_user(int(k)) 178 | b.append( 179 | f"{user.mention if user else 'Invalid User {}'.format(k)} - {stats[k]['missed']}" 180 | ) 181 | embed = discord.Embed(title="Penalty Statistics", colour=0xFF0000) 182 | embed.add_field(name="Penalties Scored", value="\n".join(a)) 183 | embed.add_field(name="Penalties Missed", value="\n".join(b)) 184 | await ctx.send(embed=embed) 185 | else: 186 | await ctx.send("No stats available.") 187 | 188 | @stats.command() 189 | async def assists(self, ctx): 190 | """Players with the most assists.""" 191 | stats = await self.config.guild(ctx.guild).stats() 192 | stats = stats["assists"] 193 | if stats: 194 | a = [] 195 | for k in sorted(stats, key=stats.get, reverse=True)[:10]: 196 | user = self.bot.get_user(int(k)) 197 | a.append(f"{user.mention if user else 'Invalid User {}'.format(k)} - {stats[k]}") 198 | embed = discord.Embed( 199 | title="Assist Statistics", description="\n".join(a), colour=0xFF0000 200 | ) 201 | await ctx.send(embed=embed) 202 | else: 203 | await ctx.send("No stats available.") 204 | --------------------------------------------------------------------------------