local cqueues = require 'cqueues' local websocket = require'http.websocket' local request = require'http.request' local json = require 'dkjson' local class = require 'r.class' local BasePylon = require 'pylon' local Channel = require 'channel' local consts = require 'discord.consts' local opcodes = consts.opcodes local exec_webhook = require'discord.the' local API_BASE = "https://discord.com/api/v10/" local Discord = class.extend(BasePylon) function Discord.init(self) self:_check_fields "token" self.channel_to_webhook = {} end local function identify_payload(token) local I = consts.intents return json.encode{ op = opcodes.identify, d = { properties = {os="wilson",browser="wilson",device="wilson"}, intents = I.guilds + I.guild_messages + I.message_content, token = token } } end function Discord._req(self, url, payload) local req = request.new_from_uri(API_BASE..url) req.headers:upsert(':method',payload and 'POST' or 'GET') req.headers:upsert('authorization','Bot '..self.token) req.headers:upsert('user-agent','wilson (https://g.gh0.pw/wilson/, v0.0)') if payload then req.headers:upsert('content-type','application/json') req:set_body(assert(json.encode(payload))) end local head,body = assert(req:go()) local status = head:get':status' local bod = body:get_body_as_string() assert(status:match"^2", 'status was '..status..' body '..bod) local val, _, err = json.decode(bod) if err then error(err) else return val end end function Discord._connect(self) local uri = "wss://gateway.discord.gg/?v=10&encoding=json" self.ws = websocket.new_from_uri(uri) assert(self.ws:connect()) self.ws:send(identify_payload(self.token), 'text') local me = self:_req"users/@me" self.bot_id = me.id self.log('logged in as ',me.username) for busname, bus in pairs(self.wilson.busses) do for _, channel in ipairs(bus) do if channel.pylon == self then self:add_channel(channel) end end end end function Discord.add_channel(self, channel) local webhooks = self:_req("channels/"..channel.descriptor.."/webhooks") for _,wh in pairs(webhooks) do if wh.application_id == self.bot_id then self.log("Found existing webhook for channel",channel.descriptor) self.channel_to_webhook[channel.descriptor] = wh return end end self.log("Making webhook for channel",channel.descriptor) local wh = self:_req("channels/"..channel.descriptor.."/webhooks", { name=self.name:gsub("discord",""):gsub("clyde","").." wilson hook" }) self.channel_to_webhook[channel.descriptor] = wh end function Discord._heartbeating(self, interval_ms) local interval = interval_ms / 1000 cqueues.sleep(interval * math.random()) while true do self.ws:send(json.encode{ op = opcodes.heartbeat, d = self.sequence_number, }) cqueues.sleep(interval) end end function Discord.recving(self) for packet in self.ws:each() do local event = json.decode(packet) self.log:debug(event.s, event.op, event.t) if event.op ~= opcodes.dispatch then self.log:debug(event.d) end if event.s then self.sequence_number = event.s end if event.op == opcodes.hello then self.cq:wrap(self._heartbeating, self, event.d.heartbeat_interval) elseif event.op == opcodes.dispatch then self:handle_dispatch(event) end end end function Discord.handle_dispatch(self, event) local d = event.d if event.t == 'MESSAGE_CREATE' then self.log:proto('<',d.channel_id,d.author.username,d.content) if self.channel_to_webhook[d.channel_id] and d.author.id ~= self.channel_to_webhook[d.channel_id].id and d.author.id ~= self.bot_id then self.wilson:deliver(Channel(self, d.channel_id), { body = d.content, sender = '[d]' .. d.author.username, }) end elseif event.t == 'READY' then self.log:debug(event.d) end end function Discord.sending(self) for dest_channel, message in self.inbox:iter() do self.log:proto('>',dest_channel,message.sender,message.body) exec_webhook(self.channel_to_webhook[dest_channel.descriptor].url, { username = message.sender, content = message.body, }) end end return Discord