diff --git a/ASCII/DejaVu.ttf b/ASCII/DejaVu.ttf
new file mode 100644
index 0000000..383458a
Binary files /dev/null and b/ASCII/DejaVu.ttf differ
diff --git a/ASCII/README.md b/ASCII/README.md
new file mode 100644
index 0000000..37a00e9
--- /dev/null
+++ b/ASCII/README.md
@@ -0,0 +1,221 @@
+[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=T8E56M6SP9JH2)
+
+
+ASCII Art Plugin
+Convert text to ASCII art or image URLs to ASCII/ANSI art. Covert ASCII art to PNG. Get ASCII weather, moon phase, and cryptocurrecy rates.
+
+Requires Limnoria https://github.com/ProgVal/Limnoria and Python3
+
+pip install -r requirements.txt
+
+```
+config protocols.irc.throttletime 0.0
+config plugins.ascii.delay 0.5
+```
+If you want delays < 1.0 seconds then disable Limnoria's throttling and use this plugin's delay option. 0.5 is 2 lines per second for example. You can also use --delay with commands and override the delay with your own. Default delay config can
+be set per channel. Set a value that won't get you kicked.
+
+```
+!cq
+@cq
+.cq
+etc.
+```
+Stop the scroll. cq command must be prefixed by a command character. This command can not be renamed.
+
+Support for the Paste.ee API to save art conversions for later use.
+Get an API key from https://paste.ee/account/api (FREE. Not required to use plugin, disabled by default).
+```
+config plugins.ascii.pasteAPI (set paste.ee API key)
+config channel plugins.ascii.pasteEnable (enable/disable paste.ee links)
+```
+
+ASCII Weather from wttr.in
+```
+wttr
+wttr moon
+wttr --16 (use 16 colors. Default)
+wttr --99 (use 99 colors)
+wttr ?u (use imperial units, degrees F, miles, etc.)
+wttr ?m (use metric units, degrees C, km, etc.)
+wttr ?<1-3> (number of days to forecast)
+```
+
+
+
+ASCII crypto exchange rates from rate.sx
+```
+rate (get rates in united states dollars)
+rate --sub (get rates in EUR, USD, BTC, etc.)
+rate (get a graph showing rate fluctuation)
+rate --sub (get graphs with desired currency)
+```
+
+
+Return a random fortune entry from http://www.asciiartfarts.com/fortune.txt
+```
+fortune
+```
+
+Cowsay. Generate a cowsay ascii. choose character with --type. https://easyapis.soue.tk/api/cowsay
+```
+cow --type (make a cowsay message. type is optional. defaults to cow)
+ ________
+< cowsay >
+ --------
+ \ ^__^
+ \ (oo)\_______
+ (__)\ )\/\
+ ||----w |
+ || ||
+```
+
+Text-to-ASCII Art (split lines with | ex. ascii|art for large fonts):
+```
+ascii (convert to ascii art)
+ascii --font (to use chosen )
+ascii --color (to set a foreground )
+ascii --color (to set a foreground/background )
+fontlist (get list of availble )
+
+ 88 88
+ "" ""
+,adPPYYba, ,adPPYba, ,adPPYba, 88 88
+"" `Y8 I8[ "" a8" "" 88 88
+,adPPPPP88 `"Y8ba, 8b 88 88
+88, ,88 aa ]8I "8a, ,aa 88 88
+`"8bbdP"Y8 `"YbbdP"' `"Ybbd8"' 88 88
+```
+
+Image URL to ASCII/ANSI Art:
+```
+img (convert an image to ascii art using 99 color palette)
+img --w <###> (how many columns wide. defauls to 100)
+img --16 (convert using 16 color palette)
+img --ascii (convert image to colored ascii art)
+img --block (colored space block art)
+img --chars "TEXT" (convert image to colorized custom text)
+img --ramp "TEXT" (use a custom greyscale ramp e.g. " .-:=+x#%@")
+img --ramp "░▒▓█" (image to colorized ansi shader blocks)
+img --nocolor (text only greyscale character ramp output)
+img --invert (invert the greyscale character ramp)
+img --bg <0-99> (set a background color)
+img --fg <0-99> (set a foreground color)
+img --fast (use Euclidean color difference.)
+img --slow (use cie2000 color difference. best quality, default)
+img --dither (dither source image to 256 colors. trades off quality for speed)
+```
+Here are some images using 99 color default output:
+
+^ output of img https://i.imgur.com/aF9wihd.jpg (image command with default settings)
+
+^ output of img --block https://i.imgur.com/aF9wihd.jpg (image command with colored space blocks)
+
+^ output of img --ascii https://i.imgur.com/aF9wihd.jpg (image command with colored space blocks)
+```
+***************+*++++++++++++++++++++++++++++++++++++++=++=++========================---------------
+***********+++++++++++++++++++++++++++++++++++++++++++++++============================--------------
+***********++++++++++++++++++++++++++++***++++**********+++++++======================---------------
+********++++++++++++++++++++++++++++*******++****#********+***++++====================--------------
+********++*++++++++++++++++++++++************##%%#####%#*****++***++==================--------------
+******+*++++++++++++++++++++++++**+***#*+++*%@%%#*##%###****++++++++++===============---------------
+******+++++++++++++++++++++++**++++####++*+##@@#+**##%%@@%##*****++++==+=========-------------------
+******++++++++++++++++++++++*#*=*+####*+*++%++@*=*#%@@@@@@@%#**++++==+============------------------
+****+++++++++++++++++++++++*+##++#%###+=*+-+#=*%%@@@@@@@@@@@%##*+======+========--------------------
+***++++++++++++++++++++++++**###*++***+=*#+==#%#****##%%@@@@@@%#*==============---------------------
+**++++++++++++++++++++++++++**#%%%##%@@@@%*+=++=+#%%%@%%@@@@@@@@@#+============-=-------------------
+***++++++++==========++++++++#%@@@@@@@@#*+**#*==*%@@@%@@@@@%%@@@%@@*+===========--------------------
+**+++++++**#%%@@%#*+=---===+*%%@@@@@@@**#%%@@@#*+++**=*##*****#%@@@@#+===========-------------------
+*++++++++#%@@@%###%%%%#*=--=+*%@@@@@@%+#%@@@#%@#++=++++=+++*****##%@@%*=======-==-------------------
+*++++++++*###%%#*++*%@@@@@#++*%@@@@@@%*%@@@@*#*=++*++=++**+++++++++*****====---=--------------------
+**++++++++**###%@%%%@@@@@@@%##@@@@@@@@*%@%%#*+=+**+++++*++====++++++**##*=--------------------------
+**++++++++++**##%@@#*#%%@@###%@@@@@@@@*++++==++**=+++**+=========++****#%%*-------------------------
+***+++++++++++**#%%#+++#%@#+**####%%@#+=+++**++*++++*#*+=====+++==+==+*#**%#=-----------------------
+*+++++++++++++++**#%%*+*#%#+****#****#*+****++**+++***+=====++=---:-==#@%**#%=----------------------
+++++++++++++++++++**#%%%%*+=+**#*****%*####*++**++++**+====++=-+%%*=:-=#@@#+#%=---------------------
+++++++++++++++++++++*##%%#++++*#**#*#*+*##*+++#*++++**+===+++=-*@@@@*--=+%%++##-----------------::::
++++++++++++++++++++++*###**+++**###*+++****+++#*+++++**=====+===*%@@%=-==+**+*#=----------------::::
++++++++++++++++++++++++*++++=+#####+=+++*#*+++*#+++*+*#+++==++==--=+=====++***#*---------------:::::
++++++++++++++++++++++========*####*++++++##+++*#*++*++*#++==+++=====--===++***%*---------------:::::
+++++++++++++++++++++++++++**%#*##*+++==++*%#****#*****+*#*+==++*+=======++**##%+---------:::::::::::
++++++++++++++++++++++++***##**#%#+++++++++*%%#**####*****##****##**++++++*##%%*--------:::::::::::::
++++++++++++++++++++++++####**#%%#++++++++++*#%####%%######%%%*+*@@@@%###%%@@%*+==----:-:::::::::::::
++++++++++++++++++++++++#%%#####*#****++++++**#%%%%%@@@@@@@@@@%**@@@@@@@@@%%%%#*++==--:::::::::::::::
++++++++++++++++++=+++++*#%%%%%%%###****+***###%%@@@@@@@@@@@@@@@@@@@@%*#%%%%%%##*+++=-:::::::::::::::
+++++++++++++++++++++++****#%%%%#############%%@@@@%#%@@@#*%@@@@@@@@@= .+%%@%%%#*++=--:::::::::::::::
++++++++++++++++++++++*+++*###******#*#%%%%%%%%%#*+++++**: :%@@@@@@@@%+*%#%%%%%%#*++===----::::::::::
+++++++++++++++++==++*++**###**###*#**##%##****++====+==-===+%@@@@@@%%@######%%@@@@@@%%#**++===-:::::
+++++++++++++++++++=+++***#######**##**#%#*#*####*++++=====+===+*********####%%@@@@@@@@@@%%##**+==--:
++++++++++++++++++++++++****###%##%%#**###%%%%%%%%##*++==========+++++**++**%%%@@@@@@@@@@@@@@%%##*+=-
++++++++++++++++++==+++++****###%%%%##*##%@@@%%@%%%####***+====++++***##***#%%%@@@@@@@@@@@@@@@@@%%#*+
+++++++++++++++++++++++++***#######%#*###%%@@@@@@%@@%%@%%#####******##%#%%@@@@@@@@@@@@@@@@@@@@@@%%%#*
+++++++++++++++++++++*****######%#########%%%@@@@@@@@@@@@@@@%%%%%%%%%@@@@@@@@@@@@@@@@@@@@@@@@@@@%####
++++++++++++++=+++++++**#%%#####%%%%%%###%%#%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%####***
+++++++++++++++++++***##%%@%##%%%%%%%%%%@@%%@%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%#*******#
+++++++++++++=++++*##%%%##%%%%%%%%%%%%%%%%%#%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%#***++**##
+++++++++++==+****#%%%%####%##%%%%%%%#**++*+*@%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%****++*##
+++++++++++**####**####%###%#%%#@%@%#*+*****#**%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%###**#***#
+++++++++*#######**####%%##%##*%@@@%%##*+**+**%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%@@@@%#*****#**
++++++**########%##%%%%%%%#####%@@@@%**+*#**#%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%@%%%#**##**#
++++**###########+#%%%%%##%%%*#%@@@@#**#####%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%%####**###*
++**#%%%%%%%%##*++*#####**##***###%%####*****####**%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%#########%#
+##%%%%%%%%%#*++=+++*****+++++++++++++===========+#%%@@@@@@@@@@@@@@@@@@@@@%%%%@@@@@@@@@@@@%%%%%##*##%
+%%%@@@@@@@%#*++====+*****++*+++++++++=========++***%@@@@@@@@@@@@@@@@@%%%#**###%%@@@@@@@@@@@@@@%%#**#
+%@@@@@@@###**+++=+++++**#*+***++*++++++++++==+***+*%%@@@@@@@@@@@@@@@%%###**####%%%@@@@@@@@@@@@@@@%##
+@@@@@@@%#++**+***+++*****+**#####***++++==+=+****+*#%%@@@@@@@@@@@@@@%%####**##%@%%%%%%%##%%@@@@@@@@@
+```
+^ Output of img --nocolor --invert https://i.imgur.com/aF9wihd.jpg
+
+
+Scroll ASCII/ANSI Art text files
+```
+scroll (playback of ansi/ascii art .txt files from the web)
+```
+
+Create PNG images from ascii art text files.
+```
+png
+png --size 10-99 (set a font size. defaults to 15pt)
+png --bg 0-99 (set a background color)
+png --fg 0-99 (set a foreground color)
+```
+Get your imgur Client ID from https://imgur.com/account/settings (FREE).
+```
+config plugins.ascii.imgurAPI (set imgur API key)
+```
+
+THE COMMANDS BELOW REQUIRE ADDITIONAL INSTALLS AND ARE OPTIONAL. YOU WILL NEED TO VISIT THE GITHUB
+PAGES BELOW AND INSTALL THE PROGRAMS IF YOU WANT TO USE THESE COMMANDS.
+
+ANSI Art to IRC converrter:
+```
+a2m (conversion and playback of ansi art .ans files from the web.)
+```
+a2m command requires A2M https://github.com/tat3r/a2m (optional. disable command if not installing a2m.)
+
+Picture to Unicode
+```
+p2u
+```
+
+^ Output of p2u https://i.imgur.com/aF9wihd.jpg
+
+p2u command requires p2u https://git.trollforge.org/p2u/about/ (optional. disable command if not installing p2u.)
+
+TDFiglet. Text to tdfiglet
+```
+tdf [-f ] (select font with -f )
+fonts (list of figlet fonts)
+```
+
+tdf command requires tdfiglet https://github.com/tat3r/tdfiglet (optional. disable command if not installing tdfiglet.)
+
+Toilet. Requires installation of toilet. sudo apt install toilet etc. (optional as usual)
+```
+toilet -f -F (do the text to toilet stuff)
+```
+get fonts. looks for fonts in /usr/share/figlet/
+```
+fonts --toilet
+```
+
diff --git a/ASCII/__init__.py b/ASCII/__init__.py
new file mode 100644
index 0000000..8b77afe
--- /dev/null
+++ b/ASCII/__init__.py
@@ -0,0 +1,43 @@
+###
+# Copyright (c) 2019 oddluck
+# All rights reserved.
+#
+#
+###
+
+"""
+ASCII: Uses API to retrieve information
+"""
+
+import supybot
+import supybot.world as world
+
+# Use this for the version of this plugin. You may wish to put a CVS keyword
+# in here if you're keeping the plugin in CVS or some similar system.
+__version__ = ""
+
+# XXX Replace this with an appropriate author or supybot.Author instance.
+__author__ = supybot.authors.unknown
+
+# This is a dictionary mapping supybot.Author instances to lists of
+# contributions.
+__contributors__ = {}
+
+# This is a url where the most recent plugin package can be downloaded.
+__url__ = ''
+
+from . import config
+from . import plugin
+from imp import reload
+# In case we're being reloaded.
+reload(config)
+reload(plugin)
+# Add more reloads here if you add third-party modules and want them to be
+# reloaded when this plugin is reloaded. Don't forget to import them as well!
+
+if world.testing:
+ from . import test
+
+Class = plugin.Class
+configure = config.configure
+
diff --git a/ASCII/config.py b/ASCII/config.py
new file mode 100644
index 0000000..18893b1
--- /dev/null
+++ b/ASCII/config.py
@@ -0,0 +1,57 @@
+###
+# Copyright (c) 2019, oddluck
+# All rights reserved.
+#
+#
+###
+
+import supybot.conf as conf
+import supybot.registry as registry
+try:
+ from supybot.i18n import PluginInternationalization
+ _ = PluginInternationalization('ASCII')
+except:
+ # Placeholder that allows to run the plugin on a bot
+ # without the i18n module
+ _ = lambda x: x
+
+
+def configure(advanced):
+ # This will be called by supybot to configure this module. advanced is
+ # a bool that specifies whether the user identified themself as an advanced
+ # user or not. You should effect your configuration by manipulating the
+ # registry as appropriate.
+ from supybot.questions import expect, anything, something, yn
+ conf.registerPlugin('ASCII', True)
+
+ASCII = conf.registerPlugin('ASCII')
+
+conf.registerGlobalValue(ASCII, 'pasteAPI',
+registry.String('', _("""Paste.ee API Key""")))
+
+conf.registerGlobalValue(ASCII, 'imgurAPI',
+registry.String('', _("""Imgur Client ID""")))
+
+conf.registerChannelValue(ASCII, 'pasteEnable',
+registry.Boolean(False, _("""Turns on and off paste.ee support""")))
+
+conf.registerChannelValue(ASCII, 'delay',
+registry.Float(1.0, _("""Set the time delay betwen lines. Not currently implemented.""")))
+
+conf.registerChannelValue(ASCII, 'dither',
+registry.Boolean(True, _("""Enable dithering. Results in much faster rendering at a slight decrease in quality. Default: True""")))
+
+conf.registerChannelValue(ASCII, 'speed',
+registry.String('Slow', _("""Set the speed of the color rendering. 'Slow' (default) to use CIEDE2000 color difference. 'Fast' to use Euclidean color difference.""")))
+
+conf.registerChannelValue(ASCII, 'imgDefault',
+registry.String('1/2', _("""Set the default art type for the img command. Options are 'ascii', '1/2' (default), '1/4', 'block', and 'no-color'""")))
+
+conf.registerChannelValue(ASCII, 'asciiWidth',
+registry.Integer(100, _("""Set the default column width for ascii art images""")))
+
+conf.registerChannelValue(ASCII, 'blockWidth',
+registry.Integer(70, _("""Set the default column width for 1/2 and 1/4 block art images""")))
+
+conf.registerChannelValue(ASCII, 'colors',
+registry.Integer(99, _("""Set the default number of colors to use. Options are 16 for colors 0-15 only, 83 for colors 16-98 only, and 99 (default) to use all available colors""")))
diff --git a/ASCII/plugin.py b/ASCII/plugin.py
new file mode 100644
index 0000000..8f90237
--- /dev/null
+++ b/ASCII/plugin.py
@@ -0,0 +1,1713 @@
+###
+# Copyright (c) 2019 oddluck
+# All rights reserved.
+#
+#
+###
+
+import supybot.ansi as ansi
+import supybot.utils as utils
+from supybot.commands import *
+import supybot.plugins as plugins
+import supybot.ircutils as ircutils
+import supybot.callbacks as callbacks
+import supybot.ircmsgs as ircmsgs
+import os
+import requests
+from PIL import Image, ImageOps, ImageFont, ImageDraw, ImageEnhance
+import numpy as np
+import sys, math
+from fake_useragent import UserAgent
+import re
+import pexpect
+import time
+import random as random
+import pyimgur
+from bs4 import BeautifulSoup
+
+try:
+ from supybot.i18n import PluginInternationalization
+ _ = PluginInternationalization('Weed')
+except ImportError:
+ # Placeholder that allows to run the plugin on a bot
+ # without the i18n module
+ _ = lambda x: x
+
+class ASCII(callbacks.Plugin):
+ """Uses API to retrieve information"""
+ threaded = True
+
+ def __init__(self, irc):
+ self.__parent = super(ASCII, self)
+ self.__parent.__init__(irc)
+ self.colors = 99
+ self.stopped = {}
+ self.old_color = None
+ self.rgbColors = [
+ (255,255,255),
+ (0,0,0),
+ (0,0,127),
+ (0,147,0),
+ (255,0,0),
+ (127,0,0),
+ (156,0,156),
+ (252,127,0),
+ (255,255,0),
+ (0,252,0),
+ (0,147,147),
+ (0,255,255),
+ (0,0,252),
+ (255,0,255),
+ (127,127,127),
+ (210,210,210),
+ (71,0,0),
+ (71,33,0),
+ (71,71,0),
+ (50,71,0),
+ (0,71,0),
+ (0,71,44),
+ (0,71,71),
+ (0,39,71),
+ (0,0,71),
+ (46,0,71),
+ (71,0,71),
+ (71,0,42),
+ (116,0,0),
+ (116,58,0),
+ (116,116,0),
+ (81,116,0),
+ (0,116,0),
+ (0,116,73),
+ (0,116,116),
+ (0,64,116),
+ (0,0,116),
+ (75,0,116),
+ (116,0,116),
+ (116,0,69),
+ (181,0,0),
+ (181,99,0),
+ (181,181,0),
+ (125,181,0),
+ (0,181,0),
+ (0,181,113),
+ (0,181,181),
+ (0,99,181),
+ (0,0,181),
+ (117,0,181),
+ (181,0,181),
+ (181,0,107),
+ (255,0,0),
+ (255,140,0),
+ (255,255,0),
+ (178,255,0),
+ (0,255,0),
+ (0,255,160),
+ (0,255,255),
+ (0,140,255),
+ (0,0,255),
+ (165,0,255),
+ (255,0,255),
+ (255,0,152),
+ (255,89,89),
+ (255,180,89),
+ (255,255,113),
+ (207,255,96),
+ (111,255,111),
+ (101,255,201),
+ (109,255,255),
+ (89,180,255),
+ (89,89,255),
+ (196,89,255),
+ (255,102,255),
+ (255,89,188),
+ (255,156,156),
+ (255,211,156),
+ (255,255,156),
+ (226,255,156),
+ (156,255,156),
+ (156,255,219),
+ (156,255,255),
+ (156,211,255),
+ (156,156,255),
+ (220,156,255),
+ (255,156,255),
+ (255,148,211),
+ (0,0,0),
+ (19,19,19),
+ (40,40,40),
+ (54,54,54),
+ (77,77,77),
+ (101,101,101),
+ (129,129,129),
+ (159,159,159),
+ (188,188,188),
+ (226,226,226),
+ (255,255,255)]
+ self.colors83= {
+ (11.5497, 31.8768, 18.1739):16,
+ (17.5866, 15.7066, 25.9892):17,
+ (29.0208, -8.5776, 37.5533):18,
+ (27.2543, -19.015, 35.3673):19,
+ (25.2798, -34.2963, 32.8426):20,
+ (25.8276, -27.5812, 10.7515):21,
+ (26.6245, -19.1316, -5.6261):22,
+ (14.986, 1.2467, -23.6473):23,
+ (4.1091, 27.6851, -41.3905):24,
+ (9.2862, 34.8709, -32.6869):25,
+ (14.3696, 39.0991, -24.2113):26,
+ (12.6512, 34.8073, -6.066):27,
+ (22.699, 44.779, 34.3145):28,
+ (31.2054, 21.9979, 41.7676):29,
+ (47.2407, -12.0488, 52.8125):30,
+ (44.5753, -27.9355, 49.6338):31,
+ (41.9858, -48.1745, 46.4957):32,
+ (42.7207, -39.1444, 16.0528):33,
+ (43.8747, -26.8746, -7.9028):34,
+ (26.5278, 3.8603, -34.8152):35,
+ (11.0, 44.2673, -60.2918):36,
+ (19.0423, 48.637, -46.7161):37,
+ (26.6606, 54.9202, -34.0091):38,
+ (24.1377, 48.6121, -6.8774):39,
+ (37.5243, 61.9327, 51.9413):40,
+ (50.3904, 27.7338, 58.553):41,
+ (71.4677, -16.6651, 73.0447):42,
+ (67.5818, -39.987, 68.408):43,
+ (64.1995, -66.6294, 64.3075):44,
+ (65.1526, -54.8772, 23.9922):45,
+ (66.8122, -37.1703, -10.9303):46,
+ (41.6262, 7.9137, -50.0682):47,
+ (21.343, 61.2273, -83.3898):48,
+ (32.0743, 66.9878, -65.2716):49,
+ (43.0033, 75.9603, -47.0378):50,
+ (39.3866, 66.9043, -7.4929):51,
+ (53.2329, 80.1093, 67.2201):52,
+ (69.4811, 36.8308, 75.4949):53,
+ (97.1382, -21.5559, 94.4825):54,
+ (92.125, -51.6335, 88.501):55,
+ (87.737, -86.1846, 83.1812):56,
+ (88.9499, -71.2147, 31.6061):57,
+ (91.1165, -48.0796, -14.1381):58,
+ (58.0145, 11.3842, -65.6058):59,
+ (32.3026, 79.1967, -107.8637):60,
+ (45.9331, 86.4699, -84.8483):61,
+ (60.3199, 98.2542, -60.843):62,
+ (55.6111, 86.4597, -9.1916):63,
+ (60.8927, 62.8729, 35.0702):64,
+ (78.8241, 18.6736, 56.0796):65,
+ (97.6208, -17.6977, 66.4162):66,
+ (94.1539, -37.4631, 68.7023):67,
+ (89.8813, -66.1541, 56.3842):68,
+ (91.0093, -53.1765, 13.8066):69,
+ (92.571, -38.7824, -11.8131):70,
+ (70.8615, -4.4808, -45.0866):71,
+ (47.6091, 49.3215, -82.3961):72,
+ (58.1323, 68.3853, -64.8302):73,
+ (68.0079, 76.2368, -48.6298):74,
+ (63.3723, 71.7112, -18.3923):75,
+ (74.4686, 36.8822, 15.7988):76,
+ (87.1187, 8.2035, 33.0227):77,
+ (98.1056, -13.9188, 47.2642):78,
+ (96.0337, -24.7493, 44.2003):79,
+ (92.1264, -48.2196, 38.3812):80,
+ (93.3211, -36.9827, 8.0947):81,
+ (94.2302, -28.9926, -9.1665):82,
+ (82.3123, -6.9657, -27.1167):83,
+ (68.0684, 23.3938, -49.2364):84,
+ (73.6833, 41.0464, -40.0349):85,
+ (77.4342, 51.3197, -33.9217):86,
+ (74.2811, 48.1595, -14.7725):87,
+ (0.0, 0.0, 0.0):88,
+ (5.8822, 0.0022, -0.0022):89,
+ (16.1144, 0.0022, -0.0033):90,
+ (22.6151, 0.0018, -0.004):91,
+ (32.7476, 0.0018, -0.0044):92,
+ (42.7837, 0.0032, -0.0055):93,
+ (53.9767, 0.0034, -0.0063):94,
+ (65.4912, 0.0036, -0.0074):95,
+ (76.2461, 0.0044, -0.0083):96,
+ (89.8837, 0.0048, -0.0094):97,
+ (100.0, 0.0053, -0.0104):98}
+ self.colors16 = {
+ (100.0, 0.0053, -0.0104):0,
+ (0.0, 0.0, 0.0):1,
+ (12.8119, 47.2407, -64.3396):2,
+ (52.8041, -57.1624, 55.1703):3,
+ (53.2329, 80.1093, 67.2201):4,
+ (25.2966, 47.7847, 37.7562):5,
+ (36.8705, 68.0659, -42.1489):6,
+ (66.4237, 42.1616, 73.4335):7,
+ (97.1382, -21.5559, 94.4825):8,
+ (86.8105, -85.4149, 82.4382):9,
+ (55.0455, -31.8888, -9.3772):10,
+ (91.1165, -48.0796, -14.1381):11,
+ (31.8712, 78.4892, -106.9003):12,
+ (60.3199, 98.2542, -60.843):13,
+ (53.1928, 0.0029, -0.0061):14,
+ (84.1985, 0.0045, -0.0089):15}
+ self.colors99= {
+ (100.0, 0.0053, -0.0104):0,
+ (0.0, 0.0, 0.0):1,
+ (12.8119, 47.2407, -64.3396):2,
+ (52.8041, -57.1624, 55.1703):3,
+ (53.2329, 80.1093, 67.2201):4,
+ (25.2966, 47.7847, 37.7562):5,
+ (36.8705, 68.0659, -42.1489):6,
+ (66.4237, 42.1616, 73.4335):7,
+ (97.1382, -21.5559, 94.4825):8,
+ (86.8105, -85.4149, 82.4382):9,
+ (55.0455, -31.8888, -9.3772):10,
+ (91.1165, -48.0796, -14.1381):11,
+ (31.8712, 78.4892, -106.9003):12,
+ (60.3199, 98.2542, -60.843):13,
+ (53.1928, 0.0029, -0.0061):14,
+ (84.1985, 0.0045, -0.0089):15,
+ (11.5497, 31.8768, 18.1739):16,
+ (17.5866, 15.7066, 25.9892):17,
+ (29.0208, -8.5776, 37.5533):18,
+ (27.2543, -19.015, 35.3673):19,
+ (25.2798, -34.2963, 32.8426):20,
+ (25.8276, -27.5812, 10.7515):21,
+ (26.6245, -19.1316, -5.6261):22,
+ (14.986, 1.2467, -23.6473):23,
+ (4.1091, 27.6851, -41.3905):24,
+ (9.2862, 34.8709, -32.6869):25,
+ (14.3696, 39.0991, -24.2113):26,
+ (12.6512, 34.8073, -6.066):27,
+ (22.699, 44.779, 34.3145):28,
+ (31.2054, 21.9979, 41.7676):29,
+ (47.2407, -12.0488, 52.8125):30,
+ (44.5753, -27.9355, 49.6338):31,
+ (41.9858, -48.1745, 46.4957):32,
+ (42.7207, -39.1444, 16.0528):33,
+ (43.8747, -26.8746, -7.9028):34,
+ (26.5278, 3.8603, -34.8152):35,
+ (11.0, 44.2673, -60.2918):36,
+ (19.0423, 48.637, -46.7161):37,
+ (26.6606, 54.9202, -34.0091):38,
+ (24.1377, 48.6121, -6.8774):39,
+ (37.5243, 61.9327, 51.9413):40,
+ (50.3904, 27.7338, 58.553):41,
+ (71.4677, -16.6651, 73.0447):42,
+ (67.5818, -39.987, 68.408):43,
+ (64.1995, -66.6294, 64.3075):44,
+ (65.1526, -54.8772, 23.9922):45,
+ (66.8122, -37.1703, -10.9303):46,
+ (41.6262, 7.9137, -50.0682):47,
+ (21.343, 61.2273, -83.3898):48,
+ (32.0743, 66.9878, -65.2716):49,
+ (43.0033, 75.9603, -47.0378):50,
+ (39.3866, 66.9043, -7.4929):51,
+ (69.4811, 36.8308, 75.4949):53,
+ (92.125, -51.6335, 88.501):55,
+ (87.737, -86.1846, 83.1812):56,
+ (88.9499, -71.2147, 31.6061):57,
+ (58.0145, 11.3842, -65.6058):59,
+ (32.3026, 79.1967, -107.8637):60,
+ (45.9331, 86.4699, -84.8483):61,
+ (55.6111, 86.4597, -9.1916):63,
+ (60.8927, 62.8729, 35.0702):64,
+ (78.8241, 18.6736, 56.0796):65,
+ (97.6208, -17.6977, 66.4162):66,
+ (94.1539, -37.4631, 68.7023):67,
+ (89.8813, -66.1541, 56.3842):68,
+ (91.0093, -53.1765, 13.8066):69,
+ (92.571, -38.7824, -11.8131):70,
+ (70.8615, -4.4808, -45.0866):71,
+ (47.6091, 49.3215, -82.3961):72,
+ (58.1323, 68.3853, -64.8302):73,
+ (68.0079, 76.2368, -48.6298):74,
+ (63.3723, 71.7112, -18.3923):75,
+ (74.4686, 36.8822, 15.7988):76,
+ (87.1187, 8.2035, 33.0227):77,
+ (98.1056, -13.9188, 47.2642):78,
+ (96.0337, -24.7493, 44.2003):79,
+ (92.1264, -48.2196, 38.3812):80,
+ (93.3211, -36.9827, 8.0947):81,
+ (94.2302, -28.9926, -9.1665):82,
+ (82.3123, -6.9657, -27.1167):83,
+ (68.0684, 23.3938, -49.2364):84,
+ (73.6833, 41.0464, -40.0349):85,
+ (77.4342, 51.3197, -33.9217):86,
+ (74.2811, 48.1595, -14.7725):87,
+ (5.8822, 0.0022, -0.0022):89,
+ (16.1144, 0.0022, -0.0033):90,
+ (22.6151, 0.0018, -0.004):91,
+ (32.7476, 0.0018, -0.0044):92,
+ (42.7837, 0.0032, -0.0055):93,
+ (53.9767, 0.0034, -0.0063):94,
+ (65.4912, 0.0036, -0.0074):95,
+ (76.2461, 0.0044, -0.0083):96,
+ (89.8837, 0.0048, -0.0094):97}
+ self.x256colors99 = [1,5,32,30,2,38,34,96,94,4,56,8,60,13,11,0,1,36,2,48,48,60,32,34,47,47,47,72,3,33,10,59,59,59,44,45,45,46,71,71,9,68,45,69,46,83,56,68,57,57,69,11,28,38,37,49,49,60,30,93,47,72,72,72,31,33,10,59,59,59,43,45,45,46,71,71,9,68,57,69,70,83,56,68,68,57,69,70,5,39,38,49,49,61,41,93,50,50,72,72,30,30,94,84,84,84,43,43,45,46,71,71,43,68,80,81,82,83,55,68,68,80,81,70,40,51,6,50,50,61,41,64,63,50,73,73,30,41,76,87,85,84,42,42,95,96,84,84,55,67,79,81,82,83,55,67,80,80,81,82,40,51,51,50,50,73,41,64,63,75,13,73,53,7,76,87,85,85,42,65,77,76,87,85,42,66,78,78,15,97,55,67,79,79,81,82,4,64,63,63,13,13,7,64,63,75,75,74,53,64,76,87,87,86,65,65,65,76,87,86,8,77,77,77,97,86,8,66,66,78,78,0,1,89,89,90,91,91,92,92,92,93,93,14,94,94,95,95,95,96,96,96,15,97,97,97]
+ self.x256colors16 = [1,5,3,3,2,6,10,15,14,4,9,8,12,13,11,0,1,2,2,2,12,12,3,10,12,12,12,12,3,10,10,10,14,14,3,3,10,10,10,10,9,9,9,11,11,11,9,9,9,9,11,11,5,6,2,12,12,12,3,14,14,12,12,12,3,3,10,14,14,14,3,3,10,10,10,15,9,9,9,11,11,11,9,9,9,9,11,11,5,6,6,6,6,12,14,14,6,6,6,13,3,14,14,14,14,13,3,3,3,11,11,11,9,9,9,11,11,11,9,9,9,9,11,11,5,6,6,6,6,6,7,4,6,13,13,13,7,7,14,14,13,13,8,8,15,15,15,15,8,9,9,15,15,15,9,9,9,9,11,11,4,6,6,6,13,13,7,4,13,13,13,13,7,7,4,13,13,13,8,7,7,15,15,13,8,8,8,15,15,15,8,8,8,9,0,0,4,4,13,13,13,13,7,4,13,13,13,13,7,7,4,13,13,13,7,7,7,15,15,13,8,8,8,15,15,15,8,8,8,8,0,0,1,1,1,1,1,1,1,14,14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,0]
+ self.x256colors83 = [88,28,32,30,36,38,34,96,94,52,56,54,60,62,58,98,88,36,36,48,48,60,32,34,47,47,47,72,32,33,34,59,59,59,44,45,45,46,71,71,44,68,45,69,46,83,56,68,57,57,69,58,28,38,37,49,49,60,30,93,47,72,72,72,31,33,34,59,59,59,43,45,45,46,71,71,56,68,57,69,70,83,56,68,68,57,69,70,28,39,38,49,49,61,41,93,50,50,72,72,30,30,94,84,84,84,43,43,45,46,71,71,43,68,80,81,82,83,55,68,68,80,81,70,40,51,51,50,50,61,41,64,63,50,73,73,30,41,76,87,85,84,42,42,95,96,84,84,55,67,79,81,82,83,55,67,80,80,81,82,40,51,51,50,50,73,41,64,63,75,62,73,53,53,76,87,85,85,42,65,77,76,87,85,42,66,78,78,97,97,55,67,79,79,81,82,52,64,63,63,62,62,52,64,63,75,75,74,53,64,76,87,87,86,65,65,65,76,87,86,54,77,77,77,97,86,54,66,66,78,78,98,88,89,89,90,91,91,92,92,92,93,93,94,94,94,95,95,95,96,96,96,97,97,97,97]
+ self.x16colors = {
+ '30':'01',
+ '31':'04',
+ '32':'03',
+ '33':'08',
+ '34':'02',
+ '35':'06',
+ '36':'10',
+ '37':'15',
+ '30;1':'14',
+ '31;1':'07',
+ '32;1':'09',
+ '33;1':'08',
+ '34;1':'12',
+ '35;1':'13',
+ '36;1':'11',
+ '37;1':'00',
+ '40':'01',
+ '41':'04',
+ '42':'03',
+ '43':'08',
+ '44':'02',
+ '45':'06',
+ '46':'10',
+ '47':'15',
+ '40;1':'14',
+ '41;1':'07',
+ '42;1':'09',
+ '43;1':'08',
+ '44;1':'12',
+ '45;1':'13',
+ '46;1':'11',
+ '47;1':'00'}
+
+ def doPrivmsg(self, irc, msg):
+ channel = msg.args[0]
+ self.stopped.setdefault(channel, None)
+ if msg.args[1].lower().strip()[1:] == 'cq':
+ self.stopped[channel] = True
+
+ def doPaste(self, description, paste):
+ try:
+ apikey = self.registryValue('pasteAPI')
+ payload = {'description':description,'sections':[{'contents':paste}]}
+ headers = {'X-Auth-Token':apikey}
+ post_response = requests.post(url='https://api.paste.ee/v1/pastes', json=payload, headers=headers)
+ response = post_response.json()
+ return response['link'].replace('/p/', '/r/')
+ except:
+ return "Error. Did you set a valid Paste.ee API Key? https://paste.ee/account/api"
+
+ def renderImage(self, text, size=18, defaultBg = 1, defaultFg = 0):
+ try:
+ if utf8 and not isinstance(text, unicode):
+ text = text.decode('utf-8')
+ except:
+ pass
+ text = text.replace('\t', ' ')
+ self.strip_colors_regex = re.compile('(\x03([0-9]{1,2})(,[0-9]{1,2})?)|[\x0f\x02\x1f\x03\x16]').sub
+ path = os.path.dirname(os.path.abspath(__file__))
+ defaultFont = "{0}/DejaVu.ttf".format(path)
+ def strip_colors(string):
+ return self.strip_colors_regex('', string)
+ _colorRegex = re.compile('(([0-9]{1,2})(,([0-9]{1,2}))?)')
+ IGNORE_CHRS = ('\x16','\x1f','\x02', '\x03', '\x0f')
+ lineLens = [len(line) for line in strip_colors(text).splitlines()]
+ maxWidth, height = max(lineLens), len(lineLens)
+ font = ImageFont.truetype(defaultFont, size)
+ fontX = 10
+ fontY = 20
+ imageX, imageY = maxWidth * fontX, height * fontY
+ image = Image.new('RGB', (imageX, imageY), self.rgbColors[defaultBg])
+ draw = ImageDraw.Draw(image)
+ dtext, drect, match, x, y, fg, bg = draw.text, draw.rectangle, _colorRegex.match, 0, 0, defaultFg, defaultBg
+ start = time.time()
+ for text in text.split('\n'):
+ ll, i = len(text), 0
+ while i < ll:
+ chr = text[i]
+ if chr == "\x03":
+ m = match(text[i+1:i+6])
+ if m:
+ i+= len(m.group(1))
+ fg = int(m.group(2))
+ if m.group(4) is not None:
+ bg = int(m.group(4))
+ else:
+ bg, fg = defaultBg, defaultFg
+ elif chr == "\x0f":
+ fg, bg = defaultFg, defaultBg
+ elif chr not in IGNORE_CHRS:
+ if bg != defaultBg: # bg is not white, render it
+ drect((x, y, x+fontX, y+fontY), fill=self.rgbColors[bg])
+ if bg != fg: # text will show, render it. this saves a lot of time!
+ dtext((x, y), chr, font=font, fill=self.rgbColors[fg])
+ x += fontX
+ i += 1
+ y += fontY
+ fg, bg, x = defaultFg, defaultBg, 0
+ return image, imageX, imageY
+
+ def getColor(self, pixel, speed):
+ pixel = tuple(pixel)
+ if self.colors == 16:
+ colors = list(self.colors16.keys())
+ elif self.colors == 99:
+ colors = list(self.colors99.keys())
+ else:
+ colors = list(self.colors83.keys())
+ try:
+ return self.matches[pixel]
+ except KeyError:
+ closest_colors = sorted(colors, key=lambda color: self.distance(color, self.rgb2lab(pixel), speed))
+ closest_color = closest_colors[0]
+ if self.colors == 16:
+ self.matches[pixel] = self.colors16[closest_color]
+ elif self.colors == 99:
+ self.matches[pixel] = self.colors99[closest_color]
+ else:
+ self.matches[pixel] = self.colors83[closest_color]
+ return self.matches[pixel]
+
+ def rgb2lab (self, inputColor) :
+ num = 0
+ RGB = [0, 0, 0]
+ for value in inputColor :
+ value = float(value) / 255
+ if value > 0.04045 :
+ value = ( ( value + 0.055 ) / 1.055 ) ** 2.4
+ else :
+ value = value / 12.92
+ RGB[num] = value * 100
+ num = num + 1
+ XYZ = [0, 0, 0,]
+ X = RGB [0] * 0.4124 + RGB [1] * 0.3576 + RGB [2] * 0.1805
+ Y = RGB [0] * 0.2126 + RGB [1] * 0.7152 + RGB [2] * 0.0722
+ Z = RGB [0] * 0.0193 + RGB [1] * 0.1192 + RGB [2] * 0.9505
+ XYZ[ 0 ] = round( X, 4 )
+ XYZ[ 1 ] = round( Y, 4 )
+ XYZ[ 2 ] = round( Z, 4 )
+ XYZ[ 0 ] = float( XYZ[ 0 ] ) / 95.047 # ref_X = 95.047 Observer= 2°, Illuminant= D65
+ XYZ[ 1 ] = float( XYZ[ 1 ] ) / 100.0 # ref_Y = 100.000
+ XYZ[ 2 ] = float( XYZ[ 2 ] ) / 108.883 # ref_Z = 108.883
+ num = 0
+ for value in XYZ :
+ if value > 0.008856 :
+ value = value ** ( 0.3333333333333333 )
+ else :
+ value = ( 7.787 * value ) + ( 16 / 116 )
+ XYZ[num] = value
+ num = num + 1
+ Lab = [0, 0, 0]
+ L = ( 116 * XYZ[ 1 ] ) - 16
+ a = 500 * ( XYZ[ 0 ] - XYZ[ 1 ] )
+ b = 200 * ( XYZ[ 1 ] - XYZ[ 2 ] )
+ Lab [ 0 ] = round( L, 4 )
+ Lab [ 1 ] = round( a, 4 )
+ Lab [ 2 ] = round( b, 4 )
+ return Lab
+
+ def ciede2000(self, color1, color2):
+ """
+ Calculates color difference according to the `CIEDE 2000`_ formula. This is
+ the most accurate algorithm currently implemented but also the most complex
+ and slowest. Like CIE1994 it is largely based in CIE L*C*h* space, but with
+ several modifications to account for perceptual uniformity flaws.
+ .. _CIEDE 2000: https://en.wikipedia.org/wiki/Color_difference#CIEDE2000
+ """
+ # See WP article and Sharma 2005 for important implementation notes:
+ # http://www.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf
+ #
+ # Yes, there's lots of locals; but this is easiest to understand as it's a
+ # near straight translation of the math
+ # pylint: disable=too-many-locals
+ C_ = (
+ math.sqrt(color1[1] ** 2 + color1[2] ** 2) +
+ math.sqrt(color2[1] ** 2 + color2[2] ** 2)
+ ) / 2
+
+ G = (1 - math.sqrt(C_ ** 7 / (C_ ** 7 + 25 ** 7))) / 2
+ a1_prime = (1 + G) * color1[1]
+ a2_prime = (1 + G) * color2[1]
+ C1_prime = math.sqrt(a1_prime ** 2 + color1[2] ** 2)
+ C2_prime = math.sqrt(a2_prime ** 2 + color2[2] ** 2)
+ L_ = (color1[0] + color2[0]) / 2
+ C_ = (C1_prime + C2_prime) / 2
+ h1 = (
+ 0.0 if color1[2] == a1_prime == 0 else
+ math.degrees(math.atan2(color1[2], a1_prime)) % 360
+ )
+ h2 = (
+ 0.0 if color2[2] == a2_prime == 0 else
+ math.degrees(math.atan2(color2[2], a2_prime)) % 360
+ )
+ if C1_prime * C2_prime == 0.0:
+ dh = 0.0
+ h_ = h1 + h2
+ elif abs(h1 - h2) <= 180:
+ dh = h2 - h1
+ h_ = (h1 + h2) / 2
+ else:
+ if h2 > h1:
+ dh = h2 - h1 - 360
+ else:
+ dh = h2 - h1 + 360
+ if h1 + h2 >= 360:
+ h_ = (h1 + h2 - 360) / 2
+ else:
+ h_ = (h1 + h2 + 360) / 2
+
+ dL = color2[0] - color1[0]
+ dC = C2_prime - C1_prime
+ dH = 2 * math.sqrt(C1_prime * C2_prime) * math.sin(math.radians(dh / 2))
+ T = (
+ 1 -
+ 0.17 * math.cos(math.radians(h_ - 30)) +
+ 0.24 * math.cos(math.radians(2 * h_)) +
+ 0.32 * math.cos(math.radians(3 * h_ + 6)) -
+ 0.20 * math.cos(math.radians(4 * h_ - 63))
+ )
+ SL = 1 + (0.015 * (L_ - 50) ** 2) / math.sqrt(20 + (L_ - 50) ** 2)
+ SC = 1 + 0.045 * C_
+ SH = 1 + 0.015 * C_ * T
+ RT = (
+ -2 * math.sqrt(C_ ** 7 / (C_ ** 7 + 25 ** 7)) *
+ math.sin(math.radians(60 * math.exp(-(((h_ - 275) / 25) ** 2))))
+ )
+ delta_e = math.sqrt(
+ (dL / SL) ** 2 +
+ (dC / SC) ** 2 +
+ (dH / SH) ** 2 +
+ RT * (dC / SC) * (dH / SH)
+ )
+ return delta_e
+
+ def distance(self, c1, c2, speed):
+ if speed == 'fast':
+ delta_e = math.sqrt((c1[0] - c2[0]) **2 + (c1[1] - c2[1]) ** 2 + (c1[2] - c2[2]) **2)
+ elif speed == 'slow':
+ delta_e = self.ciede2000(c1, c2)
+ return delta_e
+
+ def process_ansi(self, ansi):
+ if self.colors == 16:
+ colors = self.x256colors16
+ elif self.colors == 99:
+ colors = self.x256colors99
+ else:
+ colors = self.x256colors83
+ x16color1 = None
+ x16color2 = None
+ x256color1 = None
+ x256color2 = None
+ effect = None
+ ansi = ansi.lower().strip('\x1b[').strip('m').split(';')
+ if len(ansi) > 1:
+ i = 0
+ while i < len(ansi):
+ if i >= len(ansi):
+ break
+ elif ansi[i]== '0':
+ effect = 0
+ i += 1
+ continue
+ elif ansi[i] == '1':
+ effect = 1
+ i += 1
+ continue
+ elif ansi[i] == '4':
+ effect = 4
+ i += 1
+ continue
+ elif ansi[i] == '2':
+ effect = 2
+ i += 1
+ continue
+ elif int(ansi[i]) > 29 and int(ansi[i]) < 38:
+ if effect == 1 or ansi[-1] == '1':
+ x16color2 = self.x16colors['{0};1'.format(ansi[i])]
+ effect = None
+ i += 1
+ continue
+ else:
+ x16color2 = self.x16colors[ansi[i]]
+ i += 1
+ continue
+ elif int(ansi[i]) > 39 and int(ansi[i]) < 48:
+ if effect == 1 or ansi[-1] == '1':
+ x16color2 = self.x16colors['{0};1'.format(ansi[i])]
+ effect = None
+ i += 1
+ continue
+ else:
+ x16color2 = self.x16colors[ansi[i]]
+ i += 1
+ continue
+ elif ansi[i] == '38':
+ x256color1 = colors[int(ansi[i+2])]
+ i += 3
+ continue
+ elif ansi[i] == '48':
+ x256color2 = colors[int(ansi[i+2])]
+ i += 3
+ continue
+ else:
+ i += 1
+ continue
+ if x16color1 and x16color2:
+ color = '\x03{0},{1}'.format(x16color1, x16color2)
+ elif x256color1 and x256color2:
+ color = '\x03{0},{1}'.format('%02d' % x256color1, '%02d' % x256color2)
+ elif x16color1:
+ color = '\x03{0}'.format(x16color1)
+ elif x16color2:
+ color = '\x0399,{0}'.format(x16color2)
+ elif x256color1:
+ color = '\x03{0}'.format('%02d' % x256color1)
+ elif x256color2:
+ color = '\x0399,{0}'.format('%02d' % x256color2)
+ else:
+ color = ''
+ if effect == 1:
+ color += '\x02'
+ if effect == 4:
+ color += '\x1F'
+ elif len(ansi[0]) > 0:
+ if ansi[0] == '0':
+ color = '\x0F'
+ elif ansi[0] == '1' or ansi[0] == '2':
+ color = '\x02'
+ elif ansi[0] == '4':
+ color = '\x1F'
+ elif int(ansi[0]) > 29 and int(ansi[0]) < 38:
+ color = '\x03{0}'.format(self.x16colors[ansi[0]])
+ elif int(ansi[0]) > 39 and int(ansi[0]) < 48:
+ color = '\x0399,{0}'.format(self.x16colors[ansi[0]])
+ elif ansi[0][-1] == 'c':
+ color = ' ' * int(ansi[0][:-1])
+ else:
+ color = ''
+ else:
+ color = ''
+ if color != self.old_color:
+ self.old_color = color
+ return color
+ else:
+ return ''
+
+ def ansi2irc(self, output):
+ output = output.replace('\x1b[0m\x1b', '\x1b')
+ output = re.sub(r'\x1B\[[0-?]*[ -/]*[@-~]', lambda m: self.process_ansi(m.group(0)), output)
+ output = re.sub('\x0399,(\d\d)\x03(\d\d)', '\x03\g<2>,\g<1>', output)
+ return output
+
+ def png(self, irc, msg, args, optlist, url):
+ """[--bg] [--fg]
+ Generate PNG from text file
+ """
+ optlist = dict(optlist)
+ if 'bg' in optlist:
+ bg = optlist.get('bg')
+ else:
+ bg = 1
+ if 'fg' in optlist:
+ fg = optlist.get('fg')
+ else:
+ fg = 0
+ if url.startswith("https://paste.ee/p/"):
+ url = re.sub("https://paste.ee/p/", "https://paste.ee/r/", url)
+ ua = UserAgent()
+ header = {'User-Agent':str(ua.random)}
+ r = requests.head(url, headers=header)
+ if "text/plain" in r.headers["content-type"]:
+ file = requests.get(url, headers=header)
+ else:
+ irc.reply("Invalid file type.", private=False, notice=False)
+ return
+ file = file.content.decode()
+ im, x, y = self.renderImage(file, 18, bg, fg)
+ path = os.path.dirname(os.path.abspath(__file__))
+ filepath = "{0}/tmp/tldr.png".format(path)
+ im.save(filepath, "PNG")
+ CLIENT_ID = self.registryValue('imgurAPI')
+ imgur = pyimgur.Imgur(CLIENT_ID)
+ uploaded_image = imgur.upload_image(filepath, title=url)
+ irc.reply(uploaded_image.link, noLengthCheck=True, private=False, notice=False)
+ png = wrap(png, [getopts({'bg':'int', 'fg':'int'}), ('text')])
+
+ def ascii(self, irc, msg, args, channel, optlist, text):
+ """[] [--font ] [--color ] []
+ Text to ASCII art
+ """
+ if not channel:
+ channel = msg.args[0]
+ channel = msg.args[0]
+ optlist = dict(optlist)
+ font = None
+ words = []
+ if text:
+ text = text.strip()
+ if '|' in text:
+ words = text.split('|')
+ if 'color' in optlist:
+ color = optlist.get('color')
+ if "," in color:
+ color = color.split(",")
+ color1 = color[0].strip()
+ color2 = color[1].strip()
+ else:
+ color1 = color
+ color2 = None
+ else:
+ color1 = None
+ color2 = None
+ if 'font' in optlist:
+ font = optlist.get('font')
+ if words:
+ for word in words:
+ if word.strip():
+ data = requests.get("https://artii.herokuapp.com/make?text={0}&font={1}".format(word.strip(), font))
+ for line in data.text.splitlines():
+ if line.strip():
+ irc.reply(ircutils.mircColor(line, color1, color2), prefixNick=False, private=False, notice=False)
+ else:
+ data = requests.get("https://artii.herokuapp.com/make?text={0}&font={1}".format(text, font))
+ for line in data.text.splitlines():
+ if line.strip():
+ irc.reply(ircutils.mircColor(line, color1, color2), prefixNick=False, private=False, notice=False, to=channel)
+ elif 'font' not in optlist:
+ if words:
+ for word in words:
+ if word.strip():
+ data = requests.get("https://artii.herokuapp.com/make?text={0}&font=univers".format(word.strip()))
+ for line in data.text.splitlines():
+ if line.strip():
+ irc.reply(ircutils.mircColor(line, color1, color2), prefixNick=False, private=False, notice=False, to=channel)
+ else:
+ data = requests.get("https://artii.herokuapp.com/make?text={0}&font=univers".format(text))
+ for line in data.text.splitlines():
+ if line.strip():
+ irc.reply(ircutils.mircColor(line, color1, color2), prefixNick=False, private=False, notice=False, to=channel)
+
+ ascii = wrap(ascii, [optional('channel'), getopts({'font':'text', 'color':'text'}), ('text')])
+
+ def fontlist(self, irc, msg, args):
+ """
+ get list of fonts for text-to-ascii-art
+ """
+ fontlist = requests.get("https://artii.herokuapp.com/fonts_list")
+ response = sorted(fontlist.text.split('\n'))
+ irc.reply(str(response).replace('\'', '').replace('[', '').replace(']', ''))
+ fontlist = wrap(fontlist)
+
+ def img(self, irc, msg, args, channel, optlist, url):
+ """[<#channel>] [--delay #.#] [--w <###>] [--s <#.#] [--16] [--99] [--83] [--ascii] [--block] [--1/2] [--1/4] [--chars ] [--ramp ] [--bg <0-98>] [--fg <0-98>] [--no-color] [--invert] [--dither] [--no-dither]
+ Image to ASCII Art.
+ --w columns.
+ --s saturation (1.0).
+ --16 colors 0-15.
+ --99 colors 0-98.
+ --83 colors 16-98.
+ --ascii color ascii.
+ --block space block.
+ --1/4 for 1/4 block.
+ --1/2 for 1/2 block
+ --chars color text.
+ --ramp set ramp (".:-=+*#%@").
+ --bg <0-98> set bg.
+ --fg <0-99> set fg.
+ --no-color greyscale ascii.
+ --invert inverts ramp.
+ --dither to reduce source colors.
+ --no-dither for no color reduction.
+ """
+ if not channel:
+ channel = msg.args[0]
+ optlist = dict(optlist)
+ gscale = "\xa0"
+ if '16' in optlist:
+ self.colors = 16
+ elif '83' in optlist:
+ self.colors = 83
+ elif '99' in optlist:
+ self.colors = 99
+ else:
+ self.colors = self.registryValue('colors', msg.args[0])
+ if 'fast' in optlist:
+ speed = 'fast'
+ elif 'slow' in optlist:
+ speed = 'slow'
+ else:
+ speed = self.registryValue('speed', msg.args[0]).lower()
+ if 'delay' in optlist:
+ delay = optlist.get('delay')
+ else:
+ delay = self.registryValue('delay', msg.args[0])
+ if 'dither' in optlist:
+ dither = True
+ elif 'no-dither' in optlist:
+ dither = False
+ else:
+ dither = self.registryValue('dither', msg.args[0])
+ if 'bg' in optlist:
+ bg = optlist.get('bg')
+ else:
+ bg = 1
+ if 'fg' in optlist:
+ fg = optlist.get('fg')
+ else:
+ fg = 99
+ if 'chars' in optlist:
+ type = 'ascii'
+ gscale = optlist.get('chars')
+ elif 'ramp' in optlist:
+ type = 'ascii'
+ gscale = optlist.get('ramp')
+ elif 'ascii' in optlist and bg == 0 or bg == 98:
+ type = 'ascii'
+ gscale = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:\"^`'."
+ elif 'ascii' in optlist:
+ type = 'ascii'
+ gscale = ".'`^\":;Il!i><~+_-?][}{1)(|\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$"
+ elif '1/4' in optlist:
+ type = '1/4'
+ elif '1/2' in optlist:
+ type = '1/2'
+ elif 'block' in optlist:
+ type = 'ascii'
+ gscale = '\xa0'
+ else:
+ type = self.registryValue('imgDefault', msg.args[0]).lower()
+ if 'no-color' in optlist and 'ramp' not in optlist and bg == 0 or bg == 98:
+ type = 'no-color'
+ gscale = "@%#*+=-:. "
+ elif 'no-color' in optlist and 'ramp' not in optlist:
+ type = 'no-color'
+ gscale = " .:-=+*#%@"
+ elif 'no-color' in optlist and 'chars' not in optlist:
+ type = 'no-color'
+ if not gscale.strip():
+ gscale = '\xa0'
+ if 'invert' in optlist and 'chars' not in optlist and gscale != '\xa0':
+ gscale = gscale[::-1]
+ if 'w' in optlist:
+ cols = optlist.get('w')
+ elif type == 'ascii' or type == 'no-color' or type == 'block':
+ cols = self.registryValue('asciiWidth', msg.args[0])
+ else:
+ cols = self.registryValue('blockWidth', msg.args[0])
+ if type == '1/4':
+ cols = cols * 2
+ if 's' in optlist:
+ s = float(optlist.get('s'))
+ path = os.path.dirname(os.path.abspath(__file__))
+ filepath = "{0}/tmp".format(path)
+ filename = "{0}/{1}".format(filepath, url.split('/')[-1])
+ ua = UserAgent()
+ header = {'User-Agent':str(ua.random)}
+ image_formats = ("image/png", "image/jpeg", "image/jpg", "image/gif")
+ r = requests.head(url, headers=header)
+ if r.headers["content-type"] in image_formats:
+ response = requests.get(url, headers=header)
+ else:
+ irc.reply("Invalid file type.", private=False, notice=False)
+ return
+ if response.status_code == 200:
+ with open("{0}".format(filename), 'wb') as f:
+ f.write(response.content)
+ # open image and convert to grayscale
+ image = Image.open(filename)
+ if image.mode == 'RGBA':
+ image = Image.alpha_composite(Image.new("RGBA", image.size, self.rgbColors[bg] + (255,)), image)
+ if image.mode != 'RGB':
+ image = image.convert('RGB')
+ try:
+ os.remove(filename)
+ except:
+ pass
+ # store dimensions
+ W, H = image.size[0], image.size[1]
+ # compute width of tile
+ w = W/cols
+ # compute tile height based on aspect ratio and scale
+ if type == '1/2':
+ scale = 1.0
+ else:
+ scale = 0.5
+ h = w/scale
+ # compute number of rows
+ rows = int(H/h)
+ image = ImageOps.autocontrast(image)
+ if type != 'no-color':
+ image2 = image.resize((cols, rows), Image.LANCZOS)
+ if 's' in optlist:
+ image2 = ImageEnhance.Color(image2).enhance(s)
+ if dither:
+ image2 = image2.convert('P', palette=Image.ADAPTIVE)
+ image2 = image2.convert('RGB')
+ colormap = np.array(image2)
+ self.matches = {}
+ # ascii image is a list of character strings
+ aimg = []
+ if type == '1/2':
+ k = 0
+ for j in range(0, rows - 1, 2):
+ # append an empty string
+ aimg.append("")
+ old_color = "99,99"
+ for i in range(cols):
+ color1 = '%02d' % self.getColor(colormap[j][i].tolist(), speed)
+ color2 = '%02d' % self.getColor(colormap[j+1][i].tolist(), speed)
+ color = "{0},{1}".format(color1, color2)
+ if color != old_color:
+ if color1 == color2:
+ gsval = " "
+ color = "0,{0}".format(color1)
+ else:
+ gsval = "▀"
+ alt_gsval = "▄"
+ if gsval != " " and color == "{0},{1}".format(old_color.split(',')[1], old_color.split(',')[0]) and 'tops' not in optlist:
+ aimg[k] += alt_gsval
+ elif gsval == " " and "{0}".format(color1) == "{0}".format(old_color.split(',')[1]):
+ aimg[k] += " "
+ else:
+ aimg[k] += "\x03{0}{1}".format(color, gsval)
+ old_color = color
+ else:
+ if color1 == color2:
+ aimg[k] += " "
+ else:
+ aimg[k] += "▀"
+ for i in range(0,98):
+ i = '%02d' % i
+ aimg[k] = re.sub("\x030,{0}(\s+)\x03(\d\d),{0}".format(i), "\x03\g<2>,{0}\g<1>".format(i), aimg[k])
+ for i in range(0,98):
+ i = '%02d' % i
+ aimg[k] = aimg[k].replace("{0}".format(i), "{0}".format(int(i)))
+ k += 1
+ elif type == '1/4':
+ k = 0
+ for j in range(0, rows - 1, 2):
+ # append an empty string
+ aimg.append("")
+ old_color = "99,99"
+ for i in range(0, cols - 1, 2):
+ color1 = '%02d' % self.getColor(colormap[j][i].tolist(), speed)
+ color2 = '%02d' % self.getColor(colormap[j+1][i].tolist(), speed)
+ color3 = '%02d' % self.getColor(colormap[j][i+1].tolist(), speed)
+ color4 = '%02d' % self.getColor(colormap[j+1][i+1].tolist(), speed)
+ if color1 == color2 and color1 == color3 and color1 == color4:
+ gsval = " "
+ color = "0,{0}".format(color1)
+ elif color1 == color4 and color2 == color3:
+ gsval = "▚"
+ color = "{0},{1}".format(color1, color2)
+ elif color1 == color2 and color1 == color3 and color1 != color4:
+ gsval = "▛"
+ color = "{0},{1}".format(color1, color4)
+ elif color3 == color4 and color2 == color4 and color3 != color1:
+ gsval = "▟"
+ color = "{0},{1}".format(color2, color1)
+ elif color1 == color2 and color1 == color4 and color1 != color3:
+ gsval = "▙"
+ color = "{0},{1}".format(color1, color3)
+ elif color1 == color3 and color1 == color4 and color1 != color2:
+ gsval = "▜"
+ color = "{0},{1}".format(color1, color2)
+ elif color1 == color3 and color2 == color4 and color1 != color2 and color3 != color4:
+ gsval = "▀"
+ color = "{0},{1}".format(color1, color4)
+ elif color1 == color2 and color3 == color4 and color1 != color3 and color2 != color4:
+ gsval = "▌"
+ color = "{0},{1}".format(color1, color4)
+ else:
+ row1 = '%02d' % self.getColor(np.average([tuple(colormap[j][i].tolist()), tuple(colormap[j][i+1].tolist())], axis=0).tolist(), speed)
+ row2 = '%02d' % self.getColor(np.average([tuple(colormap[j+1][i+1].tolist()), tuple(colormap[j][i+1].tolist())], axis=0).tolist(), speed)
+ if row2 == color1 and row2 != color3:
+ gsval = "▙"
+ color = "{0},{1}".format(row2, color3)
+ elif row1 == color2 and row1 != color4:
+ gsval = "▛"
+ color = "{0},{1}".format(row1, color4)
+ elif row2 == color3 and row2 != color1:
+ gsval = "▟"
+ color = "{0},{1}".format(row2, color1)
+ elif row1 == color4 and row1 != color2:
+ gsval = "▜"
+ color = "{0},{1}".format(row1, color2)
+ else:
+ col1 = '%02d' % self.getColor(np.average([tuple(colormap[j][i].tolist()), tuple(colormap[j+1][i].tolist())], axis=0).tolist(), speed)
+ col2 = '%02d' % self.getColor(np.average([tuple(colormap[j][i+1].tolist()), tuple(colormap[j+1][i+1].tolist())], axis=0).tolist(), speed)
+ if col1 == color4 and col1 != color3:
+ gsval = "▙"
+ color = "{0},{1}".format(col1, color3)
+ elif col1 == color3 and col1 != color4:
+ gsval = "▛"
+ color = "{0},{1}".format(col1, color4)
+ elif col2 == color2 and col2 != color1:
+ gsval = "▟"
+ color = "{0},{1}".format(col2, color1)
+ elif col2 == color1 and col2 != color2:
+ gsval = "▜"
+ color = "{0},{1}".format(col2, color2)
+ elif row1 != row2:
+ gsval = "▀"
+ color = "{0},{1}".format(row1, row2)
+ elif col1 != col2:
+ gsval = "▌"
+ color = "{0},{1}".format(col1, col2)
+ elif row1 == row2:
+ gsval = " "
+ color = "0,{0}".format(row1)
+ elif col1 == col2:
+ gsval = " "
+ color = "0,{0}".format(col1)
+ if color != old_color:
+ if gsval == " " and "{0}".format(color.split(',')[1]) == "{0}".format(old_color.split(',')[1]):
+ aimg[k] += "{0}".format(gsval)
+ elif gsval == "▚" and color == "{0},{1}".format(old_color.split(',')[1], old_color.split(',')[0]):
+ gsval = "▞"
+ aimg[k] += "{0}".format(gsval)
+ elif gsval == "▀" and color == "{0},{1}".format(old_color.split(',')[1], old_color.split(',')[0]):
+ gsval = "▄"
+ aimg[k] += "{0}".format(gsval)
+ elif gsval == "▌" and color == "{0},{1}".format(old_color.split(',')[1], old_color.split(',')[0]):
+ gsval = "▐"
+ aimg[k] += "{0}".format(gsval)
+ elif gsval == "▛" and color == "{0},{1}".format(old_color.split(',')[1], old_color.split(',')[0]):
+ gsval = "▟"
+ aimg[k] += "{0}".format(gsval)
+ elif gsval == "▟" and color == "{0},{1}".format(old_color.split(',')[1], old_color.split(',')[0]):
+ gsval = "▛"
+ aimg[k] += "{0}".format(gsval)
+ elif gsval == "▜" and color == "{0},{1}".format(old_color.split(',')[1], old_color.split(',')[0]):
+ gsval = "▙"
+ aimg[k] += "{0}".format(gsval)
+ elif gsval == "▙" and color == "{0},{1}".format(old_color.split(',')[1], old_color.split(',')[0]):
+ gsval = "▜"
+ aimg[k] += "{0}".format(gsval)
+ else:
+ old_color = color
+ # append char to string
+ aimg[k] += "\x03{0}{1}".format(color, gsval)
+ else:
+ aimg[k] += "{0}".format(gsval)
+ for i in range(0,98):
+ i = '%02d' % i
+ aimg[k] = re.sub("\x030,{0}(\s+)\x03(\d\d),{0}".format(i), "\x03\g<2>,{0}\g<1>".format(i), aimg[k])
+ for i in range(0,98):
+ i = '%02d' % i
+ aimg[k] = aimg[k].replace("{0}".format(i), "{0}".format(int(i)))
+ k += 1
+ else:
+ if 'chars' not in optlist and gscale != '\xa0':
+ image = image.resize((cols, rows), Image.LANCZOS)
+ image = image.convert('L')
+ lumamap = np.array(image)
+ # generate list of dimensions
+ char = 0
+ for j in range(rows):
+ # append an empty string
+ aimg.append("")
+ old_color = None
+ for i in range(cols):
+ if 'chars' not in optlist and gscale != '\xa0':
+ # get average luminance
+ avg = int(np.average(lumamap[j][i]))
+ # look up ascii char
+ gsval = gscale[int((avg * (len(gscale) - 1))/255)]
+ elif 'chars' in optlist and gscale != '\xa0':
+ if char < len(gscale):
+ gsval = gscale[char]
+ char += 1
+ else:
+ char = 0
+ gsval = gscale[char]
+ char += 1
+ else:
+ gsval = '\xa0'
+ # get color value
+ if type != 'no-color' and i == 0:
+ color = self.getColor(colormap[j][i].tolist(), speed)
+ old_color = color
+ if 'bg' not in optlist:
+ if gsval != '\xa0':
+ if gsval.isdigit():
+ color = "{:02d}".format(int(color))
+ aimg[j] += "\x03{0}{1}".format(color, gsval)
+ else:
+ aimg[j] += "\x03{0}{1}".format(int(color), gsval)
+ else:
+ aimg[j] += "\x030,{0} ".format(int(color))
+ else:
+ if gsval != '\xa0':
+ if gsval.isdigit():
+ newbg = "{:02d}".format(int(bg))
+ aimg[j] += "\x03{0},{1}{2}".format(int(color), newbg, gsval)
+ else:
+ aimg[j] += "\x03{0},{1}{2}".format(int(color), int(bg), gsval)
+ else:
+ aimg[j] += "\x030,{0} ".format(int(color))
+ elif type != 'no-color' and gsval != ' ':
+ color = self.getColor(colormap[j][i].tolist(), speed)
+ if color != old_color:
+ old_color = color
+ # append ascii char to string
+ if 'bg' not in optlist:
+ if gsval != '\xa0':
+ if gsval.isdigit():
+ color = "{:02d}".format(int(color))
+ aimg[j] += "\x03{0}{1}".format(color, gsval)
+ else:
+ aimg[j] += "\x03{0}{1}".format(int(color), gsval)
+ else:
+ aimg[j] += "\x030,{0} ".format(int(color))
+ else:
+ if gsval != '\xa0':
+ if gsval.isdigit():
+ newbg = "{:02d}".format(int(bg))
+ aimg[j] += "\x03{0},{1}{2}".format(int(color), newbg, gsval)
+ else:
+ aimg[j] += "\x03{0},{1}{2}".format(int(color), int(bg), gsval)
+ else:
+ aimg[j] += "\x030,{0} ".format(int(color))
+ else:
+ aimg[j] += "{0}".format(gsval)
+ else:
+ aimg[j] += "{0}".format(gsval)
+ # return txt image
+ output = aimg
+ paste = ""
+ self.stopped[msg.args[0]] = False
+ for line in output:
+ if type == 'no-color' and 'fg' in optlist and 'bg' in optlist:
+ newbg = "{:02d}".format(int(bg))
+ line = "\x03{0},{1}{2}".format(int(fg), newbg, line)
+ elif type == 'no-color' and 'fg' in optlist:
+ newfg = "{:02d}".format(int(fg))
+ line = "\x03{0}{1}".format(newfg, line)
+ elif type == 'no-color' and 'bg' in optlist:
+ newbg = "{:02d}".format(int(bg))
+ line = "\x0399,{0}{1}".format(newbg, line)
+ if self.registryValue('pasteEnable', msg.args[0]):
+ paste += line + "\n"
+ if not self.stopped[msg.args[0]]:
+ time.sleep(delay)
+ irc.reply(line, prefixNick=False, noLengthCheck=True, private=False, notice=False, to=channel)
+ if self.registryValue('pasteEnable', msg.args[0]):
+ irc.reply(self.doPaste(url, paste), private=False, notice=False, to=channel)
+ img = wrap(img,[optional('channel'), getopts({'w':'int', 'invert':'', 'fast':'', 'slow':'', '16':'', '99':'', '83':'', 'delay':'float', 'dither':'', 'no-dither':'', 'chars':'text', 'bg':'int', 'fg':'int', 'ramp':'text', 'no-color':'', 'block':'', 'ascii':'', '1/4':'', '1/2':'', 's':'float', 'tops':''}), ('text')])
+
+ def scroll(self, irc, msg, args, channel, optlist, url):
+ """[] [--delay]
+ Play ASCII/ANSI art text files from web links.
+ """
+ if not channel:
+ channel = msg.args[0]
+ optlist = dict(optlist)
+ self.stopped[msg.args[0]] = False
+ if 'delay' in optlist:
+ delay = optlist.get('delay')
+ else:
+ delay = self.registryValue('delay', msg.args[0])
+ if url.startswith("https://paste.ee/p/"):
+ url = re.sub("https://paste.ee/p/", "https://paste.ee/r/", url)
+ ua = UserAgent()
+ header = {'User-Agent':str(ua.random)}
+ r = requests.head(url, headers=header)
+ if "text/plain" in r.headers["content-type"]:
+ file = requests.get(url, headers=header)
+ else:
+ irc.reply("Invalid file type.", private=False, notice=False)
+ return
+ file = file.content.decode()
+ for line in file.splitlines():
+ if line.strip() and not self.stopped[msg.args[0]]:
+ time.sleep(delay)
+ irc.reply(line, prefixNick = False, noLengthCheck=True, private=False, notice=False, to=channel)
+ scroll = wrap(scroll, [optional('channel'), getopts({'delay':'float'}), ('text')])
+
+ def a2m(self, irc, msg, args, channel, optlist, url):
+ """[] [--delay] [--l] [--r] [--n] [--p] [--t] [--w]
+ Convert ANSI files to IRC formatted text. https://github.com/tat3r/a2m
+ """
+ if not channel:
+ channel = msg.args[0]
+ optlist = dict(optlist)
+ opts = ''
+ if 'l' in optlist:
+ l = optlist.get('l')
+ opts += '-l {0} '.format(l)
+ if 'r' in optlist:
+ r = optlist.get('r')
+ opts += '-r {0} '.format(r)
+ if 'n' in optlist:
+ opts += '-n '.format(n)
+ if 'p' in optlist:
+ opts += '-p '.format(p)
+ if 't' in optlist:
+ t = optlist.get('t')
+ opts += '-t {0} '.format(t)
+ if 'w' in optlist:
+ w = optlist.get('w')
+ opts += '-w {0} '.format(w)
+ else:
+ opts += '-w 80 '
+ if 'delay' in optlist:
+ delay = optlist.get('delay')
+ else:
+ delay = self.registryValue('delay', msg.args[0])
+ ua = UserAgent()
+ header = {'User-Agent':str(ua.random)}
+ r = requests.head(url, headers=header)
+ try:
+ if "text/plain" in r.headers["content-type"] or "application/octet-stream" in r.headers["content-type"] and int(r.headers["content-length"]) < 1000000:
+ path = os.path.dirname(os.path.abspath(__file__))
+ filepath = "{0}/tmp".format(path)
+ filename = "{0}/{1}".format(filepath, url.split('/')[-1])
+ r = requests.get(url, headers=header)
+ open(filename, 'wb').write(r.content)
+ try:
+ output = pexpect.run('a2m {0} {1}'.format(opts.strip(), str(filename)))
+ try:
+ os.remove(filename)
+ except:
+ pass
+ except:
+ irc.reply("Error. Have you installed A2M? https://github.com/tat3r/a2m", private=False, notice=False)
+ return
+ else:
+ irc.reply("Invalid file type.")
+ return
+ except:
+ irc.reply("Invalid file type.")
+ return
+ paste = ""
+ self.stopped[msg.args[0]] = False
+ for line in output.splitlines():
+ line = line.decode()
+ if self.registryValue('pasteEnable', msg.args[0]):
+ paste += line + "\n"
+ if line.strip() and not self.stopped[msg.args[0]]:
+ time.sleep(delay)
+ irc.reply(line, prefixNick = False, noLengthCheck=True, private=False, notice=False, to=channel)
+ if self.registryValue('pasteEnable', msg.args[0]):
+ irc.reply(self.doPaste(url, paste), private=False, notice=False, to=channel)
+ a2m = wrap(a2m, [optional('channel'), getopts({'l':'int', 'r':'int', 't':'int', 'w':'int', 'delay':'float'}), ('text')])
+
+ def p2u(self, irc, msg, args, channel, optlist, url):
+ """[] [--b] [--f] [--p] [--s] [--t] [--w] [--delay]
+ Picture to Unicode. https://git.trollforge.org/p2u/about/
+ """
+ if not channel:
+ channel = msg.args[0]
+ optlist = dict(optlist)
+ opts = ''
+ if 'b' in optlist:
+ b = optlist.get('b')
+ opts += '-b {0} '.format(b)
+ if 'f' in optlist:
+ f = optlist.get('f')
+ opts += '-f {0} '.format(f)
+ else:
+ opts += '-f m '
+ if 'p' in optlist:
+ p = optlist.get('p')
+ opts += '-p {0} '.format(p)
+ else:
+ opts += '-p x '
+ if 's' in optlist:
+ s = optlist.get('s')
+ opts += '-s {0} '.format(s)
+ if 't' in optlist:
+ t = optlist.get('t')
+ opts += '-t {0} '.format(t)
+ if 'w' in optlist:
+ w = optlist.get('w')
+ opts += '-w {0} '.format(w)
+ else:
+ w = self.registryValue('blockWidth', msg.args[0])
+ opts += '-w {0} '.format(w)
+ if 'delay' in optlist:
+ delay = optlist.get('delay')
+ else:
+ delay = self.registryValue('delay', msg.args[0])
+ path = os.path.dirname(os.path.abspath(__file__))
+ filepath = "{0}/tmp".format(path)
+ filename = "{0}/{1}".format(filepath, url.split('/')[-1])
+ ua = UserAgent()
+ header = {'User-Agent':str(ua.random)}
+ image_formats = ("image/png", "image/jpeg", "image/jpg", "image/gif")
+ r = requests.head(url, headers=header)
+ if r.headers["content-type"] in image_formats:
+ response = requests.get(url, headers=header)
+ else:
+ irc.reply("Invalid file type.", private=False, notice=False)
+ return
+ if response.status_code == 200:
+ with open("{0}".format(filename), 'wb') as f:
+ f.write(response.content)
+ try:
+ output = pexpect.run('p2u -f m {0} {1}'.format(opts.strip(), str(filename)))
+ try:
+ os.remove(filename)
+ except:
+ pass
+ except:
+ irc.reply("Error. Have you installed p2u? https://git.trollforge.org/p2u", private=False, notice=False)
+ return
+ paste = ""
+ self.stopped[msg.args[0]] = False
+ for line in output.splitlines():
+ line = line.decode()
+ line = re.sub('^\x03 ', ' ', line)
+ if self.registryValue('pasteEnable', msg.args[0]):
+ paste += line + "\n"
+ if line.strip() and not self.stopped[msg.args[0]]:
+ time.sleep(delay)
+ irc.reply(line, prefixNick = False, noLengthCheck=True, private=False, notice=False, to=channel)
+ if self.registryValue('pasteEnable', msg.args[0]):
+ irc.reply(self.doPaste(url, paste), private=False, notice=False, to=channel)
+ else:
+ irc.reply("Unexpected file type or link format", private=False, notice=False)
+ p2u = wrap(p2u, [optional('channel'), getopts({'b':'int', 'f':'text', 'p':'text', 's':'int', 't':'int', 'w':'int', 'delay':'float'}), ('text')])
+
+ def tdf(self, irc, msg, args, channel, optlist, text):
+ """[] [--f] [--j] [--w] [--e] [--r] [--i][--delay]
+ Text to TheDraw ANSI Fonts. http://www.roysac.com/thedrawfonts-tdf.html
+ --f [font] Specify font file used.
+ --j l|r|c Justify left, right, or center. Default is left.
+ --w n Set screen width. Default is 80.
+ --c a|m Color format ANSI or mirc. Default is ANSI.
+ --i Print font details.
+ --r Use random font.
+ """
+ if not channel:
+ channel = msg.args[0]
+ optlist = dict(optlist)
+ opts = ''
+ if 'f' in optlist:
+ f = optlist.get('f')
+ opts += '-f {0} '.format(f.lower())
+ else:
+ opts += '-r '
+ if 'j' in optlist:
+ j = optlist.get('j')
+ opts += '-j {0} '.format(j)
+ if 'w' in optlist:
+ w = optlist.get('w')
+ opts += '-w {0} '.format(w)
+ else:
+ opts += '-w 80 '
+ if 'e' in optlist:
+ e = optlist.get('e')
+ opts += '-e {0} '.format(e)
+ if 'r' in optlist:
+ opts += '-r '
+ if 'delay' in optlist:
+ delay = optlist.get('delay')
+ else:
+ delay = self.registryValue('delay', msg.args[0])
+ if 'i' in optlist:
+ opts += '-i '
+ try:
+ output = pexpect.run('tdfiglet -c m {0} {1}'.format(opts.strip(), r'{}'.format(text)))
+ except:
+ irc.reply("Error. Have you installed tdfiglet? https://github.com/tat3r/tdfiglet", private=False, notice=False)
+ return
+ paste = ""
+ self.stopped[msg.args[0]] = False
+ output = output.decode().replace('\r\r\n', '\r\n')
+ for line in output.splitlines():
+ line = re.sub('\x03\x03\s*', '\x0F ', line)
+ line = re.sub('\x0F\s*\x03$', '', line)
+ if self.registryValue('pasteEnable', msg.args[0]):
+ paste += line + "\n"
+ if not line.strip() and not self.stopped[msg.args[0]]:
+ time.sleep(delay)
+ irc.reply('\xa0', prefixNick = False, noLengthCheck=True, private=False, notice=False, to=channel)
+ elif not self.stopped[msg.args[0]]:
+ time.sleep(delay)
+ irc.reply(line, prefixNick = False, noLengthCheck=True, private=False, notice=False, to=channel)
+ if self.registryValue('pasteEnable', msg.args[0]):
+ irc.reply(self.doPaste(text, paste), private=False, notice=False, to=channel)
+ tdf = wrap(tdf, [optional('channel'), getopts({'f':'text', 'j':'text', 'w':'int', 'e':'text', 'r':'', 'i':'', 'delay':'float'}), ('text')])
+
+ def toilet(self, irc, msg, args, channel, optlist, text):
+ """[] [--f fontname] [--F filter1,filter2,etc.] [--w] [--delay]
+ Toilet. -f to select font. -F to select filters. Separate multiple filters with a comma.
+ """
+ if not channel:
+ channel = msg.args[0]
+ optlist = dict(optlist)
+ opts = ''
+ if 'f' in optlist:
+ f = optlist.get('f')
+ opts += '-f {0} '.format(f)
+ if 'F' in optlist:
+ filter = optlist.get('F')
+ if ',' in filter:
+ filter = filter.split(',')
+ for i in range(len(filter)):
+ opts += '-F {0} '.format(filter[i])
+ else:
+ opts += '-F {0} '.format(filter)
+ if 'w' in optlist:
+ w = optlist.get('w')
+ opts += '-w {0} '.format(w)
+ elif 'W' in optlist:
+ opts += '-W '
+ else:
+ opts += '-w 100 '
+ if 's' in optlist:
+ opts += '-s '
+ elif 'k' in optlist:
+ opts += '-k '
+ elif 'o' in optlist:
+ opts += '-o '
+ elif 'S' in optlist:
+ opts += '-S '
+ if 'delay' in optlist:
+ delay = optlist.get('delay')
+ else:
+ delay = self.registryValue('delay', msg.args[0])
+ try:
+ output = pexpect.run('toilet --irc {0} {1}'.format(opts.strip(), text))
+ except:
+ irc.reply("Error. Have you installed toilet?", private=False, notice=False)
+ return
+ paste = ""
+ self.stopped[msg.args[0]] = False
+ output = output.decode().replace('\r\r\n', '\r\n')
+ for line in output.splitlines():
+ if self.registryValue('pasteEnable', msg.args[0]):
+ paste += line + "\n"
+ if not line.strip() and not self.stopped[msg.args[0]]:
+ time.sleep(delay)
+ irc.reply('\xa0', prefixNick = False, noLengthCheck=True, private=False, notice=False, to=channel)
+ elif not self.stopped[msg.args[0]]:
+ time.sleep(delay)
+ irc.reply(line, prefixNick = False, noLengthCheck=True, private=False, notice=False, to=channel)
+ if self.registryValue('pasteEnable', msg.args[0]):
+ irc.reply(self.doPaste(text, paste), private=False, notice=False, to=channel)
+ toilet = wrap(toilet, [optional('channel'), getopts({'f':'text', 'F':'text', 's':'', 'S':'', 'k':'', 'w':'int', 'W':'', 'o':'', 'delay':'float'}), ('text')])
+
+ def fonts(self, irc, msg, args, optlist):
+ """[--toilet]
+ List figlets. Default list are tdf fonts. --toilet for toilet fonts
+ """
+ optlist = dict(optlist)
+ if 'toilet' in optlist:
+ try:
+ reply = ", ".join(sorted(os.listdir("/usr/share/figlet")))
+ irc.reply(reply, prefixNick=False)
+ except:
+ irc.reply("Sorry, unable to access font directory /usr/share/figlet")
+ else:
+ try:
+ reply = ", ".join(sorted(os.listdir("/usr/local/share/tdfiglet/fonts/")))
+ irc.reply("http://www.roysac.com/thedrawfonts-tdf.html", prefixNick=False)
+ irc.reply(reply, prefixNick=False)
+ except FileNotFoundError:
+ reply = ", ".join(sorted(os.listdir("/usr/share/figlet")))
+ irc.reply(reply, prefixNick=False)
+ except:
+ irc.reply("Sorry, unable to access font directories /usr/local/share/tdfiglet/fonts/ or /usr/share/figlet")
+ fonts = wrap(fonts, [getopts({'toilet':''})])
+
+ def wttr(self, irc, msg, args, channel, optlist, location):
+ """[] [--16] [--99]
+ ASCII weather report from wttr.in for .
+ --16 for 16 colors (default).
+ --99 for 99 colors.
+ Get moon phase with 'wttr moon'.
+ ?u (use imperial units).
+ ?m (metric).
+ ?<1-3> (number of days)
+ """
+ if not channel:
+ channel = msg.args[0]
+ optlist = dict(optlist)
+ if 'delay' in optlist:
+ delay = optlist.get('delay')
+ else:
+ delay = self.registryValue('delay', msg.args[0])
+ if '16' in optlist:
+ self.colors = 16
+ elif '99' in optlist:
+ self.colors = 99
+ else:
+ self.colors = self.registryValue('colors', msg.args[0])
+ if 'fast' in optlist:
+ speed = 'fast'
+ elif 'slow' in optlist:
+ speed = 'slow'
+ else:
+ speed = 'fast'
+ file = requests.get("http://wttr.in/{0}".format(location))
+ output = file.content.decode()
+ self.matches = {}
+ output = self.ansi2irc(output)
+ output = re.sub('⚡', '☇ ', output)
+ output = re.sub('‘‘', '‘ ', output)
+ paste = ""
+ self.stopped[msg.args[0]] = False
+ for line in output.splitlines():
+ line = line.strip('\x0F')
+ if not line.strip() and not self.stopped[msg.args[0]]:
+ if self.registryValue('pasteEnable', msg.args[0]):
+ paste += line + "\n"
+ time.sleep(delay)
+ irc.reply('\xa0', prefixNick = False, noLengthCheck=True, private=False, notice=False, to=channel)
+ elif line.strip() and not self.stopped[msg.args[0]] and not line.startswith("Follow"):
+ if self.registryValue('pasteEnable', msg.args[0]):
+ paste += line + "\n"
+ time.sleep(delay)
+ irc.reply(line, prefixNick = False, noLengthCheck=True, private=False, notice=False, to=channel)
+ if self.registryValue('pasteEnable', msg.args[0]):
+ irc.reply(self.doPaste(location, paste), private=False, notice=False, to=channel)
+ wttr = wrap(wttr, [optional('channel'), getopts({'delay':'float', '16':'', '99':'', 'fast':'', 'slow':''}), ('text')])
+
+ def rate(self, irc, msg, args, channel, optlist, coin):
+ """[] [--16] [--99] [--sub ] [coin]
+ Crypto exchange rate info from rate.sx. http://rate.sx/:help. Use --sub to set subdomain e.g. eur, btc, etc.
+ Get a graph with [coin] e.g. 'rate btc'.
+ --16 for 16 colors (default).
+ --99 for 99 colors.
+ """
+ if not channel:
+ channel = msg.args[0]
+ optlist = dict(optlist)
+ if 'delay' in optlist:
+ delay = optlist.get('delay')
+ else:
+ delay = self.registryValue('delay', msg.args[0])
+ if '16' in optlist:
+ self.colors = 16
+ elif '99' in optlist:
+ self.colors = 99
+ else:
+ self.colors = self.registryValue('colors', msg.args[0])
+ if 'fast' in optlist:
+ speed = 'fast'
+ elif 'slow' in optlist:
+ speed = 'slow'
+ else:
+ speed = 'fast'
+ if 'sub' in optlist:
+ sub = optlist.get('sub')
+ else:
+ sub = 'usd'
+ if not coin:
+ coin = ''
+ self.matches= {}
+ file = requests.get("http://{0}.rate.sx/{1}".format(sub, coin))
+ output = file.content.decode()
+ output = self.ansi2irc(output)
+ output = output.replace('\x1b(B', '')
+ paste = ""
+ self.stopped[msg.args[0]] = False
+ for line in output.splitlines():
+ line = line.strip('\x0F')
+ if not line.strip() and not self.stopped[msg.args[0]]:
+ if self.registryValue('pasteEnable', msg.args[0]):
+ paste += line + "\n"
+ time.sleep(delay)
+ irc.reply('\xa0', prefixNick = False, noLengthCheck=True, private=False, notice=False, to=channel)
+ elif line.strip() and not self.stopped[msg.args[0]] and "Follow @igor_chubin" not in line:
+ if self.registryValue('pasteEnable', msg.args[0]):
+ paste += line + "\n"
+ time.sleep(delay)
+ irc.reply(line, prefixNick = False, noLengthCheck=True, private=False, notice=False, to=channel)
+ if self.registryValue('pasteEnable', msg.args[0]):
+ irc.reply(self.doPaste(coin, paste), private=False, notice=False, to=channel)
+ rate = wrap(rate, [optional('channel'), getopts({'delay':'float', '16':'', '99':'', 'sub':'text', 'fast':'', 'slow':''}), optional('text')])
+
+ def cow(self, irc, msg, args, channel, optlist, text):
+ """[] [--delay] [--type ]
+ Cowsay
+ """
+ if not channel:
+ channel = msg.args[0]
+ optlist = dict(optlist)
+ if 'delay' in optlist:
+ delay = optlist.get('delay')
+ else:
+ delay = self.registryValue('delay', msg.args[0])
+ if 'type' in optlist:
+ type = optlist.get('type')
+ else:
+ type = 'default'
+ data = requests.get("https://easyapis.soue.tk/api/cowsay?text={0}&type={1}".format(text, type))
+ self.stopped[msg.args[0]] = False
+ paste = ''
+ for line in data.text.splitlines():
+ if self.registryValue('pasteEnable', msg.args[0]):
+ paste += line + "\n"
+ if not line.strip() and not self.stopped[msg.args[0]]:
+ time.sleep(delay)
+ irc.reply('\xa0', prefixNick = False, noLengthCheck=True, private=False, notice=False, to=channel)
+ elif line.strip() and not self.stopped[msg.args[0]] and "Follow @igor_chubin" not in line:
+ time.sleep(delay)
+ irc.reply(line, prefixNick = False, noLengthCheck=True, private=False, notice=False, to=channel)
+ if self.registryValue('pasteEnable', msg.args[0]):
+ irc.reply(self.doPaste(text, paste), private=False, notice=False, to=channel)
+ cow = wrap(cow, [optional('channel'), getopts({'delay':'float', 'type':'text'}), ('text')])
+
+ def fortune(self, irc, msg, args, channel, optlist):
+ """[]
+ Returns a random ASCII from http://www.asciiartfarts.com/fortune.txt
+ """
+ if not channel:
+ channel = msg.args[0]
+ optlist = dict(optlist)
+ if 'delay' in optlist:
+ delay = optlist.get('delay')
+ else:
+ delay = self.registryValue('delay', msg.args[0])
+ self.stopped[msg.args[0]] = False
+ data = requests.get("http://www.asciiartfarts.com/fortune.txt")
+ fortunes = data.text.split('%\n')
+ fortune = random.randrange(0, len(fortunes))
+ for line in fortunes[fortune].splitlines():
+ if not line.strip() and not self.stopped[msg.args[0]]:
+ time.sleep(delay)
+ irc.reply('\xa0', prefixNick = False, noLengthCheck=True, private=False, notice=False, to=channel)
+ elif line.strip() and not self.stopped[msg.args[0]] and "Follow @igor_chubin" not in line:
+ time.sleep(delay)
+ irc.reply(line, prefixNick = False, noLengthCheck=True, private=False, notice=False, to=channel)
+ fortune = wrap(fortune, [optional('channel'), getopts({'delay':'float'})])
+
+ def mircart(self, irc, msg, args, channel, optlist, search):
+ """[] (search text)
+ Search https://mircart.org/ and scroll first result
+ """
+ if not channel:
+ channel = msg.args[0]
+ optlist = dict(optlist)
+ if 'delay' in optlist:
+ delay = optlist.get('delay')
+ else:
+ delay = self.registryValue('delay', msg.args[0])
+ self.stopped[msg.args[0]] = False
+ ua = UserAgent()
+ header = {'User-Agent':str(ua.random)}
+ data = requests.get("https://mircart.org/?s={0}".format(search), headers=header)
+ soup = BeautifulSoup(data.text)
+ url = soup.find(href=re.compile(".txt"))
+ data = requests.get(url.get('href'), headers=header)
+ output = data.content.decode()
+ for line in output.splitlines():
+ if not line.strip() and not self.stopped[msg.args[0]]:
+ time.sleep(delay)
+ irc.reply('\xa0', prefixNick = False, noLengthCheck=True, private=False, notice=False, to=channel)
+ elif line.strip() and not self.stopped[msg.args[0]] and "Follow @igor_chubin" not in line:
+ time.sleep(delay)
+ irc.reply(line, prefixNick = False, noLengthCheck=True, private=False, notice=False, to=channel)
+ irc.reply(url.get('href'))
+ mircart = wrap(mircart, [optional('channel'), getopts({'delay':'float'}), ('text')])
+
+ def cq(self, irc, msg, args):
+ """
+ Stop the scroll.
+ """
+ if not self.stopped[msg.args[0]]:
+ self.stopped[msg.args[0]] = True
+ irc.reply("Stopping.")
+ cq = wrap(cq)
+
+Class = ASCII
diff --git a/ASCII/requirements.txt b/ASCII/requirements.txt
new file mode 100644
index 0000000..8ea5419
--- /dev/null
+++ b/ASCII/requirements.txt
@@ -0,0 +1,6 @@
+requests
+numpy
+pillow
+fake-useragent
+pexpect
+pyimgur
diff --git a/ASCII/test.py b/ASCII/test.py
new file mode 100644
index 0000000..b545906
--- /dev/null
+++ b/ASCII/test.py
@@ -0,0 +1,17 @@
+
+17 lines (10 sloc) 216 Bytes
+###
+# Copyright (c) 2019, oddluck
+# All rights reserved.
+#
+#
+###
+
+from supybot.test import *
+
+
+class AdviceTestCase(PluginTestCase):
+ plugins = ('ASCII',)
+
+
+# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
\ No newline at end of file
diff --git a/ASCII/tmp/.gitignore b/ASCII/tmp/.gitignore
new file mode 100644
index 0000000..5e7d273
--- /dev/null
+++ b/ASCII/tmp/.gitignore
@@ -0,0 +1,4 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore