←︎ scroll :: ff1f7c4


1
commit ff1f7c496cdf678e9bae0354a274928525cc7587
2
Author: acidvegas <acid.vegas@acid.vegas>
3
Date:   Sun Mar 22 03:38:50 2020 -0400
4
5
    massive cleanup, big updates coming
6
---
7
 LICENSE                        |   2 +-
8
 README.md                      |  20 +---
9
 scroll/{data => }/.gitignore   |   0
10
 scroll/art/.gitignore          |   2 +
11
 scroll/core/ascii2png.py       | 166 -----------------------------
12
 scroll/core/config.py          |   9 +-
13
 scroll/core/constants.py       | 229 -----------------------------------------
14
 scroll/core/database.py        |  35 ++-----
15
 scroll/core/debug.py           |  55 ----------
16
 scroll/core/functions.py       |  18 ----
17
 scroll/core/irc.py             | 216 +++++++++++++-------------------------
18
 scroll/core/mircart/canvas.py  |  82 ---------------
19
 scroll/core/mircart/mircart.py | 216 --------------------------------------
20
 scroll/core/mircart/png.py     |  60 -----------
21
 scroll/data/DejaVuSansMono.ttf | Bin 340712 -> 0 bytes
22
 scroll/data/art/.gitignore     |   4 -
23
 scroll/data/logs/.gitignore    |   4 -
24
 scroll/scroll.py               |  15 ++-
25
 18 files changed, 91 insertions(+), 1042 deletions(-)
26
27
diff --git a/LICENSE b/LICENSE
28
index b63b809..0f72fc8 100644
29
--- a/LICENSE
30
+++ b/LICENSE
31
@@ -1,6 +1,6 @@
32
 ISC License
33
 
34
-Copyright (c) 2019, acidvegas <acid.vegas@acid.vegas>
35
+Copyright (c) 2020, acidvegas <acid.vegas@acid.vegas>
36
 
37
 Permission to use, copy, modify, and/or distribute this software for any
38
 purpose with or without fee is hereby granted, provided that the above
39
diff --git a/README.md b/README.md
40
index c011a50..d629389 100644
41
--- a/README.md
42
+++ b/README.md
43
@@ -5,16 +5,15 @@
44
 
45
 ## Requirements
