[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/DigitalPulseSoftware/NotaBot/master/bot.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 discordia = require('discordia')
local enums = discordia.enums
local wrap = coroutine.wrap

discordia.extensions() -- load all helpful Lua library extensions

local client = discordia.Client({
	cacheAllMembers = true,
--	logLevel = 4,
	gatewayIntents = bit.bor(
		-- All intents except guildIntegrations
		enums.gatewayIntent.guilds,
		enums.gatewayIntent.guildMembers,
		enums.gatewayIntent.guildModeration,
		enums.gatewayIntent.guildEmojis,
		enums.gatewayIntent.guildWebhooks,
		enums.gatewayIntent.guildInvites,
		enums.gatewayIntent.guildVoiceStates,
		enums.gatewayIntent.guildMessages,
		enums.gatewayIntent.guildMessageReactions,
		enums.gatewayIntent.guildMessageTyping,
		enums.gatewayIntent.directMessage,
		enums.gatewayIntent.directMessageRections,
		enums.gatewayIntent.directMessageTyping,
		-- privileged intents
		enums.gatewayIntent.guildPresences
	)
})

local function code(str)
    return string.format('```\n%s```', str)
end

local function printLine(...)
    local ret = {}
    for i = 1, select('#', ...) do
        local arg = tostring(select(i, ...))
        table.insert(ret, arg)
    end
    return table.concat(ret, '\t')
end

dofile("utils.lua")

-- Config

Config = {}
local func, err = loadfile("config.lua", "bt", Config)
if (not func) then
	print("Failed to load config file:\n" .. code(tostring(err)))
	return
end

local ret, err = pcall(func)
if (not ret) then
	print("Failed to execute config file:\n" .. code(tostring(err)))
	return
end

-- Bot code

Bot = {}
Bot.Client = client
Bot.Commands = {}
Bot.Events = {}
Bot.Modules = {}
Bot.ConfigType = enums.enum {
	Boolean  = 0,
	Category = 1,
	Channel  = 2,
	Custom   = 3,
	Duration = 4,
	Emoji    = 5,
	Guild    = 6,
	Integer  = 7,
	Member   = 8,
	Message  = 9,
	Number   = 10,
	Role     = 11,
	String   = 12,
	User	 = 13
}

Bot.ConfigTypeString = {}
for name,value in pairs(Bot.ConfigType) do
	Bot.ConfigTypeString[value] = name
end

Bot.ConfigTypeToString = {
	[Bot.ConfigType.Boolean] = tostring,
	[Bot.ConfigType.Category] = function (value, guild)
		local channel = guild:getChannel(value)
		return channel and channel.mentionString or ""
	end,
	[Bot.ConfigType.Channel] = function (value, guild)
		local channel = guild:getChannel(value)
		return channel and channel.mentionString or ""
	end,
	[Bot.ConfigType.Custom] = function (value, guild) return "" end,
	[Bot.ConfigType.Duration] = function (value, guild) return Bot:FormatDuration(guild, value) end,
	[Bot.ConfigType.Emoji] = function (value, guild)
		local emojiData = Bot:GetEmojiData(guild, value)
		return emojiData and emojiData.MentionString or ""
	end,
	[Bot.ConfigType.Integer] = tostring,
	[Bot.ConfigType.Guild] = function (value)
		local guild = client:getGuild(value)
		return guild and guild.name or ""
	end,
	[Bot.ConfigType.Member] = function (value, guild)
		local member = guild:getMember(value)
		return member and member.user.mentionString or ""
	end,
	[Bot.ConfigType.Message] = tostring,
	[Bot.ConfigType.Number] = function (value) return type(value) == "number" end,
	[Bot.ConfigType.Role] = function (value, guild)
		local role = guild:getRole(value)
		return role and role.mentionString or ""
	end,
	[Bot.ConfigType.String] = tostring,
	[Bot.ConfigType.User] = function (value, guild)
		local user = client:getUser(value)
		return user and user.mentionString or ""
	end
}

Bot.ConfigTypeParameter = {
	[Bot.ConfigType.Boolean] = function (value, guild) 
		if (value == "yes" or value == "1" or value == "true") then
			return true
		elseif (value == "no" or value == "0" or value == "false") then
			return false
		end
	end,
	[Bot.ConfigType.Category] = function (value, guild)
		local channel, err = Bot:DecodeChannel(guild, value)
		if (not channel) then
			return nil, err
		end

		if (channel.type ~= enums.channelType.category) then
			return nil, "expected category"
		end

		return channel
	end,
	[Bot.ConfigType.Channel] = function (value, guild)
		return Bot:DecodeChannel(guild, value)
	end,
	[Bot.ConfigType.Custom] = function (value, guild) 
		return nil
	end,
	[Bot.ConfigType.Duration] = function (value, guild)
		return string.ConvertToTime(value)
	end,
	[Bot.ConfigType.Emoji] = function (value, guild)
		return Bot:DecodeEmoji(guild, value)
	end,
	[Bot.ConfigType.Integer] = function (value, guild)
		return tonumber(value:match("^(%d+)$"))
	end,
	[Bot.ConfigType.Guild] = function (value)
		local success, err = util.ValidateSnowflake(value)
		if not success then
			return nil, err
		end
	
		local guild = client:getGuild(value)
		if not guild then
			return nil, value .. " is not a guild I know"
		end

		return guild
	end,
	[Bot.ConfigType.Member] = function (value, guild)
		return Bot:DecodeMember(guild, value)
	end,
	[Bot.ConfigType.Message] = function (value, guild)
		return Bot:DecodeMessage(value, nil, true)
	end,
	[Bot.ConfigType.Number] = function (value)
		return tonumber(value)
	end,
	[Bot.ConfigType.Role] = function (value, guild)
		return Bot:DecodeRole(guild, value)
	end,
	[Bot.ConfigType.String] = function (value, guild)
		return value
	end,
	[Bot.ConfigType.User] = function (value, guild)
		return Bot:DecodeUser(value)
	end
}

Bot.ConfigTypeParser = {
	[Bot.ConfigType.Boolean] = function (value, guild) 
		if (value == "yes" or value == "1" or value == "true") then
			return true
		elseif (value == "no" or value == "0" or value == "false") then
			return false
		end
	end,
	[Bot.ConfigType.Category] = function (value, guild)
		local channel, err = Bot:DecodeChannel(guild, value)
		if (not channel) then
			return nil, err
		end

		if (channel.type ~= enums.channelType.category) then
			return nil, "expected category"
		end

		return channel.id
	end,
	[Bot.ConfigType.Channel] = function (value, guild)
		local channel = Bot:DecodeChannel(guild, value)
		return channel and channel.id
	end,
	[Bot.ConfigType.Custom] = function (value, guild) 
		return nil
	end,
	[Bot.ConfigType.Duration] = function (value, guild)
		return string.ConvertToTime(value)
	end,
	[Bot.ConfigType.Emoji] = function (value, guild)
		local emojiData = Bot:DecodeEmoji(guild, value)
		return emojiData and emojiData.Name
	end,
	[Bot.ConfigType.Integer] = function (value, guild)
		return tonumber(value:match("^(%d+)$"))
	end,
	[Bot.ConfigType.Guild] = function (value)
		local success, err = util.ValidateSnowflake(value)
		if not success then
			return nil, err
		end
	
		local guild = client:getGuild(value)
		if not guild then
			return nil, value .. " is not a guild I know"
		end

		return guild.id
	end,
	[Bot.ConfigType.Member] = function (value, guild)
		local member = Bot:DecodeMember(guild, value)
		return member and member.id
	end,
	[Bot.ConfigType.Message] = function (value, guild)
		local message = Bot:DecodeMessage(value, nil, true)
		return message and Bot:GenerateMessageLink(message)
	end,
	[Bot.ConfigType.Number] = function (value)
		return tonumber(value)
	end,
	[Bot.ConfigType.Role] = function (value, guild)
		local role = Bot:DecodeRole(guild, value)
		return role and role.id
	end,
	[Bot.ConfigType.String] = function (value, guild)
		return value
	end,
	[Bot.ConfigType.User] = function (value, guild)
		local user = Bot:DecodeUser(value)
		return user and user.id
	end
}

client:onSync("ready", function ()
	print("Logged in as " .. client.user.username)
end)

client:on("guildAvailable", function (guild)
	print(string.format("Guild %s (%d members)", guild.name, guild.totalMemberCount))
end)

client:on("guildCreate", function (guild)
	client:info("Bot was added to guild %s", guild.name)
end)

client:on("guildDelete", function (guild)
	client:info("Bot was removed from guild %s", guild.name)
end)

function Bot:Save()
	local stopwatch = discordia.Stopwatch()

	for _, moduleTable in pairs(self.Modules) do
		self:ProtectedCall(string.format("Module (%s) persistent data save", moduleTable.Name), moduleTable.SavePersistentData, moduleTable)
	end

	client:info("Modules data saved (%.3fs)", stopwatch.milliseconds / 1000)
end

-- Why is this required Oo
local env = setmetatable({}, { __index = _G })
env.Bot = Bot
env.Client = client
env.Config = Config
env.discordia = discordia
env.require = require

local function loadbotfile(file)
	local f, err = loadfile(file, "t", env)
	if (not f) then
		error(file .. " failed to compile: " .. err)
	end

	local success, err = pcall(f)
	if (not success) then
		error(file .. " failed to execute: " .. err)
	end
end

loadbotfile("bot_emoji.lua")
loadbotfile("bot_utility.lua")
loadbotfile("bot_localization.lua")
loadbotfile("bot_commands.lua")
loadbotfile("bot_modules.lua")
loadbotfile("bot_timers.lua")

Bot:CreateRepeatTimer(5 * 60, -1, function()
	Bot:Save()
end)

Bot:RegisterCommand({
	Name = "exec",
	Args = {
		{Name = "filename", Type = Bot.ConfigType.String}
	},
	PrivilegeCheck = function (member) return member.id == Config.OwnerUserId end,

	Help = "Executes a file",
	Func = function (message, fileName)
		local sandbox = setmetatable({ }, { __index = _G })
		sandbox.Bot = Bot
		sandbox.Client = client
		sandbox.Config = Config
		sandbox.CommandMessage = message
		sandbox.Discordia = discordia
		sandbox.require = require

		local lines = {}
		sandbox.print = function(...)
			table.insert(lines, printLine(...))
		end

		local func, err = loadfile(fileName, "bt", sandbox)
		if (not func) then
			message:reply("Failed to load file:\n" .. code(tostring(err)))
			return
		end

		local ret, err = pcall(func)
		if (not ret) then
			message:reply("Failed to call file:\n" .. code(tostring(err)))
			return
		end

		if (#lines > 0) then
			lines = table.concat(lines, '\n')
			if #lines > 1990 then -- truncate long messages
				lines = lines:sub(1, 1990)
			end

			message:reply(code(lines))
		end
	end
})

Bot:RegisterCommand({
	Name = "save",
	Args = {},
	PrivilegeCheck = function (member) return member.id == Config.OwnerUserId end,

	Help = "Saves bot data",
	Func = function (message)
		Bot:Save()
		message:reply("Bot data saved")
	end
})

Bot:RegisterCommand({
	Name = "reboot",
	Args = {},
	PrivilegeCheck = function (member) return member.id == Config.OwnerUserId end,

	Help = "Restart bot",
	Func = function (message)
		Bot:Save()
		message:reply("Saving and rebooting...")
		os.exit(0)
	end
})

Bot:RegisterCommand({
	Name = "debugtimer",
	Args = {},
	PrivilegeCheck = function (member) return member.id == Config.OwnerUserId end,

	Help = "Gets last timer check timestamp",
	Func = function (message)
		message:reply(string.format("Last timer check occurred on ", Bot.LastTimerExecution))
	end
})

client:run('Bot ' .. Config.Token)

for k,moduleFile in pairs(Config.AutoloadModules) do
	wrap(function ()
		local moduleTable, err, codeErr = Bot:LoadModuleFile(moduleFile)
		if (moduleTable) then
			client:info("Auto-loaded module \"%s\"", moduleTable.Name)
		else
			local errorMessage = err
			if (codeErr) then
				errorMessage = errorMessage .. "\n" .. codeErr
			end

			client:error(errorMessage)
		end
	end)()
end

Web Proxy Viewer  |  New URL  |  Original Page