[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/DigitalPulseSoftware/NotaBot/master/module_channels.lua [Back]  [Original]

-- Copyright (C) 2018 Jrme Leclercq
-- This file is part of the "Not a Bot" application
-- For conditions of distribution and use, see copyright notice in LICENSE

local bot = Bot
local client = Client
local discordia = Discordia
local enums = discordia.enums
local timer = require("timer")

discordia.extensions() -- load all helpful extensions

Module.Name = "channels"

local emojiOrder = {
	zero = 0,
	one = 1,
	two = 2,
	three = 3,
	four = 4,
	five = 5,
	six = 6,
	seven = 7,
	eight = 8,
	nine = 9
}

local function arrayRemoveValue(t, v)
	local i = 1
	while (i  0) then
										table.insert(actionStr, string.format("**Adds role%s** %s\n", #addedRoles > 1 and "s" or "", table.concat(addedRoles, ", ")))
									end
								end

								if (actions.RemoveRoles) then
									local removedRoles = {}
									for _, roleId in pairs(actions.RemoveRoles) do
										local role = guild:getRole(roleId)
										if (role) then
											table.insert(removedRoles, role.mentionString)
										else
											table.insert(removedRoles, "") 
										end
									end

									if (#removedRoles > 0) then
										table.insert(actionStr, string.format("**Removes role%s** %s\n", #removedRoles > 1 and "s" or "", table.concat(removedRoles, ", ")))
									end
								end

								if (actions.SendMessage) then
									table.insert(actionStr, string.format("**Sends private message:**\n\"%s\"\n", actions.SendMessage))
								end

								if (actions.ToggleRoles) then
									local toggleRoles = {}
									for _, roleId in pairs(actions.ToggleRoles) do
										local role = guild:getRole(roleId)
										if (role) then
											table.insert(toggleRoles, role.mentionString)
										else
											table.insert(toggleRoles, "") 
										end
									end

									if (#toggleRoles > 0) then
										table.insert(actionStr, string.format("**Toggles role%s** %s", #toggleRoles > 1 and "s" or "", table.concat(toggleRoles, ", ")))
									end
								end

								local emoji = bot:GetEmojiData(guild, emoji)

								table.insert(fields, {
									name = string.format("- %s:", emoji and emoji.MentionString or ""),
									value = table.concat(actionStr, "\n")
								})
							end

							commandMessage:reply({
								embed = { 
									description = string.format("Message in %s:\n%s", channel.mentionString, bot:GenerateMessageLink(message)),
									fields = fields,
									footer = {
										text = "Use `!updatechannelconfig    ` to update channels reactions settings."
									}
								}
							})
						end
					end
				end
			end
		end
	})

	self:RegisterCommand({
		Name = "updatechannelconfig",
		Args = {
			{Name = "message", Type = Bot.ConfigType.Message},
			{Name = "emoji", Type = Bot.ConfigType.Emoji},
			{Name = "action", Type = Bot.ConfigType.String},
			{Name = "value", Type = Bot.ConfigType.String, Optional = true}
		},
		PrivilegeCheck = function (member) return member:hasPermission(enums.permission.administrator) end,

		Help = "Configures the channel module messages and reactions",
		Func = function (commandMessage, message, emoji, action, value)
			local guild = commandMessage.guild
			local member = commandMessage.member
			local config = self:GetConfig(guild)

			local GetReactionActionsConfig = function (channelId, messageId, reaction, noCreate)
				local guildConfig = config.ReactionActions

				local channelTable = guildConfig[channelId]
				if (not channelTable) then
					if (noCreate) then
						return
					end

					channelTable = {}
					guildConfig[channelId] = channelTable
				end

				local messageTable = channelTable[messageId]
				if (not messageTable) then
					if (noCreate) then
						return
					end

					messageTable = {}
					channelTable[messageId] = messageTable
				end

				local reactionActions = messageTable[reaction]
				if (not reactionActions) then
					if (noCreate) then
						return
					end

					reactionActions = {}
					messageTable[reaction] = reactionActions
				end

				return reactionActions
			end

			local success = false

			if (action == "addrole") then
				local role, err = Bot:DecodeRole(guild, value)
				if (not role) then
					commandMessage:reply(string.format("Invalid role: %s", err))
					return
				end

				if not member:hasPermission(enums.permission.manageRoles) then
					commandMessage:reply("you need to have the manage roles permission to toggle a role")
					return
				end

				if role.position > member.highestRole.position then
					commandMessage:reply("you cannot add or remove a role higher than your own")
					return
				end

				local roleValue = tostring(role.id)

				local reactionActions = GetReactionActionsConfig(message.channel.id, message.id, emoji.Name)
				if (reactionActions.AddRoles) then
					if (not table.search(reactionActions.AddRoles, roleValue)) then
						table.insert(reactionActions.AddRoles, tostring(role.id))
					end
				else
					reactionActions.AddRoles = {roleValue}
				end

				if (reactionActions.RemoveRoles) then
					arrayRemoveValue(reactionActions.RemoveRoles, roleValue)
				end

				if (reactionActions.ToggleRoles) then
					arrayRemoveValue(reactionActions.ToggleRoles, roleValue)
				end

				success = true
				commandMessage:reply(string.format("Reactions on %s for %s will now adds role %s (%s)", Bot:GenerateMessageLink(message), emoji.MentionString, role.name, role.id))
			elseif (action == "removerole") then
				local role, err = Bot:DecodeRole(guild, value)
				if (not role) then
					commandMessage:reply(string.format("Invalid role: %s", err))
					return
				end

				if not member:hasPermission(enums.permission.manageRoles) then
					commandMessage:reply("you need to have the manage roles permission to toggle a role")
					return
				end

				if role.position > member.highestRole.position then
					commandMessage:reply("you cannot add or remove a role higher than your own")
					return
				end

				local roleValue = tostring(role.id)

				local reactionActions = GetReactionActionsConfig(message.channel.id, message.id, emoji.Name)
				if (reactionActions.RemoveRoles) then
					if (not table.search(reactionActions.RemoveRoles, roleValue)) then
						table.insert(reactionActions.RemoveRoles, tostring(role.id))
					end
				else
					reactionActions.RemoveRoles = {roleValue}
				end

				if (reactionActions.AddRoles) then
					arrayRemoveValue(reactionActions.AddRoles, roleValue)
				end

				if (reactionActions.ToggleRoles) then
					arrayRemoveValue(reactionActions.ToggleRoles, roleValue)
				end

				success = true
				commandMessage:reply(string.format("Reactions on %s for %s will now remove role %s (%s)", Bot:GenerateMessageLink(message), emoji.MentionString, role.name, role.id))
			elseif (action == "togglerole") then
				local role, err = Bot:DecodeRole(guild, value)
				if (not role) then
					commandMessage:reply(string.format("Invalid role: %s", err))
					return
				end

				if not member:hasPermission(enums.permission.manageRoles) then
					commandMessage:reply("you need to have the manage roles permission to toggle a role")
					return
				end

				if role.position > member.highestRole.position then
					commandMessage:reply("you cannot add or remove a role higher than your own")
					return
				end

				local roleValue = tostring(role.id)

				local reactionActions = GetReactionActionsConfig(message.channel.id, message.id, emoji.Name)
				if (reactionActions.ToggleRoles) then
					if (not table.search(reactionActions.ToggleRoles, roleValue)) then
						table.insert(reactionActions.ToggleRoles, tostring(role.id))
					end
				else
					reactionActions.ToggleRoles = {roleValue}
				end

				if (reactionActions.AddRoles) then
					arrayRemoveValue(reactionActions.AddRoles, roleValue)
				end

				if (reactionActions.RemoveRoles) then
					arrayRemoveValue(reactionActions.RemoveRoles, roleValue)
				end

				success = true
				commandMessage:reply(string.format("Reactions on %s for %s will now toggle role %s (%s)", Bot:GenerateMessageLink(message), emoji.MentionString, role.name, role.id))
			elseif (action == "send") then
				if (not value) then
					commandMessage:reply("Empty message")
					return
				end

				local reactionActions = GetReactionActionsConfig(message.channel.id, message.id, emoji.Name)
				reactionActions.SendMessage = value

				success = true
				commandMessage:reply(string.format("Reactions on %s for %s will now send private message: %s", Bot:GenerateMessageLink(message), emoji.MentionString, value))
			elseif (action == "clear") then
				local reactionActions = GetReactionActionsConfig(message.channel.id, message.id, emoji.Name, true)
				if (reactionActions) then
					reactionActions.AddRoles = nil
					reactionActions.RemoveRoles = nil
					reactionActions.ToggleRoles = nil
					reactionActions.SendMessage = nil
				end

				success = true
				commandMessage:reply(string.format("Reactions actions on %s for %s have been cleared", Bot:GenerateMessageLink(message), emoji.MentionString))
			else
				commandMessage:reply("Invalid action (must be addrole/clear/removerole/send/togglerole)")
			end

			if (success) then
				self:SaveGuildConfig(guild)
				commandMessage:reply(string.format("Configuration of module %s has been saved, use the `!reload %s` command to activate it", self.Name, self.Name))
			end
		end
	})
	return true
end

function Module:GetReactionActions(guild, channelId, messageId, reaction, noCreate)
	local reactionKey = string.format("%s_%s_%s", channelId, messageId, reaction)

	local data = self:GetData(guild)
	local roleActions = data.ReactionActions[reactionKey]
	if (not roleActions and not noCreate) then
		roleActions = {}
		roleActions.Add = {}
		roleActions.Remove = {}
		roleActions.Toggle = {}

		data.ReactionActions[reactionKey] = roleActions
	end

	return roleActions
end

function Module:OnEnable(guild)
	local config = self:GetConfig(guild)

	local data = self:GetData(guild)
	data.ReactionActions = {}

	self:HandleConfig(guild, config)

	return true
end

function Module:HandleConfig(guild, config)
	self:LogInfo(guild, "Processing roles...")

	local ProcessRole = function (channelId, messageId, reaction, roleId, field)
		local roleActions = self:GetReactionActions(guild, channelId, messageId, reaction)
		table.insert(roleActions[field], roleId)
	end

	local configUpdated = false
	for channelId,messageTable in pairs(config.ReactionActions) do
		for messageId,reactionTable in pairs(messageTable) do
			for reaction, actions in pairs(reactionTable) do
				local hasActions = false

				if (actions.AddRoles) then
					arrayFilter(actions.AddRoles, function (roleId)
						local role = guild:getRole(roleId)
						if (role) then
							ProcessRole(channelId, messageId, reaction, roleId, "Add")
							hasActions = true

							return true
						else
							self:LogWarning(guild, "Role %s not found", roleId)
							configUpdated = true

							return false
						end
					end)
				end

				if (actions.RemoveRoles) then
					arrayFilter(actions.RemoveRoles, function (roleId)
						local role = guild:getRole(roleId)
						if (role) then
							ProcessRole(channelId, messageId, reaction, roleId, "Remove")
							hasActions = true

							return true
						else
							self:LogWarning(guild, "Role %s not found", roleId)
							configUpdated = true

							return false
						end
					end)
				end

				if (actions.ToggleRoles) then
					arrayFilter(actions.ToggleRoles, function (roleId)
						local role = guild:getRole(roleId)
						if (role) then
							ProcessRole(channelId, messageId, reaction, roleId, "Toggle")
							hasActions = true

							return true
						else
							self:LogWarning(guild, "Role %s not found", roleId)
							configUpdated = true

							return false
						end
					end)
				end

				if (actions.SendMessage) then
					local roleActions = self:GetReactionActions(guild, channelId, messageId, reaction)
					roleActions.Message = actions.SendMessage

					hasActions = true
				end

				if (not hasActions) then
					reactionTable[reaction] = nil
					configUpdated = true
				end
			end

			if (next(reactionTable) == nil) then
				messageTable[messageId] = nil
				configUpdated = true
			end
		end

		if (next(messageTable) == nil) then
			config.ReactionActions[channelId] = nil
			configUpdated = true
		end
	end

	self:LogInfo(guild, "Adding emojis to concerned messages...")

	-- Make sure reactions are present on messages
	for channelId,messageTable in pairs(config.ReactionActions) do
		local channel = guild:getChannel(channelId)
		if (channel) then
			for messageId,reactionTable in pairs(messageTable) do
				local message = channel:getMessage(messageId)
				if (message) then
					local hasReaction = {}
					local messageReactions = {}
					for k,reaction in pairs(message.reactions) do
						local emoji = bot:GetEmojiData(guild, reaction.emojiId or reaction.emojiName)
						if (emoji) then
							hasReaction[emoji.Name] = true
						else
							self:LogError(guild, "found reaction which does not exist in guild: %s", reaction.emojiName)
						end

						local expectedCount = 0
						if (reaction.me) then
							expectedCount = 1
						end

						if (reaction.count ~= expectedCount) then
							messageReactions[reaction] = reaction:getUsers()
						end
					end

					local reactionToAdd = {}
					for reaction, _ in pairs(reactionTable) do
						local emoji = bot:GetEmojiData(guild, reaction)
						if (emoji) then
							if (not hasReaction[emoji.Name]) then
								table.insert(reactionToAdd, reaction)
							end
						else
							self:LogError(guild, "Emoji \"%s\" does not exist", reaction.emojiName)
						end
					end

					table.sort(reactionToAdd, function (a, b)
						local i = emojiOrder[a]
						local j = emojiOrder[b]
						if (i and j) then
							return i < j
						else
							return a < b
						end
					end)

					for _, reaction in pairs(reactionToAdd) do
						local emoji = Bot:GetEmojiData(guild, reaction)
						local success, err = message:addReaction(emoji.Emoji or emoji.Id)
						if (not success) then
							self:LogWarning(guild, "Failed to add reaction %s on message %s (channel: %s): %s", emoji.Name, message.id, channelId, err)
						end
					end

					-- Handle users reactions
					for reaction, users in pairs(messageReactions) do
						local emoji = bot:GetEmojiData(guild, reaction.emojiId or reaction.emojiName)
						if (emoji) then
							for _, user in pairs(users) do
								if (self:HandleReactionAdd(guild, user.id, channelId, messageId, emoji.Name)) then
									local success, err = reaction:delete(user.id)
									if (not success) then
										self:LogWarning(guild, "Failed to remove reaction on message (%s)", err)
									end						
								end
							end
						end
					end
				else
					self:LogError(guild, "Message %s no longer exists in channel %s", messageId, channelId)
					messageTable[messageId] = nil
					configUpdated = true
				end
			end
		else
			self:LogError(guild, "Channel %s no longer exists", channelId)
			config.ReactionActions[channelId] = nil
			configUpdated = true
		end
	end

	if (configUpdated) then
		self:SaveGuildConfig(guild)
	end
end

function Module:HandleConfigUpdate(guild, config, configName)
	if (not configName or configName == "ReactionActions") then
		self:HandleConfig(guild, config)
	end
end

function Module:HandleReactionAdd(guild, userId, channelId, messageId, reactionName)
	if (client.user.id == userId) then
		return false
	end

	local roleActions = self:GetReactionActions(guild, channelId, messageId, reactionName, true)
	if (not roleActions) then
		return false
	end

	local isActive = false

	local member = guild:getMember(userId)
	if (not member) then
		return true
	end

	for _,roleId in pairs(roleActions.Add) do
		local role = guild:getRole(roleId)
		if (role) then
			if (not member:hasRole(role)) then
				self:LogInfo(guild, "Adding %s%s to %s", role.name, (role.color ~= 0) and " (colored)" or "", member.tag)

				local success, err = member:addRole(roleId)
				if (not success) then
					self:LogWarning(guild, "Failed to add role %s to %s: %s", role.name, member.tag, err)
				end
			end

			isActive = true
		else
			self:LogWarning(guild, "Role %s appears to have been removed", roleId)
		end
	end

	for _,roleId in pairs(roleActions.Remove) do
		local role = guild:getRole(roleId)
		if (role) then
			if (member:hasRole(role)) then
				self:LogInfo(guild, "Removing %s%s from %s", role.name, (role.color ~= 0) and " (colored)" or "", member.tag)

				local success, err = member:removeRole(roleId)
				if (not success) then
					self:LogWarning(guild, "Failed to remove role %s from %s: %s", role.name, member.tag, err)
				end
			end

			isActive = true
		else
			self:LogWarning(guild, "Role %s appears to have been removed", roleId)
		end
	end

	for _,roleId in pairs(roleActions.Toggle) do
		local role = guild:getRole(roleId)
		if (role) then
			local hasRole = member:hasRole(role)
			self:LogInfo(guild, "Toggling %s%s (%s) from %s", role.name, (role.color ~= 0) and " (colored)" or "", hasRole and "removing" or "adding", member.tag)

			local success, err
			if (hasRole) then
				success, err = member:removeRole(role)
			else
				success, err = member:addRole(role)
			end

			if (not success) then
				self:LogWarning(guild, "Failed to toggle role %s from %s: %s", role.name, member.tag, err)
			end

			isActive = true
		else
			self:LogWarning(guild, "Role %s appears to have been removed", roleId)
		end
	end

	if (roleActions.Message) then
		local privateChannel = member.user:getPrivateChannel()
		if (privateChannel) then
			local success, err = privateChannel:send(string.format("[From %s]\n%s", guild.name, roleActions.Message))
			if (not success) then
				self:LogWarning(guild, "Failed to send reaction message to %s (maybe user disabled private messages from this server?): %s", member.user.tag, err)
			end

			isActive = true
		else
			self:LogWarning(guild, "Failed to get private channel with %s", member.user.tag)
		end
	end

	return isActive
end

function Module:OnReactionAdd(reaction, userId)
	if (not bot:IsPublicChannel(reaction.message.channel)) then
		return
	end

	local emoji = bot:GetEmojiData(reaction.message.guild, reaction.emojiId or reaction.emojiName)
	if (not emoji) then
		return
	end

	if (self:HandleReactionAdd(reaction.message.channel.guild, userId, reaction.message.channel.id, reaction.message.id, emoji.Name)) then
		timer.sleep(1000) -- Wait a bit before removing reaction (so user won't think it failed)
		local success, err = reaction.message:removeReaction(emoji.Emoji or emoji.Id, userId)
		if (not success) then
			self:LogWarning(reaction.message.guild, "Failed to remove reaction for message (%s)", err)
		end
	end
end

function Module:OnReactionAddUncached(channel, messageId, reactionIdOrName, userId)
	if (not bot:IsPublicChannel(channel)) then
		return
	end

	local emoji = bot:GetEmojiData(channel.guild, reactionIdOrName)
	if (not emoji) then
		self:LogWarning(channel.guild, "Emoji %s was used but not found in guild", reactionIdOrName)
		return
	end

	if (self:HandleReactionAdd(channel.guild, userId, channel.id, messageId, emoji.Name)) then
		local message = channel:getMessage(messageId)
		if (message) then
			timer.sleep(1000) -- Wait a bit before removing reaction (so user won't think it failed)
			local success, err = message:removeReaction(emoji.Emoji or emoji.Id, userId)
			if (not success) then
				self:LogWarning(channel.guild, "Failed to remove reaction for uncached message (%s)", err)
			end
		end
	end
end

Web Proxy Viewer  |  New URL  |  Original Page