46
 - [Python](https://www.python.org/downloads/) *(**Note:** This script was developed to be used with the latest version of Python)*
47
-- [Pillow](https://pypi.org/project/Pillow/) *(Required by [core/mircart.py](scroll/core/mircart.py))*
48
 
49
 ## Setup
50
-Edit the [core/config.py](scroll/core/config.py) file and then place your art files in the [data/art](scroll/data/art) directory.
51
+Edit the [core/config.py](scroll/core/config.py) file and then place your art files in the [art](scroll/art) directory.
52
 
53
 This repository by itself does not contain any art. A large organized collection of IRC art can be cloned from the [ircart](https://github.com/ircart/ircart) repository directory into your [data/art](scroll/data/art) directory:
54
 
55
-`git clone https://github.com/ircart/ircart.git $SCROLL_DIR/scroll/data/art` *(change the `$SCROLL_DIR` to where you cloned it)*
56
+`git clone https://github.com/ircart/ircart.git $SCROLL_DIR/scroll/art` *(change the `$SCROLL_DIR` to where you cloned it)*
57
 
58
-**Warning:** Try not to have any filenames in your [data/art](scroll/data/art) directory that are the same as any of the [Commands](#commands) below or you won't be able to play them!
59
+**Warning:** Try not to have any filenames in your [art](scroll/art) directory that are the same as any of the [Commands](#commands) below or you won't be able to play them!
60
 
61
 ## Commands
62
 ### User Commands
63
@@ -23,12 +22,9 @@ This repository by itself does not contain any art. A large organized collection
64
 | @scroll                         | information about scroll                                                                                                                         |
65
 | .ascii dirs                     | list of ascii directories                                                                                                                        |
66
 | .ascii list                     | list of ascii filenames                                                                                                                          |
67
-| .ascii png \<url>               | convert an art paste into an image *(must be a [Pastebin](https://pastebin.com/) or [Termbin](https://termbin.com/) link)*                       |
68
 | .ascii random [dir]             | play random art, optionally from the [dir] directory only                                                                                        |
69
-| .ascii remote \<url>            | play remote art pastes *(must be a [Pastebin](https://pastebin.com/) or [Termbin](https://termbin.com/) link)*                                   |
70
 | .ascii search \<query>          | search [data/art](scroll/data/art) for \<query>                                                                                                  |
71
 | .ascii stop                     | stop playing art                                                                                                                                 |
72
-| .ascii upload [\<url> \<title>] | list of uploaded art pastes or upload \<url> as \<title> *(must be a [Pastebin](https://pastebin.com/) or [Termbin](https://termbin.com/) link)* |
73
 | .ascii \<name> [\<trunc>]       | play \<name> art from [data/art](scroll/data/art) *(see usage below)*                                                                            |
74
 
75
 #### Note
76
@@ -45,35 +41,27 @@ The \<trunc> argument for the `.ascii` command allows you to truncate lines off
77
 | .update                       | update the [data/art](scroll/data/art) directory *(see usage below)*                                |
78
 
79
 #### Note
80
-The `.update` command is if you cloned a git repository, like the one mentioned in the [Setup](#setup) section, for your [data/ascii](scroll/data/ascii) directory. This will simply perform a `git pull` on the repository to update it.
81
+The `.update` command is if you cloned a git repository, like the one mentioned in the [Setup](#setup) section, for your [art](scroll/art) directory. This will simply perform a `git pull` on the repository to update it.
82
 
83
 ## Config Settings
84
 | Setting         | Description                                                                           |
85
 | --------------- | ------------------------------------------------------------------------------------- |
86
 | max_lines       | maximum number of lines an art can be to be played outside of the **#scroll** channel |
87
-| max_png_bytes   | maximum size *(in bytes)* for `.ascii png` usage                                      |
88
 | max_results     | maximum number of results returned from a search                                      |
89
-| max_uploads     | maximum number of uploads to store                                                    |
90
-| max_uploads_per | maximum number of uploads per-nick                                                    |
91
 | throttle_cmd    | command usage throttle in seconds                                                     |
92
 | throttle_msg    | message throttle in seconds                                                           |
93
-| throttle_png    | `.ascii png` throttle in seconds                                                      |
94
 | rnd_exclude     | directories to ignore with `.ascii random`. *(comma-seperated list)*                  |
95
 
96
 ## Screens
97
 This section is not finished. Screens will be added soon.
98
 
99
 ## Todo
100
-* Check paste size before downloading.
101
-* Store image bytes in memory so creating files locally is not required.
102
 * Improve truncation of the left and right by retaining the last known color code.
103
 * Add ascii tagging to database. Lets you tag asciis with hashtags for searching. Maybe include an author tag also.
104
 * Add an ascii amplification factor and zigzag to the trunc array, and maybe rename trunc to morph.
105
-* Use the maximum allowed upload size for IMGUR upload, and track it to a maximum of 50 per hour. 
106
 * Controls for admins to move, rename, delete, and download ascii on the fly.
107
 
108
 ## Mirrors
109
 - [acid.vegas](https://acid.vegas/scroll) *(main)*
110
-- [SuperNETs](https://git.supernets.org/ircart/scroll)
111
 - [GitHub](https://github.com/ircart/scroll)
112
 - [GitLab](https://gitlab.com/ircart/scroll)
113
diff --git a/scroll/data/.gitignore b/scroll/.gitignore
114
similarity index 100%
115
rename from scroll/data/.gitignore
116
rename to scroll/.gitignore
117
diff --git a/scroll/art/.gitignore b/scroll/art/.gitignore
118
new file mode 100644
119
index 0000000..c96a04f
120
--- /dev/null
121
+++ b/scroll/art/.gitignore
122
@@ -0,0 +1,2 @@
123
+*
124
+!.gitignore
125
diff --git a/scroll/core/ascii2png.py b/scroll/core/ascii2png.py
126
deleted file mode 100644
127
index 7c82486..0000000
128
--- a/scroll/core/ascii2png.py
129
+++ /dev/null
130
@@ -1,166 +0,0 @@
131
-#!/usr/bin/env python
132
-# -*- coding: utf-8 -*-
133
-# Scroll IRC Art Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
134
-# ascii2png.py
135
-
136
-'''
137
-Credits to VXP for making the original "pngbot" script (https://github.com/lalbornoz/MiRCARTools)
138
-'''
139
-
140
-import os
141
-import urllib.request
142
-
143
-from PIL import Image, ImageDraw, ImageFont
144
-
145
-def flip_cell_state(cellState, bit):
146
-	if cellState & bit:
147
-		return cellState & ~bit
148
-	else:
149
-		return cellState | bit
150
-
151
-def parse_char(colourSpec, curColours):
152
-	if len(colourSpec) > 0:
153
-		colourSpec = colourSpec.split(',')
154
-		if len(colourSpec) == 2 and len(colourSpec[1]) > 0:
155
-			return (int(colourSpec[0] or curColours[0]), int(colourSpec[1]))
156
-		elif len(colourSpec) == 1 or len(colourSpec[1]) == 0:
157
-			return (int(colourSpec[0]), curColours[1])
158
-	else:
159
-		return (15, 1)
160
-
161
-def ascii_png(url):
162
-	text_file = os.path.join('data','temp.txt')
163
-	if os.path.isfile(text_file):
164
-		os.remove(text_file)
165
-	urllib.request.urlretrieve(url, text_file)
166
-	data = open(text_file)
167
-	inCurColourSpec = ''
168
-	inCurRow = -1
169
-	inLine = data.readline()
170
-	inSize = [0, 0]
171
-	inMaxCols = 0
172
-	outMap = []
173
-	while inLine:
174
-		inCellState = 0x00
175
-		inParseState = 1
176
-		inCurCol = 0
177
-		inMaxCol = len(inLine)
178
-		inCurColourDigits = 0
179
-		inCurColours = (15, 1)
180
-		inCurColourSpec = ''
181
-		inCurRow += 1
182
-		outMap.append([])
183
-		inRowCols = 0
184
-		inSize[1] += 1
185
-		while inCurCol < inMaxCol:
186
-			inChar = inLine[inCurCol]
187
-			if inChar in set('\r\n'):
188
-				inCurCol += 1
189
-			elif inParseState == 1:
190
-				inCurCol += 1
191
-				if inChar == '':
192
-					inCellState = flip_cell_state(inCellState, 0x01)
193
-				elif inChar == '':
194
-					inParseState = 2
195
-				elif inChar == '':
196
-					inCellState = flip_cell_state(inCellState, 0x02)
197
-				elif inChar == '':
198
-					inCellState |= 0x00
199
-					inCurColours = (15, 1)
200
-				elif inChar == '':
201
-					inCurColours = (inCurColours[1], inCurColours[0])
202
-				elif inChar == '':
203
-					inCellState = flip_cell_state(inCellState, 0x04)
204
-				else:
205
-					inRowCols += 1
206
-					outMap[inCurRow].append([*inCurColours, inCellState, inChar])
207
-			elif inParseState == 2 or inParseState == 3:
208
-				if inChar == ',' and inParseState == 2:
209
-					if (inCurCol + 1) < inMaxCol and not inLine[inCurCol + 1] in set('0123456789'):
210
-						inCurColours = parse_char(inCurColourSpec, inCurColours)
211
-						inCurColourDigits = 0
212
-						inCurColourSpec = ''
213
-						inParseState = 1
214
-					else:
215
-						inCurCol += 1
216
-						inCurColourDigits = 0
217
-						inCurColourSpec += inChar
218
-						inParseState = 3
219
-				elif inChar in set('0123456789') and inCurColourDigits == 0:
220
-					inCurCol += 1
221
-					inCurColourDigits += 1
222
-					inCurColourSpec += inChar
223
-				elif inChar in set('0123456789') and inCurColourDigits == 1 and inCurColourSpec[-1] == '0':
224
-					inCurCol += 1
225
-					inCurColourDigits += 1
226
-					inCurColourSpec += inChar
227
-				elif inChar in set('012345') and inCurColourDigits == 1 and inCurColourSpec[-1] == '1':
228
-					inCurCol += 1
229
-					inCurColourDigits += 1
230
-					inCurColourSpec += inChar
231
-				else:
232
-					inCurColours = parse_char(inCurColourSpec, inCurColours)
233
-					inCurColourDigits = 0
234
-					inCurColourSpec = ''
235
-					inParseState = 1
236
-		inMaxCols = max(inMaxCols, inRowCols)
237
-		inLine = data.readline()
238
-	inSize[0] = inMaxCols
239
-	canvas_data = outMap
240
-	numRowCols = 0
241
-	for numRow in range(len(outMap)):
242
-		numRowCols = max(numRowCols, len(outMap[numRow]))
243
-	for numRow in range(len(outMap)):
244
-		if len(outMap[numRow]) != numRowCols:
245
-			for numColOff in range(numRowCols - len(outMap[numRow])):
246
-				outMap[numRow].append([1,1,0,' '])
247
-		outMap[numRow].insert(0,[1,1,0,' '])
248
-		outMap[numRow].append([1,1,0,' '])
249
-	outMap.insert(0,[[1,1,0,' ']] * len(outMap[0]))
250
-	outMap.append([[1,1,0,' ']] * len(outMap[0]))
251
-	inCanvasMap = outMap
252
-	outImgFont = ImageFont.truetype(os.path.join('data','DejaVuSansMono.ttf'), 11)
253
-	outImgFontSize = [*outImgFont.getsize(' ')]
254
-	outImgFontSize[1] += 3
255
-	ColorsBold   = [[255,255,255],[85,85,85],[85,85,255],[85,255,85],[255,85,85],[255,85,85],[255,85,255],[255,255,85],[255,255,85],[85,255,85],[85,255,255],[85,255,255],[85,85,255],[255,85,255],[85,85,85],[255,255,255]]
256
-	ColorsNormal = [[255,255,255],[0,0,0],[0,0,187],[0,187,0],[255,85,85],[187,0,0],[187,0,187],[187,187,0],[255,255,85],[85,255,85],[0,187,187],[85,255,255],[85,85,255],[255,85,255],[85,85,85],[187,187,187]]
257
-	inSize = (len(inCanvasMap[0]), len(inCanvasMap))
258
-	outSize = [a*b for a,b in zip(inSize, outImgFontSize)]
259
-	outCurPos = [0, 0]
260
-	outImg = Image.new('RGBA', outSize, (*ColorsNormal[1], 255))
261
-	outImgDraw = ImageDraw.Draw(outImg)
262
-	for inCurRow in range(len(inCanvasMap)):
263
-		for inCurCol in range(len(inCanvasMap[inCurRow])):
264
-			inCurCell = inCanvasMap[inCurRow][inCurCol]
265
-			outColours = [0, 0]
266
-			if inCurCell[2] & 0x01:
267
-				if inCurCell[3] != ' ':
268
-					if inCurCell[3] == '█':
269
-						outColours[1] = ColorsNormal[inCurCell[0]]
270
-					else:
271
-						outColours[0] = ColorsBold[inCurCell[0]]
272
-						outColours[1] = ColorsNormal[inCurCell[1]]
273
-				else:
274
-					outColours[1] = ColorsNormal[inCurCell[1]]
275
-			else:
276
-				if inCurCell[3] != ' ':
277
-					if inCurCell[3] == '█':
278
-						outColours[1] = ColorsNormal[inCurCell[0]]
279
-					else:
280
-						outColours[0] = ColorsNormal[inCurCell[0]]
281
-						outColours[1] = ColorsNormal[inCurCell[1]]
282
-				else:
283
-					outColours[1] = ColorsNormal[inCurCell[1]]
284
-			outImgDraw.rectangle((*outCurPos,outCurPos[0] + outImgFontSize[0], outCurPos[1] + outImgFontSize[1]), fill=(*outColours[1], 255))
285
-			if  not inCurCell[3] in ' █' and outColours[0] != outColours[1]:
286
-				outImgDraw.text(outCurPos,inCurCell[3], (*outColours[0], 255), outImgFont)
287
-			if inCurCell[2] & 0x04:
288
-				outColours[0] = ColorsNormal[inCurCell[0]]
289
-				outImgDraw.line(xy=(outCurPos[0], outCurPos[1] + (outImgFontSize[1] - 2), outCurPos[0] + outImgFontSize[0], outCurPos[1] + (outImgFontSize[1] - 2)), fill=(*outColours[0], 255))
290
-			outCurPos[0] += outImgFontSize[0]
291
-		outCurPos[0] = 0
292
-		outCurPos[1] += outImgFontSize[1]
293
-	out_file = os.path.join('data','temp.png')
294
-	if os.path.isfile(out_file):
295
-		os.remove(out_file)
296
-	outImg.save(out_file)
297
diff --git a/scroll/core/config.py b/scroll/core/config.py
298
index 61fbf6a..93f274e 100644
299
--- a/scroll/core/config.py
300
+++ b/scroll/core/config.py
301
@@ -5,17 +5,14 @@
302
 class connection:
303
 	server     = 'irc.server.com'
304
 	port       = 6667
305
-	proxy      = None # Must be in IP:PORT format (Socks5 proxies only)
306
 	ipv6       = False
307
 	ssl        = False
308
-	ssl_verify = False
309
 	vhost      = None
310
 	channel    = '#chats'
311
 	key        = None
312
 
313
 class cert:
314
 	key      = None
315
-	file     = None
316
 	password = None
317
 
318
 class ident:
319
@@ -24,13 +21,9 @@ class ident:
320
 	realname = 'acid.vegas/scroll'
321
 
322
 class login:
323
-	network  = None
324
 	nickserv = None
325
 	operator = None
326
 
327
 class settings:
328
 	admin = 'nick!user@host' # Must be in nick!user@host format (Wildcards accepted)
329
-	log   = False
330
-	modes = None
331
-
332
-IMGUR_API_KEY = 'CHANGEME' # https://apidocs.imgur.com/
333
+	modes = None
334
diff --git a/scroll/core/constants.py b/scroll/core/constants.py
335
deleted file mode 100644
336
index 109eb9a..0000000
337
--- a/scroll/core/constants.py
338
+++ /dev/null
339
@@ -1,229 +0,0 @@
340
-#!/usr/bin/env python
341
-# Scroll IRC Art Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
342
-# constants.py
343
-
344
-# Control Characters
345
-bold      = '\x02'
346
-color     = '\x03'
347
-italic    = '\x1D'
348
-underline = '\x1F'
349
-reverse   = '\x16'
350
-reset     = '\x0f'
351
-
352
-# Color Codes
353
-white       = '00'
354
-black       = '01'
355
-blue        = '02'
356
-green       = '03'
357
-red         = '04'
358
-brown       = '05'
359
-purple      = '06'
360
-orange      = '07'
361
-yellow      = '08'
362
-light_green = '09'
363
-cyan        = '10'
364
-light_cyan  = '11'
365
-light_blue  = '12'
366
-pink        = '13'
367
-grey        = '14'
368
-light_grey  = '15'
369
-
370
-# Events
371
-PASS     = 'PASS'
372
-NICK     = 'NICK'
373
-USER     = 'USER'
374
-OPER     = 'OPER'
375
-MODE     = 'MODE'
376
-SERVICE  = 'SERVICE'
377
-QUIT     = 'QUIT'
378
-SQUIT    = 'SQUIT'
379
-JOIN     = 'JOIN'
380
-PART     = 'PART'
381
-TOPIC    = 'TOPIC'
382
-NAMES    = 'NAMES'
383
-LIST     = 'LIST'
384
-INVITE   = 'INVITE'
385
-KICK     = 'KICK'
386
-PRIVMSG  = 'PRIVMSG'
387
-NOTICE   = 'NOTICE'
388
-MOTD     = 'MOTD'
389
-LUSERS   = 'LUSERS'
390
-VERSION  = 'VERSION'
391
-STATS    = 'STATS'
392
-LINKS    = 'LINKS'
393
-TIME     = 'TIME'
394
-CONNECT  = 'CONNECT'
395
-TRACE    = 'TRACE'
396
-ADMIN    = 'ADMIN'
397
-INFO     = 'INFO'
398
-SERVLIST = 'SERVLIST'
399
-SQUERY   = 'SQUERY'
400
-WHO      = 'WHO'
401
-WHOIS    = 'WHOIS'
402
-WHOWAS   = 'WHOWAS'
403
-KILL     = 'KILL'
404
-PING     = 'PING'
405
-PONG     = 'PONG'
406
-ERROR    = 'ERROR'
407
-AWAY     = 'AWAY'
408
-REHASH   = 'REHASH'
409
-DIE      = 'DIE'
410
-RESTART  = 'RESTART'
411
-SUMMON   = 'SUMMON'
412
-USERS    = 'USERS'
413
-WALLOPS  = 'WALLOPS'
414
-USERHOST = 'USERHOST'
415
-ISON     = 'ISON'
416
-
417
-# Event Numerics
418
-RPL_WELCOME          = '001'
419
-RPL_YOURHOST         = '002'
420
-RPL_CREATED          = '003'
421
-RPL_MYINFO           = '004'
422
-RPL_ISUPPORT         = '005'
423
-RPL_TRACELINK        = '200'
424
-RPL_TRACECONNECTING  = '201'
425
-RPL_TRACEHANDSHAKE   = '202'
426
-RPL_TRACEUNKNOWN     = '203'
427
-RPL_TRACEOPERATOR    = '204'
428
-RPL_TRACEUSER        = '205'
429
-RPL_TRACESERVER      = '206'
430
-RPL_TRACESERVICE     = '207'
431
-RPL_TRACENEWTYPE     = '208'
432
-RPL_TRACECLASS       = '209'
433
-RPL_STATSLINKINFO    = '211'
434
-RPL_STATSCOMMANDS    = '212'
435
-RPL_STATSCLINE       = '213'
436
-RPL_STATSILINE       = '215'
437
-RPL_STATSKLINE       = '216'
438
-RPL_STATSYLINE       = '218'
439
-RPL_ENDOFSTATS       = '219'
440
-RPL_UMODEIS          = '221'
441
-RPL_SERVLIST         = '234'
442
-RPL_SERVLISTEND      = '235'
443
-RPL_STATSLLINE       = '241'
444
-RPL_STATSUPTIME      = '242'
445
-RPL_STATSOLINE       = '243'
446
-RPL_STATSHLINE       = '244'
447
-RPL_LUSERCLIENT      = '251'
448
-RPL_LUSEROP          = '252'
449
-RPL_LUSERUNKNOWN     = '253'
450
-RPL_LUSERCHANNELS    = '254'
451
-RPL_LUSERME          = '255'
452
-RPL_ADMINME          = '256'
453
-RPL_ADMINLOC1        = '257'
454
-RPL_ADMINLOC2        = '258'
455
-RPL_ADMINEMAIL       = '259'
456
-RPL_TRACELOG         = '261'
457
-RPL_TRYAGAIN         = '263'
458
-RPL_NONE             = '300'
459
-RPL_AWAY             = '301'
460
-RPL_USERHOST         = '302'
461
-RPL_ISON             = '303'
462
-RPL_UNAWAY           = '305'
463
-RPL_NOWAWAY          = '306'
464
-RPL_WHOISUSER        = '311'
465
-RPL_WHOISSERVER      = '312'
466
-RPL_WHOISOPERATOR    = '313'
467
-RPL_WHOWASUSER       = '314'
468
-RPL_ENDOFWHO         = '315'
469
-RPL_WHOISIDLE        = '317'
470
-RPL_ENDOFWHOIS       = '318'
471
-RPL_WHOISCHANNELS    = '319'
472
-RPL_LIST             = '322'
473
-RPL_LISTEND          = '323'
474
-RPL_CHANNELMODEIS    = '324'
475
-RPL_NOTOPIC          = '331'
476
-RPL_TOPIC            = '332'
477
-RPL_INVITING         = '341'
478
-RPL_INVITELIST       = '346'
479
-RPL_ENDOFINVITELIST  = '347'
480
-RPL_EXCEPTLIST       = '348'
481
-RPL_ENDOFEXCEPTLIST  = '349'
482
-RPL_VERSION          = '351'
483
-RPL_WHOREPLY         = '352'
484
-RPL_NAMREPLY         = '353'
485
-RPL_LINKS            = '364'
486
-RPL_ENDOFLINKS       = '365'
487
-RPL_ENDOFNAMES       = '366'
488
-RPL_BANLIST          = '367'
489
-RPL_ENDOFBANLIST     = '368'
490
-RPL_ENDOFWHOWAS      = '369'
491
-RPL_INFO             = '371'
492
-RPL_MOTD             = '372'
493
-RPL_ENDOFINFO        = '374'
494
-RPL_MOTDSTART        = '375'
495
-RPL_ENDOFMOTD        = '376'
496
-RPL_YOUREOPER        = '381'
497
-RPL_REHASHING        = '382'
498
-RPL_YOURESERVICE     = '383'
499
-RPL_TIME             = '391'
500
-RPL_USERSSTART       = '392'
501
-RPL_USERS            = '393'
502
-RPL_ENDOFUSERS       = '394'
503
-RPL_NOUSERS          = '395'
504
-ERR_NOSUCHNICK       = '401'
505
-ERR_NOSUCHSERVER     = '402'
506
-ERR_NOSUCHCHANNEL    = '403'
507
-ERR_CANNOTSENDTOCHAN = '404'
508
-ERR_TOOMANYCHANNELS  = '405'
509
-ERR_WASNOSUCHNICK    = '406'
510
-ERR_TOOMANYTARGETS   = '407'
511
-ERR_NOSUCHSERVICE    = '408'
512
-ERR_NOORIGIN         = '409'
513
-ERR_NORECIPIENT      = '411'
514
-ERR_NOTEXTTOSEND     = '412'
515
-ERR_NOTOPLEVEL       = '413'
516
-ERR_WILDTOPLEVEL     = '414'
517
-ERR_BADMASK          = '415'
518
-ERR_UNKNOWNCOMMAND   = '421'
519
-ERR_NOMOTD           = '422'
520
-ERR_NOADMININFO      = '423'
521
-ERR_FILEERROR        = '424'
522
-ERR_NONICKNAMEGIVEN  = '431'
523
-ERR_ERRONEUSNICKNAME = '432'
524
-ERR_NICKNAMEINUSE    = '433'
525
-ERR_NICKCOLLISION    = '436'
526
-ERR_USERNOTINCHANNEL = '441'
527
-ERR_NOTONCHANNEL     = '442'
528
-ERR_USERONCHANNEL    = '443'
529
-ERR_NOLOGIN          = '444'
530
-ERR_SUMMONDISABLED   = '445'
531
-ERR_USERSDISABLED    = '446'
532
-ERR_NOTREGISTERED    = '451'
533
-ERR_NEEDMOREPARAMS   = '461'
534
-ERR_ALREADYREGISTRED = '462'
535
-ERR_NOPERMFORHOST    = '463'
536
-ERR_PASSWDMISMATCH   = '464'
537
-ERR_YOUREBANNEDCREEP = '465'
538
-ERR_KEYSET           = '467'
539
-ERR_CHANNELISFULL    = '471'
540
-ERR_UNKNOWNMODE      = '472'
541
-ERR_INVITEONLYCHAN   = '473'
542
-ERR_BANNEDFROMCHAN   = '474'
543
-ERR_BADCHANNELKEY    = '475'
544
-ERR_BADCHANMASK      = '476'
545
-ERR_BANLISTFULL      = '478'
546
-ERR_NOPRIVILEGES     = '481'
547
-ERR_CHANOPRIVSNEEDED = '482'
548
-ERR_CANTKILLSERVER   = '483'
549
-ERR_UNIQOPRIVSNEEDED = '485'
550
-ERR_NOOPERHOST       = '491'
551
-ERR_UMODEUNKNOWNFLAG = '501'
552
-ERR_USERSDONTMATCH   = '502'
553
-RPL_STARTTLS         = '670'
554
-ERR_STARTTLS         = '691'
555
-RPL_MONONLINE        = '730'
556
-RPL_MONOFFLINE       = '731'
557
-RPL_MONLIST          = '732'
558
-RPL_ENDOFMONLIST     = '733'
559
-ERR_MONLISTFULL      = '734'
560
-RPL_LOGGEDIN         = '900'
561
-RPL_LOGGEDOUT        = '901'
562
-ERR_NICKLOCKED       = '902'
563
-RPL_SASLSUCCESS      = '903'
564
-ERR_SASLFAIL         = '904'
565
-ERR_SASLTOOLONG      = '905'
566
-ERR_SASLABORTED      = '906'
567
-ERR_SASLALREADY      = '907'
568
-RPL_SASLMECHS        = '908'
569
diff --git a/scroll/core/database.py b/scroll/core/database.py
570
index d302349..b9a020c 100644
571
--- a/scroll/core/database.py
572
+++ b/scroll/core/database.py
573
@@ -2,11 +2,10 @@
574
 # Scroll IRC Art Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
575
 # database.py
576
 
577
-import os
578
 import re
579
 import sqlite3
580
 
581
-db  = sqlite3.connect(os.path.join('data', 'scroll.db'), check_same_thread=False)
582
+db  = sqlite3.connect('scroll.db', check_same_thread=False)
583
 sql = db.cursor()
584
 
585
 def check():
586
@@ -14,16 +13,11 @@ def check():
587
 	if not len(tables):
588
 		sql.execute('CREATE TABLE IGNORE (IDENT TEXT NOT NULL);')
589
 		sql.execute('CREATE TABLE SETTINGS (SETTING TEXT NOT NULL, VALUE TEXT NOT NULL);')
590
-		sql.execute('CREATE TABLE UPLOADS (NICK TEXT NOT NULL, URL TEXT NOT NULL, TITLE TEXT NOT NULL);')
591
-		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('max_lines',       '300'))
592
-		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('max_png_bytes',   '2000000'))
593
-		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('max_results',     '10'))
594
-		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('max_uploads',     '25'))
595
-		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('max_uploads_per', '5'))
596
-		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('rnd_exclude',     'ansi,big,birds,hang,pokemon'))
597
-		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('throttle_cmd',    '3'))
598
-		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('throttle_msg',    '0.03'))
599
-		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('throttle_png',    '0.03'))
600
+		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('max_lines',    '300'))
601
+		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('max_results',  '10'))
602
+		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('rnd_exclude',  'ansi,big,birds,hang,pokemon'))
603
+		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('throttle_cmd', '3'))
604
+		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('throttle_msg', '0.03'))
605
 		db.commit()
606
 
607
 class Ignore:
608
@@ -56,19 +50,4 @@ class Settings:
609
 
610
 	def update(setting, value):
611
 		sql.execute('UPDATE SETTINGS SET VALUE=? WHERE SETTING=?', (value, setting))
612
-		db.commit()
613
-
614
-class Uploads:
615
-	def add(nick, url, title):
616
-		sql.execute('INSERT INTO UPLOADS (NICK,URL,TITLE) VALUES (?,?,?)', (nick,url,title))
617
-		db.commit()
618
-
619
-	def read(nick=None):
620
-		if nick:
621
-			return sql.execute('SELECT NICK,URL,TITLE FROM UPLOADS WHERE NICK=?', (nick,)).fetchall()
622
-		else:
623
-			return sql.execute('SELECT NICK,URL,TITLE FROM UPLOADS').fetchall()
624
-
625
-	def remove(url):
626
-		sql.execute('DELETE FROM UPLOADS WHERE URL=?', (url,))
627
-		db.commit()
628
+		db.commit()
629
diff --git a/scroll/core/debug.py b/scroll/core/debug.py
630
deleted file mode 100644
631
index 2f43707..0000000
632
--- a/scroll/core/debug.py
633
+++ /dev/null
634
@@ -1,55 +0,0 @@
635
-#!/usr/bin/env python
636
-# Scroll IRC Art Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
637
-# debug.py
638
-
639
-import ctypes
640
-import logging
641
-import os
642
-import sys
643
-
644
-from logging.handlers import RotatingFileHandler
645
-
646
-import config
647
-
648
-def check_privileges():
649
-	if check_windows():
650
-		return True if ctypes.windll.shell32.IsUserAnAdmin() else False
651
-	else:
652
-		return True if os.getuid() == 0 or os.geteuid() == 0 else False
653
-
654
-def check_version(major):
655
-	return True if sys.version_info.major == major else False
656
-
657
-def check_windows():
658
-	return True if os.name == 'nt' else False
659
-
660
-def clear():
661
-	os.system('cls') if check_windows() else os.system('clear')
662
-
663
-def error(msg, reason=None):
664
-	logging.error(f'[!] - {msg} ({reason})') if reason else logging.error('[!] - ' + msg)
665
-
666
-def error_exit(msg):
667
-	raise SystemExit('[!] - ' + msg)
668
-
669
-def info():
670
-	clear()
671
-	print('#'*56)
672
-	print('#{0}#'.format(''.center(54)))
673
-	print('#{0}#'.format('Scroll IRC Art Bot'.center(54)))
674
-	print('#{0}#'.format('Developed by acidvegas in Python'.center(54)))
675
-	print('#{0}#'.format('https://acid.vegas/scroll'.center(54)))
676
-	print('#{0}#'.format(''.center(54)))
677
-	print('#'*56)
678
-
679
-def irc(msg):
680
-	logging.debug('[~] - ' + msg)
681
-
682
-def setup_logger():
683
-	stream_handler = logging.StreamHandler(sys.stdout)
684
-	if config.settings.log:
685
-		log_file     = os.path.join(os.path.join('data','logs'), 'scroll.log')
686
-		file_handler = RotatingFileHandler(log_file, maxBytes=256000, backupCount=3)
687
-		logging.basicConfig(level=logging.NOTSET, format='%(asctime)s | %(message)s', datefmt='%I:%M:%S', handlers=(file_handler,stream_handler))
688
-	else:
689
-		logging.basicConfig(level=logging.NOTSET, format='%(asctime)s | %(message)s', datefmt='%I:%M:%S', handlers=(stream_handler,))
690
diff --git a/scroll/core/functions.py b/scroll/core/functions.py
691
index 6c7b13a..f5746e4 100644
692
--- a/scroll/core/functions.py
693
+++ b/scroll/core/functions.py
694
@@ -2,14 +2,9 @@
695
 # Scroll IRC Art Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
696
 # functions.py
697
 
698
-import base64
699
-import json
700
-import os
701
 import random
702
 import re
703
 import subprocess
704
-import urllib.parse
705
-import urllib.request
706
 
707
 import config
708
 
709
@@ -29,19 +24,6 @@ def check_trunc(trunc):
710
 	else:
711
 		return False
712
 
713
-def get_size(file_path):
714
-	return os.path.getsize(file_path)
715
-
716
-def get_source(url):
717
-	return urllib.request.urlopen(url, timeout=15).read().decode('utf8')
718
-
719
-def imgur_upload(file_path):
720
-	data = urllib.parse.urlencode({'image':base64.b64encode(open(file_path,'rb').read()),'key':config.IMGUR_API_KEY,'name':'ASCII uploaded via Scroll','title':'ASCII uploaded via Scroll','type':'base64'}).encode('utf8')
721
-	headers = {'Authorization':'Client-ID ' + config.IMGUR_API_KEY}
722
-	req = urllib.request.Request('https://api.imgur.com/3/upload.json', data, headers)
723
-	response = json.loads(urllib.request.urlopen(req).read())
724
-	return response['data']['link']
725
-
726
 def is_admin(ident):
727
 	return re.compile(config.settings.admin.replace('*','.*')).search(ident)
728
 
729
diff --git a/scroll/core/irc.py b/scroll/core/irc.py
730
index afb0547..2769b2d 100644
731
--- a/scroll/core/irc.py
732
+++ b/scroll/core/irc.py
733
@@ -6,34 +6,41 @@ import glob
734
 import os
735
 import random
736
 import socket
737
+import ssl
738
 import threading
739
 import time
740
 
741
-import ascii2png
742
 import config
743
-import constants
744
 import database
745
-import debug
746
 import functions
747
 
748
-# Load optional modules
749
-if config.connection.ssl:
750
-	import ssl
751
-if config.connection.proxy:
752
-	try:
753
-		import sock
754
-	except ImportError:
755
-		debug.error_exit('Missing PySocks module! (https://pypi.python.org/pypi/PySocks)') # Required for proxy support.
756
+# Control characters & color codes
757
+bold        = '\x02'
758
+underline   = '\x1F'
759
+reset       = '\x0f'
760
+white       = '00'
761
+black       = '01'
762
+blue        = '02'
763
+green       = '03'
764
+red         = '04'
765
+brown       = '05'
766
+purple      = '06'
767
+orange      = '07'
768
+yellow      = '08'
769
+light_green = '09'
770
+cyan        = '10'
771
+light_cyan  = '11'
772
+light_blue  = '12'
773
+pink        = '13'
774
+grey        = '14'
775
+light_grey  = '15'
776
 
777
 def color(msg, foreground, background=None):
778
-	return f'\x03{foreground},{background}{msg}{constants.reset}' if background else f'\x03{foreground}{msg}{constants.reset}'
779
-
780
-ascii_dir = os.path.join('data', 'art')
781
+	return f'\x03{foreground},{background}{msg}{reset}' if background else f'\x03{foreground}{msg}{reset}'
782
 
783
 class IRC(object):
784
 	def __init__(self):
785
 		self.last     = 0
786
-		self.last_png = 0
787
 		self.playing  = False
788
 		self.slow     = False
789
 		self.stopper  = False
790
@@ -41,65 +48,43 @@ class IRC(object):
791
 
792
 	def connect(self):
793
 		try:
794
-			self.create_socket()
795
+			self.sock = socket.socket(socket.AF_INET6) if config.connection.ipv6 else socket.socket()
796
+			if config.connection.vhost:
797
+				self.sock.bind((config.connection.vhost, 0))
798
+			if config.connection.ssl:
799
+				ctx = ssl.create_default_context()
800
+				if config.cert.file:
801
+					ctx.load_cert_chain(config.cert.file, password=config.cert.password)
802
+				self.sock = ctx.wrap_socket(self.sock)
803
 			self.sock.connect((config.connection.server, config.connection.port))
804
-			self.register()
805
-		except socket.error as ex:
806
-			debug.error('Failed to connect to IRC server.', ex)
807
+			Commands.raw(f'USER {config.ident.username} 0 * :{config.ident.realname}')
808
+			Commands.raw('NICK '+ config.ident.nickname)
809
+		except Exception as ex:
810
+			print(f'[!] - Failed to connect to IRC server! ({ex!s})')
811
 			Events.disconnect()
812
 		else:
813
 			self.listen()
814
 
815
-	def create_socket(self):
816
-		family = socket.AF_INET6 if config.connection.ipv6 else socket.AF_INET
817
-		if config.connection.proxy:
818
-			proxy_server, proxy_port = config.connection.proxy.split(':')
819
-			self.sock = socks.socksocket(family, socket.SOCK_STREAM)
820
-			self.sock.setblocking(0)
821
-			self.sock.settimeout(15)
822
-			self.sock.setproxy(socks.PROXY_TYPE_SOCKS5, proxy_server, int(proxy_port))
823
-		else:
824
-			self.sock = socket.socket(family, socket.SOCK_STREAM)
825
-		if config.connection.vhost:
826
-			self.sock.bind((config.connection.vhost, 0))
827
-		if config.connection.ssl:
828
-			ctx = ssl.create_default_context()
829
-			if config.cert.file:
830
-				ctx.load_cert_chain(config.cert.file, config.cert.key, config.cert.password)
831
-			if config.connection.ssl_verify:
832
-				ctx.verify_mode = ssl.CERT_REQUIRED
833
-				ctx.load_default_certs()
834
-			else:
835
-				ctx.check_hostname = False
836
-				ctx.verify_mode = ssl.CERT_NONE
837
-			self.sock = ctx.wrap_socket(self.sock)
838
-
839
 	def listen(self):
840
 		while True:
841
 			try:
842
 				data = self.sock.recv(1024).decode('utf-8')
843
 				for line in (line for line in data.split('\r\n') if len(line.split()) >= 2):
844
-					debug.irc(line)
845
+					print('[~] - ' + line)
846
 					Events.handle(line)
847
 			except (UnicodeDecodeError, UnicodeEncodeError):
848
 				pass
849
 			except Exception as ex:
850
-				debug.error('Unexpected error occured.', ex)
851
+				print(f'[!] - Unexpected error occured! ({ex!s})')
852
 				break
853
 		Events.disconnect()
854
 
855
-	def register(self):
856
-		if config.login.network:
857
-			Commands.raw('PASS ' + config.login.network)
858
-		Commands.raw(f'USER {config.ident.username} 0 * :{config.ident.realname}')
859
-		Commands.raw('NICK '+ config.ident.nickname)
860
-
861
 class Commands:
862
 	def error(chan, msg, reason=None):
863
 		if reason:
864
-			Commands.sendmsg(chan, '[{0}] {1} {2}'.format(color('ERROR', constants.red), msg, color(f'({reason})', constants.grey)))
865
+			Commands.sendmsg(chan, '[{0}] {1} {2}'.format(color('ERROR', red), msg, color(f'({reason})', grey)))
866
 		else:
867
-			Commands.sendmsg(chan, '[{0}] {1}'.format(color('ERROR', constants.red), msg))
868
+			Commands.sendmsg(chan, '[{0}] {1}'.format(color('ERROR', red), msg))
869
 
870
 	def join_channel(chan, key=None):
871
 		Commands.raw(f'JOIN {chan} {key}') if key else Commands.raw('JOIN ' + chan)
872
@@ -111,9 +96,8 @@ class Commands:
873
 			if len(data.splitlines()) > functions.floatint(database.Settings.get('max_lines')) and chan != '#scroll':
874
 				Commands.error(chan, 'File is too big.', 'Take it to #scroll')
875
 			else:
876
-				name = ascii_file.split(ascii_dir)[1]
877
 				data = data.splitlines()[trunc[0]:-trunc[1]] if trunc else data.splitlines()
878
-				Commands.sendmsg(chan, ascii_file.split(ascii_dir)[1])
879
+				Commands.sendmsg(chan, f'{bold}the ascii gods have chosen: {underline}{ascii_file[4:-4]}')
880
 				for line in (line for line in data if line):
881
 					if Bot.stopper:
882
 						break
883
@@ -121,7 +105,7 @@ class Commands:
884
 						line = line[trunc[2]:-trunc[3]]
885
 					Commands.sendmsg(chan, ' '*trunc[4] + line) if trunc else Commands.sendmsg(chan, line)
886
 		except Exception as ex:
887
-			debug.error('Error occured in the play function!', ex)
888
+			print(f'Error occured in the play function! ({ex!s})')
889
 		finally:
890
 			Bot.stopper = False
891
 			Bot.playing = False
892
@@ -149,20 +133,11 @@ class Events:
893
 		time.sleep(15)
894
 		Bot.connect()
895
 
896
-	def kick(chan, kicked):
897
-		if kicked == config.ident.nickname:
898
-			if chan == config.connection.channel:
899
-				time.sleep(3)
900
-				Commands.join_channel(chan, config.connection.key)
901
-			elif chan == '#scroll':
902
-				time.sleep(3)
903
-				Commands.join_channel(chan)
904
-
905
 	def message(ident, nick, chan, msg):
906
 		try:
907
 			args = msg.split()
908
 			if msg == '@scroll':
909
-				Commands.sendmsg(chan, constants.bold + 'Scroll IRC Bot - Developed by acidvegas in Python - https://acid.vegas/scroll')
910
+				Commands.sendmsg(chan, bold + 'Scroll IRC Bot - Developed by acidvegas in Python - https://github.com/ircart/scroll')
911
 			elif args[0] == '.ascii' and not database.Ignore.check(ident):
912
 				if Bot.playing and msg == '.ascii stop':
913
 					Bot.stopper = True
914
@@ -173,81 +148,31 @@ class Events:
915
 				elif len(args) >= 2:
916
 					Bot.slow = False
917
 					if args[1] == 'dirs' and len(args) == 2:
918
-						dirs = sorted(glob.glob(os.path.join(ascii_dir, '*/')))
919
+						dirs = sorted(glob.glob('art/*/'))
920
 						for directory in dirs:
921
 							name = os.path.basename(os.path.dirname(directory))
922
 							file_count = str(len(glob.glob(os.path.join(directory, '*.txt'))))
923
-							Commands.sendmsg(chan, '[{0}] {1} {2}'.format(color(str(dirs.index(directory)+1).zfill(2), constants.pink), name.ljust(10), color(f'({file_count})', constants.grey)))
924
-					elif args[1] == 'png' and len(args) == 3:
925
-						url = args[2]
926
-						if url.startswith('https://pastebin.com/raw/') or url.startswith('http://termbin.com/'):
927
-							ascii2png.ascii_png(url)
928
-							ascii_file = os.path.join('data','temp.png')
929
-							if os.path.getsize(ascii_file) < int(database.Settings.get('png_max_bytes')):
930
-								Commands.sendmsg(chan, functions.imgur_upload(os.path.join('data','temp.png')))
931
-								Bot.last_png = time.time()
932
-							else:
933
-								Commands.error(chan, 'File too large', '2MB Max')
934
-						else:
935
-							Commands.error(chan, 'Invalid URL.', 'Only PasteBin & TermBin URLs can be used.')
936
-					elif args[1] == 'remote' and len(args) == 3:
937
-						url = args[2]
938
-						if url.startswith('https://pastebin.com/raw/') or url.startswith('https://termbin.com/'):
939
-							data = functions.get_source(url)
940
-							if data:
941
-								for item in data.split('\n'):
942
-									Commands.sendmsg(chan, item)
943
-							else:
944
-								Commands.error(chan, 'Found no data on URL!')
945
-						else:
946
-							Commands.error(chan, 'Invalid URL!', 'Must be a pastebin.com or termbin.com link.')
947
+							Commands.sendmsg(chan, '[{0}] {1} {2}'.format(color(str(dirs.index(directory)+1).zfill(2), pink), name.ljust(10), color(f'({file_count})', grey)))
948
 					elif args[1] == 'search' and len(args) == 3:
949
 						query   = args[2]
950
-						results = glob.glob(os.path.join(ascii_dir, f'**/*{query}*.txt'), recursive=True)
951
+						results = glob.glob(f'art/**/*{query}*.txt', recursive=True)
952
 						if results:
953
 							results = results[:int(database.Settings.get('max_results'))]
954
 							for file_name in results:
955
 								count = str(results.index(file_name)+1)
956
-								Commands.sendmsg(chan, '[{0}] {1}'.format(color(count.zfill(2), constants.pink), os.path.basename(file_name)))
957
+								Commands.sendmsg(chan, '[{0}] {1}'.format(color(count.zfill(2), pink), os.path.basename(file_name)[:-4]))
958
 						else:
959
 							Commands.error(chan, 'No results found.')
960
-					elif args[1] == 'upload':
961
-						if len(args) == 2:
962
-							uploads = database.Uploads.read()
963
-							if uploads:
964
-								for upload in uploads:
965
-									Commands.sendmsg(chan, '[{0}] {1} - {2} - {3}.txt'.format(color(uploads.index(upload)+1, constants.pink), color(upload[0], constants.yellow), color(upload[1], constants.grey), upload[2]))
966
-							else:
967
-								Commands.error(chan, 'No uploaded files!', 'Use ".ascii upload <url> <title>" to upload.')
968
-						elif len(args) == 4:
969
-							url = args[2]
970
-							if url.startswith('https://pastebin.com/raw/') or url.startswith('https://termbin.com/'):
971
-								if url not in [item[1] for item in database.Uploads.read()]:
972
-									title = args[3].lower()[:20]
973
-									check = (glob.glob(os.path.join(ascii_dir, f'**/{title}.txt'), recursive=True)[:1] or [None])[0]
974
-									if not check:
975
-										if len(database.Uploads.read(nick)) == int(database.Settings.get('max_uploads_per')):
976
-											database.Uploads.remove(database.Uploads.read(nick)[0][1])
977
-										elif len(database.Uploads.read()) == int(database.Settings.get('max_uploads')):
978
-											database.Uploads.remove(database.Uploads.read()[0][1])
979
-										database.Uploads.add(nick, url, title)
980
-										Commands.sendmsg(chan, 'Uploaded!')
981
-									else:
982
-										Commands.error(chan, 'File with that title already exists!')
983
-								else:
984
-									Commands.error(chan, 'URL is already uploaded!')
985
-							else:
986
-								Commands.error(chan, 'Invalid URL.', 'Only PasteBin & TermBin URLs can be used.')
987
 					elif args[1] == 'random':
988
 						if len(args) == 2:
989
-							ascii_file = random.choice([file for file in glob.glob(os.path.join(ascii_dir, '**/*.txt'), recursive=True) if os.path.basename(os.path.dirname(file)) not in database.Settings.get('rnd_exclude').split(',')])
990
+							ascii_file = random.choice([file for file in glob.glob('art/**/*.txt', recursive=True) if os.path.basename(os.path.dirname(file)) not in database.Settings.get('rnd_exclude').split(',')])
991
 							threading.Thread(target=Commands.play, args=(chan, ascii_file)).start()
992
 						elif len(args) == 3:
993
 							dir = args[2]
994
-							if '../' in dir:
995
+							if not dir.isalpha():
996
 								Commands.error(chan, 'Nice try nerd!')
997
-							elif os.path.isdir(os.path.join(ascii_dir, dir)):
998
-								ascii_file = random.choice(glob.glob(os.path.join(ascii_dir, dir + '/*.txt'), recursive=True))
999
+							elif os.path.isdir('art/' + dir):