├── .gitignore ├── .replit ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── animations ├── helloworld.txt └── rick.txt ├── banner.png ├── customrs.gif ├── docker-compose.yml ├── example.gif ├── requirements.txt ├── slacky.gif ├── slacky ├── __init__.py ├── __main__.py ├── api │ ├── __init__.py │ └── auth.py ├── config │ ├── __init__.py │ └── load.py ├── constants │ ├── __init__.py │ └── emojis.py ├── helpers │ └── __init__.py └── plugins │ ├── __init__.py │ └── custom │ ├── __init__.py │ ├── deepfry │ ├── __init__.py │ ├── deepfrylogic.py │ ├── flare.png │ ├── haarcascade_eye.xml │ ├── haarcascade_frontalface_default.xml │ └── plugin.py │ ├── example.py │ └── stockpic │ ├── __init__.py │ └── plugin.py └── version.txt /.gitignore: -------------------------------------------------------------------------------- 1 | config.json 2 | **/*__pycache__ 3 | **/*.pyc 4 | *.json 5 | **/*wormy.txt 6 | **/*wormy2.txt 7 | Dockerfile.apptapp 8 | Dockerfile.reggora 9 | max.yml -------------------------------------------------------------------------------- /.replit: -------------------------------------------------------------------------------- 1 | language = "python3" 2 | run = "python3 -m slacky" 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### Update 1.8.3: 2 | 3 | - Adds update check in bot and `stats` command. 4 | 5 | ### Update 1.8.2: 6 | 7 | - Fix `setstatus` command from being `status` command 8 | 9 | ### Update 1.8.1: 10 | 11 | - Fix requirements to include `pandas` 12 | 13 | ### Update 1.8: 14 | 15 | - Adds `coronastatus` command to check Wuhan Coronavirus Status 16 | 17 | ### Update 1.7: 18 | 19 | - Adds `stockpic` command. 20 | - Requires Shutterstock Credentials 21 | 22 | ### Update 1.6: 23 | 24 | - Adds `deepfry` command from forked [deeppyer](https://github.com/Ovyerus/deeppyer) 25 | - `deepfry` is a custom command and must be loaded in `plugins/__init__.py` manually. Requires `OpenCV 4`. 26 | 27 | ## Update 1.5.5: 28 | 29 | - Fix `ping` command 30 | 31 | ### Update 1.5.4: 32 | 33 | - Fix `help` with `ping` command 34 | 35 | ### Update 1.5.3: 36 | 37 | - New format for `ping` and `stats` command 38 | 39 | ### Update 1.5.2: 40 | 41 | - Fixes `heartbeat` command and adds `ping` command 42 | - New `help` command and subcommands 43 | 44 | ### Update 1.5.1: 45 | 46 | - Merges @TotallyChase's PR 47 | - Performance and Optimization Boost 48 | - Plugin loading refactor 49 | - Adds `msgstatus` command 50 | 51 | ### Update 1.5: 52 | 53 | - Huge refactor with `cmd_setup` function 54 | - Error Handling 55 | 56 | ### Update 1.4.6: 57 | 58 | - Fix `winfo` formatting 59 | 60 | ### Update 1.4.5: 61 | 62 | - Fix `listener` command 63 | 64 | ### Update 1.4.4: 65 | 66 | - Update `help` command 67 | - Better Error Handling on Custom Replies 68 | 69 | ### Update 1.4.3: 70 | 71 | - Uses attachments to make replies prettier 72 | - Fixes `ani` command breaking 73 | 74 | ### Update 1.4.2: 75 | 76 | - Fix slowing bug due to hitting `winfo` each time 77 | 78 | ### Update 1.4.1: 79 | 80 | - Update requirements.txt 81 | 82 | ### Update 1.4: 83 | 84 | - New Plugin Loading 85 | - Error Handling 86 | - Adds `winfo`, `convinfo`, and `uinfo` commands 87 | 88 | ### Update 1.3.2: 89 | 90 | - More Error Handling 91 | - Adds `rick` animation as default 92 | - Adds looping argument to `ani` command. New syntax `ani [ani_name] [loop count]` 93 | 94 | ### Update 1.3.1: 95 | 96 | - Hotfix for `errors` command formatting 97 | 98 | ### Update 1.3: 99 | 100 | - Adds `animations` and the `ani` command. Read Wiki for more info on animations. 101 | - New bots error handling and `errors` command to display errors triggered 102 | 103 | ### Update 1.2.2: 104 | 105 | - Fix emoji handling in the `react` command 106 | 107 | ### Update 1.2.1: 108 | 109 | - Fix `customrs` command 110 | - Regex sucks! 111 | 112 | ### Update 1.2: 113 | 114 | - Add `stats, space, ud` commands. 115 | - Clean up more error handling 116 | - Adds BotMetaData class for bot data like uptime, commands run, errors, etc 117 | 118 | ### Update 1.1.4: 119 | 120 | - Fix Custom Replies 121 | - More Error Handling 122 | 123 | ### Update 1.1.3: 124 | 125 | - Better Error Handling 126 | - Fix some broken commands 127 | 128 | ### Update 1.1.2: 129 | 130 | - Clean Up Code a Bit 131 | 132 | ### Update 1.1.1: 133 | 134 | - Adds Custom Config Creation/Selection Argument 135 | - Error Handling Updates for Ctrl+C on Config Wizard 136 | 137 | ### Update 1.1: 138 | 139 | - Adds Custom Replies. Read the [Wiki](https://github.com/M4cs/Slacky/wiki) to learn how to use them. 140 | - Bug fixes -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7.4 2 | MAINTAINER Max Bridgland 3 | 4 | RUN mkdir -p /usr/src/slacky 5 | WORKDIR /usr/src/app 6 | 7 | ADD requirements.txt /usr/src/app/requirements.txt 8 | 9 | RUN pip install -r requirements.txt 10 | 11 | RUN apt-get update && apt-get install libopencv-dev python3-opencv -y 12 | 13 | ADD . /usr/src/app 14 | 15 | CMD python -m slacky -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Max Bridgland 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |


3 |

