diff --git a/.gitignore b/.gitignore index f3fa40e..7b30424 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ __pycache__ /.vscode /bot-env /guilds +/groups /logo /utils analytics.json diff --git a/README.md b/README.md index 538d910..1e55aea 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,10 @@ ## Quick start: -* Clone the repository: `git clone https://github.com/gregzaal/Auto-Voice-Channels.git` +* Clone the repository: `git clone https://github.com/Jakimbo/Auto-Voice-Channels.git` * Go to the directory: `cd Auto-Voice-Channels` * Make folder to store guild settings: `mkdir guilds` +* Make folder to store group settings: `mkdir groups` * Install pip: `sudo apt-get -y install python3-pip` * Install venv: `pip3 install virtualenv` * Make venv: `python3 -m virtualenv bot-env` diff --git a/cfg.py b/cfg.py index f222192..4d4dcc7 100644 --- a/cfg.py +++ b/cfg.py @@ -15,6 +15,8 @@ # Store settings so we don't have to read them from drive all the time GUILD_SETTINGS = {} +GROUP_SETTINGS = {} +PREV_GROUP_SETTINGS = {} PREV_GUILD_SETTINGS = {} # Store Patreon stuff so we can use it globally and don't have to read it all on every is_gold check diff --git a/commands/__init__.py b/commands/__init__.py index 99e2f52..cdee858 100644 --- a/commands/__init__.py +++ b/commands/__init__.py @@ -11,6 +11,7 @@ bitrate, channelinfo, create, + creategroup, dcnf, defaultlimit, disable, @@ -24,6 +25,7 @@ limit, listroles, logging, + merge, name, nick, patreon, @@ -35,9 +37,11 @@ removealias, rename, restrict, + resetgroup, restrictions, servercheck, source, + split, showtextchannelsto, template, textchannelname, @@ -56,6 +60,8 @@ "bitrate": bitrate.command, "channelinfo": channelinfo.command, "create": create.command, + "creategroup": creategroup.command, + "cg": creategroup.command, "dcnf": dcnf.command, "defaultlimit": defaultlimit.command, "disable": disable.command, @@ -71,6 +77,8 @@ "listroles": listroles.command, "lock": limit.command, "logging": logging.command, + "merge": merge.command, + "m": merge.command, "name": name.command, "nick": nick.command, "patreon": patreon.command, @@ -82,10 +90,14 @@ "public": public.command, "removealias": removealias.command, "rename": rename.command, + "resetgroup": resetgroup.command, + "rg": resetgroup.command, "restrict": restrict.command, "restrictions": restrictions.command, "servercheck": servercheck.command, "source": source.command, + "split": split.command, + "sp": split.command, "showtextchannelsto": showtextchannelsto.command, "template": template.command, "textchannelname": textchannelname.command, diff --git a/commands/create.py b/commands/create.py index 3412ee2..4094562 100644 --- a/commands/create.py +++ b/commands/create.py @@ -20,7 +20,7 @@ ] ] - + async def execute(ctx, params): guild = ctx['guild'] default_name = "➕ New Session" diff --git a/commands/creategroup.py b/commands/creategroup.py new file mode 100644 index 0000000..891c9c6 --- /dev/null +++ b/commands/creategroup.py @@ -0,0 +1,41 @@ +import discord +import functions as func +from commands.base import Cmd + +help_text = [ + [ + ("Usage:", " GROUP NAME"), + ("Description:", + "Make a new voice channel group. This will create a new catagory containing a primary channel that will create secondary channels, as well as a \n" + "'Tavern' channel that will be used for holding the entire group, and a text channel for chatting and bot commands (You can also create another private\n" + "text channel for commands if you wish, it just needs to be under the same category and you will need to give the bot permission to use it ). After\n" + "creating this gorup, you can use the merge (or m) command to move all users out of their voice channels made in the category and place them into\n" + "the 'Tavern' channel. You can then use split (or sp) to move all the users out of the 'Tavern' channel and back into the ones they came from"), + ] +] + + +async def execute(ctx, params): + guild = ctx['guild'] + default_name = "➕ New Session" + group_name = ' '.join(params) + + try: + await func.create_group(guild, group_name, default_name, ctx['message'].author) + except discord.errors.Forbidden: + return False, "I don't have permission to create categories." + except discord.errors.HTTPException as e: + return False, "An HTTPException occurred: {}".format(e.text) + + response = ("A new voice channel group called " + group_name + " has been created. " + "You can now use the split and merge commnands within this category\n".format(default_name, + ctx['print_prefix'])) + return True, response + + +command = Cmd( + execute=execute, + help_text=help_text, + params_required=0, + admin_required=True, +) diff --git a/commands/merge.py b/commands/merge.py new file mode 100644 index 0000000..fd137ff --- /dev/null +++ b/commands/merge.py @@ -0,0 +1,34 @@ +import functions as func +from commands.base import Cmd + +help_text = [ + [ + ("Usage:", ""), + ("Description:", + "When used in a text channel belonging to a group, will merge in channels made using the 'New Session' channel\n" + "and place them into that groups 'Tavern' channel"), + ] +] + + +async def execute(ctx, params): + channel = ctx['channel'] + guild = ctx['guild'] + CategoryID = channel.category_id + + #print(CategoryID) + + await func.merge_channels(guild, channel) + + return True, ("Successfully merged channels") + + + + +command = Cmd( + execute=execute, + help_text=help_text, + params_required=0, + admin_required=False, + voice_required=False, +) diff --git a/commands/resetgroup.py b/commands/resetgroup.py new file mode 100644 index 0000000..5828ce7 --- /dev/null +++ b/commands/resetgroup.py @@ -0,0 +1,33 @@ +import functions as func +from commands.base import Cmd + +help_text = [ + [ + ("Usage:", ""), + ("Description:", + "Once you have completed using the group, run this command so that the voice channels being preserved by the.\n" + "merge command can be automatically deleted again"), + ] +] + + +async def execute(ctx, params): + channel = ctx['channel'] + guild = ctx['guild'] + + #print(CategoryID) + + await func.reset_group(guild, channel) + + return True, ("Successfully reset group") + + + + +command = Cmd( + execute=execute, + help_text=help_text, + params_required=0, + admin_required=False, + voice_required=False, +) diff --git a/commands/split.py b/commands/split.py new file mode 100644 index 0000000..1a8c7bf --- /dev/null +++ b/commands/split.py @@ -0,0 +1,34 @@ +import functions as func +from commands.base import Cmd + +help_text = [ + [ + ("Usage:", ""), + ("Description:", + "Will take all users in the 'Tavern' channel and move them from that voice channel back into the one's they were\n" + "previously in before the merge command was used"), + ] +] + + +async def execute(ctx, params): + channel = ctx['channel'] + guild = ctx['guild'] + client = ctx['client'] + + #print(CategoryID) + + await func.split_channels(guild, channel, client) + + return True, ("Successfully split channel into groups") + + + + +command = Cmd( + execute=execute, + help_text=help_text, + params_required=0, + admin_required=False, + voice_required=False, +) diff --git a/default_group_settings.json b/default_group_settings.json new file mode 100644 index 0000000..9dbe6aa --- /dev/null +++ b/default_group_settings.json @@ -0,0 +1,3 @@ +{ + "group_channels":{} +} diff --git a/functions.py b/functions.py index ab414fa..4a4674a 100644 --- a/functions.py +++ b/functions.py @@ -142,6 +142,8 @@ def toggle_position(guild, chid): return "error" + + @utils.func_timer() def get_channel_games(channel): settings = utils.get_serv_settings(channel.guild) @@ -697,6 +699,10 @@ async def custom_name(guild, c, u, n): settings = utils.get_serv_settings(guild) for p, pv in settings['auto_channels'].items(): for s, sv in pv['secondaries'].items(): + print("s") + print(s) + print("sv") + print(sv) if s == c.id: if n.lower() == 'reset': del settings['auto_channels'][p]['secondaries'][s]['name'] @@ -1034,6 +1040,45 @@ def get_voice_context_channel_ids(guild, settings=None): return channel_ids +@utils.func_timer() +async def create_group(guild, gname, cname, author): + overwrites = { + guild.me: discord.PermissionOverwrite(read_messages=True, + connect=True, + manage_channels=True, + move_members=True) + } + + group_merge_channel_name = gname +" Tavern" + group_text_channel_name = gname +" Commands" + + g = await guild.create_category(gname, overwrites=overwrites) + m = await guild.create_voice_channel(group_merge_channel_name, overwrites=overwrites, category=g) + c = await guild.create_voice_channel(cname, overwrites=overwrites, category=g) + await guild.create_text_channel(group_text_channel_name, overwrites=overwrites, category=g) + + + settings = utils.get_serv_settings(guild) + group_settings = utils.get_group_settings(guild) + settings['auto_channels'][c.id] = {"secondaries": {}} + settings['server_contact'] = author.id + + group_settings['group_channels'][g.id] = {'channels': {},'merge_channel': {}} + group_settings['group_channels'][g.id]['merge_channel'] = m.id + utils.set_serv_settings(guild, settings) + utils.set_group_settings(guild, group_settings) + + await server_log( + guild, + "🆕 {} (`{}`) created a new group channel (`{}`)".format( + user_hash(author), author.id, c.id + ), 1, settings + ) + + return g + + + @utils.func_timer() async def create_primary(guild, cname, author): overwrites = { @@ -1058,6 +1103,86 @@ async def create_primary(guild, cname, author): return c +@utils.func_timer() +async def merge_channels(guild, channel): + + group_settings = utils.get_group_settings(guild) + #print(settings) + CategoryID = channel.category_id + group_settings["group_channels"][channel.category_id]['channels'] = {} + + #Clear group_channels for this category of junk data + group_merge_channel_id = group_settings["group_channels"][channel.category_id]["merge_channel"] + utils.set_group_settings(guild, group_settings) + + #group_merge_channel = utils.get_merge_channel(settings, channel) + + group_merge_channel = guild.get_channel(group_merge_channel_id) + + + VoiceChannel_Length = len(guild.voice_channels) + + VoiceChannel_User_List = [] + + + + #Make a list of all the channels and users in the category where the command was executed + #Stores the list under the group_channel in settings + for i in range(VoiceChannel_Length): + if (guild.voice_channels[i].category_id == CategoryID) & (guild.voice_channels[i].id != group_merge_channel_id): + for members in guild.voice_channels[i].members: + VoiceChannel_User_List.append(members.id) + group_settings["group_channels"][CategoryID]["channels"][guild.voice_channels[i].id] = {"users": {}} + #print(VoiceChannel_User_List) + group_settings["group_channels"][CategoryID]["channels"][guild.voice_channels[i].id]["users"] = VoiceChannel_User_List + #print(VoiceChannel_User_List) + try: + await members.move_to(group_merge_channel) + #print("hello") + except discord.errors.HTTPException as e: + log("Failed to move user {}: {}".format(members.display_name, e.text), guild) + #print("NotToday!") + return + VoiceChannel_User_List = [] + + + + utils.set_group_settings(guild, group_settings) + + + return + +@utils.func_timer() +async def split_channels(guild, channel, client): + group_settings = utils.get_group_settings(guild) + CategoryID = channel.category_id + + for gid, gc in group_settings['group_channels'].items(): + for cid, u in gc['channels'].items(): + for uid in u['users']: + try: + member = guild.get_member(uid) + await member.move_to(guild.get_channel(cid)) + except discord.errors.HTTPException as e: + log("Failed to move user {}: {}".format(member.display_name, e.text), guild) + + + + return + +@utils.func_timer() +async def reset_group(guild, channel): + group_settings = utils.get_group_settings(guild) + CategoryID = channel.category_id + + for cid in group_settings['group_channels'][CategoryID]['channels'].items(): + group_settings['group_channels'][CategoryID]['channels'] = {} + + + utils.set_group_settings(guild, group_settings) + + return + @utils.func_timer(2.5) async def create_secondary(guild, primary, creator, private=False): @@ -1134,7 +1259,7 @@ async def create_secondary(guild, primary, creator, private=False): if ('above' in settings['auto_channels'][primary.id] and settings['auto_channels'][primary.id]['above'] is False): above = False - c_position = primary.position + c_position = 0 if not above: secondaries = settings['auto_channels'][primary.id]['secondaries'].keys() c_position += 1 + len([v for v in voice_channels if v.id in secondaries and v.position > primary.position]) @@ -1285,6 +1410,14 @@ async def create_secondary(guild, primary, creator, private=False): @utils.func_timer() async def delete_secondary(guild, channel): + settings = utils.get_serv_settings(guild) + group_settings = utils.get_group_settings(guild) + category_id = channel.category_id + try: + if group_settings["group_channels"][category_id]["channels"][channel.id]: + return + except Exception: + if channel_is_requested(channel): return lock_channel_request(channel) diff --git a/utils.py b/utils.py index 2e94de0..c91e33b 100644 --- a/utils.py +++ b/utils.py @@ -167,7 +167,7 @@ def set_serv_settings(guild, settings): # num_channels += len(settings['auto_channels'][p]['secondaries']) # if num_channels < prev_num_channels: - # print("{}REM:{} {} ln:{} fn:{} gt:{} gnt:{}".format( + # print("{}REM:{} {} ln:{} fn:{} gt:{} gnt:{}".format( # ' ' * 16, # prev_num_channels - num_channels, # guild.id, @@ -182,6 +182,43 @@ def set_serv_settings(guild, settings): fp = os.path.join(cfg.SCRIPT_DIR, 'guilds', str(guild.id) + '.json') return write_json(fp, settings) +@func_timer() +def get_group_settings(guild, force_refetch=False): + if guild.id in cfg.GROUP_SETTINGS and not force_refetch: + cfg.PREV_GROUP_SETTINGS[guild.id] = deepcopy(cfg.GROUP_SETTINGS[guild.id]) + return cfg.GROUP_SETTINGS[guild.id] + + + fp = os.path.join(cfg.SCRIPT_DIR, 'groups', str(guild.id) + '.json') + if not os.path.exists(fp): + write_json(fp, read_json(os.path.join(cfg.SCRIPT_DIR, 'default_group_settings.json'))) + data = read_json(fp) + + + old_data = deepcopy(data) + for gid, gc in old_data['group_channels'].items(): + del data['group_channels'][gid] + old_gc = deepcopy(gc) + for cid, u in old_gc['channels'].items(): + del gc['channels'][cid] + gc['channels'][int(cid)] = u + #for uid in u['users']: + # del uid[cid] = uid + #cid['users'][int(uid)] = u + data['group_channels'][int(gid)] = gc + + + + cfg.GROUP_SETTINGS[guild.id] = data + cfg.PREV_GROUP_SETTINGS[guild.id] = data + return cfg.GROUP_SETTINGS[guild.id] + +@func_timer() +def set_group_settings(guild, settings): + cfg.GROUP_SETTINGS[guild.id] = settings + fp = os.path.join(cfg.SCRIPT_DIR, 'groups', str(guild.id) + '.json') + return write_json(fp, settings) + @func_timer() def permastore_secondary(cid): @@ -236,6 +273,12 @@ def get_creator_id(settings, channel): if s == channel.id: return sv['creator'] +@func_timer() +def get_merge_channel(settings, channel): + for p in settings['group_channels'].items(): + if p == channel.category_id: + return p['merge_channel'] + @func_timer() def plain_mention(mention):