summaryrefslogtreecommitdiff
path: root/discord/pylon.lua
blob: fa2638d4d91379579384bdfac5bfe5568c897075 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
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