4 | 5 | 6 | [![GitHub stars](https://img.shields.io/github/stars/M4cs/Slacky)](https://github.com/M4cs/Slacky/stargazers) [![GitHub forks](https://img.shields.io/github/forks/M4cs/Slacky)](https://github.com/M4cs/Slacky/network) [![GitHub license](https://img.shields.io/github/license/M4cs/Slacky)](https://github.com/M4cs/Slacky/blob/master/LICENSE) [![Discord Server](https://img.shields.io/badge/Discord-Join%20For%20Support-blue)](https://discord.gg/JjZwJVF) [![Run on Repl.it](https://repl.it/badge/github/M4cs/Slacky)](https://repl.it/github/M4cs/Slacky) 7 | 8 | 9 | The First Python Selfbot for Slack Workspaces :star: 10 | 11 | ## What is this? 12 | 13 | Slacky is a self-bot for Slack. What is a selfbot? Unlike a normal Slack Bot, Slacky makes all it's request using your personal user token so that it can act as you. Therefore it will edit and post as you rather than a third-party bot making it look like you are doing things extremely fast like custom replies, animations, and more! 14 | 15 | There are a ton of default commands, and easily loadable custom plugins. You are even able to run it in multiple workspaces at once. Read more below for info on the wiki and more commands. 16 | 17 | Discord for Support: https://discord.gg/JjZwJVF 18 | 19 | ### This README doesn't have all the info! I've written a readme with a bunch of info on the bot [here](https://github.com/M4cs/Slacky/wiki). Check it out for information on how to Install, Setup, and Customize Slacky! 20 | 21 | ### Read how to run the bot in multiple workspaces [here](https://github.com/M4cs/Slacky/wiki/Multiple-Workspaces) 22 | 23 | ### [View Changelog Here](https://github.com/M4cs/Slacky/blob/master/CHANGELOG.md) 24 | 25 |

26 |

Slack Client


27 |

28 | 29 |

30 |

Bot Server


31 |

32 | 33 | 34 | ## Default Commands 35 | 36 | Assumes prefix is `~` 37 | 38 | | Command | Description | Usage | 39 | | :--: | :--: | :--: | 40 | | heartbeat | Check if bot is up or not | ~heartbeat | 41 | | ping | Get response time from server hosting bot | ~ping | 42 | | uinfo | Get info about a user | ~uinfo @user | 43 | | winfo | Get info about the current workspace | ~winfo | 44 | | convinfo | Get info about conversation/channel | ~convinfo #chantag|optional | 45 | | info | Get info about the bot | ~info | 46 | | errors | See all errors the bot has encountered | ~errors | 47 | | msgstatus | Enable/Disable random status emoji on msg | ~msgstatus | 48 | | customrs | Manage custom replies to messages | [Read Wiki](https://github.com/M4cs/Slacky/wiki) | 49 | | stats | Get info about the bot running | ~stats | 50 | | ascii | Generate ASCII art from a phrase | ~ascii msg | 51 | | shift | CrEaTe ShIfT tExT lIkE tHiS | ~shift phrase | 52 | | subspace | Replace spaces with emojis | ~subspace :emoji: msg | 53 | | space | Add a space in between each character | ~space phrase | 54 | | ani | Run animation from animation folder | [Read Wiki](https://github.com/M4cs/Slacky/wiki) | 55 | | ud | Get urban dictionary definiton | ~ud query | 56 | | setprefix | Sets bot command prefix | ~setprefix prefix | 57 | | xkcd | Get Daily xkcd comic | ~xkcd | 58 | | delete | Delete X num of your msgs | ~delete num_of_msgs | 59 | | react | React to last sent message | ~react :emoji: | 60 | | reactrand | React to with random emoji | ~reactrand | 61 | | reactspam | Spam 23 Reactions (Notification Spam) | ~randspam | 62 | | howdoi | Find code snippets from stack overflow | ~howdoi loop over list python | 63 | | listener | Add or remove listeners | ~listener add/delete phrase | 64 | | listener list | List all listener words | ~listener list | 65 | | help | Display this message | ~help | 66 | 67 | ## Contributing 68 | 69 | Contributions can be done through Pull Requests or by creating a plugin pack. 70 | 71 | ## License 72 | 73 | I ask that if you fork this do not release as your own. Please credit and mention it's built off this. In fact building a plugin pack rather than a new bot entirely would be best! The license is MIT so I won't stop you but I'm just asking that you respect my wishes :smile: 74 | -------------------------------------------------------------------------------- /animations/helloworld.txt: -------------------------------------------------------------------------------- 1 | 0.1 2 | h 3 | [f#] 4 | he 5 | [f#] 6 | hel 7 | [f#] 8 | hell 9 | [f#] 10 | hello 11 | [f#] 12 | hello w 13 | [f#] 14 | hello wo 15 | [f#] 16 | hello wor 17 | [f#] 18 | hello worl 19 | [f#] 20 | hello world 21 | [f#] -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4cs/Slacky/992dc75297295878ba37fa64a5ff44c50beac890/banner.png -------------------------------------------------------------------------------- /customrs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4cs/Slacky/992dc75297295878ba37fa64a5ff44c50beac890/customrs.gif -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | slacky: 4 | build: . 5 | image: slacky 6 | stdin_open: true 7 | tty: true 8 | volumes: 9 | - ./:/usr/src/slacky -------------------------------------------------------------------------------- /example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4cs/Slacky/992dc75297295878ba37fa64a5ff44c50beac890/example.gif -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | colorama 2 | pyfiglet 3 | terminaltables 4 | howdoi 5 | httpx 6 | slackclient 7 | nest_asyncio 8 | pillow 9 | opencv-python 10 | requests 11 | pandas -------------------------------------------------------------------------------- /slacky.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4cs/Slacky/992dc75297295878ba37fa64a5ff44c50beac890/slacky.gif -------------------------------------------------------------------------------- /slacky/__init__.py: -------------------------------------------------------------------------------- 1 | from slacky.config import load_config as lc 2 | from slacky.api.auth import authenticate 3 | from colorama import init 4 | from colorama import Fore, Back, Style 5 | from time import time 6 | from argparse import ArgumentParser 7 | import httpx, json, logging, getpass 8 | import nest_asyncio 9 | import datetime 10 | 11 | nest_asyncio.apply() 12 | 13 | class Prefixes: 14 | info = str('[' + Fore.GREEN + Style.BRIGHT + 'INFO' + Style.RESET_ALL + '] ') 15 | warning = str('[' + Fore.YELLOW + Style.BRIGHT + 'WARNING' + Style.RESET_ALL + '] ') 16 | event = str('[' + Fore.BLUE + Style.BRIGHT + 'EVENT' + Style.RESET_ALL + '] ') 17 | error = str('[' + Fore.RED + Style.BRIGHT + 'ERROR' + Style.RESET_ALL + '] ') 18 | start = str('[' + Fore.LIGHTBLUE_EX + Style.BRIGHT + 'SLACKY' + Style.RESET_ALL + '] ') 19 | 20 | class BotMetaData: 21 | def __init__(self): 22 | self.start_time = datetime.datetime.now() 23 | self.command_count = 0 24 | self.error_count = 0 25 | self.warning_count = 0 26 | self.message_count = 0 27 | self.errors = [] 28 | self.msgstatus = False 29 | self.needs_update = False 30 | 31 | def error(self, e): 32 | print(Prefixes.error + str(e)) 33 | self.errors.append(str(e)) 34 | self.error_count += 1 35 | 36 | def get_uptime(self): 37 | now = datetime.datetime.now() 38 | uptime = now - self.start_time 39 | days = uptime.days 40 | hours, remainder = divmod(uptime.seconds, 3600) 41 | minutes, seconds = divmod(remainder, 60) 42 | string = "" 43 | if days == 0: 44 | pass 45 | else: 46 | string += str(str(days) + " days, ") 47 | if hours == 0: 48 | pass 49 | else: 50 | string += str(str(hours) + " hrs, ") 51 | if minutes == 0: 52 | pass 53 | else: 54 | string += str(str(minutes) + " min, ") 55 | if seconds == 0: 56 | pass 57 | else: 58 | string += str(str(seconds) + " sec.") 59 | if string.endswith(', '): 60 | string = string[:-2] 61 | else: 62 | pass 63 | return string 64 | 65 | class CustomReplies: 66 | def __init__(self, config): 67 | self.custom_replies = config['custom_replies'] 68 | self.last_sent = False 69 | 70 | def add(self, custom_reply): 71 | self.custom_replies.append(custom_reply) 72 | with open('config.json', 'r+') as file: 73 | obj = json.load(file) 74 | obj['custom_replies'] = self.custom_replies 75 | file.seek(0) 76 | json.dump(obj, file, indent=4) 77 | file.truncate() 78 | 79 | def delete(self, num): 80 | del self.custom_replies[int(num)] 81 | with open('config.json', 'r+') as file: 82 | obj = json.load(file) 83 | obj['custom_replies'] = self.custom_replies 84 | file.seek(0) 85 | json.dump(obj, file, indent=4) 86 | file.truncate() 87 | 88 | class Listeners: 89 | def __init__(self, config): 90 | self.listeners = config['listeners'] 91 | 92 | def add(self, phrase): 93 | self.listeners.append(phrase) 94 | with open('config.json', 'r+') as file: 95 | obj = json.load(file) 96 | obj['listeners'] = self.listeners 97 | file.seek(0) 98 | json.dump(obj, file, indent=4) 99 | file.truncate() 100 | 101 | def delete(self, phrase): 102 | num = self.listeners.index(phrase) 103 | del self.listeners[num] 104 | with open('config.json', 'r+') as file: 105 | obj = json.load(file) 106 | obj['listeners'] = self.listeners 107 | file.seek(0) 108 | json.dump(obj, file, indent=4) 109 | file.truncate() 110 | 111 | def check_user(user): 112 | if user == config['user']: 113 | return True 114 | else: 115 | return False 116 | 117 | def config_parser(): 118 | parser = ArgumentParser() 119 | parser.add_argument('-c', '--config', help='Optional path to load different config or create new') 120 | return parser 121 | 122 | try: 123 | parser = config_parser() 124 | args = parser.parse_args() 125 | if args.config: 126 | config_path = args.config 127 | else: 128 | config_path = './config.json' 129 | with open('version.txt', 'r') as file: 130 | version = str(file.read()) 131 | print(Prefixes.start + 'Welcome to Slacky {} | The First Python Self-Bot for Slack!'.format(version)) 132 | print(Prefixes.event + 'Searching for New Updates...') 133 | 134 | remote_v = httpx.get('https://raw.githubusercontent.com/M4cs/Slacky/master/version.txt') 135 | rv = remote_v.content.decode('utf-8') 136 | bot = BotMetaData() 137 | if version != rv: 138 | print(Prefixes.warning + 'Newer Version Available! Please re-pull to update.') 139 | bot.needs_update = True 140 | else: 141 | print(Prefixes.info + 'Up to Date!') 142 | config = lc(config_path) 143 | if not config: 144 | print(Prefixes.warning + 'No Config File Found. Starting Wizard.') 145 | print(Prefixes.start + 'Go to https://slackyauth.maxbridgland.com/authorize and Grab a Token for Slacky 2.0') 146 | token = input('> ') 147 | print(Prefixes.start + 'Enter User ID. Google How To Get This.') 148 | user_id = input('> ') 149 | print(Prefixes.start + 'Enter Desired Prefix (Default: ~)') 150 | prefix = input('> ') 151 | if prefix == '' or prefix == None: 152 | prefix = '~' 153 | else: 154 | prefix = prefix 155 | print(Prefixes.info + 'Entered Token:', token) 156 | print(Prefixes.info + 'Entered User ID:', user_id) 157 | print(Prefixes.info + 'Entered Prefix:', prefix) 158 | print(Prefixes.start + 'Press ENTER to Confirm Information or Ctrl+C to Quit.') 159 | getpass.getpass('') 160 | with open(config_path, 'w+') as file: 161 | config = { 162 | 'token': token, 163 | 'user': user_id, 164 | 'prefix': prefix, 165 | 'listeners': [], 166 | 'custom_replies': [] 167 | } 168 | json.dump(config, file, indent=4) 169 | print(Prefixes.event + 'Config Saved! Please Restart To Use Slacky') 170 | exit(0) 171 | 172 | print(Prefixes.info + 'Config Loaded') 173 | print(Prefixes.event + 'Attempting to Authenticate with Slack', end='\r') 174 | listener = Listeners(config) 175 | customrs = CustomReplies(config) 176 | client = authenticate(config) 177 | if not client: 178 | print(Prefixes.error + 'Could Not Authenticate with Slack! Please check your config and token!') 179 | print(' ' * 65, end='\r') 180 | user = client.users_info(user=config['user']) 181 | team = client.team_info()['team']['domain'] 182 | print(Prefixes.info + 'Logged in as {}@{}'.format(user['user']['name'], team)) 183 | except KeyboardInterrupt: 184 | print(Prefixes.event + 'Shutdown Called') 185 | exit(0) 186 | -------------------------------------------------------------------------------- /slacky/__main__.py: -------------------------------------------------------------------------------- 1 | from slacky import config, client, Prefixes 2 | from slacky.plugins import * 3 | from time import sleep 4 | import slack 5 | import httpx 6 | import re 7 | 8 | print(Prefixes.event + 'Loading Plugins') 9 | 10 | commands = { 11 | 'heartbeat': lambda **payload: heartbeat, 12 | 'ping': lambda **payload: ping, 13 | 'stats' : lambda **payload: stats, 14 | 'setprefix': lambda **payload: setprefix, 15 | 'space' : lambda **payload: space, 16 | 'winfo': lambda **payload: winfo, 17 | 'uinfo' : lambda **payload: uinfo, 18 | 'convinfo': lambda **payload: convinfo, 19 | 'ani' : lambda **payload: animations, 20 | 'errors': lambda **payload: errors, 21 | 'ud' : lambda **payload: ud, 22 | 'help': lambda **payload: shelp, 23 | 'delete' : lambda **payload: delete, 24 | 'ascii': lambda **payload: ascii, 25 | 'reactrand' : lambda **payload: reactrand, 26 | 'reactspam': lambda **payload: reactspam, 27 | 'customrs' : lambda **payload: customrscmd, 28 | 'howdoi': lambda **payload: howdoicmd, 29 | 'subspace' : lambda **payload: sub_space, 30 | 'xkcd': lambda **payload: xkcd, 31 | 'react' : lambda **payload: react, 32 | 'info': lambda **payload: info, 33 | 'shift' : lambda **payload: shift, 34 | 'status': lambda **payload: status, 35 | 'listener': lambda **payload: listenercmd, 36 | 'msgstatus': lambda **payload: msgstatus, 37 | # Uncomment to load deepfry, requires OpenCV 4 38 | # 'deepfry': lambda **payload: deepfry, 39 | # Uncomment to load shutterstock, requires API credentials, 40 | 'stockpic': lambda **payload: stockpic, 41 | 'coronastatus': lambda **payload: coronavirus 42 | } 43 | 44 | @slack.RTMClient.run_on(event='message') 45 | def _cmdcheck(**payload): 46 | prefix = config['prefix'] 47 | text = payload['data'].get('text') 48 | if not text: 49 | return "no text" 50 | cmd = re.search(r'{}(.*?) |{}(.*?)$'.format(prefix, prefix), text) 51 | if not cmd: 52 | return "no command" 53 | cmd = cmd.group(1) if cmd.group(1) else cmd.group(2) 54 | if cmd in commands.keys(): 55 | func = commands.get(cmd)() 56 | if func: 57 | return func(**payload) 58 | 59 | @slack.RTMClient.run_on(event='message') 60 | def _customrsd(**payload): 61 | return customrsd(**payload) 62 | 63 | @slack.RTMClient.run_on(event='message') 64 | def _listenerd(**payload): 65 | return listenerd(**payload) 66 | 67 | def run_client(rtm): 68 | rtm.start() 69 | 70 | slack_token = config['token'] 71 | rtmclient = slack.RTMClient(token=slack_token) 72 | print(Prefixes.event + 'Default Plugins Loaded') 73 | print(Prefixes.event + 'Custom Plugins Loaded (If Any)') 74 | try: 75 | print(Prefixes.event + 'Running Bot...') 76 | print(Prefixes.start + 'Log Output:') 77 | rtmclient.ping_interval = 2 78 | run_client(rtmclient) 79 | except KeyboardInterrupt: 80 | print(Prefixes.event + 'Shutdown Called') 81 | exit(0) 82 | except Exception as e: 83 | bot.error(e) 84 | bot.error_count += 1 85 | print(Prefixes.event + 'Attempting To Auto Reconnect...') 86 | time.sleep(2) 87 | run_client(rtmclient) 88 | -------------------------------------------------------------------------------- /slacky/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4cs/Slacky/992dc75297295878ba37fa64a5ff44c50beac890/slacky/api/__init__.py -------------------------------------------------------------------------------- /slacky/api/auth.py: -------------------------------------------------------------------------------- 1 | import slack 2 | 3 | def authenticate(config): 4 | try: 5 | client = slack.WebClient(token=config['token']) 6 | return client 7 | except: 8 | return None -------------------------------------------------------------------------------- /slacky/config/__init__.py: -------------------------------------------------------------------------------- 1 | from .load import load_config -------------------------------------------------------------------------------- /slacky/config/load.py: -------------------------------------------------------------------------------- 1 | import json, os 2 | 3 | def load_config(cp): 4 | if not os.path.exists(cp): 5 | return None 6 | with open(cp, 'rb') as config_file: 7 | config = json.load(config_file) 8 | return config -------------------------------------------------------------------------------- /slacky/constants/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4cs/Slacky/992dc75297295878ba37fa64a5ff44c50beac890/slacky/constants/__init__.py -------------------------------------------------------------------------------- /slacky/constants/emojis.py: -------------------------------------------------------------------------------- 1 | emojis = ['hash', 'keycap_star', 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'copyright', 'registered', 'mahjong', 'black_joker', 'a', 'b', 'o2', 'parking', 'ab', 'cl', 'cool', 'free', 'id', 'new', 'ng', 'ok', 'sos', 'up', 'vs', 'flag-ac', 'flag-ad', 'flag-ae', 'flag-af', 'flag-ag', 'flag-ai', 'flag-al', 'flag-am', 'flag-ao', 'flag-aq', 'flag-ar', 'flag-as', 'flag-at', 'flag-au', 'flag-aw', 'flag-ax', 'flag-az', 'flag-ba', 'flag-bb', 'flag-bd', 'flag-be', 'flag-bf', 'flag-bg', 'flag-bh', 'flag-bi', 'flag-bj', 'flag-bl', 'flag-bm', 'flag-bn', 'flag-bo', 'flag-bq', 'flag-br', 'flag-bs', 'flag-bt', 'flag-bv', 'flag-bw', 'flag-by', 'flag-bz', 'flag-ca', 'flag-cc', 'flag-cd', 'flag-cf', 'flag-cg', 'flag-ch', 'flag-ci', 'flag-ck', 'flag-cl', 'flag-cm', 'cn', 'flag-co', 'flag-cp', 'flag-cr', 'flag-cu', 'flag-cv', 'flag-cw', 'flag-cx', 'flag-cy', 'flag-cz', 'de', 'flag-dg', 'flag-dj', 'flag-dk', 'flag-dm', 'flag-do', 'flag-dz', 'flag-ea', 'flag-ec', 'flag-ee', 'flag-eg', 'flag-eh', 'flag-er', 'es', 'flag-et', 'flag-eu', 'flag-fi', 'flag-fj', 'flag-fk', 'flag-fm', 'flag-fo', 'fr', 'flag-ga', 'gb', 'flag-gd', 'flag-ge', 'flag-gf', 'flag-gg', 'flag-gh', 'flag-gi', 'flag-gl', 'flag-gm', 'flag-gn', 'flag-gp', 'flag-gq', 'flag-gr', 'flag-gs', 'flag-gt', 'flag-gu', 'flag-gw', 'flag-gy', 'flag-hk', 'flag-hm', 'flag-hn', 'flag-hr', 'flag-ht', 'flag-hu', 'flag-ic', 'flag-id', 'flag-ie', 'flag-il', 'flag-im', 'flag-in', 'flag-io', 'flag-iq', 'flag-ir', 'flag-is', 'it', 'flag-je', 'flag-jm', 'flag-jo', 'jp', 'flag-ke', 'flag-kg', 'flag-kh', 'flag-ki', 'flag-km', 'flag-kn', 'flag-kp', 'kr', 'flag-kw', 'flag-ky', 'flag-kz', 'flag-la', 'flag-lb', 'flag-lc', 'flag-li', 'flag-lk', 'flag-lr', 'flag-ls', 'flag-lt', 'flag-lu', 'flag-lv', 'flag-ly', 'flag-ma', 'flag-mc', 'flag-md', 'flag-me', 'flag-mf', 'flag-mg', 'flag-mh', 'flag-mk', 'flag-ml', 'flag-mm', 'flag-mn', 'flag-mo', 'flag-mp', 'flag-mq', 'flag-mr', 'flag-ms', 'flag-mt', 'flag-mu', 'flag-mv', 'flag-mw', 'flag-mx', 'flag-my', 'flag-mz', 'flag-na', 'flag-nc', 'flag-ne', 'flag-nf', 'flag-ng', 'flag-ni', 'flag-nl', 'flag-no', 'flag-np', 'flag-nr', 'flag-nu', 'flag-nz', 'flag-om', 'flag-pa', 'flag-pe', 'flag-pf', 'flag-pg', 'flag-ph', 'flag-pk', 'flag-pl', 'flag-pm', 'flag-pn', 'flag-pr', 'flag-ps', 'flag-pt', 'flag-pw', 'flag-py', 'flag-qa', 'flag-re', 'flag-ro', 'flag-rs', 'ru', 'flag-rw', 'flag-sa', 'flag-sb', 'flag-sc', 'flag-sd', 'flag-se', 'flag-sg', 'flag-sh', 'flag-si', 'flag-sj', 'flag-sk', 'flag-sl', 'flag-sm', 'flag-sn', 'flag-so', 'flag-sr', 'flag-ss', 'flag-st', 'flag-sv', 'flag-sx', 'flag-sy', 'flag-sz', 'flag-ta', 'flag-tc', 'flag-td', 'flag-tf', 'flag-tg', 'flag-th', 'flag-tj', 'flag-tk', 'flag-tl', 'flag-tm', 'flag-tn', 'flag-to', 'flag-tr', 'flag-tt', 'flag-tv', 'flag-tw', 'flag-tz', 'flag-ua', 'flag-ug', 'flag-um', 'flag-un', 'us', 'flag-uy', 'flag-uz', 'flag-va', 'flag-vc', 'flag-ve', 'flag-vg', 'flag-vi', 'flag-vn', 'flag-vu', 'flag-wf', 'flag-ws', 'flag-xk', 'flag-ye', 'flag-yt', 'flag-za', 'flag-zm', 'flag-zw', 'koko', 'sa', 'u7121', 'u6307', 'u7981', 'u7a7a', 'u5408', 'u6e80', 'u6709', 'u6708', 'u7533', 'u5272', 'u55b6', 'ideograph_advantage', 'accept', 'cyclone', 'foggy', 'closed_umbrella', 'night_with_stars', 'sunrise_over_mountains', 'sunrise', 'city_sunset', 'city_sunrise', 'rainbow', 'bridge_at_night', 'ocean', 'volcano', 'milky_way', 'earth_africa', 'earth_americas', 'earth_asia', 'globe_with_meridians', 'new_moon', 'waxing_crescent_moon', 'first_quarter_moon', 'moon', 'full_moon', 'waning_gibbous_moon', 'last_quarter_moon', 'waning_crescent_moon', 'crescent_moon', 'new_moon_with_face', 'first_quarter_moon_with_face', 'last_quarter_moon_with_face', 'full_moon_with_face', 'sun_with_face', 'star2', 'stars', 'thermometer', 'mostly_sunny', 'barely_sunny', 'partly_sunny_rain', 'rain_cloud', 'snow_cloud', 'lightning', 'tornado', 'fog', 'wind_blowing_face', 'hotdog', 'taco', 'burrito', 'chestnut', 'seedling', 'evergreen_tree', 'deciduous_tree', 'palm_tree', 'cactus', 'hot_pepper', 'tulip', 'cherry_blossom', 'rose', 'hibiscus', 'sunflower', 'blossom', 'corn', 'ear_of_rice', 'herb', 'four_leaf_clover', 'maple_leaf', 'fallen_leaf', 'leaves', 'mushroom', 'tomato', 'eggplant', 'grapes', 'melon', 'watermelon', 'tangerine', 'lemon', 'banana', 'pineapple', 'apple', 'green_apple', 'pear', 'peach', 'cherries', 'strawberry', 'hamburger', 'pizza', 'meat_on_bone', 'poultry_leg', 'rice_cracker', 'rice_ball', 'rice', 'curry', 'ramen', 'spaghetti', 'bread', 'fries', 'sweet_potato', 'dango', 'oden', 'sushi', 'fried_shrimp', 'fish_cake', 'icecream', 'shaved_ice', 'ice_cream', 'doughnut', 'cookie', 'chocolate_bar', 'candy', 'lollipop', 'custard', 'honey_pot', 'cake', 'bento', 'stew', 'fried_egg', 'fork_and_knife', 'tea', 'sake', 'wine_glass', 'cocktail', 'tropical_drink', 'beer', 'beers', 'baby_bottle', 'knife_fork_plate', 'champagne', 'popcorn', 'ribbon', 'gift', 'birthday', 'jack_o_lantern', 'christmas_tree', 'santa', 'fireworks', 'sparkler', 'balloon', 'tada', 'confetti_ball', 'tanabata_tree', 'crossed_flags', 'bamboo', 'dolls', 'flags', 'wind_chime', 'rice_scene', 'school_satchel', 'mortar_board', 'medal', 'reminder_ribbon', 'studio_microphone', 'level_slider', 'control_knobs', 'film_frames', 'admission_tickets', 'carousel_horse', 'ferris_wheel', 'roller_coaster', 'fishing_pole_and_fish', 'microphone', 'movie_camera', 'cinema', 'headphones', 'art', 'tophat', 'circus_tent', 'ticket', 'clapper', 'performing_arts', 'video_game', 'dart', 'slot_machine', '8ball', 'game_die', 'bowling', 'flower_playing_cards', 'musical_note', 'notes', 'saxophone', 'guitar', 'musical_keyboard', 'trumpet', 'violin', 'musical_score', 'running_shirt_with_sash', 'tennis', 'ski', 'basketball', 'checkered_flag', 'snowboarder', 'woman-running', 'man-running', 'runner', 'woman-surfing', 'man-surfing', 'surfer', 'sports_medal', 'trophy', 'horse_racing', 'football', 'rugby_football', 'woman-swimming', 'man-swimming', 'swimmer', 'woman-lifting-weights', 'man-lifting-weights', 'weight_lifter', 'woman-golfing', 'man-golfing', 'golfer', 'racing_motorcycle', 'racing_car', 'cricket_bat_and_ball', 'volleyball', 'field_hockey_stick_and_ball', 'ice_hockey_stick_and_puck', 'table_tennis_paddle_and_ball', 'snow_capped_mountain', 'camping', 'beach_with_umbrella', 'building_construction', 'house_buildings', 'cityscape', 'derelict_house_building', 'classical_building', 'desert', 'desert_island', 'national_park', 'stadium', 'house', 'house_with_garden', 'office', 'post_office', 'european_post_office', 'hospital', 'bank', 'atm', 'hotel', 'love_hotel', 'convenience_store', 'school', 'department_store', 'factory', 'izakaya_lantern', 'japanese_castle', 'european_castle', 'rainbow-flag', 'waving_white_flag', 'pirate_flag', 'flag-england', 'flag-scotland', 'flag-wales', 'waving_black_flag', 'rosette', 'label', 'badminton_racquet_and_shuttlecock', 'bow_and_arrow', 'amphora', 'skin-tone-2', 'skin-tone-3', 'skin-tone-4', 'skin-tone-5', 'skin-tone-6', 'rat', 'mouse2', 'ox', 'water_buffalo', 'cow2', 'tiger2', 'leopard', 'rabbit2', 'cat2', 'dragon', 'crocodile', 'whale2', 'snail', 'snake', 'racehorse', 'ram', 'goat', 'sheep', 'monkey', 'rooster', 'chicken', 'dog2', 'pig2', 'boar', 'elephant', 'octopus', 'shell', 'bug', 'ant', 'bee', 'beetle', 'fish', 'tropical_fish', 'blowfish', 'turtle', 'hatching_chick', 'baby_chick', 'hatched_chick', 'bird', 'penguin', 'koala', 'poodle', 'dromedary_camel', 'camel', 'dolphin', 'mouse', 'cow', 'tiger', 'rabbit', 'cat', 'dragon_face', 'whale', 'horse', 'monkey_face', 'dog', 'pig', 'frog', 'hamster', 'wolf', 'bear', 'panda_face', 'pig_nose', 'feet', 'chipmunk', 'eyes', 'eye-in-speech-bubble', 'eye', 'ear', 'nose', 'lips', 'tongue', 'point_up_2', 'point_down', 'point_left', 'point_right', 'facepunch', 'wave', 'ok_hand', '+1', '-1', 'clap', 'open_hands', 'crown', 'womans_hat', 'eyeglasses', 'necktie', 'shirt', 'jeans', 'dress', 'kimono', 'bikini', 'womans_clothes', 'purse', 'handbag', 'pouch', 'mans_shoe', 'athletic_shoe', 'high_heel', 'sandal', 'boot', 'footprints', 'bust_in_silhouette', 'busts_in_silhouette', 'boy', 'girl', 'male-farmer', 'male-cook', 'male-student', 'male-singer', 'male-artist', 'male-teacher', 'male-factory-worker', 'man-boy-boy', 'man-boy', 'man-girl-boy', 'man-girl-girl', 'man-girl', 'man-man-boy', 'man-man-boy-boy', 'man-man-girl', 'man-man-girl-boy', 'man-man-girl-girl', 'man-woman-boy', 'man-woman-boy-boy', 'man-woman-girl', 'man-woman-girl-boy', 'man-woman-girl-girl', 'male-technologist', 'male-office-worker', 'male-mechanic', 'male-scientist', 'male-astronaut', 'male-firefighter', 'male_red_haired', 'male_curly_haired', 'male_bald', 'male_white_haired', 'male-doctor', 'male-judge', 'male-pilot', 'man-heart-man', 'man-kiss-man', 'man', 'female-farmer', 'female-cook', 'female-student', 'female-singer', 'female-artist', 'female-teacher', 'female-factory-worker', 'woman-boy-boy', 'woman-boy', 'woman-girl-boy', 'woman-girl-girl', 'woman-girl', 'woman-woman-boy', 'woman-woman-boy-boy', 'woman-woman-girl', 'woman-woman-girl-boy', 'woman-woman-girl-girl', 'female-technologist', 'female-office-worker', 'female-mechanic', 'female-scientist', 'female-astronaut', 'female-firefighter', 'female_red_haired', 'female_curly_haired', 'female_bald', 'female_white_haired', 'female-doctor', 'female-judge', 'female-pilot', 'woman-heart-man', 'woman-heart-woman', 'woman-kiss-man', 'woman-kiss-woman', 'woman', 'family', 'couple', 'two_men_holding_hands', 'two_women_holding_hands', 'female-police-officer', 'male-police-officer', 'cop', 'woman-with-bunny-ears-partying', 'man-with-bunny-ears-partying', 'dancers', 'bride_with_veil', 'blond-haired-woman', 'blond-haired-man', 'person_with_blond_hair', 'man_with_gua_pi_mao', 'woman-wearing-turban', 'man-wearing-turban', 'man_with_turban', 'older_man', 'older_woman', 'baby', 'female-construction-worker', 'male-construction-worker', 'construction_worker', 'princess', 'japanese_ogre', 'japanese_goblin', 'ghost', 'angel', 'alien', 'space_invader', 'imp', 'skull', 'woman-tipping-hand', 'man-tipping-hand', 'information_desk_person', 'female-guard', 'male-guard', 'guardsman', 'dancer', 'lipstick', 'nail_care', 'woman-getting-massage', 'man-getting-massage', 'massage', 'woman-getting-haircut', 'man-getting-haircut', 'haircut', 'barber', 'syringe', 'pill', 'kiss', 'love_letter', 'ring', 'gem', 'couplekiss', 'bouquet', 'couple_with_heart', 'wedding', 'heartbeat', 'broken_heart', 'two_hearts', 'sparkling_heart', 'heartpulse', 'cupid', 'blue_heart', 'green_heart', 'yellow_heart', 'purple_heart', 'gift_heart', 'revolving_hearts', 'heart_decoration', 'diamond_shape_with_a_dot_inside', 'bulb', 'anger', 'bomb', 'zzz', 'boom', 'sweat_drops', 'droplet', 'dash', 'hankey', 'muscle', 'dizzy', 'speech_balloon', 'thought_balloon', 'white_flower', '100', 'moneybag', 'currency_exchange', 'heavy_dollar_sign', 'credit_card', 'yen', 'dollar', 'euro', 'pound', 'money_with_wings', 'chart', 'seat', 'computer', 'briefcase', 'minidisc', 'floppy_disk', 'cd', 'dvd', 'file_folder', 'open_file_folder', 'page_with_curl', 'page_facing_up', 'date', 'calendar', 'card_index', 'chart_with_upwards_trend', 'chart_with_downwards_trend', 'bar_chart', 'clipboard', 'pushpin', 'round_pushpin', 'paperclip', 'straight_ruler', 'triangular_ruler', 'bookmark_tabs', 'ledger', 'notebook', 'notebook_with_decorative_cover', 'closed_book', 'book', 'green_book', 'blue_book', 'orange_book', 'books', 'name_badge', 'scroll', 'memo', 'telephone_receiver', 'pager', 'fax', 'satellite_antenna', 'loudspeaker', 'mega', 'outbox_tray', 'inbox_tray', 'package', 'e-mail', 'incoming_envelope', 'envelope_with_arrow', 'mailbox_closed', 'mailbox', 'mailbox_with_mail', 'mailbox_with_no_mail', 'postbox', 'postal_horn', 'newspaper', 'iphone', 'calling', 'vibration_mode', 'mobile_phone_off', 'no_mobile_phones', 'signal_strength', 'camera', 'camera_with_flash', 'video_camera', 'tv', 'radio', 'vhs', 'film_projector', 'prayer_beads', 'twisted_rightwards_arrows', 'repeat', 'repeat_one', 'arrows_clockwise', 'arrows_counterclockwise', 'low_brightness', 'high_brightness', 'mute', 'speaker', 'sound', 'loud_sound', 'battery', 'electric_plug', 'mag', 'mag_right', 'lock_with_ink_pen', 'closed_lock_with_key', 'key', 'lock', 'unlock', 'bell', 'no_bell', 'bookmark', 'link', 'radio_button', 'back', 'end', 'on', 'soon', 'top', 'underage', 'keycap_ten', 'capital_abcd', 'abcd', '1234', 'symbols', 'abc', 'fire', 'flashlight', 'wrench', 'hammer', 'nut_and_bolt', 'hocho', 'gun', 'microscope', 'telescope', 'crystal_ball', 'six_pointed_star', 'beginner', 'trident', 'black_square_button', 'white_square_button', 'red_circle', 'large_blue_circle', 'large_orange_diamond', 'large_blue_diamond', 'small_orange_diamond', 'small_blue_diamond', 'small_red_triangle', 'small_red_triangle_down', 'arrow_up_small', 'arrow_down_small', 'om_symbol', 'dove_of_peace', 'kaaba', 'mosque', 'synagogue', 'menorah_with_nine_branches', 'clock1', 'clock2', 'clock3', 'clock4', 'clock5', 'clock6', 'clock7', 'clock8', 'clock9', 'clock10', 'clock11', 'clock12', 'clock130', 'clock230', 'clock330', 'clock430', 'clock530', 'clock630', 'clock730', 'clock830', 'clock930', 'clock1030', 'clock1130', 'clock1230', 'candle', 'mantelpiece_clock', 'hole', 'man_in_business_suit_levitating', 'female-detective', 'male-detective', 'sleuth_or_spy', 'dark_sunglasses', 'spider', 'spider_web', 'joystick', 'man_dancing', 'linked_paperclips', 'lower_left_ballpoint_pen', 'lower_left_fountain_pen', 'lower_left_paintbrush', 'lower_left_crayon', 'raised_hand_with_fingers_splayed', 'middle_finger', 'spock-hand', 'black_heart', 'desktop_computer', 'printer', 'three_button_mouse', 'trackball', 'frame_with_picture', 'card_index_dividers', 'card_file_box', 'file_cabinet', 'wastebasket', 'spiral_note_pad', 'spiral_calendar_pad', 'compression', 'old_key', 'rolled_up_newspaper', 'dagger_knife', 'speaking_head_in_silhouette', 'left_speech_bubble', 'right_anger_bubble', 'ballot_box_with_ballot', 'world_map', 'mount_fuji', 'tokyo_tower', 'statue_of_liberty', 'japan', 'moyai', 'grinning', 'grin', 'joy', 'smiley', 'smile', 'sweat_smile', 'laughing', 'innocent', 'smiling_imp', 'wink', 'blush', 'yum', 'relieved', 'heart_eyes', 'sunglasses', 'smirk', 'neutral_face', 'expressionless', 'unamused', 'sweat', 'pensive', 'confused', 'confounded', 'kissing', 'kissing_heart', 'kissing_smiling_eyes', 'kissing_closed_eyes', 'stuck_out_tongue', 'stuck_out_tongue_winking_eye', 'stuck_out_tongue_closed_eyes', 'disappointed', 'worried', 'angry', 'rage', 'cry', 'persevere', 'triumph', 'disappointed_relieved', 'frowning', 'anguished', 'fearful', 'weary', 'sleepy', 'tired_face', 'grimacing', 'sob', 'open_mouth', 'hushed', 'cold_sweat', 'scream', 'astonished', 'flushed', 'sleeping', 'dizzy_face', 'no_mouth', 'mask', 'smile_cat', 'joy_cat', 'smiley_cat', 'heart_eyes_cat', 'smirk_cat', 'kissing_cat', 'pouting_cat', 'crying_cat_face', 'scream_cat', 'slightly_frowning_face', 'slightly_smiling_face', 'upside_down_face', 'face_with_rolling_eyes', 'woman-gesturing-no', 'man-gesturing-no', 'no_good', 'woman-gesturing-ok', 'man-gesturing-ok', 'ok_woman', 'woman-bowing', 'man-bowing', 'bow', 'see_no_evil', 'hear_no_evil', 'speak_no_evil', 'woman-raising-hand', 'man-raising-hand', 'raising_hand', 'raised_hands', 'woman-frowning', 'man-frowning', 'person_frowning', 'woman-pouting', 'man-pouting', 'person_with_pouting_face', 'pray', 'rocket', 'helicopter', 'steam_locomotive', 'railway_car', 'bullettrain_side', 'bullettrain_front', 'train2', 'metro', 'light_rail', 'station', 'tram', 'train', 'bus', 'oncoming_bus', 'trolleybus', 'busstop', 'minibus', 'ambulance', 'fire_engine', 'police_car', 'oncoming_police_car', 'taxi', 'oncoming_taxi', 'car', 'oncoming_automobile', 'blue_car', 'truck', 'articulated_lorry', 'tractor', 'monorail', 'mountain_railway', 'suspension_railway', 'mountain_cableway', 'aerial_tramway', 'ship', 'woman-rowing-boat', 'man-rowing-boat', 'rowboat', 'speedboat', 'traffic_light', 'vertical_traffic_light', 'construction', 'rotating_light', 'triangular_flag_on_post', 'door', 'no_entry_sign', 'smoking', 'no_smoking', 'put_litter_in_its_place', 'do_not_litter', 'potable_water', 'non-potable_water', 'bike', 'no_bicycles', 'woman-biking', 'man-biking', 'bicyclist', 'woman-mountain-biking', 'man-mountain-biking', 'mountain_bicyclist', 'woman-walking', 'man-walking', 'walking', 'no_pedestrians', 'children_crossing', 'mens', 'womens', 'restroom', 'baby_symbol', 'toilet', 'wc', 'shower', 'bath', 'bathtub', 'passport_control', 'customs', 'baggage_claim', 'left_luggage', 'couch_and_lamp', 'sleeping_accommodation', 'shopping_bags', 'bellhop_bell', 'bed', 'place_of_worship', 'octagonal_sign', 'shopping_trolley', 'hammer_and_wrench', 'shield', 'oil_drum', 'motorway', 'railway_track', 'motor_boat', 'small_airplane', 'airplane_departure', 'airplane_arriving', 'satellite', 'passenger_ship', 'scooter', 'motor_scooter', 'canoe', 'sled', 'flying_saucer', 'skateboard', 'zipper_mouth_face', 'money_mouth_face', 'face_with_thermometer', 'nerd_face', 'thinking_face', 'face_with_head_bandage', 'robot_face', 'hugging_face', 'the_horns', 'call_me_hand', 'raised_back_of_hand', 'left-facing_fist', 'right-facing_fist', 'handshake', 'crossed_fingers', 'i_love_you_hand_sign', 'face_with_cowboy_hat', 'clown_face', 'nauseated_face', 'rolling_on_the_floor_laughing', 'drooling_face', 'lying_face', 'woman-facepalming', 'man-facepalming', 'face_palm', 'sneezing_face', 'face_with_raised_eyebrow', 'star-struck', 'zany_face', 'shushing_face', 'face_with_symbols_on_mouth', 'face_with_hand_over_mouth', 'face_vomiting', 'exploding_head', 'pregnant_woman', 'breast-feeding', 'palms_up_together', 'selfie', 'prince', 'man_in_tuxedo', 'mrs_claus', 'woman-shrugging', 'man-shrugging', 'shrug', 'woman-cartwheeling', 'man-cartwheeling', 'person_doing_cartwheel', 'woman-juggling', 'man-juggling', 'juggling', 'fencer', 'woman-wrestling', 'man-wrestling', 'wrestlers', 'woman-playing-water-polo', 'man-playing-water-polo', 'water_polo', 'woman-playing-handball', 'man-playing-handball', 'handball', 'wilted_flower', 'drum_with_drumsticks', 'clinking_glasses', 'tumbler_glass', 'spoon', 'goal_net', 'first_place_medal', 'second_place_medal', 'third_place_medal', 'boxing_glove', 'martial_arts_uniform', 'curling_stone', 'lacrosse', 'softball', 'flying_disc', 'croissant', 'avocado', 'cucumber', 'bacon', 'potato', 'carrot', 'baguette_bread', 'green_salad', 'shallow_pan_of_food', 'stuffed_flatbread', 'egg', 'glass_of_milk', 'peanuts', 'kiwifruit', 'pancakes', 'dumpling', 'fortune_cookie', 'takeout_box', 'chopsticks', 'bowl_with_spoon', 'cup_with_straw', 'coconut', 'broccoli', 'pie', 'pretzel', 'cut_of_meat', 'sandwich', 'canned_food', 'leafy_green', 'mango', 'moon_cake', 'bagel', 'smiling_face_with_3_hearts', 'partying_face', 'woozy_face', 'hot_face', 'cold_face', 'pleading_face', 'lab_coat', 'goggles', 'hiking_boot', 'womans_flat_shoe', 'crab', 'lion_face', 'scorpion', 'turkey', 'unicorn_face', 'eagle', 'duck', 'bat', 'shark', 'owl', 'fox_face', 'butterfly', 'deer', 'gorilla', 'lizard', 'rhinoceros', 'shrimp', 'squid', 'giraffe_face', 'zebra_face', 'hedgehog', 'sauropod', 't-rex', 'cricket', 'kangaroo', 'llama', 'peacock', 'hippopotamus', 'parrot', 'raccoon', 'lobster', 'mosquito', 'microbe', 'badger', 'swan', 'bone', 'leg', 'foot', 'tooth', 'female_superhero', 'male_superhero', 'female_supervillain', 'male_supervillain', 'cheese_wedge', 'cupcake', 'salt', 'face_with_monocle', 'adult', 'child', 'older_adult', 'bearded_person', 'person_with_headscarf', 'woman_in_steamy_room', 'man_in_steamy_room', 'person_in_steamy_room', 'woman_climbing', 'man_climbing', 'person_climbing', 'woman_in_lotus_position', 'man_in_lotus_position', 'person_in_lotus_position', 'female_mage', 'male_mage', 'mage', 'female_fairy', 'male_fairy', 'fairy', 'female_vampire', 'male_vampire', 'vampire', 'mermaid', 'merman', 'merperson', 'female_elf', 'male_elf', 'elf', 'female_genie', 'male_genie', 'genie', 'female_zombie', 'male_zombie', 'zombie', 'brain', 'orange_heart', 'billed_cap', 'scarf', 'gloves', 'coat', 'socks', 'red_envelope', 'firecracker', 'jigsaw', 'test_tube', 'petri_dish', 'dna', 'compass', 'abacus', 'fire_extinguisher', 'toolbox', 'bricks', 'magnet', 'luggage', 'lotion_bottle', 'thread', 'yarn', 'safety_pin', 'teddy_bear', 'broom', 'basket', 'roll_of_paper', 'soap', 'sponge', 'receipt', 'nazar_amulet', 'bangbang', 'interrobang', 'tm', 'information_source', 'left_right_arrow', 'arrow_up_down', 'arrow_upper_left', 'arrow_upper_right', 'arrow_lower_right', 'arrow_lower_left', 'leftwards_arrow_with_hook', 'arrow_right_hook', 'watch', 'hourglass', 'keyboard', 'eject', 'fast_forward', 'rewind', 'arrow_double_up', 'arrow_double_down', 'black_right_pointing_double_triangle_with_vertical_bar', 'black_left_pointing_double_triangle_with_vertical_bar', 'black_right_pointing_triangle_with_double_vertical_bar', 'alarm_clock', 'stopwatch', 'timer_clock', 'hourglass_flowing_sand', 'double_vertical_bar', 'black_square_for_stop', 'black_circle_for_record', 'm', 'black_small_square', 'white_small_square', 'arrow_forward', 'arrow_backward', 'white_medium_square', 'black_medium_square', 'white_medium_small_square', 'black_medium_small_square', 'sunny', 'cloud', 'umbrella', 'snowman', 'comet', 'phone', 'ballot_box_with_check', 'umbrella_with_rain_drops', 'coffee', 'shamrock', 'point_up', 'skull_and_crossbones', 'radioactive_sign', 'biohazard_sign', 'orthodox_cross', 'star_and_crescent', 'peace_symbol', 'yin_yang', 'wheel_of_dharma', 'white_frowning_face', 'relaxed', 'female_sign', 'male_sign', 'aries', 'taurus', 'gemini', 'cancer', 'leo', 'virgo', 'libra', 'scorpius', 'sagittarius', 'capricorn', 'aquarius', 'pisces', 'chess_pawn', 'spades', 'clubs', 'hearts', 'diamonds', 'hotsprings', 'recycle', 'infinity', 'wheelchair', 'hammer_and_pick', 'anchor', 'crossed_swords', 'medical_symbol', 'scales', 'alembic', 'gear', 'atom_symbol', 'fleur_de_lis', 'warning', 'zap', 'white_circle', 'black_circle', 'coffin', 'funeral_urn', 'soccer', 'baseball', 'snowman_without_snow', 'partly_sunny', 'thunder_cloud_and_rain', 'ophiuchus', 'pick', 'helmet_with_white_cross', 'chains', 'no_entry', 'shinto_shrine', 'church', 'mountain', 'umbrella_on_ground', 'fountain', 'golf', 'ferry', 'boat', 'skier', 'ice_skate', 'woman-bouncing-ball', 'man-bouncing-ball', 'person_with_ball', 'tent', 'fuelpump', 'scissors', 'white_check_mark', 'airplane', 'email', 'fist', 'hand', 'v', 'writing_hand', 'pencil2', 'black_nib', 'heavy_check_mark', 'heavy_multiplication_x', 'latin_cross', 'star_of_david', 'sparkles', 'eight_spoked_asterisk', 'eight_pointed_black_star', 'snowflake', 'sparkle', 'x', 'negative_squared_cross_mark', 'question', 'grey_question', 'grey_exclamation', 'exclamation', 'heavy_heart_exclamation_mark_ornament', 'heart', 'heavy_plus_sign', 'heavy_minus_sign', 'heavy_division_sign', 'arrow_right', 'curly_loop', 'loop', 'arrow_heading_up', 'arrow_heading_down', 'arrow_left', 'arrow_up', 'arrow_down', 'black_large_square', 'white_large_square', 'star', 'o', 'wavy_dash', 'part_alternation_mark', 'congratulations', 'secret'] -------------------------------------------------------------------------------- /slacky/helpers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4cs/Slacky/992dc75297295878ba37fa64a5ff44c50beac890/slacky/helpers/__init__.py -------------------------------------------------------------------------------- /slacky/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | from slacky import config, client, Prefixes, listener, check_user, customrs, bot, version 2 | from slacky.constants.emojis import emojis 3 | from slack.errors import SlackApiError 4 | from terminaltables import DoubleTable 5 | from howdoi import howdoi 6 | from threading import Thread 7 | from pyfiglet import Figlet 8 | from io import BytesIO 9 | import requests 10 | import pandas as pd 11 | import json 12 | import slack 13 | import httpx 14 | import time 15 | import random 16 | import re 17 | import glob 18 | import os 19 | import ntpath 20 | 21 | def cmd_setup(command, **payload): 22 | data = payload['data'] 23 | channel_id = data['channel'] 24 | user = data.get('user') 25 | timestamp = data.get('ts') 26 | web_client = client 27 | text = data.get('text') 28 | if text and check_user(user): 29 | text_split = text.split(' ') 30 | cmd = text_split[0] 31 | if cmd == config['prefix'] + command: 32 | print(Prefixes.event + 'Ran Command: {}'.format(command)) 33 | bot.command_count += 1 34 | return data, channel_id, user, timestamp, web_client, text, text_split 35 | else: 36 | return None, None, None, None, None, None, None 37 | else: 38 | return None, None, None, None, None, None, None 39 | 40 | from slacky.plugins.custom import * 41 | 42 | def coronavirus(**payload): 43 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('coronastatus', **payload) 44 | if data: 45 | try: 46 | web_client.chat_update( 47 | channel=channel_id, 48 | ts=timestamp, 49 | text="One Second" 50 | ) 51 | except SlackApiError as e: 52 | bot.error(e) 53 | csv_content = requests.get('https://docs.google.com/spreadsheets/d/1yZv9w9zRKwrGTaR-YzmAqMefw4wMlaXocejdxZaTs6w/export?usp=sharing&format=csv').content 54 | df = pd.read_csv(BytesIO(csv_content), index_col=0) 55 | places = df['Country/Region'] 56 | confirmed = df['Confirmed'] 57 | deaths = df['Deaths'] 58 | recovered = df['Recovered'] 59 | total_confirm = confirmed.sum() 60 | total_deaths = deaths.sum() 61 | total_recov = recovered.sum() 62 | region_dict = {} 63 | for place, confirm, death in zip(places, confirmed, deaths): 64 | if region_dict.get(place): 65 | region_dict[place]['confirms'] = int(region_dict[place].get('confirms')) + int(confirm) if str(confirm).lower() != 'nan' else 0 66 | region_dict[place]['deaths'] = int(region_dict[place].get('deaths')) + int(death) if str(death).lower() != 'nan' else 0 67 | else: 68 | region_dict[place] = { 69 | 'confirms': int(confirm) if str(confirm).lower() != 'nan' else 0, 70 | 'deaths': int(death) if str(death).lower() != 'nan' else 0, 71 | } 72 | blocks = [ 73 | { 74 | 'type': 'section', 75 | 'text': { 76 | 'type': 'mrkdwn', 77 | 'text': '*Status of Coronavirus:*' 78 | } 79 | }, 80 | { 81 | 'type': 'section', 82 | 'fields': [ 83 | { 84 | 'type': 'mrkdwn', 85 | 'text': ':mask: *Confirmed Cases: {}*'.format(int(total_confirm)) 86 | }, 87 | { 88 | 'type': 'mrkdwn', 89 | 'text': ':skull: *Deaths: {}*'.format(int(total_deaths)) 90 | }, 91 | { 92 | 'type': 'mrkdwn', 93 | 'text': ':muscle: *Recovered: {}*'.format(int(total_recov)) 94 | } 95 | ] 96 | } 97 | ] 98 | blocks.append({"type": "section", "fields": []}) 99 | count = 0 100 | for k, v in region_dict.items(): 101 | if count == 10: 102 | break 103 | blocks[2]['fields'].append({ 104 | "type": "mrkdwn", 105 | "text": "*Region:* {}\n*Confirmed:* {}\n*Deaths:* {}\n==================\n".format(k, region_dict[k]['confirms'], region_dict[k]['deaths']) 106 | }) 107 | count += 1 108 | blocks.append({"type": "section", "fields": []}) 109 | new_count = 0 110 | for k, v in region_dict.items(): 111 | if new_count >= count: 112 | blocks[3]['fields'].append({ 113 | "type": "mrkdwn", 114 | "text": "*Region:* {}\n*Confirmed:* {}\n*Deaths:* {}\n=================\n".format(k, region_dict[k]['confirms'], region_dict[k]['deaths']) 115 | }) 116 | new_count += 1 117 | 118 | try: 119 | web_client.chat_update( 120 | channel=channel_id, 121 | ts=timestamp, 122 | attachments=[{ 123 | 'color': '#f43d2d', 124 | 'blocks': blocks 125 | }] 126 | ) 127 | except SlackApiError as e: 128 | bot.error(e) 129 | 130 | def ping(**payload): 131 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('ping', **payload) 132 | if data: 133 | real_ts = float(timestamp) 134 | now = float(time.time()) 135 | relay = int(float(now - real_ts) * 1000) 136 | try: 137 | web_client.chat_update( 138 | channel=channel_id, 139 | ts=timestamp, 140 | text='', 141 | attachments=[ 142 | { 143 | "color": "#f8f33a", 144 | "blocks": [ 145 | { 146 | "type": "section", 147 | "text": { 148 | "type": "mrkdwn", 149 | "text": ":signal_strength: *Response Time:* {}ms".format(relay) 150 | } 151 | } 152 | ] 153 | } 154 | ] 155 | ) 156 | except SlackApiError as e: 157 | bot.error(e) 158 | 159 | def msgstatus(**payload): 160 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('msgstatus', **payload) 161 | if data: 162 | if bot.msgstatus: 163 | bot.msgstatus = False 164 | try: 165 | web_client.chat_update( 166 | channel=channel_id, 167 | ts=timestamp, 168 | text="On-Message Status Change Disabled" 169 | ) 170 | except SlackApiError as e: 171 | bot.error(e) 172 | else: 173 | bot.msgstatus = True 174 | try: 175 | web_client.chat_update( 176 | channel=channel_id, 177 | ts=timestamp, 178 | text="On-Message Status Change Enabled" 179 | ) 180 | except SlackApiError as e: 181 | bot.error(e) 182 | 183 | def winfo(**payload): 184 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('winfo', **payload) 185 | if data: 186 | try: 187 | web_client.chat_update( 188 | channel=channel_id, 189 | ts=timestamp, 190 | text="Getting Workspace Info.." 191 | ) 192 | except SlackApiError as e: 193 | bot.error(e) 194 | try: 195 | t_info = web_client.team_info() 196 | except SlackApiError as e: 197 | bot.error(e) 198 | try: 199 | chan_count = 0 200 | cursor = "" 201 | while True: 202 | c_info = web_client.conversations_list(limit=100,cursor=cursor) 203 | for _ in c_info['channels']: 204 | chan_count += 1 205 | if c_info.get('response_metadata').get("next_cursor"): 206 | cursor = c_info['response_metadata'].get("next_cursor") 207 | pass 208 | else: 209 | break 210 | except SlackApiError as e: 211 | bot.error(e) 212 | 213 | if t_info and chan_count: 214 | team = t_info['team'] 215 | blocks = [ 216 | { 217 | "type": "section", 218 | "text": { 219 | "type": "mrkdwn", 220 | "text": ":slack: *Team Info for {}:*".format(team['name']) 221 | }, 222 | "accessory": { 223 | "type": "image", 224 | "image_url": team['icon']['image_230'], 225 | "alt_text": "Team Icon" 226 | } 227 | }, 228 | { 229 | "type": "section", 230 | "fields": [ 231 | { 232 | "type": "mrkdwn", 233 | "text": "*ID:* {}".format(team['id']) 234 | }, 235 | { 236 | "type": "mrkdwn", 237 | "text": "*Domain:* {}.slack.com".format(team['domain']) 238 | }, 239 | { 240 | "type": "mrkdwn", 241 | "text": "*E-Mail Format:* email@{}".format(team.get('email_domain') if team.get('email_domain') else "N/A") 242 | }, 243 | { 244 | "type": "mrkdwn", 245 | "text": "*Enterprise Name:* {}".format(team.get('enterprise_name') if team.get('enterprise_name') else 'N/A') 246 | }, 247 | { 248 | "type": "mrkdwn", 249 | "text": "*Channel Count:* {}".format(chan_count) 250 | } 251 | ] 252 | } 253 | ] 254 | try: 255 | web_client.chat_update( 256 | channel=channel_id, 257 | ts=timestamp, 258 | attachments=[ 259 | { 260 | 'color': '#0f87ff', 261 | 'blocks': blocks 262 | } 263 | ] 264 | ) 265 | except SlackApiError as e: 266 | bot.error(e) 267 | 268 | 269 | 270 | def uinfo(**payload): 271 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('uinfo', **payload) 272 | if data: 273 | if len(text_split) != 2: 274 | try: 275 | web_client.chat_update( 276 | channel=channel_id, 277 | ts=timestamp, 278 | text="Correct syntax is: `uinfo @user`" 279 | ) 280 | except SlackApiError as e: 281 | bot.error(e) 282 | else: 283 | try: 284 | web_client.chat_update( 285 | channel=channel_id, 286 | ts=timestamp, 287 | text="Getting User Info.." 288 | ) 289 | except SlackApiError as e: 290 | bot.error(e) 291 | try: 292 | user_list = client.users_list() 293 | except SlackApiError as e: 294 | bot.error(e) 295 | if user_list: 296 | query = text_split[1].split('@')[1].split('>')[0] 297 | match = None 298 | for wuser in user_list['members']: 299 | if wuser['id'] == query: 300 | match = wuser 301 | break 302 | if match: 303 | blocks = [ 304 | { 305 | 'type': 'section', 306 | 'text': { 307 | 'type': 'mrkdwn', 308 | 'text': ':slack: *User Information:*' 309 | } 310 | }, 311 | { 312 | 'type': 'section', 313 | 'text': { 314 | 'type': 'mrkdwn', 315 | 'text': '*Name:* {}\n*Status:* {}'.format(match['real_name'], match['profile'].get('status_text') if match['profile'].get('status_text') else 'N/A') 316 | }, 317 | 'fields': [ 318 | { 319 | 'type': 'mrkdwn', 320 | 'text': '*Title:* {}'.format(match['profile'].get('title') if match['profile'].get('title') else 'N/A') 321 | }, 322 | { 323 | 'type': 'mrkdwn', 324 | 'text': '*Username:* {}'.format(match['name']) 325 | }, 326 | { 327 | 'type': 'mrkdwn', 328 | 'text': '*E-Mail:* {}'.format(match['profile']['email']) 329 | }, 330 | { 331 | 'type': 'mrkdwn', 332 | 'text': '*Timezone:* {}'.format(match['tz_label']) 333 | }, 334 | { 335 | 'type': 'mrkdwn', 336 | 'text': '*Phone:* {}'.format(match['profile'].get('phone') if match['profile'].get('phone') else 'N/A') 337 | } 338 | ], 339 | 'accessory': { 340 | 'type': 'image', 341 | 'image_url': match['profile'].get('image_512'), 342 | 'alt_text': 'Profile Picture' 343 | } 344 | } 345 | ] 346 | try: 347 | web_client.chat_update( 348 | channel=channel_id, 349 | ts=timestamp, 350 | attachments=[{ 351 | 'color': '#0f87ff', 352 | 'blocks': blocks 353 | }] 354 | ) 355 | except SlackApiError as e: 356 | bot.error(e) 357 | 358 | else: 359 | try: 360 | web_client.chat_update( 361 | channel=channel_id, 362 | ts=timestamp, 363 | text="No User Found." 364 | ) 365 | except SlackApiError as e: 366 | bot.error(e) 367 | 368 | def errors(**payload): 369 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('errors', **payload) 370 | if data: 371 | if len(bot.errors) > 0: 372 | blocks = [ 373 | { 374 | 'type': 'section', 375 | 'text': { 376 | 'type': 'mrkdwn', 377 | 'text': '*Slacky Bot Errors:*' 378 | } 379 | } 380 | ] 381 | msg = "" 382 | for error in bot.errors: 383 | msg += str(error + '\n') 384 | blocks.append({ 385 | 'type': 'section', 386 | 'text': { 387 | 'type': 'mrkdwn', 388 | 'text': '```' + msg + '```' 389 | } 390 | }) 391 | try: 392 | web_client.chat_update( 393 | channel=channel_id, 394 | ts=timestamp, 395 | attachments=[{ 396 | 'color': '#0f87ff', 397 | 'blocks': blocks 398 | }] 399 | ) 400 | except SlackApiError as e: 401 | bot.error(e) 402 | 403 | else: 404 | try: 405 | web_client.chat_update( 406 | channel=channel_id, 407 | ts=timestamp, 408 | text='No Errors Triggered!' 409 | ) 410 | except SlackApiError as e: 411 | bot.error(e) 412 | 413 | 414 | def animations(**payload): 415 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('ani', **payload) 416 | if data: 417 | if len(text_split) < 2: 418 | try: 419 | web_client.chat_delete( 420 | channel=channel_id, 421 | ts=timestamp 422 | ) 423 | except SlackApiError as e: 424 | bot.error(e) 425 | 426 | else: 427 | if len(text_split) == 2: 428 | loop = 1 429 | elif len(text_split) == 3: 430 | loop = int(text_split[2]) 431 | target_file = None 432 | for file in glob.glob('animations/*.txt'): 433 | if ntpath.basename(file).strip('.txt') == text_split[1]: 434 | target_file = os.path.realpath(file) 435 | if target_file: 436 | with open(target_file, 'r') as anif: 437 | full_ani = anif.read() 438 | lines = full_ani.split('\n') 439 | interval = lines[0] 440 | msgs = [] 441 | tmp_msg = [] 442 | for line in lines[1:]: 443 | if ''.join(line).startswith('[f#]'): 444 | line = '[f#]' 445 | if line != '[f#]': 446 | tmp_msg.append(line) 447 | else: 448 | msgs.append(tmp_msg) 449 | tmp_msg = [] 450 | for i in range(loop): 451 | for msg in msgs: 452 | try: 453 | web_client.chat_update( 454 | channel=channel_id, 455 | ts=timestamp, 456 | text='```' + '\n'.join(msg) + '```' 457 | ) 458 | except SlackApiError as e: 459 | bot.error(e) 460 | 461 | time.sleep(float(interval)) 462 | else: 463 | try: 464 | web_client.chat_update( 465 | channel=channel_id, 466 | ts=timestamp, 467 | text="**No animation with that name found!**" 468 | ) 469 | except SlackApiError as e: 470 | bot.error(e) 471 | 472 | 473 | def stats(**payload): 474 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('stats', **payload) 475 | if data: 476 | workspace = client.team_info()['team']['name'] 477 | real_ts = float(timestamp) 478 | now = float(time.time()) 479 | relay = int(float(now - real_ts) * 1000) 480 | if bot.needs_update: 481 | version_text = str(version) + " (Update Available)" 482 | else: 483 | version_text = str(version) 484 | blocks = [ 485 | { 486 | "type": "section", 487 | "text": { 488 | "type": "mrkdwn", 489 | "text": ":slack: *Slacky Bot Statistics:*" 490 | } 491 | }, 492 | { 493 | "type": "section", 494 | "fields": [ 495 | { 496 | "type": "mrkdwn", 497 | "text": ":clock1: Uptime: *{}*".format(bot.get_uptime()) 498 | }, 499 | { 500 | "type": "mrkdwn", 501 | "text": ":bangbang: *Error Count*: *{}*".format(bot.error_count) 502 | }, 503 | { 504 | "type": "mrkdwn", 505 | "text": ":keyboard: *Command Count*: *{}*".format(bot.command_count) 506 | }, 507 | { 508 | "type": "mrkdwn", 509 | "text": ":warning: *Warning Count*: *{}*".format(bot.warning_count) 510 | }, 511 | { 512 | "type": "mrkdwn", 513 | "text": ":computer: *Current Workspace*: *{}*".format(workspace) 514 | }, 515 | { 516 | "type": "mrkdwn", 517 | "text": ":eyes: *Msgs Parsed*: *{}*".format(bot.message_count) 518 | }, 519 | { 520 | "type": "mrkdwn", 521 | "text": ":signal_strength: *Response Time:* {}ms".format(relay) 522 | }, 523 | { 524 | "type": "mrkdwn", 525 | "text": "*Version*: {}".format(version_text) 526 | } 527 | ] 528 | } 529 | ] 530 | try: 531 | web_client.chat_update( 532 | channel=channel_id, 533 | ts=timestamp, 534 | text="", 535 | attachments=[{ 536 | 'color': '#0f87ff', 537 | 'blocks': blocks 538 | }] 539 | ) 540 | except SlackApiError as e: 541 | bot.error(e) 542 | 543 | def customrscmd(**payload): 544 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('customrs', **payload) 545 | if data: 546 | if len(text_split) < 2: 547 | try: 548 | web_client.chat_update( 549 | channel=channel_id, 550 | text="Missing Arguments. Check the wiki for more information.", 551 | ts=timestamp 552 | ) 553 | bot.warning_count += 1 554 | except SlackApiError as e: 555 | bot.error(e) 556 | 557 | else: 558 | action = text_split[1] 559 | if action == "add": 560 | if len(text_split) < 4: 561 | try: 562 | web_client.chat_update( 563 | channel=channel_id, 564 | text="Missing Arguments. Check the wiki for more information.", 565 | ts=timestamp 566 | ) 567 | bot.warning_count += 1 568 | except SlackApiError as e: 569 | bot.error(e) 570 | 571 | else: 572 | ans = re.findall(r'["“‘\'](.*?)[\'’”"]', text) 573 | trigger = ans[0] 574 | reply = ans[1] 575 | is_strict = text_split[-1] 576 | if is_strict == "strict": 577 | is_strict = True 578 | else: 579 | is_strict = False 580 | custom_r = { 581 | 'trigger': trigger, 582 | 'reply': reply, 583 | 'is_strict': is_strict 584 | } 585 | customrs.add(custom_r) 586 | try: 587 | web_client.chat_update( 588 | channel=channel_id, 589 | text="Added Custom Reply. Trigger is \"{}\" and Reply will be \"{}\".\nStrict: {}".format(trigger, reply, is_strict), 590 | ts=timestamp 591 | ) 592 | except SlackApiError as e: 593 | bot.error(e) 594 | 595 | elif action == "delete": 596 | if len(text_split) < 3: 597 | try: 598 | web_client.chat_update( 599 | channel=channel_id, 600 | text="Missing Arguments. Check the wiki for more information.", 601 | ts=timestamp 602 | ) 603 | bot.warning_count += 1 604 | except SlackApiError as e: 605 | bot.error(e) 606 | 607 | else: 608 | num = text_split[2] 609 | try: 610 | customrs.delete(num) 611 | try: 612 | web_client.chat_update( 613 | channel=channel_id, 614 | text="Deleted Custom Reply.", 615 | ts=timestamp 616 | ) 617 | except SlackApiError as e: 618 | bot.error(e) 619 | except IndexError as e: 620 | try: 621 | web_client.chat_update( 622 | channel=channel_id, 623 | text="No Custom Reply with That ID", 624 | ts=timestamp 625 | ) 626 | except SlackApiError as e: 627 | bot.error(e) 628 | elif action == "list": 629 | blocks = [] 630 | if len(customrs.custom_replies) > 0: 631 | for custom_reply in customrs.custom_replies: 632 | blocks.append({ 633 | "type": "section", 634 | "text": { 635 | "type": "mrkdwn", 636 | "text": "*#*: {}\n*Trigger:* {}\n*Reply:* {}\n*Strict:* {}".format(customrs.custom_replies.index(custom_reply), custom_reply['trigger'], custom_reply['reply'], custom_reply['is_strict']) 637 | } 638 | }) 639 | try: 640 | web_client.chat_update( 641 | channel=channel_id, 642 | blocks=blocks, 643 | ts=timestamp 644 | ) 645 | except SlackApiError as e: 646 | bot.error(e) 647 | 648 | else: 649 | try: 650 | web_client.chat_update( 651 | channel=channel_id, 652 | text="No Custom Replies Set! Add some to your config or use the customrs command.", 653 | ts=timestamp 654 | ) 655 | except SlackApiError as e: 656 | bot.error(e) 657 | 658 | 659 | def customrsd(**payload): 660 | bot.message_count += 1 661 | data = payload['data'] 662 | channel_id = data['channel'] 663 | user = data.get('user') 664 | timestamp = data.get('ts') 665 | text = data.get('text') 666 | if text: 667 | if user != config['user']: 668 | if not config['prefix'] in text: 669 | for custom_reply in customrs.custom_replies: 670 | if not custom_reply['is_strict']: 671 | if custom_reply['trigger'].lower() in text.lower(): 672 | try: 673 | client.chat_postMessage( 674 | channel=channel_id, 675 | text=custom_reply['reply'], 676 | as_user=True, 677 | ts=timestamp 678 | ) 679 | except SlackApiError as e: 680 | bot.error(e) 681 | 682 | else: 683 | if text.lower() == custom_reply['trigger'].lower(): 684 | try: 685 | client.chat_postMessage( 686 | channel=channel_id, 687 | text=custom_reply['reply'], 688 | as_user=True, 689 | ts=timestamp 690 | ) 691 | except SlackApiError as e: 692 | bot.error(e) 693 | 694 | 695 | def ascii(**payload): 696 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('ascii', **payload) 697 | if data: 698 | if len(text_split) < 2: 699 | try: 700 | web_client.chat_update( 701 | channel=channel_id, 702 | text="You need to feed a string to asciify!", 703 | ts=timestamp 704 | ) 705 | except SlackApiError as e: 706 | bot.error(e) 707 | 708 | else: 709 | rest = ' '.join(text_split[1:]) 710 | f = Figlet(font='slant') 711 | ascii_text = f.renderText(rest) 712 | try: 713 | web_client.chat_update( 714 | channel=channel_id, 715 | text="```{}```".format(ascii_text), 716 | ts=timestamp 717 | ) 718 | except SlackApiError as e: 719 | bot.error(e) 720 | 721 | 722 | def status(**payload): 723 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('setstatus', **payload) 724 | if data: 725 | if len(text_split) < 3: 726 | print(Prefixes.warning + 'Missing Arguments! Read Help For Information') 727 | bot.warning_count += 1 728 | else: 729 | emoji = text_split[1] 730 | phrase = ' '.join(text_split[2:]) 731 | try: 732 | web_client.users_profile_set( 733 | profile= { 734 | "status_text": phrase, 735 | "status_emoji": emoji, 736 | "status_expiration": 0 737 | }) 738 | web_client.chat_update( 739 | channel=channel_id, 740 | text="Set Status Successfully!", 741 | ts=timestamp 742 | ) 743 | except SlackApiError as e: 744 | bot.error(e) 745 | 746 | try: 747 | web_client.chat_update( 748 | channel=channel_id, 749 | text="Failed to set status.", 750 | ts=timestamp 751 | ) 752 | except SlackApiError as e: 753 | bot.error(e) 754 | 755 | 756 | def setprefix(**payload): 757 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('setprefix', **payload) 758 | if data: 759 | if len(text_split) != 2: 760 | try: 761 | web_client.chat_update( 762 | channel=channel_id, 763 | text="Please only feed one argument for your prefix. You can repeat special chars to act as a longer prefix.", 764 | ts=timestamp 765 | ) 766 | except SlackApiError as e: 767 | bot.error(e) 768 | 769 | else: 770 | prefix = text_split[1] 771 | config['prefix'] = prefix 772 | with open('config.json', 'r+') as file: 773 | obj = json.load(file) 774 | obj['prefix'] = prefix 775 | file.seek(0) 776 | json.dump(obj, file, indent=4) 777 | file.truncate() 778 | try: 779 | web_client.chat_update( 780 | channel=channel_id, 781 | text="Updated Prefix to: `{}`".format(prefix), 782 | ts=timestamp 783 | ) 784 | except SlackApiError as e: 785 | bot.error(e) 786 | 787 | 788 | def shelp(**payload): 789 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('help', **payload) 790 | if data: 791 | attachments = [ 792 | { 793 | "color": "#0f87ff", 794 | "blocks": [ 795 | { 796 | "type": "section", 797 | "text": { 798 | "type": "mrkdwn", 799 | "text": "*Slacky Help Menu:*" 800 | }, 801 | "fields": [ 802 | { 803 | "type": "mrkdwn", 804 | "text": "*General*\n`{}help general`\n".format(config['prefix']) 805 | }, 806 | { 807 | "type": "mrkdwn", 808 | "text": "*Fun*\n`{}help fun`\n".format(config['prefix']) 809 | }, 810 | { 811 | "type": "mrkdwn", 812 | "text": "*Info*\n`{}help info`\n".format(config['prefix']) 813 | }, 814 | { 815 | "type": "mrkdwn", 816 | "text": "*Custom Replies*\n`{}help cr`\n".format(config['prefix']) 817 | } 818 | ] 819 | } 820 | ] 821 | }] 822 | attachments_general = [{ 823 | "color": "#d94c63", 824 | "blocks": [ 825 | { 826 | "type": "section", 827 | "text": { 828 | "type": "mrkdwn", 829 | "text": "*General Commands:*" 830 | }, 831 | "fields": [ 832 | { 833 | "type": "mrkdwn", 834 | "text": "*ascii*\nConvert String To ASCII Art\n`{}ascii `\n".format(config['prefix']) 835 | }, 836 | { 837 | "type": "mrkdwn", 838 | "text": "*ani*\nPlay Animation\n`{}ani [loops]`\n".format(config['prefix']) 839 | }, 840 | { 841 | "type": "mrkdwn", 842 | "text": "*delete*\nDelete Last X Messages\n`{}delete `\n".format(config['prefix']) 843 | }, 844 | { 845 | "type": "mrkdwn", 846 | "text": "*howdoi*\nGet Code Snippet From Stack Overflow\n`{}howdoi `\n".format(config['prefix']) 847 | }, 848 | { 849 | "type": "mrkdwn", 850 | "text": "*react*\nReact to Last Message\n`{}react `\n".format(config['prefix']) 851 | }, 852 | { 853 | "type": "mrkdwn", 854 | "text": "*reactrand*\nReact to Last Message with Random Emoji\n`{}reactrand`\n".format(config['prefix']) 855 | }, 856 | { 857 | "type": "mrkdwn", 858 | "text": "*reactspam*\nSpam Last Message with Reactions\n`{}reactspam`\n".format(config['prefix']) 859 | }, 860 | { 861 | "type": "mrkdwn", 862 | "text": "*setstatus*\nSet Your Slack Status\n`{}setstatus `\n".format(config['prefix']) 863 | } 864 | ] 865 | } 866 | ] 867 | }] 868 | 869 | attachments_crs = [{ 870 | "color": "#b34cd9", 871 | "blocks": [ 872 | { 873 | "type": "section", 874 | "text": { 875 | "type": "mrkdwn", 876 | "text": "*Custom Reply Commands:*" 877 | }, 878 | "fields": [ 879 | { 880 | "type": "mrkdwn", 881 | "text": "*customrs add*\nAdd Custom Reply\n`{}customrs add \"\" \"\" [strict|optional]`\n".format(config['prefix']) 882 | }, 883 | { 884 | "type": "mrkdwn", 885 | "text": "*customrs delete*\nDelete Custom Reply\n`{}customrs delete `\n".format(config['prefix']) 886 | }, 887 | { 888 | "type": "mrkdwn", 889 | "text": "*customrs list*\nList Custom Replies\n`{}customrs list`\n".format(config['prefix']) 890 | }, 891 | { 892 | "type": "mrkdwn", 893 | "text": "*listener add*\nAdd a Listener\n`{}listener add `\n".format(config['prefix']) 894 | }, 895 | { 896 | "type": "mrkdwn", 897 | "text": "*listener delete*\nDelete a Listener\n`{}listener delete `\n".format(config['prefix']) 898 | }, 899 | ] 900 | } 901 | ] 902 | }] 903 | attachments_fun = [{ 904 | "color": "#4cd992", 905 | "blocks": [ 906 | { 907 | "type": "section", 908 | "text": { 909 | "type": "mrkdwn", 910 | "text": "*Fun Commands:*" 911 | }, 912 | "fields": [ 913 | { 914 | "type": "mrkdwn", 915 | "text": "*space*\nAdd a Space In Between Characters\n`{}space `\n".format(config['prefix']) 916 | }, 917 | { 918 | "type": "mrkdwn", 919 | "text": "*msgstatus*\nEnable/Disable Random Emoji in Status on Msgs\n`{}msgstatus`".format(config['prefix']) 920 | }, 921 | { 922 | "type": "mrkdwn", 923 | "text": "*subspace*\nSubstitute Every Space with an Emoji\n`{}subspace `\n".format(config['prefix']) 924 | }, 925 | { 926 | "type": "mrkdwn", 927 | "text": "*shift*\nGeNeRaTe ShIfT tExT\n`{}shift `\n".format(config['prefix']) 928 | }, 929 | { 930 | "type": "mrkdwn", 931 | "text": "*xkcd*\nGet The Daily xkcd Comic\n`{}xkcd`\n".format(config['prefix']) 932 | }, 933 | { 934 | "type": "mrkdwn", 935 | "text": "*deepfry*\nDeepfry User Image or Image from URL\n`{}deepfry`\n*CUSTOM PLUGIN, MUST LOAD MANUALLY*".format(config['prefix']) 936 | } 937 | ] 938 | } 939 | ] 940 | }] 941 | attachments_info = [{ 942 | "color": "#0f874a", 943 | "blocks": [ 944 | { 945 | "type": "section", 946 | "text": { 947 | "type": "mrkdwn", 948 | "text": "*Info Commands:*" 949 | }, 950 | "fields": [ 951 | { 952 | "type": "mrkdwn", 953 | "text": "*help*\nDisplay Help Menu\n`{}help`\n".format(config['prefix']) 954 | }, 955 | { 956 | "type": "mrkdwn", 957 | "text": "*info*\nDisplay Info About Bot\n`{}info`\n".format(config['prefix']) 958 | }, 959 | { 960 | "type": "mrkdwn", 961 | "text": "*convinfo*\nGet Info About Chat/Channel\n`{}convinfo [#channel]`\n".format(config['prefix']) 962 | }, 963 | { 964 | "type": "mrkdwn", 965 | "text": "*uinfo*\nGet Info About User\n`{}uinfo @user`\n".format(config['prefix']) 966 | }, 967 | { 968 | "type": "mrkdwn", 969 | "text": "*winfo*\nGet Info About Workspace\n`{}winfo`\n".format(config['prefix']) 970 | }, 971 | { 972 | "type": "mrkdwn", 973 | "text": "*stats*\nGet Stats About Bot\n`{}stats`\n".format(config['prefix']) 974 | }, 975 | { 976 | "type": "mrkdwn", 977 | "text": "*ping*\nGet Response Time of Bot\n`{}ping`\n".format(config['prefix']) 978 | }, 979 | { 980 | "type": "mrkdwn", 981 | "text": "*listener list*\nList All Listeners\n`{}listener list`\n".format(config['prefix']) 982 | } 983 | ] 984 | }, 985 | ] 986 | }] 987 | if len(text_split) < 2: 988 | try: 989 | web_client.chat_update( 990 | channel=channel_id, 991 | ts=timestamp, 992 | text='', 993 | attachments=attachments 994 | ) 995 | except SlackApiError as e: 996 | bot.error(e) 997 | else: 998 | if text_split[1] == 'general': 999 | try: 1000 | web_client.chat_update( 1001 | channel=channel_id, 1002 | ts=timestamp, 1003 | text='', 1004 | attachments=attachments_general 1005 | ) 1006 | except SlackApiError as e: 1007 | bot.error(e) 1008 | elif text_split[1] == 'fun': 1009 | try: 1010 | web_client.chat_update( 1011 | channel=channel_id, 1012 | ts=timestamp, 1013 | text='', 1014 | attachments=attachments_fun 1015 | ) 1016 | except SlackApiError as e: 1017 | bot.error(e) 1018 | elif text_split[1] == 'crs': 1019 | try: 1020 | web_client.chat_update( 1021 | channel=channel_id, 1022 | ts=timestamp, 1023 | text='', 1024 | attachments=attachments_crs 1025 | ) 1026 | except SlackApiError as e: 1027 | bot.error(e) 1028 | elif text_split[1] == 'info': 1029 | try: 1030 | web_client.chat_update( 1031 | channel=channel_id, 1032 | ts=timestamp, 1033 | text='', 1034 | attachments=attachments_info 1035 | ) 1036 | except SlackApiError as e: 1037 | bot.error(e) 1038 | 1039 | def reactrand(**payload): 1040 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('reactrand', **payload) 1041 | if data: 1042 | try: 1043 | web_client.chat_delete( 1044 | channel=channel_id, 1045 | ts=timestamp 1046 | ) 1047 | except SlackApiError as e: 1048 | bot.error(e) 1049 | 1050 | conv_info = client.conversations_history(channel=channel_id, count=1) 1051 | latest_ts = conv_info['messages'][0]['ts'] 1052 | try: 1053 | web_client.reactions_add( 1054 | channel=channel_id, 1055 | timestamp=latest_ts, 1056 | name=random.choice(emojis) 1057 | ) 1058 | except SlackApiError as e: 1059 | bot.error(e) 1060 | 1061 | 1062 | def reactspam(**payload): 1063 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('reactspam', **payload) 1064 | if data: 1065 | try: 1066 | web_client.chat_delete( 1067 | channel=channel_id, 1068 | ts=timestamp 1069 | ) 1070 | except SlackApiError as e: 1071 | bot.error(e) 1072 | 1073 | conv_info = client.conversations_history(channel=channel_id, count=1) 1074 | latest_ts = conv_info['messages'][0]['ts'] 1075 | for _ in range(23): 1076 | try: 1077 | web_client.reactions_add( 1078 | channel=channel_id, 1079 | timestamp=latest_ts, 1080 | name=random.choice(emojis) 1081 | ) 1082 | except SlackApiError as e: 1083 | bot.error(e) 1084 | 1085 | 1086 | def ud(**payload): 1087 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('ud', **payload) 1088 | if data: 1089 | if len(text_split) < 2: 1090 | web_client.chat_delete( 1091 | channel=channel_id, 1092 | ts=timestamp 1093 | ) 1094 | print(Prefixes.warning + 'Missing Arguments! Read Help For Information') 1095 | bot.warning_count += 1 1096 | else: 1097 | api = 'http://urbanscraper.herokuapp.com/define/' 1098 | term = '+'.join(text_split[1:]) 1099 | query = str(api + term) 1100 | res = httpx.get(query).json() 1101 | definition = res['definition'] 1102 | url = res['url'] 1103 | blocks = [ 1104 | { 1105 | "type": "section", 1106 | "text": { 1107 | "type": "mrkdwn", 1108 | "text": "*Urban Dictionary Definition For:* {}\n\n{}\n\n<{}|Link To Entry>".format(term, definition, url) 1109 | } 1110 | } 1111 | ] 1112 | try: 1113 | web_client.chat_update( 1114 | channel=channel_id, 1115 | ts=timestamp, 1116 | blocks=blocks 1117 | ) 1118 | except SlackApiError as e: 1119 | bot.error(e) 1120 | 1121 | 1122 | def space(**payload): 1123 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('space', **payload) 1124 | if data: 1125 | if len(text_split) < 2: 1126 | web_client.chat_delete( 1127 | channel=channel_id, 1128 | ts=timestamp 1129 | ) 1130 | print(Prefixes.warning + 'Missing Arguments! Read Help For Information') 1131 | bot.warning_count += 1 1132 | else: 1133 | rest = ' '.join(text_split[1:]) 1134 | new_string = "" 1135 | for char in rest: 1136 | new_string += str(char + " ") 1137 | try: 1138 | web_client.chat_update( 1139 | channel=channel_id, 1140 | ts=timestamp, 1141 | text=new_string 1142 | ) 1143 | except SlackApiError as e: 1144 | bot.error(e) 1145 | 1146 | def sub_space(**payload): 1147 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('subspace', **payload) 1148 | if data: 1149 | if len(text_split) < 3: 1150 | web_client.chat_delete( 1151 | channel=channel_id, 1152 | ts=timestamp 1153 | ) 1154 | print(Prefixes.warning + 'Missing Arguments! Read Help For Information') 1155 | bot.warning_count += 1 1156 | else: 1157 | emoji = text_split[1] 1158 | rest = ' '.join(text_split[2:]) 1159 | rest = rest.replace(' ', ' {} '.format(emoji)) 1160 | try: 1161 | web_client.chat_update( 1162 | channel=channel_id, 1163 | text=rest, 1164 | ts=timestamp 1165 | ) 1166 | except SlackApiError as e: 1167 | bot.error(e) 1168 | 1169 | 1170 | def delete(**payload): 1171 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('delete', **payload) 1172 | if data: 1173 | web_client.chat_delete( 1174 | channel=channel_id, 1175 | ts=timestamp 1176 | ) 1177 | if len(text_split) < 2: 1178 | print(Prefixes.warning + 'Missing Arguments! Read Help For Information') 1179 | bot.warning_count += 1 1180 | else: 1181 | print(Prefixes.event + 'Ran Command: delete') 1182 | bot.command_count += 1 1183 | msgs = int(text_split[1]) 1184 | conv_hist = web_client.conversations_history(channel=channel_id, count=msgs if msgs <= 100 else 100) 1185 | msg_ts = [] 1186 | for i in conv_hist['messages']: 1187 | for k, v in i.items(): 1188 | if k == "user" and v == config['user']: 1189 | msg_ts.append(i['ts']) 1190 | for ts in msg_ts: 1191 | try: 1192 | web_client.chat_delete( 1193 | channel=channel_id, 1194 | ts=ts 1195 | ) 1196 | except SlackApiError as e: 1197 | bot.error(e) 1198 | 1199 | 1200 | 1201 | def shift(**payload): 1202 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('shift', **payload) 1203 | if data: 1204 | if len(text_split) < 2: 1205 | web_client.chat_delete( 1206 | channel=channel_id, 1207 | ts=timestamp 1208 | ) 1209 | print(Prefixes.error + 'Missing Arguments! Please read the help menu for more info!') 1210 | bot.warning_count += 1 1211 | else: 1212 | rest = ' '.join(text_split[1:]) 1213 | new_text = "" 1214 | count = 0 1215 | for char in rest: 1216 | if count == 0: 1217 | new_text += char.upper() 1218 | count = 1 1219 | else: 1220 | new_text += char.lower() 1221 | count = 0 1222 | try: 1223 | web_client.chat_update( 1224 | channel=channel_id, 1225 | text=new_text, 1226 | ts=timestamp 1227 | ) 1228 | except SlackApiError as e: 1229 | bot.error(e) 1230 | 1231 | 1232 | def info(**payload): 1233 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('info', **payload) 1234 | if data: 1235 | try: 1236 | attachment = [ 1237 | { 1238 | "color": "#0f87ff", 1239 | "blocks": [ 1240 | { 1241 | "type": "section", 1242 | "text": { 1243 | "type": "mrkdwn", 1244 | "text": """Running :slack: *Slacky* v{} by 1245 | *Source Code*: 1246 | *Wiki*: """.format(version, config['prefix']) 1247 | } 1248 | } 1249 | ] 1250 | } 1251 | ] 1252 | web_client.chat_update( 1253 | channel=channel_id, 1254 | ts=timestamp, 1255 | text="", 1256 | attachments=attachment 1257 | ) 1258 | except SlackApiError as e: 1259 | bot.error(e) 1260 | 1261 | 1262 | def howdoicmd(**payload): 1263 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('howdoi', **payload) 1264 | if data: 1265 | if len(text_split) < 2: 1266 | web_client.chat_delete( 1267 | channel=channel_id, 1268 | ts=timestamp 1269 | ) 1270 | print(Prefixes.error + 'Missing Arguments! Please read the help menu for more info!') 1271 | bot.warning_count += 1 1272 | try: 1273 | web_client.chat_update( 1274 | channel=channel_id, 1275 | text="Finding the answer to that...", 1276 | ts=timestamp 1277 | ) 1278 | except SlackApiError as e: 1279 | bot.error(e) 1280 | 1281 | parser = howdoi.get_parser() 1282 | args = vars(parser.parse_args(text_split[1:])) 1283 | output = howdoi.howdoi(args) 1284 | try: 1285 | web_client.chat_update( 1286 | channel=channel_id, 1287 | text="```{}```".format(output), 1288 | ts=timestamp 1289 | ) 1290 | except SlackApiError as e: 1291 | bot.error(e) 1292 | 1293 | 1294 | def heartbeat(**payload): 1295 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('heartbeat', **payload) 1296 | if data: 1297 | try: 1298 | web_client.chat_update( 1299 | channel=channel_id, 1300 | text="I'm Alive!", 1301 | ts=timestamp 1302 | ) 1303 | except SlackApiError as e: 1304 | bot.error(e) 1305 | 1306 | 1307 | def react(**payload): 1308 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('react', **payload) 1309 | if data: 1310 | try: 1311 | web_client.chat_delete( 1312 | channel=channel_id, 1313 | ts=timestamp 1314 | ) 1315 | except SlackApiError as e: 1316 | bot.error(e) 1317 | 1318 | emoji = text_split[1] 1319 | print(Prefixes.event + 'Ran Command: react') 1320 | bot.command_count += 1 1321 | conv_info = client.conversations_info(channel=channel_id) 1322 | latest = conv_info['channel']['latest'] 1323 | latest_ts = latest['ts'] 1324 | try: 1325 | web_client.reactions_add( 1326 | channel=channel_id, 1327 | timestamp=latest_ts, 1328 | name=emoji.replace(':', '') 1329 | ) 1330 | except SlackApiError as e: 1331 | bot.error(e) 1332 | 1333 | 1334 | def listenerd(**payload): 1335 | data = payload['data'] 1336 | channel_id = data['channel'] 1337 | text = data.get('text') 1338 | if bot.msgstatus: 1339 | try: 1340 | client.users_profile_set( 1341 | profile={ 1342 | 'status_emoji': ':' + str(random.choice(emojis)) + ':', 1343 | 'status_text': client.users_profile_get(user=config['user'])['profile'].get('status_text'), 1344 | 'status_expiration': 0 1345 | } 1346 | ) 1347 | except SlackApiError as e: 1348 | pass 1349 | if text: 1350 | if not config['prefix'] in text: 1351 | if len(listener.listeners) >= 1: 1352 | if any(x in text for x in listener.listeners): 1353 | print(Prefixes.event + 'Listener Triggered! Message:', text, '| Channel ID:', channel_id) 1354 | 1355 | def listenercmd(**payload): 1356 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('listener', **payload) 1357 | if data: 1358 | if len(text_split) == 1: 1359 | print(Prefixes.warning + 'Missing Arguments! Read Help For Information') 1360 | bot.warning_count += 1 1361 | else: 1362 | action = text_split[1] 1363 | phrase = ' '.join(text_split[2:]) 1364 | if action == 'add': 1365 | listener.add(phrase) 1366 | print(Prefixes.event + 'Listener Added:', phrase) 1367 | bot.command_count += 1 1368 | try: 1369 | web_client.chat_update( 1370 | channel=channel_id, 1371 | text="`{}` added to listeners.".format(phrase), 1372 | ts=timestamp 1373 | ) 1374 | except SlackApiError as e: 1375 | bot.error(e) 1376 | 1377 | elif action == 'list': 1378 | bot.command_count += 1 1379 | listeners = "" 1380 | for ear in listener.listeners: 1381 | listeners += str(ear + '\n') 1382 | try: 1383 | web_client.chat_update( 1384 | channel=channel_id, 1385 | text="```{}```".format(listeners), 1386 | ts=timestamp 1387 | ) 1388 | except SlackApiError as e: 1389 | bot.error(e) 1390 | 1391 | elif action == 'delete': 1392 | listener.delete(phrase) 1393 | bot.command_count += 1 1394 | print(Prefixes.event + 'Listener Deleted:', phrase) 1395 | try: 1396 | web_client.chat_update( 1397 | channel=channel_id, 1398 | text="`{}` removed from listeners.".format(phrase), 1399 | ts=timestamp 1400 | ) 1401 | except SlackApiError as e: 1402 | bot.error(e) 1403 | 1404 | 1405 | def xkcd(**payload): 1406 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('xkcd', **payload) 1407 | if data: 1408 | res = httpx.get('https://xkcd.com/info.0.json').json() 1409 | link = res['img'] 1410 | alt_text = res['alt'] 1411 | try: 1412 | web_client.chat_update( 1413 | channel=channel_id, 1414 | blocks=[ 1415 | { 1416 | "type": "image", 1417 | "title": { 1418 | "type": "plain_text", 1419 | "text": alt_text, 1420 | "emoji": True 1421 | }, 1422 | "image_url": link, 1423 | "alt_text": alt_text 1424 | } 1425 | ], 1426 | ts=timestamp 1427 | ) 1428 | except SlackApiError as e: 1429 | bot.error(e) 1430 | 1431 | def convinfo(**payload): 1432 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('convinfo', **payload) 1433 | if data: 1434 | try: 1435 | web_client.chat_update( 1436 | channel=channel_id, 1437 | ts=timestamp, 1438 | text="This may take a while..." 1439 | ) 1440 | except SlackApiError as e: 1441 | bot.error(e) 1442 | if len(text_split) == 2: 1443 | if '#' in text_split[1]: 1444 | fsplit = text_split[1].split('|')[0].split('#')[1] 1445 | nsplit = text_split[1].split('|')[1].strip('>') 1446 | print(fsplit) 1447 | bot.command_count += 1 1448 | messages = [] 1449 | cursor = "" 1450 | while True: 1451 | try: 1452 | hist = client.conversations_history(channel=fsplit, limit=100, cursor=cursor) 1453 | for i in hist['messages']: 1454 | messages.append(i) 1455 | if hist['has_more']: 1456 | cursor = hist['response_metadata']['next_cursor'] 1457 | pass 1458 | else: 1459 | break 1460 | except SlackApiError as e: 1461 | bot.error(e) 1462 | lols = 0 1463 | lmaos = 0 1464 | shits = 0 1465 | fucks = 0 1466 | msgs = 0 1467 | for message in messages: 1468 | msgs += 1 1469 | if str('lol') in message['text']: 1470 | lols += 1 1471 | elif str('lmao') in message['text']: 1472 | lmaos += 1 1473 | elif str('shit') in message['text']: 1474 | shits += 1 1475 | elif str('fuck') in message['text']: 1476 | fucks += 1 1477 | else: 1478 | pass 1479 | blocks = [ 1480 | { 1481 | "type": "section", 1482 | "text": { 1483 | "type": "mrkdwn", 1484 | "text": ":slack: *Conversation History for {}:*".format(nsplit.capitalize()) 1485 | } 1486 | }, 1487 | { 1488 | 'type': 'section', 1489 | 'fields': [ 1490 | { 1491 | 'type': 'mrkdwn', 1492 | 'text': '*# of LOLs:* {}'.format(lols) 1493 | }, 1494 | { 1495 | 'type': 'mrkdwn', 1496 | 'text': '*# of LMAOs:* {}'.format(lmaos) 1497 | }, 1498 | { 1499 | 'type': 'mrkdwn', 1500 | 'text': '*# of S--ts:* {}'.format(shits) 1501 | }, 1502 | { 1503 | 'type': 'mrkdwn', 1504 | 'text': '*# of F--ks:* {}'.format(fucks) 1505 | }, 1506 | { 1507 | 'type': 'mrkdwn', 1508 | 'text': '*Total Messages:* {}'.format(msgs) 1509 | } 1510 | ] 1511 | } 1512 | ] 1513 | try: 1514 | web_client.chat_update( 1515 | channel=channel_id, 1516 | ts=timestamp, 1517 | text='', 1518 | attachments=[ 1519 | { 1520 | 'color': '#0f87ff', 1521 | 'blocks': blocks 1522 | } 1523 | ] 1524 | ) 1525 | except SlackApiError as e: 1526 | bot.error(e) 1527 | else: 1528 | try: 1529 | web_client.chat_update( 1530 | channel=channel_id, 1531 | ts=timestamp, 1532 | text='Error. Bad Channel!' 1533 | ) 1534 | except SlackApiError as e: 1535 | bot.error(e) 1536 | else: 1537 | bot.command_count += 1 1538 | messages = [] 1539 | cursor = "" 1540 | while True: 1541 | try: 1542 | hist = client.conversations_history(channel=channel_id, limit=100, cursor=cursor) 1543 | for i in hist['messages']: 1544 | messages.append(i) 1545 | if hist['has_more']: 1546 | cursor = hist['response_metadata']['next_cursor'] 1547 | pass 1548 | else: 1549 | break 1550 | except SlackApiError as e: 1551 | bot.error(e) 1552 | lols = 0 1553 | lmaos = 0 1554 | shits = 0 1555 | fucks = 0 1556 | msgs = 0 1557 | for message in messages: 1558 | msgs += 1 1559 | if str('lol') in message['text']: 1560 | lols += 1 1561 | elif str('lmao') in message['text']: 1562 | lmaos += 1 1563 | elif str('shit') in message['text']: 1564 | shits += 1 1565 | elif str('fuck') in message['text']: 1566 | fucks += 1 1567 | else: 1568 | pass 1569 | blocks = [ 1570 | { 1571 | "type": "section", 1572 | "text": { 1573 | "type": "mrkdwn", 1574 | "text": ":slack: *Conversation History:*" 1575 | } 1576 | }, 1577 | { 1578 | 'type': 'section', 1579 | 'fields': [ 1580 | { 1581 | 'type': 'mrkdwn', 1582 | 'text': '*# of LOLs:* {}'.format(lols) 1583 | }, 1584 | { 1585 | 'type': 'mrkdwn', 1586 | 'text': '*# of LMAOs:* {}'.format(lmaos) 1587 | }, 1588 | { 1589 | 'type': 'mrkdwn', 1590 | 'text': '*# of S--ts:* {}'.format(shits) 1591 | }, 1592 | { 1593 | 'type': 'mrkdwn', 1594 | 'text': '*# of F--ks:* {}'.format(fucks) 1595 | }, 1596 | { 1597 | 'type': 'mrkdwn', 1598 | 'text': '*Total Messages:* {}'.format(msgs) 1599 | } 1600 | ] 1601 | } 1602 | ] 1603 | try: 1604 | web_client.chat_update( 1605 | channel=channel_id, 1606 | ts=timestamp, 1607 | attachments=[{ 1608 | 'color': '#0f87ff', 1609 | 'blocks': blocks 1610 | }] 1611 | ) 1612 | except SlackApiError as e: 1613 | bot.error(e) -------------------------------------------------------------------------------- /slacky/plugins/custom/__init__.py: -------------------------------------------------------------------------------- 1 | # Load Plugins Here 2 | from .example import custom_example 3 | # Uncomment to load Deepfry Plugin 4 | # from .deepfry import deepfry 5 | from .stockpic import stockpic -------------------------------------------------------------------------------- /slacky/plugins/custom/deepfry/__init__.py: -------------------------------------------------------------------------------- 1 | from .plugin import deepfry -------------------------------------------------------------------------------- /slacky/plugins/custom/deepfry/deepfrylogic.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | from io import BytesIO 3 | import math 4 | import pkgutil 5 | from typing import Tuple 6 | 7 | from PIL import Image, ImageOps, ImageEnhance 8 | from cv2.data import haarcascades 9 | import cv2 10 | import os 11 | import numpy 12 | 13 | __all__ = ('Colour', 'ColourTuple', 'DefaultColours', 'deepfry') 14 | 15 | Colour = Tuple[int, int, int] 16 | ColourTuple = Tuple[Colour, Colour] 17 | 18 | 19 | class DefaultColours: 20 | """Default colours provided for deepfrying""" 21 | red = ((254, 0, 2), (255, 255, 15)) 22 | blue = ((36, 113, 229), (255,) * 3) 23 | 24 | face_cascade = cv2.CascadeClassifier(os.getcwd() + '/slacky/plugins/custom/deepfry/haarcascade_frontalface_default.xml') 25 | eye_cascade = cv2.CascadeClassifier(os.getcwd() + '/slacky/plugins/custom/deepfry/haarcascade_eye.xml') 26 | flare_img = Image.open(BytesIO(pkgutil.get_data(__package__, 'flare.png'))) 27 | 28 | FlarePosition = namedtuple('FlarePosition', ['x', 'y', 'size']) 29 | 30 | 31 | def deepfryy(img=None, colours= DefaultColours.red, flares= True): 32 | """ 33 | Deepfry a given image. 34 | 35 | Parameters 36 | ---------- 37 | img : `Image` 38 | Image to manipulate. 39 | colours : `ColourTuple`, optional 40 | A tuple of the colours to apply on the image. 41 | flares : `bool`, optional 42 | Whether or not to try and detect faces for applying lens flares. 43 | 44 | Returns 45 | ------- 46 | `Image` 47 | Deepfried image. 48 | """ 49 | img = img.copy().convert('RGB') 50 | flare_positions = [] 51 | 52 | if flares: 53 | opencv_img = cv2.cvtColor(numpy.array(img), cv2.COLOR_RGB2GRAY) 54 | 55 | faces = face_cascade.detectMultiScale( 56 | opencv_img, 57 | scaleFactor=1.3, 58 | minNeighbors=5, 59 | minSize=(30, 30), 60 | flags=cv2.CASCADE_SCALE_IMAGE 61 | ) 62 | 63 | for (x, y, w, h) in faces: 64 | face_roi = opencv_img[y:y+h, x:x+w] # Get region of interest (detected face) 65 | 66 | eyes = eye_cascade.detectMultiScale(face_roi) 67 | 68 | for (ex, ey, ew, eh) in eyes: 69 | eye_corner = (ex + ew / 2, ey + eh / 2) 70 | flare_size = eh if eh > ew else ew 71 | flare_size *= 4 72 | corners = [math.floor(x) for x in eye_corner] 73 | eye_corner = FlarePosition(*corners, flare_size) 74 | 75 | flare_positions.append(eye_corner) 76 | 77 | # Crush image to hell and back 78 | img = img.convert('RGB') 79 | width, height = img.width, img.height 80 | img = img.resize((int(width ** .75), int(height ** .75)), resample=Image.LANCZOS) 81 | img = img.resize((int(width ** .88), int(height ** .88)), resample=Image.BILINEAR) 82 | img = img.resize((int(width ** .9), int(height ** .9)), resample=Image.BICUBIC) 83 | img = img.resize((width, height), resample=Image.BICUBIC) 84 | img = ImageOps.posterize(img, 4) 85 | 86 | # Generate colour overlay 87 | r = img.split()[0] 88 | r = ImageEnhance.Contrast(r).enhance(2.0) 89 | r = ImageEnhance.Brightness(r).enhance(1.5) 90 | 91 | r = ImageOps.colorize(r, colours[0], colours[1]) 92 | 93 | # Overlay red and yellow onto main image and sharpen the hell out of it 94 | img = Image.blend(img, r, 0.75) 95 | img = ImageEnhance.Sharpness(img).enhance(100.0) 96 | 97 | # Apply flares on any detected eyes 98 | for flare in flare_positions: 99 | flare_transformed = flare_img.copy().resize((flare.size,) * 2, resample=Image.BILINEAR) 100 | img.paste(flare_transformed, (flare.x, flare.y), flare_transformed) 101 | 102 | return img -------------------------------------------------------------------------------- /slacky/plugins/custom/deepfry/flare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4cs/Slacky/992dc75297295878ba37fa64a5ff44c50beac890/slacky/plugins/custom/deepfry/flare.png -------------------------------------------------------------------------------- /slacky/plugins/custom/deepfry/plugin.py: -------------------------------------------------------------------------------- 1 | from slacky import client, config, check_user, bot, version, Prefixes 2 | from .deepfrylogic import deepfryy 3 | from slack.errors import SlackApiError 4 | from PIL import Image 5 | from io import BytesIO 6 | import httpx, os 7 | 8 | def cmd_setup(command, **payload): 9 | data = payload['data'] 10 | channel_id = data['channel'] 11 | user = data.get('user') 12 | timestamp = data.get('ts') 13 | web_client = client 14 | text = data.get('text') 15 | if text and check_user(user): 16 | text_split = text.split(' ') 17 | cmd = text_split[0] 18 | if cmd == config['prefix'] + command: 19 | print(Prefixes.event + 'Ran Command: {}'.format(command)) 20 | bot.command_count += 1 21 | return data, channel_id, user, timestamp, web_client, text, text_split 22 | else: 23 | return None, None, None, None, None, None, None 24 | else: 25 | return None, None, None, None, None, None, None 26 | 27 | def deepfry(**payload): 28 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('deepfry', **payload) 29 | if data: 30 | if len(text_split) < 2: 31 | try: 32 | web_client.chat_update( 33 | channel=channel_id, 34 | ts=timestamp, 35 | text="Missing User to Deepfry!" 36 | ) 37 | except SlackApiError as e: 38 | bot.error(e) 39 | else: 40 | if 'http' in text_split[1]: 41 | image = httpx.get(text_split[1].strip('<').strip('>')).content 42 | img = Image.open(BytesIO(image)) 43 | img = deepfryy(img=img, flares=False) 44 | img.save('./tmp.jpg') 45 | try: 46 | web_client.chat_delete( 47 | channel=channel_id, 48 | ts=timestamp 49 | ) 50 | with open('tmp.jpg', 'rb') as imdata: 51 | r = httpx.post('https://slack.com/api/files.upload', data={'token': config['token'], 'channels': [channel_id], 'title': 'DEEPFRY', 'as_user': 'True'}, files={'file': imdata}) 52 | os.remove('tmp.jpg') 53 | except SlackApiError as e: 54 | bot.error(e) 55 | else: 56 | try: 57 | user_list = client.users_list() 58 | except SlackApiError as e: 59 | bot.error(e) 60 | if user_list: 61 | query = text_split[1].split('@')[1].split('>')[0] 62 | match = None 63 | for wuser in user_list['members']: 64 | if wuser['id'] == query: 65 | match = wuser 66 | break 67 | if match: 68 | url = match['profile'].get('image_512') 69 | if url: 70 | image = httpx.get(match['profile'].get('image_512')).content 71 | img = Image.open(BytesIO(image)) 72 | img = deepfryy(img=img) 73 | img.save('./tmp.jpg') 74 | try: 75 | web_client.chat_delete( 76 | channel=channel_id, 77 | ts=timestamp 78 | ) 79 | with open('tmp.jpg', 'rb') as imdata: 80 | r = httpx.post('https://slack.com/api/files.upload', data={'token': config['token'], 'channels': [channel_id], 'title': 'DEEPFRY', 'as_user': 'True'}, files={'file': imdata}) 81 | except SlackApiError as e: 82 | bot.error(e) -------------------------------------------------------------------------------- /slacky/plugins/custom/example.py: -------------------------------------------------------------------------------- 1 | from slacky import client, config, Prefixes, check_user 2 | from slack.errors import SlackApiError 3 | 4 | def custom_example(**payload): 5 | # Get Data from Payload 6 | data = payload['data'] 7 | channel_id = data['channel'] # Get Channel ID 8 | user = data.get('user') # Get User 9 | timestamp = data['ts'] # Get msg Timestamp 10 | if check_user(user): # Check if User == You 11 | web_client = client # Init Client 12 | text = data.get('text') # Get Text 13 | # Check for Command Here 14 | if text: 15 | text_split = text.split(' ') 16 | cmd = text_split[0] 17 | if cmd == config['prefix'] + 'example': 18 | # Command has been triggered 19 | print(Prefixes.event + 'Ran Command: example') 20 | # Do your logic here and then update the message at the end below. 21 | try: 22 | web_client.chat_update( 23 | channel=channel_id, 24 | ts=timestamp, 25 | text="This command is an example custom command." 26 | ) 27 | except SlackApiError as e: 28 | print(Prefixes.error + str(e)) -------------------------------------------------------------------------------- /slacky/plugins/custom/stockpic/__init__.py: -------------------------------------------------------------------------------- 1 | from .plugin import * -------------------------------------------------------------------------------- /slacky/plugins/custom/stockpic/plugin.py: -------------------------------------------------------------------------------- 1 | from slacky import config, bot, Prefixes 2 | from slacky.plugins import cmd_setup 3 | from slack.errors import SlackApiError 4 | import requests, json, random 5 | 6 | 7 | print(Prefixes.start + "Loading Shutterstock Plugin...") 8 | 9 | def stockpic(**payload): 10 | data, channel_id, user, timestamp, web_client, text, text_split = cmd_setup('stockpic', **payload) 11 | if data: 12 | if config.get('shutterstock_key') and config.get('shutterstock_secret'): 13 | try: 14 | web_client.chat_update( 15 | channel=channel_id, 16 | ts=timestamp, 17 | text="Finding an Image..." 18 | ) 19 | except SlackApiError as e: 20 | bot.error(e) 21 | res = requests.get('https://api.shutterstock.com/v2/images/search?query={}'.format('+'.join(text_split[1:])), auth=requests.auth.HTTPBasicAuth(config['shutterstock_key'], config['shutterstock_secret'])).json() 22 | images = res['data'] 23 | image_links = [] 24 | for image in images: 25 | image_links.append(image['assets']['preview']['url']) 26 | link = random.choice(image_links) 27 | try: 28 | web_client.chat_update( 29 | channel=channel_id, 30 | ts=timestamp, 31 | text='', 32 | attachments=[ 33 | { 34 | 'color': '#0a85f2', 35 | 'blocks': [ 36 | { 37 | 'type': 'image', 38 | 'title': { 39 | 'type': 'plain_text', 40 | 'text': ' '.join(text_split[1:]), 41 | 'emoji': True 42 | }, 43 | 'image_url': link, 44 | 'alt_text': 'Stock Image' 45 | } 46 | ] 47 | } 48 | ] 49 | ) 50 | except SlackApiError as e: 51 | bot.error(e) 52 | else: 53 | try: 54 | web_client.chat_update( 55 | channel=channel_id, 56 | ts=timestamp, 57 | text="Add `shutterstock_key` and `shutterstock_secret` to config with your credentials!" 58 | ) 59 | except SlackApiError as e: 60 | bot.error(e) -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | 2.0.0 --------------------------------------------------------------------------------