🢀︎ scroll :: 2d8110b


commit 2d8110b256a344bbcf1c7a5b96e148b975378a98
Author: acidvegas <acid.vegas@acid.vegas>
Date:   Sun Feb 16 16:43:02 2020 -0500

    Fixed remote exploit reading file:// and fixed floatints

diff --git a/README.md b/README.md
index ded3b6e..c011a50 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
 
 ## Requirements
 - [Python](https://www.python.org/downloads/) *(**Note:** This script was developed to be used with the latest version of Python)*
-- [Pillow](https://pypi.org/project/Pillow/) *(Required by [core/ascii2png.py](scroll/core/ascii2png.py))*
+- [Pillow](https://pypi.org/project/Pillow/) *(Required by [core/mircart.py](scroll/core/mircart.py))*
 
 ## Setup
 Edit the [core/config.py](scroll/core/config.py) file and then place your art files in the [data/art](scroll/data/art) directory.
diff --git a/scroll/core/ascii2png.py b/scroll/core/ascii2png.py
index 1194d61..7c82486 100644
--- a/scroll/core/ascii2png.py
+++ b/scroll/core/ascii2png.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# Scroll IRC Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
+# Scroll IRC Art Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
 # ascii2png.py
 
 '''
diff --git a/scroll/core/config.py b/scroll/core/config.py
index fecab55..61fbf6a 100644
--- a/scroll/core/config.py
+++ b/scroll/core/config.py
@@ -1,13 +1,13 @@
 #!/usr/bin/env python
-# Scroll IRC Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
+# Scroll IRC Art Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
 # config.py
 
 class connection:
 	server     = 'irc.server.com'
-	port       = 6697
-	proxy      = None
+	port       = 6667
+	proxy      = None # Must be in IP:PORT format (Socks5 proxies only)
 	ipv6       = False
-	ssl        = True
+	ssl        = False
 	ssl_verify = False
 	vhost      = None
 	channel    = '#chats'
@@ -29,7 +29,7 @@ class login:
 	operator = None
 
 class settings:
-	admin = 'nick!user@host' # Must be in nick!user@host format (Can use wildcards here)
+	admin = 'nick!user@host' # Must be in nick!user@host format (Wildcards accepted)
 	log   = False
 	modes = None
 
diff --git a/scroll/core/constants.py b/scroll/core/constants.py
index 03d3d52..109eb9a 100644
--- a/scroll/core/constants.py
+++ b/scroll/core/constants.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-# Scroll IRC Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
+# Scroll IRC Art Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
 # constants.py
 
 # Control Characters
diff --git a/scroll/core/database.py b/scroll/core/database.py
index d4b8f8d..d302349 100644
--- a/scroll/core/database.py
+++ b/scroll/core/database.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-# Scroll IRC Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
+# Scroll IRC Art Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
 # database.py
 
 import os
@@ -20,10 +20,10 @@ def check():
 		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('max_results',     '10'))
 		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('max_uploads',     '25'))
 		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('max_uploads_per', '5'))
+		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('rnd_exclude',     'ansi,big,birds,hang,pokemon'))
 		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('throttle_cmd',    '3'))
 		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('throttle_msg',    '0.03'))
 		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('throttle_png',    '0.03'))
-		sql.execute('INSERT INTO SETTINGS (SETTING,VALUE) VALUES (?,?)', ('rnd_exclude',     'ansi,big,birds,hang,pokemon'))
 		db.commit()
 
 class Ignore:
diff --git a/scroll/core/debug.py b/scroll/core/debug.py
index 5c29f15..2f43707 100644
--- a/scroll/core/debug.py
+++ b/scroll/core/debug.py
@@ -1,12 +1,11 @@
 #!/usr/bin/env python
-# Scroll IRC Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
+# Scroll IRC Art Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
 # debug.py
 
 import ctypes
 import logging
 import os
 import sys
-import time
 
 from logging.handlers import RotatingFileHandler
 
@@ -37,7 +36,7 @@ def info():
 	clear()
 	print('#'*56)
 	print('#{0}#'.format(''.center(54)))
-	print('#{0}#'.format('Scroll IRC Bot'.center(54)))
+	print('#{0}#'.format('Scroll IRC Art Bot'.center(54)))
 	print('#{0}#'.format('Developed by acidvegas in Python'.center(54)))
 	print('#{0}#'.format('https://acid.vegas/scroll'.center(54)))
 	print('#{0}#'.format(''.center(54)))
diff --git a/scroll/core/functions.py b/scroll/core/functions.py
index 148a5d7..6c7b13a 100644
--- a/scroll/core/functions.py
+++ b/scroll/core/functions.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-# Scroll IRC Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
+# Scroll IRC Art Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
 # functions.py
 
 import base64
diff --git a/scroll/core/irc.py b/scroll/core/irc.py
index 51f2fd7..afb0547 100644
--- a/scroll/core/irc.py
+++ b/scroll/core/irc.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-# Scroll IRC Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
+# Scroll IRC Art Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
 # irc.py
 
 import glob
@@ -108,7 +108,7 @@ class Commands:
 		try:
 			Bot.playing = True
 			data = open(ascii_file, encoding='utf8', errors='replace').read()
-			if len(data.splitlines()) > int(database.Settings.get('max_lines')) and chan != '#scroll':
+			if len(data.splitlines()) > functions.floatint(database.Settings.get('max_lines')) and chan != '#scroll':
 				Commands.error(chan, 'File is too big.', 'Take it to #scroll')
 			else:
 				name = ascii_file.split(ascii_dir)[1]
@@ -131,7 +131,7 @@ class Commands:
 
 	def sendmsg(target, msg):
 		Commands.raw(f'PRIVMSG {target} :{msg}')
-		time.sleep(functions.floatint(database.Settings.get('msg_throttle')))
+		time.sleep(functions.floatint(database.Settings.get('throttle_msg')))
 
 class Events:
 	def connect():
@@ -166,7 +166,7 @@ class Events:
 			elif args[0] == '.ascii' and not database.Ignore.check(ident):
 				if Bot.playing and msg == '.ascii stop':
 					Bot.stopper = True
-				elif time.time() - Bot.last < int(database.Settings.get('cmd_throttle')) and not functions.is_admin(ident):
+				elif time.time() - Bot.last < int(database.Settings.get('throttle_cmd')) and not functions.is_admin(ident):
 					if not Bot.slow:
 						Commands.error(chan, 'Slow down nerd!')
 						Bot.slow = True
@@ -192,7 +192,7 @@ class Events:
 							Commands.error(chan, 'Invalid URL.', 'Only PasteBin & TermBin URLs can be used.')
 					elif args[1] == 'remote' and len(args) == 3:
 						url = args[2]
-						if 'pastebin.com/raw/' in url or 'termbin.com/' in url:
+						if url.startswith('https://pastebin.com/raw/') or url.startswith('https://termbin.com/'):
 							data = functions.get_source(url)
 							if data:
 								for item in data.split('\n'):
@@ -270,7 +270,7 @@ class Events:
 								Commands.error(chan, 'Invalid file name.', 'Use ".ascii list" for a list of valid file names.')
 					Bot.last = time.time()
 		except Exception as ex:
-			if time.time() - Bot.last < int(database.Settings.get('cmd_throttle')):
+			if time.time() - Bot.last < int(database.Settings.get('throttle_cmd')):
 				if not Bot.slow:
 					Commands.sendmsg(chan, color('Slow down nerd!', constants.red))
 					Bot.slow = True
@@ -282,7 +282,7 @@ class Events:
 		if functions.is_admin(ident):
 			args = msg.split()
 			if msg == '.update':
-				output = functions.cmd(f'git -C {ascii_dir} reset --hard FETCH_HEAD')
+				output = functions.cmd(f'git -C {ascii_dir} pull')
 				if output:
 					for line in output.split('\n'):
 						Commands.sendmsg(chan, line)
diff --git a/scroll/core/mircart/canvas.py b/scroll/core/mircart/canvas.py
new file mode 100644
index 0000000..1da08d5
--- /dev/null
+++ b/scroll/core/mircart/canvas.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+
+class store():
+	def flip_cell_state(self, cellState, bit):
+		if cellState & bit:
+			return cellState & ~bit
+		else:
+			return cellState | bit
+
+	def parse_char(self, colourSpec, curColours):
+		if len(colourSpec) > 0:
+			colourSpec = colourSpec.split(',')
+			if len(colourSpec) == 2 and len(colourSpec[1]) > 0:
+				return (int(colourSpec[0] or curColours[0]), int(colourSpec[1]))
+			elif len(colourSpec) == 1 or len(colourSpec[1]) == 0:
+				return (int(colourSpec[0]), curColours[1])
+		else:
+			return (15, 1)
+
+	def importTextFile(self, pathName):
+		self.inFile = open(pathName, 'r', encoding='utf-8')
+		self.inSize = self.outMap = None;
+		inCurColourSpec = ''; inCurRow = -1;
+		inLine = self.inFile.readline()
+		inSize = [0, 0]; outMap = []; inMaxCols = 0;
+		while inLine:
+			inCellState = 0x00
+			inParseState = 1
+			inCurCol = 0; inMaxCol = len(inLine);
+			inCurColourDigits = 0; inCurColours = (15, 1); inCurColourSpec = '';
+			inCurRow += 1; outMap.append([]); inRowCols = 0; inSize[1] += 1;
+			while inCurCol < inMaxCol:
+				inChar = inLine[inCurCol]
+				if inChar in set('\r\n'):
+					inCurCol += 1
+				elif inParseState == 1:
+					inCurCol += 1
+					if inChar == '':
+						inCellState = self.flip_cell_state(inCellState, 0x01)
+					elif inChar == '':
+						inParseState = 2
+					elif inChar == '':
+						inCellState = self.flip_cell_state(inCellState, 0x02)
+					elif inChar == '':
+						inCellState |= 0x00
+						inCurColours = (15, 1)
+					elif inChar == '':
+						inCurColours = (inCurColours[1], inCurColours[0])
+					elif inChar == '':
+						inCellState = self.flip_cell_state(inCellState, 0x04)
+					else:
+						inRowCols += 1
+						outMap[inCurRow].append([*inCurColours, inCellState, inChar])
+				elif inParseState == 2 or inParseState == 3:
+					if inChar == ',' and inParseState == 2:
+						if (inCurCol + 1) < inMaxCol and not inLine[inCurCol + 1] in set('0123456789'):
+							inCurColours = self.parse_char(inCurColourSpec, inCurColours)
+							inCurColourDigits = 0; inCurColourSpec = '';
+							inParseState = 1
+						else:
+							inCurCol += 1
+							inCurColourDigits = 0; inCurColourSpec += inChar;
+							inParseState = 3
+					elif inChar in set('0123456789') and inCurColourDigits == 0:
+						inCurCol += 1
+						inCurColourDigits += 1; inCurColourSpec += inChar;
+					elif inChar in set('0123456789') and inCurColourDigits == 1 and inCurColourSpec[-1] == '0':
+						inCurCol += 1
+						inCurColourDigits += 1; inCurColourSpec += inChar;
+					elif inChar in set('012345') and inCurColourDigits == 1 and inCurColourSpec[-1] == '1':
+						inCurCol += 1
+						inCurColourDigits += 1; inCurColourSpec += inChar;
+					else:
+						inCurColours = self.parse_char(inCurColourSpec, inCurColours)
+						inCurColourDigits = 0; inCurColourSpec = '';
+						inParseState = 1
+			inMaxCols = max(inMaxCols, inRowCols)
+			inLine = self.inFile.readline()
+		inSize[0] = inMaxCols;
+		self.inSize = inSize;
+		self.outMap = outMap;
+		self.inFile.close()
\ No newline at end of file
diff --git a/scroll/core/mircart/mircart.py b/scroll/core/mircart/mircart.py
new file mode 100644
index 0000000..38d0af7
--- /dev/null
+++ b/scroll/core/mircart/mircart.py
@@ -0,0 +1,216 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Scroll IRC Bot - Developed by acidvegas in Python (https://acid.vegas/scroll))
+# mircart.py
+
+from PIL import Image, ImageDraw, ImageFont
+
+def ansi(input_file):
+	ansi_colors = [97,30,94,32,91,31,35,33,93,92,36,96,34,95,90,37]
+	canvasStore = canvas.(input_file)
+	inMap = canvasStore.outMap.copy(); del canvasStore;
+	with open(input_file, 'w+') as output_file:
+		for inCurRow in range(len(inMap)):
+			lastAttribs = canvas._CellState.CS_NONE
+			lastColours = None
+			for inCurCol in range(len(inMap[inCurRow])):
+				inCurCell = inMap[inCurRow][inCurCol]
+				if lastAttribs != inCurCell[2]:
+					if inCurCell[2] & canvas._CellState.CS_BOLD:
+						print('\u001b[1m', end='', file=output_file)
+					if inCurCell[2] & canvas._CellState.CS_UNDERLINE:
+						print('\u001b[4m', end='', file=output_file)
+					lastAttribs = inCurCell[2]
+				if lastColours == None or lastColours != inCurCell[:2]:
+					ansiBg = ansi_colors[int(inCurCell[1])] + 10
+					ansiFg = ansi_colors[int(inCurCell[0])]
+					print('\u001b[{:02d}m\u001b[{:02d}m{}'.format(ansiBg, ansiFg, inCurCell[3]), end='', file=output_file)
+					lastColours = inCurCell[:2]
+				else:
+					print(inCurCell[3], end='', file=output_file)
+			print('\u001b[0m\n', end='', file=output_file)
+
+def canonicalise(input_file):
+	canvasStore = canvas.(input_file)
+	inMap = canvasStore.outMap.copy(); del canvasStore;
+	with open(input_file, 'w+') as output_file:
+		for inCurRow in range(len(inMap)):
+			lastAttribs = canvas._CellState.CS_NONE
+			lastColours = None
+			for inCurCol in range(len(inMap[inCurRow])):
+				inCurCell = inMap[inCurRow][inCurCol]
+				if lastAttribs != inCurCell[2]:
+					if inCurCell[2] & canvas._CellState.CS_BOLD:
+						print('\u0002', end='', file=output_file)
+					if inCurCell[2] & canvas._CellState.CS_UNDERLINE:
+						print('\u001f', end='', file=output_file)
+					lastAttribs = inCurCell[2]
+				if lastColours == None or lastColours != inCurCell[:2]:
+					print('\u0003{:02d},{:02d}{}'.format(*inCurCell[:2], inCurCell[3]), end='', file=output_file)
+					lastColours = inCurCell[:2]
+				else:
+					print(inCurCell[3], end='', file=output_file)
+			print('\n', end='', file=output_file)#!/usr/bin/env python3
+
+class canvas():
+	def flip_cell_state(self, cellState, bit):
+		if cellState & bit:
+			return cellState & ~bit
+		else:
+			return cellState | bit
+
+	def parse_char(self, colourSpec, curColours):
+		if len(colourSpec) > 0:
+			colourSpec = colourSpec.split(',')
+			if len(colourSpec) == 2 and len(colourSpec[1]) > 0:
+				return (int(colourSpec[0] or curColours[0]), int(colourSpec[1]))
+			elif len(colourSpec) == 1 or len(colourSpec[1]) == 0:
+				return (int(colourSpec[0]), curColours[1])
+		else:
+			return (15, 1)
+
+	def importTextFile(self, pathName):
+		self.inFile = open(pathName, 'r', encoding='utf-8')
+		self.inSize = self.outMap = None;
+		inCurColourSpec = ''; inCurRow = -1;
+		inLine = self.inFile.readline()
+		inSize = [0, 0]; outMap = []; inMaxCols = 0;
+		while inLine:
+			inCellState = 0x00
+			inParseState = 1
+			inCurCol = 0; inMaxCol = len(inLine);
+			inCurColourDigits = 0; inCurColours = (15, 1); inCurColourSpec = '';
+			inCurRow += 1; outMap.append([]); inRowCols = 0; inSize[1] += 1;
+			while inCurCol < inMaxCol:
+				inChar = inLine[inCurCol]
+				if inChar in set('\r\n'):
+					inCurCol += 1
+				elif inParseState == 1:
+					inCurCol += 1
+					if inChar == '':
+						inCellState = self.flip_cell_state(inCellState, 0x01)
+					elif inChar == '':
+						inParseState = 2
+					elif inChar == '':
+						inCellState = self.flip_cell_state(inCellState, 0x02)
+					elif inChar == '':
+						inCellState |= 0x00
+						inCurColours = (15, 1)
+					elif inChar == '':
+						inCurColours = (inCurColours[1], inCurColours[0])
+					elif inChar == '':
+						inCellState = self.flip_cell_state(inCellState, 0x04)
+					else:
+						inRowCols += 1
+						outMap[inCurRow].append([*inCurColours, inCellState, inChar])
+				elif inParseState == 2 or inParseState == 3:
+					if inChar == ',' and inParseState == 2:
+						if (inCurCol + 1) < inMaxCol and not inLine[inCurCol + 1] in set('0123456789'):
+							inCurColours = self.parse_char(inCurColourSpec, inCurColours)
+							inCurColourDigits = 0; inCurColourSpec = '';
+							inParseState = 1
+						else:
+							inCurCol += 1
+							inCurColourDigits = 0; inCurColourSpec += inChar;
+							inParseState = 3
+					elif inChar in set('0123456789') and inCurColourDigits == 0:
+						inCurCol += 1
+						inCurColourDigits += 1; inCurColourSpec += inChar;
+					elif inChar in set('0123456789') and inCurColourDigits == 1 and inCurColourSpec[-1] == '0':
+						inCurCol += 1
+						inCurColourDigits += 1; inCurColourSpec += inChar;
+					elif inChar in set('012345') and inCurColourDigits == 1 and inCurColourSpec[-1] == '1':
+						inCurCol += 1
+						inCurColourDigits += 1; inCurColourSpec += inChar;
+					else:
+						inCurColours = self.parse_char(inCurColourSpec, inCurColours)
+						inCurColourDigits = 0; inCurColourSpec = '';
+						inParseState = 1
+			inMaxCols = max(inMaxCols, inRowCols)
+			inLine = self.inFile.readline()
+		inSize[0] = inMaxCols;
+		self.inSize = inSize;
+		self.outMap = outMap;
+		self.inFile.close()
+
+# canvasStore = canvas.(inFile=argv[1])
+# MiRCARTToPngFile(canvasStore.outMap, *argv[3:]).export(argv[2])
+class png:
+    inFile = inFromTextFile = None
+    outFontFilePath = outFontSize = None
+    _ColourMapBold   = [[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]]
+    _ColourMapNormal = [[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]]
+
+    def __init__(self, inCanvasMap, fontFilePath='DejaVuSansMono.ttf', fontSize=11):
+        self.inCanvasMap = inCanvasMap
+        self.outFontFilePath = fontFilePath; self.outFontSize = int(fontSize);
+        self.outImgFont = ImageFont.truetype(self.outFontFilePath, self.outFontSize)
+        self.outImgFontSize = [*self.outImgFont.getsize(' ')]
+        self.outImgFontSize[1] += 3
+
+    def _drawUnderLine(self, curPos, fontSize, imgDraw, fillColour):
+        imgDraw.line(xy=(curPos[0], curPos[1] + (fontSize[1] - 2), curPos[0] + fontSize[0], curPos[1] + (fontSize[1] - 2)), fill=fillColour)
+
+    def export(self, output_file):
+        inSize = (len(self.inCanvasMap[0]), len(self.inCanvasMap))
+        outSize = [a*b for a,b in zip(inSize, self.outImgFontSize)]
+        outCurPos = [0, 0]
+        outImg = Image.new('RGBA', outSize, (*self. _ColourMapNormal[1], 255))
+        outImgDraw = ImageDraw.Draw(outImg)
+        for inCurRow in range(len(self.inCanvasMap)):
+            for inCurCol in range(len(self.inCanvasMap[inCurRow])):
+                inCurCell = self.inCanvasMap[inCurRow][inCurCol]
+                outColours = [0, 0]
+                if inCurCell[2] & canvas._CellState.CS_BOLD:
+                    if inCurCell[3] != ' ':
+                        if inCurCell[3] == '█':
+                            outColours[1] = self._ColourMapNormal[inCurCell[0]]
+                        else:
+                            outColours[0] = self._ColourMapBold[inCurCell[0]]
+                            outColours[1] = self._ColourMapNormal[inCurCell[1]]
+                    else:
+                        outColours[1] = self._ColourMapNormal[inCurCell[1]]
+                else:
+                    if inCurCell[3] != ' ':
+                        if inCurCell[3] == '█':
+                            outColours[1] = self._ColourMapNormal[inCurCell[0]]
+                        else:
+                            outColours[0] = self._ColourMapNormal[inCurCell[0]]
+                            outColours[1] = self._ColourMapNormal[inCurCell[1]]
+                    else:
+                        outColours[1] = self._ColourMapNormal[inCurCell[1]]
+                outImgDraw.rectangle((*outCurPos, outCurPos[0] + self.outImgFontSize[0], outCurPos[1] + self.outImgFontSize[1]), fill=(*outColours[1], 255))
+                if not inCurCell[3] in ' █' and outColours[0] != outColours[1]:
+                    outImgDraw.text(outCurPos, inCurCell[3], (*outColours[0], 255), self.outImgFont)
+                if inCurCell[2] & canvas._CellState.CS_UNDERLINE:
+                    outColours[0] = self._ColourMapNormal[inCurCell[0]]
+                    self._drawUnderLine(outCurPos, self.outImgFontSize, outImgDraw, (*outColours[0], 255))
+                outCurPos[0] += self.outImgFontSize[0]
+            outCurPos[0] = 0
+            outCurPos[1] += self.outImgFontSize[1]
+        outImg.save(output_file)
+
+def reduce(input_file):
+	canvasStore = canvas.(input_file)
+	inMap = canvasStore.outMap.copy(); del canvasStore;
+	with open(input_file, 'w+') as output_file:
+		for inCurRow in range(len(inMap)):
+			lastAttribs = canvas._CellState.CS_NONE
+			lastColours = None
+			for inCurCol in range(len(inMap[inCurRow])):
+				inCurCell = inMap[inCurRow][inCurCol]
+				if lastAttribs != inCurCell[2]:
+					if inCurCell[2] & canvas._CellState.CS_BOLD:
+						print('\u0002', end='', file=output_file)
+					if inCurCell[2] & canvas._CellState.CS_UNDERLINE:
+						print('\u001f', end='', file=output_file)
+					lastAttribs = inCurCell[2]
+				if lastColours == None or (lastColours[0] != inCurCell[:2][0] and lastColours[1] != inCurCell[:2][1]):
+					print('\u0003{:d},{:d}{}'.format(*inCurCell[:2], inCurCell[3]), end='', file=output_file)
+					lastColours = inCurCell[:2]
+				elif lastColours[1] == inCurCell[:2][1] and lastColours[0] != inCurCell[:2][0]:
+					print('\u0003{:d}{}'.format(inCurCell[:2][0], inCurCell[3]), end='', file=output_file)
+					lastColours[0] = inCurCell[:2][0]
+				else:
+					print(inCurCell[3], end='', file=output_file)
+			print('\n', end='', file=output_file)
\ No newline at end of file
diff --git a/scroll/core/mircart/png.py b/scroll/core/mircart/png.py
new file mode 100644
index 0000000..b921f33
--- /dev/null
+++ b/scroll/core/mircart/png.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+import canvas
+from PIL import Image, ImageDraw, ImageFont
+
+# canvasStore = canvas.store(inFile=argv[1])
+# MiRCARTToPngFile(canvasStore.outMap, *argv[3:]).export(argv[2])
+class png:
+    inFile = inFromTextFile = None
+    outFontFilePath = outFontSize = None
+    _ColourMapBold   = [[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]]
+    _ColourMapNormal = [[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]]
+
+    def __init__(self, inCanvasMap, fontFilePath='DejaVuSansMono.ttf', fontSize=11):
+        self.inCanvasMap = inCanvasMap
+        self.outFontFilePath = fontFilePath; self.outFontSize = int(fontSize);
+        self.outImgFont = ImageFont.truetype(self.outFontFilePath, self.outFontSize)
+        self.outImgFontSize = [*self.outImgFont.getsize(' ')]
+        self.outImgFontSize[1] += 3
+
+    def _drawUnderLine(self, curPos, fontSize, imgDraw, fillColour):
+        imgDraw.line(xy=(curPos[0], curPos[1] + (fontSize[1] - 2), curPos[0] + fontSize[0], curPos[1] + (fontSize[1] - 2)), fill=fillColour)
+
+    def export(self, output_file):
+        inSize = (len(self.inCanvasMap[0]), len(self.inCanvasMap))
+        outSize = [a*b for a,b in zip(inSize, self.outImgFontSize)]
+        outCurPos = [0, 0]
+        outImg = Image.new('RGBA', outSize, (*self. _ColourMapNormal[1], 255))
+        outImgDraw = ImageDraw.Draw(outImg)
+        for inCurRow in range(len(self.inCanvasMap)):
+            for inCurCol in range(len(self.inCanvasMap[inCurRow])):
+                inCurCell = self.inCanvasMap[inCurRow][inCurCol]
+                outColours = [0, 0]
+                if inCurCell[2] & canvas.store._CellState.CS_BOLD:
+                    if inCurCell[3] != ' ':
+                        if inCurCell[3] == '█':
+                            outColours[1] = self._ColourMapNormal[inCurCell[0]]
+                        else:
+                            outColours[0] = self._ColourMapBold[inCurCell[0]]
+                            outColours[1] = self._ColourMapNormal[inCurCell[1]]
+                    else:
+                        outColours[1] = self._ColourMapNormal[inCurCell[1]]
+                else:
+                    if inCurCell[3] != ' ':
+                        if inCurCell[3] == '█':
+                            outColours[1] = self._ColourMapNormal[inCurCell[0]]
+                        else:
+                            outColours[0] = self._ColourMapNormal[inCurCell[0]]
+                            outColours[1] = self._ColourMapNormal[inCurCell[1]]
+                    else:
+                        outColours[1] = self._ColourMapNormal[inCurCell[1]]
+                outImgDraw.rectangle((*outCurPos, outCurPos[0] + self.outImgFontSize[0], outCurPos[1] + self.outImgFontSize[1]), fill=(*outColours[1], 255))
+                if not inCurCell[3] in ' █' and outColours[0] != outColours[1]:
+                    outImgDraw.text(outCurPos, inCurCell[3], (*outColours[0], 255), self.outImgFont)
+                if inCurCell[2] & canvas.store._CellState.CS_UNDERLINE:
+                    outColours[0] = self._ColourMapNormal[inCurCell[0]]
+                    self._drawUnderLine(outCurPos, self.outImgFontSize, outImgDraw, (*outColours[0], 255))
+                outCurPos[0] += self.outImgFontSize[0]
+            outCurPos[0] = 0
+            outCurPos[1] += self.outImgFontSize[1]
+        outImg.save(output_file)
\ No newline at end of file
diff --git a/scroll/scroll.py b/scroll/scroll.py
index e0265f1..bc0a3c3 100644
--- a/scroll/scroll.py
+++ b/scroll/scroll.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-# Scroll IRC Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
+# Scroll IRC Art Bot - Developed by acidvegas in Python (https://acid.vegas/scroll)
 # scroll.py
 
 import os