diff --git a/CHANGELOG.md b/CHANGELOG.md index 591f94a..22009a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,21 @@ # Change Log All notable changes to this project will be documented in this file. +## [1.1] - 2022-10-28 + +Version 1.1 has been officially released, with new features, bug fixes and stability issues resolved! + +### Added +- An option has been added for players to respond to polls for analytical purposes. Available for **Slack**, **Discord** and **Telegram**! + +### Fixed +- A problem with the Discord bot has been fixed. +- We have changed the way to check options such as "Photos" and "Profile picture" in the injects. + +### Updated +- We have updated all the bots dependencies and made some changes to make them work with the new versions. + + ## [1.0] - 2022-04-20 Releasing the official public version of the framework! @@ -48,7 +63,7 @@ Launching the latest _and better_ version of the framework supporting Discord! -Now the start and resume function are merged into 1 process function! ### Fixed -- Fixed a bug within the resume function, injects not properly being choose! +- Fixed a bug within the resume function, injects not being chosen correctly! ## [0.3] - 2022-01-29 [Private] diff --git a/Discord/bot.py b/Discord/bot.py index 1a1ef15..7ab153a 100644 --- a/Discord/bot.py +++ b/Discord/bot.py @@ -1,14 +1,16 @@ from discord.ext import commands from dotenv import load_dotenv -from blurple import ui import discord import os -from T3SF import T3SF +from T3SF import * load_dotenv() TOKEN = os.getenv('DISCORD_TOKEN') -bot = commands.Bot(command_prefix='!') + +intents = discord.Intents.default() +intents.message_content = True +bot = commands.Bot(command_prefix='!', intents=intents) T3SF = T3SF(bot=bot) @@ -24,6 +26,10 @@ async def on_ready(): print(f'{bot.user.id}') print('------') +@bot.event +async def on_interaction(interaction): + await T3SF.PollAnswerHandler(payload=interaction) + @bot.command(name='ping', help='Responds with pong to know if the bot is up! Usage -> !ping') async def ping(ctx): """ @@ -34,7 +40,7 @@ async def ping(ctx): :stopwatch: `{round(bot.latency*1000)}ms` """ - await ctx.send(embed=ui.Toast(ui.Style.INFO, emoji=False, text=description)) + response = await ctx.send(embed=discord.Embed(colour=discord.Colour.blue(), description=description)) @bot.command(name="start", help='Starts the Incidents Game. Usage -> !start') @commands.has_role("Game Master") @@ -65,7 +71,7 @@ async def resume(ctx, *, query): await T3SF.ProcessIncidents(function_type = "resume", ctx=ctx, itinerator=itinerator) except Exception as e: - print("ERROR - Start function") + print("ERROR - Resume function") print(e) raise diff --git a/Discord/requirements.txt b/Discord/requirements.txt index 853ba0b..1fd44ed 100644 --- a/Discord/requirements.txt +++ b/Discord/requirements.txt @@ -1,4 +1,12 @@ -blurple.py==0.5.5 -discord.py==1.7.3 -python-dotenv==0.20.0 -t3sf==1.0 \ No newline at end of file +aiohttp==3.8.3 +aiosignal==1.2.0 +async-timeout==4.0.2 +attrs==22.1.0 +charset-normalizer==2.1.1 +discord.py==2.0.1 +frozenlist==1.3.1 +idna==3.4 +multidict==6.0.2 +python-dotenv==0.21.0 +yarl==1.8.1 +t3sf==1.1 \ No newline at end of file diff --git a/MSEL.xlsx b/MSEL.xlsx new file mode 100644 index 0000000..3d0522f Binary files /dev/null and b/MSEL.xlsx differ diff --git a/MSEL_EXAMPLE.json b/MSEL_EXAMPLE.json index 480fe20..ea40d6f 100644 --- a/MSEL_EXAMPLE.json +++ b/MSEL_EXAMPLE.json @@ -1,15 +1,16 @@ [ { "#": 1, - "Real Time": "07:29 AM", + "Real Time": "07:29 PM", "Date": "Monday 9:30 AM", "Subject": "Anomalous Files Detected", "From": "Amazon Web Services", "Player": "Information Security", "Script": "We detected some anomalous files in your S3 Bucket.", - "Picture Name": "S3_Bucket.png", "Photo": "https://img2.helpnetsecurity.com/posts2018/aws-s3-buckets-public.jpg", - "Profile": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Amazon_Web_Services_Logo.svg/1024px-Amazon_Web_Services_Logo.svg.png" + "Picture Name": "S3_Bucket.png", + "Profile": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Amazon_Web_Services_Logo.svg/1024px-Amazon_Web_Services_Logo.svg.png", + "Poll": "" }, { "#": 2, @@ -19,8 +20,10 @@ "From": "SOC - BASE4", "Player": "SRE", "Script": "Please note that we detected a large number of phishing emails coming from different email addresses.\n\nIgnore messages from emails with the address @notanemail.net", + "Photo": "", "Picture Name": "Base_4_SOC.jpg", - "Profile": "https://foreseeti.com/wp-content/uploads/2021/09/Ska%CC%88rmavbild-2021-09-02-kl.-15.44.24.png" + "Profile": "https://foreseeti.com/wp-content/uploads/2021/09/Ska%CC%88rmavbild-2021-09-02-kl.-15.44.24.png", + "Poll": "" }, { "#": 3, @@ -30,8 +33,10 @@ "From": "Internal", "Player": "Data Governance", "Script": "Hey Guys!\n\nI found some files from the company exposed online, please take a look!", + "Photo": "https://assets-global.website-files.com/5efc3ccdb72aaa7480ec8179/60dc1c3c984ef85123cb0f7b_LinkedIn-Data-Breach-700-million-.png", "Picture Name": "attachment.jpg", - "Photo": "https://assets-global.website-files.com/5efc3ccdb72aaa7480ec8179/60dc1c3c984ef85123cb0f7b_LinkedIn-Data-Breach-700-million-.png" + "Profile": "", + "Poll": "" }, { "#": 4, @@ -41,7 +46,10 @@ "From": "Joaquin Lanfranconi", "Player": "Legal", "Script": "Hey folks!\n\nI'm about to release a new tool, but I'm not sure which license is better, can you help with this one?\n\nThanks in advance!", - "Picture Name": "attachment.jpg" + "Photo": "", + "Picture Name": "", + "Profile": "", + "Poll": "MIT | GPL" }, { "#": 5, @@ -52,7 +60,9 @@ "Player": "PR/Comm", "Script": "Hello, \n\nI'm inside your company, I've encrypted all your files, if you want to recover them, check the attached file.", "Picture Name": "attachment.jpg", - "Photo": "https://cdn2.hubspot.net/hubfs/486579/lp/academy/malware/wannacry_screenshot.png" + "Profile": "", + "Photo": "https://cdn2.hubspot.net/hubfs/486579/lp/academy/malware/wannacry_screenshot.png", + "Poll": "" }, { "#": 6, @@ -63,7 +73,9 @@ "Player": "Information Security", "Script": "Hello, \n\nI'm inside your company, I've encrypted all your files, if you want to recover them, check the attached file.", "Picture Name": "attachment.jpg", - "Photo": "https://cdn2.hubspot.net/hubfs/486579/lp/academy/malware/wannacry_screenshot.png" + "Profile": "", + "Photo": "https://cdn2.hubspot.net/hubfs/486579/lp/academy/malware/wannacry_screenshot.png", + "Poll": "" }, { "#": 7, @@ -74,7 +86,9 @@ "Player": "SRE", "Script": "Hello, \n\nI'm inside your company, I've encrypted all your files, if you want to recover them, check the attached file.", "Picture Name": "attachment.jpg", - "Photo": "https://cdn2.hubspot.net/hubfs/486579/lp/academy/malware/wannacry_screenshot.png" + "Profile": "", + "Photo": "https://cdn2.hubspot.net/hubfs/486579/lp/academy/malware/wannacry_screenshot.png", + "Poll": "" }, { "#": 8, @@ -85,7 +99,9 @@ "Player": "Data Governance", "Script": "Hello, \n\nI'm inside your company, I've encrypted all your files, if you want to recover them, check the attached file.", "Picture Name": "attachment.jpg", - "Photo": "https://cdn2.hubspot.net/hubfs/486579/lp/academy/malware/wannacry_screenshot.png" + "Profile": "", + "Photo": "https://cdn2.hubspot.net/hubfs/486579/lp/academy/malware/wannacry_screenshot.png", + "Poll": "" }, { "#": 9, @@ -96,7 +112,9 @@ "Player": "Legal", "Script": "Hello, \n\nI'm inside your company, I've encrypted all your files, if you want to recover them, check the attached file.", "Picture Name": "attachment.jpg", - "Photo": "https://cdn2.hubspot.net/hubfs/486579/lp/academy/malware/wannacry_screenshot.png" + "Profile": "", + "Photo": "https://cdn2.hubspot.net/hubfs/486579/lp/academy/malware/wannacry_screenshot.png", + "Poll": "" }, { "#": 10, @@ -107,6 +125,34 @@ "Player": "People", "Script": "Hello, \n\nI'm inside your company, I've encrypted all your files, if you want to recover them, check the attached file.", "Picture Name": "attachment.jpg", - "Photo": "https://cdn2.hubspot.net/hubfs/486579/lp/academy/malware/wannacry_screenshot.png" + "Profile": "", + "Photo": "https://cdn2.hubspot.net/hubfs/486579/lp/academy/malware/wannacry_screenshot.png", + "Poll": "" + }, + { + "#": 11, + "Real Time": "07:33 PM", + "Date": "Monday 11:10 AM", + "Subject": "Encrypted Files!", + "From": "Internal", + "Player": "Data Governance", + "Script": "Guys, we have a major issue!\nWhat should we do in this case, they are asking for a ransom of 2 BTC!\n\nInfoSec confirmed the encrypted files and everything!\nShould we pay them?", + "Picture Name": "encrypted_files.jpg", + "Profile": "", + "Photo": "https://cdn2.hubspot.net/hubfs/486579/lp/academy/malware/wannacry_screenshot.png", + "Poll": "Pay the ransom | Don’t pay it" + }, + { + "#": 12, + "Real Time": "07:35 PM", + "Date": "Monday 11:15 AM", + "Subject": "Data breach in your company", + "From": "New York Times", + "Player": "PR/Comm", + "Script": "Hello, \nWe have information about a data breach in your company.\nCould you please confirm, what kind of data is compromised?\nThanks!", + "Picture Name": "", + "Profile": "https://1000logos.net/wp-content/uploads/2017/04/Symbol-New-York-Times.png", + "Photo": "", + "Poll": "" } ] \ No newline at end of file diff --git a/README.md b/README.md index f73367a..db260be 100644 --- a/README.md +++ b/README.md @@ -78,10 +78,16 @@ load_dotenv() TOKEN = os.getenv('DISCORD_TOKEN') -bot = commands.Bot(command_prefix='!') +intents = discord.Intents.default() +intents.message_content = True +bot = commands.Bot(command_prefix='!', intents=intents) T3SF = T3SF(bot=bot) # We need to pass the bot's object to the framework. +@bot.event +async def on_interaction(interaction): + await T3SF.PollAnswerHandler(payload=interaction) + @bot.command(name="start", help='Starts the Incidents Game. Usage -> !start') async def start(ctx): # When the bot receives the command !start, @@ -109,6 +115,10 @@ app = AsyncApp(token=os.environ["SLACK_BOT_TOKEN"]) T3SF = T3SF(app=app) # We need to pass the app's object to the framework. +@app.action(re.compile("option")) +async def poll_handler(ack, body, payload): + await T3SF.PollAnswerHandler(ack=ack,body=body,payload=payload) + @app.message("!start") async def start(message, say): # When the bot receives the command !start, diff --git a/Slack/bot.py b/Slack/bot.py index 400e8fd..4abc1ab 100644 --- a/Slack/bot.py +++ b/Slack/bot.py @@ -8,7 +8,7 @@ from T3SF import * -logging.basicConfig(level=logging.WARNING) +logging.basicConfig(level=logging.ERROR) load_dotenv() @@ -20,6 +20,10 @@ async def regex_handler(ack, body, payload): await T3SF.RegexHandler(ack=ack,body=body,payload=payload) +@app.action(re.compile("option")) +async def poll_handler(ack, body, payload): + await T3SF.PollAnswerHandler(ack=ack,body=body,payload=payload) + @app.message('!ping') async def ping(message, say): """ @@ -27,7 +31,7 @@ async def ping(message, say): """ description = """ PING localhost (127.0.0.1): 56 data bytes\n64 bytes from 127.0.0.1: icmp_seq=0 ttl=113 time=37.758 ms\n64 bytes from 127.0.0.1: icmp_seq=1 ttl=113 time=50.650 ms\n64 bytes from 127.0.0.1: icmp_seq=2 ttl=113 time=42.493 ms\n64 bytes from 127.0.0.1: icmp_seq=3 ttl=113 time=37.637 ms\n--- localhost ping statistics ---\n4 packets transmitted, 4 packets received, 0.0% packet loss\nround-trip min/avg/max/stddev = 37.637/42.135/50.650/5.292 ms\n\n_This is not real xD_""" - await app.client.chat_postMessage(channel = message['channel'], attachments = T3SF.Slack.Formatter(color="CL_GREEN", title="πŸ“ Pong!", description=description)) + await app.client.chat_postMessage(channel = message['channel'], attachments = T3SF.Slack.Formatter(color="GREEN", title="πŸ“ Pong!", description=description)) @app.message("!start") async def start(message, say): @@ -66,6 +70,10 @@ async def handle_message_events(body, logger): """ logger.info(body) +@app.event("reaction_added") +async def handle_reaction_added_events(body, logger): + logger.info(body) + @app.error async def custom_error_handler(error, body, logger): """ diff --git a/Slack/requirements.txt b/Slack/requirements.txt index 5041214..84d1927 100644 --- a/Slack/requirements.txt +++ b/Slack/requirements.txt @@ -1,4 +1,13 @@ -python-dotenv==0.20.0 -slack-bolt==1.13.0 -slack-sdk==3.15.2 -t3sf==1.0 \ No newline at end of file +aiohttp==3.8.3 +aiosignal==1.2.0 +async-timeout==4.0.2 +attrs==22.1.0 +charset-normalizer==2.1.1 +frozenlist==1.3.1 +idna==3.4 +multidict==6.0.2 +python-dotenv==0.21.0 +slack-bolt==1.15.2 +slack-sdk==3.19.2 +yarl==1.8.1 +t3sf==1.1 \ No newline at end of file diff --git a/T3SF/T3SF.py b/T3SF/T3SF.py index cb77de5..044e478 100644 --- a/T3SF/T3SF.py +++ b/T3SF/T3SF.py @@ -16,7 +16,11 @@ if platform.lower() == "discord": try: import discord - from blurple import ui + except Exception as e: + print(e) +elif platform.lower() == "telegram": + try: + from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton except Exception as e: print(e) @@ -27,6 +31,7 @@ def __init__(self, bot = None, app = None): self.process_quit = False self.regex_ready = None self.incidents_running = False + self.poll_answered = False self.ch_names_list = [] self.players_list = [] @@ -75,12 +80,17 @@ async def TimeDifference(self, actual_real_time, previous_real_time, itinerator= description = f"The bot is Up and running!\n\nIncident: {itinerator}/{len(self.data)}\n\nWaiting {self.diff} minute(s) ({self.diff_secs} sec.) to send it." - await self.EditMessage(style="simple", color_ds = "ui.Style.INFO", color_sl = "CL_CYAN", title="βš™οΈ Bot running...", description=description, response=self.msg_gm) + await self.EditMessage(style="simple", color = "CYAN", title="βš™οΈ Bot running...", description=description, response=self.msg_gm) print(f'We have a difference of {self.diff} minute(s) - {self.diff_secs} seconds (Actual time: {datetime.now().strftime("%H:%M:%S")})') await asyncio.sleep(self.diff_secs) + if "Poll" in self._inject and self._inject['Poll'] != '' and self.poll_answered == False: + description = self._inject["Script"] + f"\n\n@channel Poll not answered within {self.diff} minute(s), Time's Up!" + await self.EditMessage(style="custom", variable="self.response_poll", color = "RED", title="Poll time ended!", description=description, response=self.response_poll) + await self.NotifyGameMasters(type_info="poll_unanswered", data={'msg_poll':self._inject["Script"]}) + except Exception as e: print("ERROR - Get Time Difference") print(e) @@ -121,7 +131,7 @@ def regex_finder(self, input): return key return False - async def NotifyGameMasters(self, type_info=str): + async def NotifyGameMasters(self, type_info=str, data=None): """ Notify the Game Masters of the different states of the bot, through messages. """ @@ -129,27 +139,37 @@ async def NotifyGameMasters(self, type_info=str): if type_info == "start_normal": title = "βš™ Starting bot..." description = "The bot it's heating up!\n\nGive us just a second!!" - self.msg_gm = await self.SendMessage(title = title, description = description, color_ds="ui.Style.WARNING", color_sl="CL_YELLOW") + self.msg_gm = await self.SendMessage(title = title, description = description, color="YELLOW") elif type_info == "started_normal": title = "Bot succesfully started! 🎈" description = "The bot is Up and running!\n\nLets the game begin!!" - self.msg_gm = await self.EditMessage(title = title, description = description, color_ds="ui.Style.SUCCESS", color_sl="CL_GREEN", response=self.msg_gm) + self.msg_gm = await self.EditMessage(title = title, description = description, color="GREEN", response=self.msg_gm) elif type_info == "start_resumed": title = "βš™ Resuming bot..." description = "The bot it's trying to resume from the desired point!\n\nGive us just a few seconds!!" - self.msg_gm = await self.SendMessage(title = title, description = description, color_ds="ui.Style.WARNING", color_sl="CL_YELLOW") + self.msg_gm = await self.SendMessage(title = title, description = description, color="YELLOW") elif type_info == "started_resumed": title = "Bot succesfully started! 🎈" description = "The bot is Up and running!\n\nLets the game begin!!" - self.msg_gm = await self.EditMessage(title = title, description = description, color_ds="ui.Style.SUCCESS", color_sl="CL_GREEN", response=self.msg_gm) + self.msg_gm = await self.EditMessage(title = title, description = description, color="GREEN", response=self.msg_gm) elif type_info == "finished_incidents": title = "πŸŽ‰ Bot Finished succesfully! πŸŽ‰" description = "The bot've just completed the entire game!\n\nHope to see you again soon!!" - self.msg_gm = await self.EditMessage(title = title, description = description, color_ds="ui.Style.SUCCESS", color_sl="CL_GREEN", response=self.msg_gm) + self.msg_gm = await self.EditMessage(title = title, description = description, color="GREEN", response=self.msg_gm) + + elif type_info == "poll_answered": + title = "πŸ“Š Poll Answered" + description = f"Poll Question: {data['msg_poll']}\nSelected Answer: {data['answer']}\nBy: @{data['user']}" + await self.SendMessage(title = title, description = description, color="GREEN", unique=True) + + elif type_info == "poll_unanswered": + title = "πŸ“Š Poll Not Answered" + description = f"Poll Question: {data['msg_poll']}\nNot answered by anyone." + await self.SendMessage(title = title, description = description, color="RED", unique=True) return True @@ -210,8 +230,12 @@ async def ProcessIncidents(self, ctx, function_type:str=None, itinerator:int=0): await self.TimeDifference(actual_real_time, previous_real_time, resumed=True, itinerator=itinerator) # Check the amount of seconds between both timestamps. print(f'{information["#"]}\n------------\n') - - await self.SendIncident(inject = information) # Sends the incident to the desired chats. + + if "Poll" in information and information['Poll'] != '': + await self.SendPoll(inject = information) + + else: + await self.SendIncident(inject = information) # Sends the incident to the desired chats. if function_type == "start": if itinerator == 0: @@ -256,6 +280,30 @@ async def SendIncident(self, inject): print(e) raise + async def SendPoll(self, inject): + try: + self._inject = inject + + if self.platform == "discord": + await self.Discord.PollHandler(self=self) + + elif self.platform == "slack": + await self.Slack.PollHandler(self=self) + + elif self.platform == "telegram": + await self.Telegram.PollHandler(self=self) + + elif self.platform == "whatsapp": + print("Sorry, this option is still under development.") + return False + + return True + + except Exception as e: + print("ERROR - SendPoll") + print(e) + raise + async def InboxesAuto(self, message=None): if self.platform == "discord": await self.Discord.InboxesAuto(self=self) @@ -278,12 +326,12 @@ async def RegexHandler(self, ack=None, body=None, payload=None, inbox=None): if payload['action_id'] == "regex_yes": regex = body['actions'][0]['value'] - color="CL_GREEN" + color="GREEN" title = "✨ Regex detected succesfully! ✨" description = f"Thanks for confirming the regex detected for the channels (I'm going to tell my creator he is so good coding :D ), we are going to use `{regex}` to match the inboxes" elif payload['action_id'] == "regex_no": - color="CL_RED" + color="RED" title = "ℹ️ Regex needed!" description = "Got it!\n Unluckily, but here we go...\nPlease send me the regex for the channels, so we can get the inboxes!\n\nExample:\ninbox-legal\nThe regex should be `inbox-`" text_input = {"action_id": "regex_custom", "label": "Please type the desired regex. EG: inbox-", "dispatch_action": True} @@ -291,11 +339,11 @@ async def RegexHandler(self, ack=None, body=None, payload=None, inbox=None): elif payload['action_id'] == "regex_custom": regex = body['actions'][0]['value'] - color="CL_GREEN" + color="GREEN" title="βœ… Regex accepted!" description=f"Thanks for confirming the regex for the channels, we are going to use `{user_regex}` to match the inboxes!" - self.response_auto = await self.EditMessage(title = title, description = description, color_sl=color, image=image, text_input=text_input, response=self.response_auto) + self.response_auto = await self.EditMessage(title = title, description = description, color=color, image=image, text_input=text_input, response=self.response_auto) if regex != None: await self.Slack.InboxesAuto(self=self,regex=regex) @@ -304,9 +352,25 @@ async def RegexHandler(self, ack=None, body=None, payload=None, inbox=None): self._ctx = inbox await self.Whatsapp.InboxFetcher(self=self, inbox=inbox) + async def PollAnswerHandler(self, ack=None, body=None, payload=None, query=None): + if self.platform == "discord": + await self.Discord.PollAnswerHandler(self=self, interaction=payload) + return True + + elif self.platform == "slack": + await ack() + await self.Slack.PollAnswerHandler(self=self, body=body, payload=payload) + return True + + elif self.platform == "telegram": + await self.Telegram.PollAnswerHandler(self=self, query=query) + return True + + elif self.platform == "whatsapp": + return False + async def SendMessage(self, - color_sl=None, - color_ds=None, + color=None, title:str=None, description:str=None, channel=None, @@ -314,14 +378,21 @@ async def SendMessage(self, author=None, buttons=None, text_input=None, - checkboxes=None): + checkboxes=None, + view=None, + unique=False, + reply_markup=None): if self.platform == "discord": - self.response = await self.Discord.SendMessage(self=self, color_ds=color_ds, title=title, description=description) - return self.response + if unique == True: + self.gm_poll_msg = await self.Discord.SendMessage(self=self, color=color, title=title, description=description, view=view, unique=unique) + return self.gm_poll_msg + else: + self.response = await self.Discord.SendMessage(self=self, color=color, title=title, description=description, view=view) + return self.response elif self.platform == "slack": - self.response = await self.Slack.SendMessage(self=self, channel = channel, title=title, description=description, color_sl=color_sl, image=image, author=author, buttons=buttons, text_input=text_input, checkboxes=checkboxes) + self.response = await self.Slack.SendMessage(self=self, channel = channel, title=title, description=description, color=color, image=image, author=author, buttons=buttons, text_input=text_input, checkboxes=checkboxes) return self.response elif self.platform == "telegram": @@ -331,9 +402,8 @@ async def SendMessage(self, elif self.platform == "whatsapp": await self.Whatsapp.SendMessage(self=self, title=title, description=description, chat=self._ctx["Chat_Name"]) - async def EditMessage(self, - color_sl=None, - color_ds=None, + async def EditMessage(self, + color=None, style:str="simple", title:str=None, description:str=None, @@ -343,18 +413,21 @@ async def EditMessage(self, author=None, buttons=None, text_input=None, - checkboxes=None): + checkboxes=None, + view=None, + reply_markup=None): if self.platform == "discord": if style == "simple": - self.response = await self.Discord.EditMessage(self=self, color_ds=color_ds, title=title, description=description) + self.response = await self.Discord.EditMessage(self=self, color=color, title=title, description=description, view=view) return self.response else: + colors = {'BLUE' : discord.Colour.dark_blue(), 'RED' : discord.Colour.red(), 'CYAN' : discord.Colour.blue(), 'GREEN' : discord.Colour.green(), 'YELLOW' : discord.Colour.yellow()} variable = eval(variable) - await variable.edit(embed=ui.Alert(eval(color_ds), title = title, name=False, emoji=False, description = description)) + await variable.edit(embed=discord.Embed(color=colors[color], title = title, description = description), view=view) elif self.platform == "slack": - self.response = await self.Slack.EditMessage(self=self, response=response, title=title, description=description, color_sl=color_sl, image=image, author=author, buttons=buttons, text_input=text_input, checkboxes=checkboxes) + self.response = await self.Slack.EditMessage(self=self, response=response, title=title, description=description, color=color, image=image, author=author, buttons=buttons, text_input=text_input, checkboxes=checkboxes) return self.response elif self.platform == "telegram": @@ -410,30 +483,93 @@ async def InjectHandler(self): player = self._inject['Player'] image = None - if not (self._inject.get('Photo') is None): + if "Photo" in self._inject and self._inject['Photo'] != '': image = self._inject['Photo'] - await self.Telegram.SendMessage(self=self, title=self._inject["Subject"], description=all_data, player=self.inboxes_all[player], image=image) + await self.Telegram.SendMessage(self=self, title=self._inject["Subject"], description=all_data, chat_id=self.inboxes_all[player], image=image) - async def SendMessage(self, title, description, ctx=None, player=None, image=None): - data = f"*{title}*\n\n{description}" + async def PollHandler(self): + self.poll_answered = False + + image = None + + player = self._inject['Player'] + + if "Photo" in self._inject and self._inject['Photo'] != '': + image = self._inject['Photo'] + + poll_options = self._inject['Poll'].split('|') + + actual_real_time = re.sub("([^0-9])", "", self._inject['Real Time'])[-2:] + + next_real_time = re.sub("([^0-9])", "", self.data[int(self._inject['#'])]['Real Time'])[-2:] + + diff = int(next_real_time) - int(actual_real_time) + if diff < 0: + diff_no_real = int(actual_real_time) - int(next_real_time) + diff = 60 - diff_no_real + + diff_secs = diff * 60 + + description = self._inject["Script"] + f"\n\nYou have {diff} minute(s) to answer this poll!" + + all_data = f'{self._inject["Script"]} \n\nYou have {diff} minute(s) to answer this poll!' + + buttons = InlineKeyboardMarkup(row_width=2) + buttons.add(InlineKeyboardButton(poll_options[0], callback_data=f"poll|{poll_options[0]}"), InlineKeyboardButton(poll_options[1], callback_data=f"poll|{poll_options[1]}")) + + self.response_poll = await self.Telegram.SendMessage(self=self, title=self._inject["Subject"], description=all_data, chat_id=self.inboxes_all[player], image=image, reply_markup=buttons) + + return True + + async def PollAnswerHandler(self, query=None): + if "poll" in query.data: + selected_option = query.data.split('|')[1] + await query.answer(f'You answered with {selected_option!r}') + + action_user = query["from"]["username"] + poll_msg = query["message"]["text"] + if poll_msg == None: + poll_msg = query["message"]["caption"] + poll_msg = poll_msg[: poll_msg.rfind('\n')] - if player != None: - chat_id = player + description = f'*Poll Answered!*\n\n {poll_msg}\n\n@{action_user} selected: {selected_option}' + + self.response_poll = await self.Telegram.EditMarkup(self=self, text=description, chat_id=query.message.chat.id, message_id=query.message.message_id, reply_markup=None) + self.poll_answered = True + await self.NotifyGameMasters(type_info="poll_answered", data={'msg_poll':poll_msg,'answer':selected_option,'user':action_user}) + return True + else: + pass + + async def SendMessage(self, title, description, ctx=None, chat_id=None, image=None, reply_markup=None): + data = f"*{title}*\n\n{description}" + + if chat_id == None: chat_id = self._ctx.chat.id if image != None: - response = await self.bot.send_photo(chat_id=chat_id, caption=data, photo=image, parse_mode = 'Markdown') + response = await self.bot.send_photo(chat_id=chat_id, caption=data, photo=image, reply_markup=reply_markup, parse_mode = 'Markdown') else: - response = await self.bot.send_message(chat_id, data, parse_mode = 'Markdown') + response = await self.bot.send_message(chat_id=chat_id, text=data, reply_markup=reply_markup, parse_mode = 'Markdown') return response - async def EditMessage(self, title, description, response, image=None): + async def EditMessage(self, title, description, response, image=None, reply_markup=None): text = f"*{title}*\n\n{description}" - response = await response.edit_text(text=text, parse_mode = 'Markdown') + try: + response = await response.edit_text(text=text, parse_mode = 'Markdown') + except: + response = await response.edit_caption(caption=text, parse_mode = 'Markdown', reply_markup=reply_markup) + return response + async def EditMarkup(self, text, chat_id, message_id, reply_markup=None, image=None): + try: + await self.bot.edit_message_text(text=text, chat_id=chat_id, message_id=message_id, reply_markup=reply_markup, parse_mode = 'Markdown') + except: + await self.bot.edit_message_caption(caption=text, chat_id=chat_id, message_id=message_id, reply_markup=reply_markup, parse_mode = 'Markdown') + class Discord(): async def InboxesAuto(self): @@ -442,7 +578,7 @@ async def InboxesAuto(self): if self.fetch_inboxes == True: - self.response_auto = await self.SendMessage(title="βš™οΈ Fetching inboxes...", description=f"Please wait while we fetch all the inboxes in this server!", color_ds="ui.Style.INFO") + self.response_auto = await self.SendMessage(title="βš™οΈ Fetching inboxes...", description=f"Please wait while we fetch all the inboxes in this server!", color="BLUE") channels = self._ctx.message.guild.channels @@ -482,7 +618,7 @@ def check_regex_channels(msg): msg = await self.bot.wait_for("message", check=check_regex_channels, timeout=50) if msg.content.lower() in ["y", "yes"]: - await self.EditMessage(style="custom", variable="self.response_auto", color_ds="ui.Style.SUCCESS", title = "✨ Regex detected succesfully! ✨", description = f"Thanks for confirming the regex detected for the channels (I'm going to tell my creator he is so good coding :D ), we are going to use `{regex}` to match the inboxes") + await self.EditMessage(style="custom", variable="self.response_auto", color="GREEN", title = "✨ Regex detected succesfully! ✨", description = f"Thanks for confirming the regex detected for the channels (I'm going to tell my creator he is so good coding :D ), we are going to use `{regex}` to match the inboxes") elif msg.content.lower() in ["n", "no"]: await self.response_auto.edit(embed=discord.Embed( @@ -497,10 +633,10 @@ def get_regex_channels(msg_regex_user): if msg_regex_user.content != "": regex = msg_regex_user - await self.EditMessage(style="custom", variable="self.response_auto", color_ds="ui.Style.SUCCESS", title="βœ… Regex accepted!", description=f"Thanks for confirming the regex for the channels, we are going to use `{msg_regex_user.content}` to match the inboxes!") + await self.EditMessage(style="custom", variable="self.response_auto", color="GREEN", title="βœ… Regex accepted!", description=f"Thanks for confirming the regex for the channels, we are going to use `{msg_regex_user.content}` to match the inboxes!") except asyncio.TimeoutError: - await self.EditMessage(style="custom", variable="self.response_auto", color_ds="ui.Style.DANGER", title = ":x: Sorry, you didn't reply on time", description = "Please start the process again.") + await self.EditMessage(style="custom", variable="self.response_auto", color="RED", title = ":x: Sorry, you didn't reply on time", description = "Please start the process again.") raise RuntimeError("We didn't detect any regex, time's up.") for player in self.players_list: @@ -516,7 +652,7 @@ def get_regex_channels(msg_regex_user): for player in self.inboxes_all: mensaje_inboxes += f"**Inbox** {player}[{self.inboxes_all[player]}]\n" - await self.EditMessage(style="custom", variable="self.response_auto", color_ds="ui.Style.SUCCESS", title=f"πŸ“© Inboxes fetched! [{len(self.inboxes_all)}]", description=mensaje_inboxes) + await self.EditMessage(style="custom", variable="self.response_auto", color="GREEN", title=f"πŸ“© Inboxes fetched! [{len(self.inboxes_all)}]", description=mensaje_inboxes) return True @@ -529,10 +665,10 @@ async def InjectHandler(self): embed = discord.Embed(title = self._inject['Subject'], description = all_data, color = discord.Colour.blue()) - if not (self._inject.get('Photo') is None): + if "Photo" in self._inject and self._inject['Photo'] != '': embed.set_image(url=self._inject['Photo']) - if not (self._inject.get('Profile') is None): + if "Profile" in self._inject and self._inject['Profile'] != '': profile_pic = self._inject['Profile'] else: profile_pic = random.choice([ @@ -547,27 +683,88 @@ async def InjectHandler(self): return embed - async def SendMessage(self, color_ds=None, title:str=None, description:str=None): - self.response = await self._ctx.send(embed=ui.Alert(eval(color_ds), title = title, name=False, emoji=False, description = description)) - return self.response + async def PollHandler(self): + self.poll_answered = False + + all_data = self._inject["Script"] + + poll_options = self._inject['Poll'].split('|') + + actual_real_time = re.sub("([^0-9])", "", self._inject['Real Time'])[-2:] + + next_real_time = re.sub("([^0-9])", "", self.data[int(self._inject['#'])]['Real Time'])[-2:] + + diff = int(next_real_time) - int(actual_real_time) + if diff < 0: + diff_no_real = int(actual_real_time) - int(next_real_time) + diff = 60 - diff_no_real + + diff_secs = diff * 60 + + view = discord.ui.View() + view.add_item(discord.ui.Button(style=discord.ButtonStyle.primary,label=poll_options[0], custom_id="poll|"+poll_options[0])) + view.add_item(discord.ui.Button(style=discord.ButtonStyle.primary,label=poll_options[1], custom_id="poll|"+poll_options[1])) + + all_data = all_data+ f"\n\nYou have {diff} minute(s) to answer this poll!" + + player = self._inject['Player'] + + inbox = self.bot.get_channel(self.inboxes_all[player]) + + embed = discord.Embed(title = self._inject['Subject'], description = all_data, color = discord.Colour.yellow()) + + if "Photo" in self._inject and self._inject['Photo'] != '': + embed.set_image(url=self._inject['Photo']) + + self.response_poll = await inbox.send(embed = embed, view=view) + + return embed + + async def PollAnswerHandler(self, interaction=None): + if "poll" in interaction.data['custom_id']: + poll_msg_og = interaction.message.embeds.copy()[0] + + title = poll_msg_og.title + + poll_msg = poll_msg_og.description + poll_msg = poll_msg[: poll_msg.rfind('\n')] + + action_user = interaction.user - async def EditMessage(self, color_ds=None, title:str=None, description:str=None): - await self.response.edit(embed=ui.Alert(eval(color_ds), title = title, name=False, emoji=False, description = description)) + selected_option = interaction.data['custom_id'].split('|')[1] + description = f'{poll_msg}\n\n@{action_user} selected: {selected_option}' + + self.poll_answered = True + self.response_poll = await interaction.response.edit_message(embed=discord.Embed(colour=discord.Colour.green(), title=title, description=description),view=None) + await self.NotifyGameMasters(type_info="poll_answered", data={'msg_poll':poll_msg,'answer':selected_option,'user':action_user}) + return True + else: + pass + + async def SendMessage(self, color="CYAN", title:str=None, description:str=None, view=None, unique=False): + colors = {'BLUE' : discord.Colour.dark_blue(), 'RED' : discord.Colour.red(), 'CYAN' : discord.Colour.blue(), 'GREEN' : discord.Colour.green(), 'YELLOW' : discord.Colour.yellow()} + + if unique == True: + self.gm_poll_msg = await self._ctx.send(embed=discord.Embed(color=colors[color], title = title, description = description), view=view) + return self.gm_poll_msg + + else: + self.response = await self._ctx.send(embed=discord.Embed(color=colors[color], title = title, description = description), view=view) + return self.response + + async def EditMessage(self, color="CYAN", title:str=None, description:str=None, view=None,): + colors = {'BLUE' : discord.Colour.dark_blue(), 'RED' : discord.Colour.red(), 'CYAN' : discord.Colour.blue(), 'GREEN' : discord.Colour.green(), 'YELLOW' : discord.Colour.yellow()} + + await self.response.edit(embed=discord.Embed(color=colors[color], title = title, description = description), view=view) return self.response class Slack(): - def Formatter(title=None, description=None, color="#5bc0de", image=None, author=None, buttons=None, text_input=None, checkboxes=None): - CL_BLUE = "#428bca" - CL_RED = "#d9534f" - CL_WHITE = "#f9f9f9" - CL_CYAN = "#5bc0de" - CL_GREEN = "#5cb85c" - CL_ORANGE = "#ffa700" - CL_YELLOW = "#ffff00" + def Formatter(title=None, description=None, color="CYAN", image=None, author=None, buttons=None, text_input=None, checkboxes=None): + colors = {'BLUE' : '#428bca', 'RED' : '#d9534f', 'WHITE' : '#f9f9f9', 'CYAN' : '#5bc0de', 'GREEN' : '#5cb85c', 'ORANGE' : '#ffa700', 'YELLOW' : '#ffff00'} fallback_text = "" - color = eval(color) + color = colors[color] result =[ { "color": color, @@ -725,13 +922,13 @@ async def InboxesAuto(self, regex=None): for player in self.inboxes_all: mensaje_inboxes += f"Inbox {player} [{self.inboxes_all[player]}]\n" - self.response_auto = await self.EditMessage(response=self.response_auto, color_sl="CL_YELLOW", title = f"πŸ“© Inboxes fetched! [{len(self.inboxes_all)}]", description=mensaje_inboxes) + self.response_auto = await self.EditMessage(response=self.response_auto, color="YELLOW", title = f"πŸ“© Inboxes fetched! [{len(self.inboxes_all)}]", description=mensaje_inboxes) self.regex_ready = True elif self.fetch_inboxes == True: - self.response_auto = await self.SendMessage(channel = self._ctx['channel'], color_sl="CL_CYAN", title="πŸ’¬ Fetching inboxes...", description=f"Please wait while we fetch all the inboxes in this server!") + self.response_auto = await self.SendMessage(channel = self._ctx['channel'], color="CYAN", title="πŸ’¬ Fetching inboxes...", description=f"Please wait while we fetch all the inboxes in this server!") channels = await self.app.client.conversations_list(types="public_channel,private_channel") @@ -766,17 +963,17 @@ async def InboxesAuto(self, regex=None): image = {"image_url":"https://i.ibb.co/34rTqMH/image.png", "name": "regex"} buttons = [{"text":"Yes!", "style": "primary", "value": regex, "action_id": "regex_yes"},{"text":"No.", "style": "danger", "value": "click_me_456", "action_id": "regex_no"}] - self.response_auto = await self.EditMessage(response=self.response_auto, color_sl="CL_GREEN", title = "ℹ️ Regex detected!", description = f"Please confirm if the regex detected for the channels, is correct so we can get the inboxes!\n\nExample:\ninbox-legal\nThe regex should be `inbox-`\n\n*Detected regex:* `{regex}`\n\n\nPlease select your answer below.", image=image, buttons = buttons) + self.response_auto = await self.EditMessage(response=self.response_auto, color="GREEN", title = "ℹ️ Regex detected!", description = f"Please confirm if the regex detected for the channels, is correct so we can get the inboxes!\n\nExample:\ninbox-legal\nThe regex should be `inbox-`\n\n*Detected regex:* `{regex}`\n\n\nPlease select your answer below.", image=image, buttons = buttons) self.regex_ready = False else: mensaje_inboxes = "" - self.response_auto = await self.SendMessage(channel = self._ctx['channel'], color_sl="CL_CYAN", title="πŸ’¬ Fetching inboxes...", description=f"Please wait while we fetch all the inboxes in this server!") + self.response_auto = await self.SendMessage(channel = self._ctx['channel'], color="CYAN", title="πŸ’¬ Fetching inboxes...", description=f"Please wait while we fetch all the inboxes in this server!") for player in self.inboxes_all: mensaje_inboxes += f"Inbox {player} [{self.inboxes_all[player]}]\n" - self.response_auto = await self.EditMessage(response=self.response_auto, color_sl="CL_YELLOW", title = f"πŸ“© Inboxes fetched! [{len(self.inboxes_all)}]", description=mensaje_inboxes) + self.response_auto = await self.EditMessage(response=self.response_auto, color="YELLOW", title = f"πŸ“© Inboxes fetched! [{len(self.inboxes_all)}]", description=mensaje_inboxes) self.regex_ready = True @@ -787,10 +984,14 @@ async def InjectHandler(self): player = self._inject['Player'] - if not (self._inject.get('Photo') is None): - image = {"name": self._inject['Picture Name'], "image_url": self._inject['Photo']} + if "Photo" in self._inject and self._inject['Photo'] != '': + if "Picture Name" in self._inject and self._inject['Picture Name'] == '' or "Photo" not in self._inject: + attachment_name = "attachment.jpg" + else: + attachment_name = self._inject['Picture Name'] + image = {"name": attachment_name, "image_url": self._inject['Photo']} - if not (self._inject.get('Profile') is None): + if "Profile" in self._inject and self._inject['Profile'] != '': author["image_url"] = self._inject['Profile'] else: @@ -801,18 +1002,65 @@ async def InjectHandler(self): ]) author["image_url"] = profile_pic - await self.SendMessage(channel = self.inboxes_all[player], title= self._inject['Subject'], description=self._inject["Script"], image=image, author=author, color_sl="CL_CYAN") + await self.SendMessage(channel = self.inboxes_all[player], title=self._inject['Subject'], description=self._inject["Script"], image=image, author=author, color="CYAN") + + return True + + async def PollHandler(self): + self.poll_answered = False + + image = None + + player = self._inject['Player'] + + if "Photo" in self._inject and self._inject['Photo'] != '': + if "Picture Name" in self._inject and self._inject['Picture Name'] == '' or "Photo" not in self._inject: + attachment_name = "attachment.jpg" + else: + attachment_name = self._inject['Picture Name'] + image = {"name": attachment_name, "image_url": self._inject['Photo']} + + poll_options = self._inject['Poll'].split('|') + + actual_real_time = re.sub("([^0-9])", "", self._inject['Real Time'])[-2:] + + next_real_time = re.sub("([^0-9])", "", self.data[int(self._inject['#'])]['Real Time'])[-2:] + + diff = int(next_real_time) - int(actual_real_time) + if diff < 0: + diff_no_real = int(actual_real_time) - int(next_real_time) + diff = 60 - diff_no_real + + diff_secs = diff * 60 + + description = self._inject["Script"] + f"\n\nYou have {diff} minute(s) to answer this poll!" + + buttons = [{"text": poll_options[0], "style": "primary", "value": 'option1', "action_id": "option1"},{"text":poll_options[1], "style": "primary", "value": "option2","action_id": "option2"}] + + self.response_poll = await self.SendMessage(channel = self.inboxes_all[player], title=self._inject['Subject'], description=description, image=image, buttons=buttons, color="YELLOW") + + return True + + async def PollAnswerHandler(self, body=None, payload=None): + poll_msg = body['message']['attachments'][0]['fallback'] + poll_msg = poll_msg[: poll_msg.rfind('\n')] + action_user = body['user']['username'] + selected_option = payload['text']['text'] + description = f'{poll_msg}\n\n@{action_user} selected: {selected_option}' + self.poll_answered = True + self.response_poll = await self.EditMessage(style="simple", color = "GREEN", title="Poll Answered!", description=description, response=self.response_poll) + await self.NotifyGameMasters(type_info="poll_answered", data={'msg_poll':poll_msg,'answer':selected_option,'user':action_user}) return True - async def SendMessage(self, color_sl=None, title:str=None, description:str=None, channel=None, image=None, author=None, buttons=None, text_input=None, checkboxes=None): + async def SendMessage(self, color=None, title:str=None, description:str=None, channel=None, image=None, author=None, buttons=None, text_input=None, checkboxes=None): if channel == None: channel = self._ctx['channel'] - self.response = await self.app.client.chat_postMessage(channel = channel, attachments = self.Slack.Formatter(title=title, description=description, color=color_sl, image=image, author=author, buttons=buttons, text_input=text_input, checkboxes=checkboxes)) + self.response = await self.app.client.chat_postMessage(channel = channel, attachments = self.Slack.Formatter(title=title, description=description, color=color, image=image, author=author, buttons=buttons, text_input=text_input, checkboxes=checkboxes)) return self.response - async def EditMessage(self, color_sl=None, title:str=None, description:str=None, response=None, image=None, author=None, buttons=None, text_input=None, checkboxes=None): - self.response = await self.app.client.chat_update(channel=response['channel'], ts=response['ts'], attachments = self.Slack.Formatter(title=title, description=description, color=color_sl, image=image, author=author, buttons=buttons, text_input=text_input, checkboxes=checkboxes)) + async def EditMessage(self, color=None, title:str=None, description:str=None, response=None, image=None, author=None, buttons=None, text_input=None, checkboxes=None): + self.response = await self.app.client.chat_update(channel=response['channel'], ts=response['ts'], attachments = self.Slack.Formatter(title=title, description=description, color=color, image=image, author=author, buttons=buttons, text_input=text_input, checkboxes=checkboxes)) return self.response class Whatsapp(): diff --git a/TODO.md b/TODO.md index d1643b5..fa71612 100644 --- a/TODO.md +++ b/TODO.md @@ -4,8 +4,8 @@ - [x] Adding and formating the README.md file. - [ ] Add support to other platforms (Teams, Signal) -- [ ] Add buttons inside the injects for analytics. +- [x] Add buttons inside the injects for analytics. - [ ] Stop/Resume/Pause Buttons for game masters [Slack/Telegram/Discord]. -- [ ] Multi company messages sender. +- [x] Multi company messages sender. - [ ] Send information for debug purposes to #log channels. - [ ] Option to fetch injects from online resources. \ No newline at end of file diff --git a/Telegram/bot.py b/Telegram/bot.py index d0949c5..ea5b5b0 100644 --- a/Telegram/bot.py +++ b/Telegram/bot.py @@ -1,9 +1,11 @@ +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton from aiogram import Bot, Dispatcher, executor, types from aiogram.types import ChatType from dotenv import load_dotenv -from T3SF import * import logging +from T3SF import * + load_dotenv() # Configure logging @@ -14,6 +16,10 @@ T3SF = T3SF(bot=bot) +@dp.callback_query_handler() +async def inline_query_handler(query: types.CallbackQuery): + await T3SF.PollAnswerHandler(query=query) + @dp.message_handler(commands="ping") async def ping(message): """ diff --git a/Telegram/requirements.txt b/Telegram/requirements.txt index d950eba..417b0ab 100644 --- a/Telegram/requirements.txt +++ b/Telegram/requirements.txt @@ -1,15 +1,15 @@ -aiogram==2.19 -aiohttp==3.8.1 +aiogram==2.22.2 +aiohttp==3.8.3 aiosignal==1.2.0 async-timeout==4.0.2 -attrs==21.4.0 +attrs==22.1.0 Babel==2.9.1 -certifi==2021.10.8 -charset-normalizer==2.0.12 -frozenlist==1.3.0 -idna==3.3 +certifi==2022.9.24 +charset-normalizer==2.1.1 +frozenlist==1.3.1 +idna==3.4 multidict==6.0.2 -python-dotenv==0.20.0 -pytz==2022.1 -yarl==1.7.2 -t3sf==1.0 \ No newline at end of file +python-dotenv==0.21.0 +pytz==2022.5 +yarl==1.8.1 +t3sf==1.1 \ No newline at end of file