summaryrefslogtreecommitdiffstats
path: root/discord.tcl
blob: 85da7f0fd7e7f45a4b22023e7691c5231ce30e6c (plain) (blame)
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
#!/usr/bin/tclsh
source www.tcl
namespace eval discord {

	package require Tcl 8.4
	package require http 2.7
	package require logger
	package require sha1
	package require base64
	package require websocket 1.3.1
	::websocket::loglevel "debug"

	package require tls
	http::register https 443 [list ::tls::socket -autoservername true]

	package require Tcl 8.5
	package require json::write 1.0.3

	package require Tcl 8.4
	package require json 1.3.3

	package require TclOO

	package require logger

	package require lambda

	#unused
	proc escape {string} {
		set r ""
		foreach t [split $string ""] {
			if [regexp {[[:print:]]} $t] {
				append r $t
			} else {
				append r "\\x[format %02X [scan $t %c]]"
			}
		}
		return $r
	}

	set html_mapping { "\"" &quot; ' &apos; & &amp; < &lt; > &gt; }

	# unused
	set httpd_body {
		method content {} {
			set in [my FormData]
			set sitekey sitekey_replace_me
			my puts "
				<form method=post>
					<script src=https://js.hcaptcha.com/1/api.js async defer></script>
					<div class=h-captcha data-sitekey=\"[string map html_mapping $sitekey]\"></div>
					<input type=submit />
					<br>
					you sent: [string map html_mapping $in]
				</form>
			"
		}
	}

	proc login {email password callback {captcha {}}} {
		if {$captcha == {}} {
			set capt null
		} else {
			set capt [::json::write string $captcha]
		}
		proc login_command {callback token} {
			upvar #0 $token state
			if {[catch {
				set token [dict get [::json::json2dict $state(body)] token]
				set user_id [dict get [::json::json2dict $state(body)] user_id]
			} result] != 0} {
				if {[catch {
					set captcha_sitekey [dict get [::json::json2dict $state(body)] captcha_sitekey]
				} result] != 0} {
					[{*}$callback error $result $state(body)]
				} else {
					[{*}$callback captcha $captcha_sitekey]
				}
			} else {
				[{*}$callback ok $token $user_id]
			}
			::http::cleanup $token
		}
		::http::geturl https://discord.com/api/v9/auth/login -query "{\"login\":[::json::write string $email],\"password\":[::json::write string $password],\"undelete\":\"false\",\"captcha_key\":$capt,\"login_source\":null,\"gift_code_sku_id\":null}" -timeout 10000 -type application/json -command "[namespace which login_command] {$callback}"
	}
	
	proc connect_example_callback {type {arg1 1}} {
		switch $type {
			ok {
				puts stderr "connect success!"
			}
			authfail {
				puts stderr "auth failed, try login again!"
			}
			error {
				puts stderr "error connecting: $arg1"
			}
		}
	}

	proc login_example_callback {type {arg1 {}}} {
		switch $type {
			ok {
				puts "ok, login successful"
			}
			captcha {
				puts "solve the captcha at address $arg1"
			}
			error {
				puts "error: $arg1"
			}
		}
	}

	oo::class create discord {
		constructor {{stor {login {} password {} token {} user_id {}}}} {
			my variable log storage
			set storage $stor
			set log [logger::init discord]
		}
		destructor {
			my variable log storage sockets
			foreach socket $sockets {
				close $socket
			}
			if {[my is_connected] != -1} {
				my disconnect
			}
			${log}::delete
			return storage
		}
		method disconnect {} {
			my variable sock
			::websocket::close $sock 1000 "Tcl/Tk discord odjemalec se poslavlja"
			unset sock
		}
		method set_login_password {login password} {
			my variable storage
			dict set storage login $login
			dict set storage password $password
		}
		method set_token {token} {
			my variable storage
			dict set storage token
		}
		# handles captcha interactively
		method login {callback} {
			my variable storage log
			proc login_callback {that callback type {arg1 ""} {arg2 ""}} {
				namespace upvar $that log log sockets sockets
				switch $type {
					ok {
						${log}::notice "login ok: token is $arg1, user_id is $arg2"
						[{*}$callback ok [list $arg1 $arg2]]
					}
					captcha {
						${log}::warn "login captcha: sitekey is $arg1"
						proc httpd {that_login_callback chan addr port} {
							namespace upvar $that_login_callback log log arg1 sitekey
							fconfigure $chan -blocking 0
							proc readable {that_login_callback} {
								namespace upvar $that_login_callback arg1 sitekey
								gets 
							}
							chan event $chan readable [list [namespace which readable] [self namespace]]
							${log}::notice "new connection to httpd from $addr:$port"
						}
						oo::class create
						set srv [socket -server [list [namespace which httpd] [self namespace]] 0]
						lappend sockets $srv
						${log}::notice "please solve captcha at http://127.0.0.1:[lindex [fconfigure $srv -sockname] 2]/captcha.html"
						[{*}$callback captcha "http://127.0.0.1:[lindex [fconfigure $srv -sockname] 2]/captcha.html"]
					}
					error {
						${log}::error "login error: message is $arg1, response from server is $arg2"
						[{*}$callback error [list $arg1 $arg2]]
					}
				}
			}
			::discord::login [dict get $storage login] [dict get $storage password] "[namespace which login_callback] [self namespace] $callback"
		}
		method connect {} {
			my variable sock log storage
			if {[my is_connected] != -1} {
				my disconnect
			}
			proc handler { sock type msg } {
				my variable log
				switch $type {
					text {
						${log}::debug "received a message: $msg"
					}
					connect {
						${log}::notice "connected"
					}
					disconnect {
						${log}::notice "disconnected"
					}
					close {
						${log}::notice "pending closure of connection"
					}
					# binary and ping are unsupported
					default {
						${log}::warn "received an unsupported handler call. type is $type, msg is $msg"
					}
				}
			}
			set sock [::websocket::open "wss://gateway.discord.gg/?encoding=json&v=9" [namespace which handler]]
			${log}::debug "created sock, $sock"
		}
		method is_connected {} {
			my variable sock log
			if {![info exists sock]} {
				return -1
			}
			if {[::websocket::conninfo $sock state] == "CONNECTED"} {
				return [::websocket::conninfo $sock peername]
			}
			return 0
		}
	}
}

::discord::discord create d
d set_login_password $env(DC_E) $env(DC_P)
d login ::discord::login_example_callback
vwait forever
::discord::login env(DC_E) env(DC_P) login_example_callback
d connect
gets stdin
d disconnect
gets stdin
d destroy