latex bot discord
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
extends Node

var template_tex := """
\\documentclass[varwidth=true]{standalone}
\\usepackage[utf8]{inputenc}
\\usepackage{xcolor}
\\usepackage{amsmath}

\\color{white}
\\begin{document}

%s

\\end{document}
"""

var f := File.new()
var thread_pool := []


func compile(source: String) -> RegEx:
	var reg := RegEx.new()
	reg.compile(source)
	return reg


func _ready():
	for _i in range(5):
		thread_pool.append(Thread.new())
	var dir = Directory.new()
	if !dir.dir_exists("res://texs"):
		dir.make_dir("texs")
	f.open("res://texs/.gdignore", File.WRITE)  # touch .gdignore
	f.close()
	var bot := DiscordBot.new()
	add_child(bot)
	var file = File.new()
	var err = file.open("res://token", File.READ)
	var token
	if err == OK:
		token = file.get_as_text()
	elif OS.has_environment("TOKEN"):
		token = OS.get_environment("TOKEN")
	else:
		push_error("token missing")
	file.close()
	bot.TOKEN = token
	bot.connect("bot_ready", self, "_on_bot_ready")
	bot.connect("message_create", self, "_on_message_create")
	bot.login()


func _on_bot_ready(bot: DiscordBot):
	bot.set_presence({"activity": {"type": "Game", "name": "Printing LaTeX"}})
	print("Logged in as " + bot.user.username + "#" + bot.user.discriminator)
	print("Listening on " + str(bot.channels.size()) + " channels and " + str(bot.guilds.size()) + " guilds.")


func _on_message_create(bot: DiscordBot, message: Message, _channel: Dictionary):
	if message.author.bot:
		return
	var msg: String
	var reg := compile("`{3}(la)?tex([^`]+)`{3}")
	var res := reg.search(message.content)
	if res:
		msg = res.strings[2]
	else:
		var reg2 := compile("!(la)?tex\\s*([\\s\\S]+)")
		var res2 := reg2.search(message.content)
		if !res2:
			return
		msg = res2.strings[2]

	msg = msg.strip_edges()

	print("----\n%s\n----" % msg)
	if !msg:
		return

	var th: Thread
	for thread in thread_pool:
		if !thread.is_alive():
			th = thread
			break
	if !th:
		thread_pool.append(Thread.new())
		th = thread_pool[-1]

	th.start(self, "latex2img", template_tex % msg)
	while true:
		yield(get_tree(), "idle_frame")
		if !th.is_alive():
			var img = th.wait_to_finish()
			if img is Dictionary:
				bot.reply(message, "No({err}): `{output}`".format(img))
				return
			bot.reply(
				message,
				"Tex:",
				{"files": [{"name": "code.png", "media_type": "image/png", "data": img.save_png_to_buffer()}]}
			)
			return


func _notification(what):
	if what == NOTIFICATION_EXIT_TREE:
		OS.execute("bash", ["-c", "rm -r texs"], false)


func latex2img(latex: String):
	randomize()
	var name := ("%s-%s" % [randi() % 201, randi() % 201]).c_escape()
	f.open("res://texs/%s.tex" % name, File.WRITE_READ)
	f.store_string(latex)
	f.close()
	var output: PoolStringArray = []
	var err = OS.execute(
		"bash", ["-c", "cd texs && latex -interaction=nonstopmode '%s.tex'" % name], true, output, true
	)
	if err:
		return {err = err, output = "(la)" + output.join(" ")}
	output.resize(0)
	var dvipng = [
		"-c",
		"dvipng -strict -bg Transparent --png -Q 250 -D 250 -T tight -o 'texs/%s.png' 'texs/%s.dvi'" % [name, name]
	]
	err = OS.execute("bash", dvipng, true, output, true)
	if err:
		return {err = err, output = "(dvi)" + output.join(" ")}
	var img := Image.new()
	err = img.load("res://texs/%s.png" % name)
	OS.execute("bash", ["-c", "rm 'texs/%s.'*" % name], false)
	return img