mirror of
git://git.acid.vegas/scroll.git
synced 2024-11-07 08:46:45 +00:00
Initial commit
This commit is contained in:
commit
ac397afd83
BIN
.screens/preview.png
Normal file
BIN
.screens/preview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
15
LICENSE
Normal file
15
LICENSE
Normal file
@ -0,0 +1,15 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2021, acidvegas <acid.vegas@acid.vegas>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
26
README.md
Normal file
26
README.md
Normal file
@ -0,0 +1,26 @@
|
||||
# scroll
|
||||
|
||||
Scroll is full-featured IRC bot that carries a **PENIS PUMP** & will brighten up all the mundane chats in your lame IRC channels with some colorful IRC artwork! Designed to be extremely stable, this bot is sure to stay rock hard & handle itself quite well!
|
||||
|
||||
All of the IRC art is loaded directly from the [@ircart/ircart](https://github.com/ircart/ircart) repository.
|
||||
|
||||
## Dependencies
|
||||
* [python](https://www.python.org/)
|
||||
* [chardet](https://pypi.org/project/chardet/) *(`pip install chardet`)*
|
||||
|
||||
## Commands
|
||||
| Command | Description |
|
||||
| --------------------------------- | ---------------------------------------------------------- |
|
||||
| `@scroll` | information about scroll |
|
||||
| `.ascii <name>` | play the \<name> art file |
|
||||
| `.ascii dirs` | list of ascii directories |
|
||||
| `.ascii list` | list of ascii filenames |
|
||||
| `.ascii random [dir]` | play random art, optionally from the [dir] directory only |
|
||||
| `.ascii search <query>` | search for art diles that match \<query> |
|
||||
| `.ascii stop` | stop playing art |
|
||||
|
||||
## Mirrors
|
||||
- [acid.vegas](https://git.acid.vegas/scroll)
|
||||
- [GitHub](https://github.com/ircart/scroll)
|
||||
- [GitLab](https://gitlab.com/ircart/scroll)
|
||||
- [SuperNETs](https://git.supernets.org/ircart/scroll)
|
284
scroll.py
Normal file
284
scroll.py
Normal file
@ -0,0 +1,284 @@
|
||||
#!/usr/bin/env python
|
||||
# Scroll IRC Art Bot - Developed by acidvegas in Python (https://git.acid.vegas/scroll)
|
||||
|
||||
import asyncio
|
||||
import random
|
||||
import ssl
|
||||
import time
|
||||
import urllib.request
|
||||
|
||||
class connection:
|
||||
server = 'irc.network.com'
|
||||
port = 6697
|
||||
ipv6 = False
|
||||
ssl = True
|
||||
vhost = None
|
||||
channel = '#chats'
|
||||
key = None
|
||||
modes = None
|
||||
|
||||
class identity:
|
||||
nickname = 'scroll'
|
||||
username = 'scroll'
|
||||
realname = 'git.acid.vegas/scroll'
|
||||
nickserv = None
|
||||
|
||||
class throttle:
|
||||
flood = 3 # delay between each command
|
||||
max_lines = 300 # maximum number of lines in art file to be played outside of #scroll
|
||||
message = 0.05 # delay between each line sent
|
||||
results = 10 # maximum number of results returned from search
|
||||
|
||||
# Formatting Control Characters / Color Codes
|
||||
bold = '\x02'
|
||||
italic = '\x1D'
|
||||
underline = '\x1F'
|
||||
reverse = '\x16'
|
||||
reset = '\x0f'
|
||||
white = '00'
|
||||
black = '01'
|
||||
blue = '02'
|
||||
green = '03'
|
||||
red = '04'
|
||||
brown = '05'
|
||||
purple = '06'
|
||||
orange = '07'
|
||||
yellow = '08'
|
||||
light_green = '09'
|
||||
cyan = '10'
|
||||
light_cyan = '11'
|
||||
light_blue = '12'
|
||||
pink = '13'
|
||||
grey = '14'
|
||||
light_grey = '15'
|
||||
|
||||
def color(msg, foreground, background=None):
|
||||
return f'\x03{foreground},{background}{msg}{reset}' if background else f'\x03{foreground}{msg}{reset}'
|
||||
|
||||
def debug(data):
|
||||
print('{0} | [~] - {1}'.format(time.strftime('%I:%M:%S'), data))
|
||||
|
||||
def error(data, reason=None):
|
||||
print('{0} | [!] - {1} ({2})'.format(time.strftime('%I:%M:%S'), data, str(reason))) if reason else print('{0} | [!] - {1}'.format(time.strftime('%I:%M:%S'), data))
|
||||
|
||||
def ssl_ctx():
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
return ctx
|
||||
|
||||
class Bot():
|
||||
def __init__(self):
|
||||
self.db = dict()
|
||||
self.last = 0
|
||||
self.loops = dict()
|
||||
self.playing = False
|
||||
self.slow = False
|
||||
self.reader = None
|
||||
self.writer = None
|
||||
|
||||
async def raw(self, data):
|
||||
self.writer.write(data[:510].encode('utf-8') + b'\r\n')
|
||||
await self.writer.drain()
|
||||
|
||||
async def action(self, chan, msg):
|
||||
await self.sendmsg(chan, f'\x01ACTION {msg}\x01')
|
||||
|
||||
async def sendmsg(self, target, msg):
|
||||
await self.raw(f'PRIVMSG {target} :{msg}')
|
||||
|
||||
async def irc_error(self, chan, msg, reason=None):
|
||||
await self.sendmsg(chan, '[{0}] {1} {2}'.format(color('ERROR', red), msg, color(f'({reason})', grey))) if reason else await self.sendmsg(chan, '[{0}] {1}'.format(color('ERROR', red), msg))
|
||||
|
||||
async def connect(self):
|
||||
while True:
|
||||
try:
|
||||
options = {
|
||||
'host' : connection.server,
|
||||
'port' : connection.port,
|
||||
'limit' : 1024,
|
||||
'ssl' : ssl_ctx() if connection.ssl else None,
|
||||
'family' : 10 if connection.ipv6 else 2,
|
||||
'local_addr' : connection.vhost
|
||||
}
|
||||
self.reader, self.writer = await asyncio.wait_for(asyncio.open_connection(**options), 15)
|
||||
await self.raw(f'USER {identity.username} 0 * :{identity.realname}')
|
||||
await self.raw('NICK ' + identity.nickname)
|
||||
except Exception as ex:
|
||||
error('failed to connect to ' + connection.server, ex)
|
||||
else:
|
||||
await self.listen()
|
||||
finally:
|
||||
self.loops = dict()
|
||||
self.playing = False
|
||||
self.slow = False
|
||||
await asyncio.sleep(30)
|
||||
|
||||
async def sync(self):
|
||||
try:
|
||||
cache = self.db
|
||||
self.db = dict()
|
||||
ascii = urllib.request.urlopen('https://raw.githubusercontent.com/ircart/ircart/master/ircart/.list').readlines()
|
||||
for item in ascii:
|
||||
item = item.decode(chardet.detect(item)['encoding']).replace('\n','').replace('\r','')
|
||||
if '/' in item:
|
||||
dir = item.split('/')[0]
|
||||
name = item.split('/')[1]
|
||||
self.db[dir] = self.db[dir]+[name,] if dir in self.db else [name,]
|
||||
else:
|
||||
self.db['root'] = self.db['root']+[item,] if 'root' in self.db else [item,]
|
||||
except Exception as ex:
|
||||
try:
|
||||
await self.irc_error(connection.channel, 'failed to sync database', ex)
|
||||
except:
|
||||
error(connection.channel, 'failed to sync database', ex)
|
||||
self.db = cache
|
||||
|
||||
async def play(self, chan, name):
|
||||
try:
|
||||
ascii = urllib.request.urlopen(f'https://raw.githubusercontent.com/ircart/ircart/master/ircart/{name}.txt', timeout=10)
|
||||
if ascii.getcode() == 200:
|
||||
ascii = ascii.readlines()
|
||||
if len(ascii) > throttle.max_lines and chan != '#scroll':
|
||||
await self.irc_error(chan, 'file is too big', 'take it to #scroll')
|
||||
else:
|
||||
await self.action(chan, 'the ascii gods have chosen... ' + color(name, cyan))
|
||||
for line in ascii:
|
||||
await self.sendmsg(chan, line.decode(chardet.detect(line)['encoding']).replace('\n','').replace('\r','') + reset)
|
||||
await asyncio.sleep(throttle.message)
|
||||
else:
|
||||
await self.irc_error(chan, 'invalid name', name)
|
||||
except Exception as ex:
|
||||
try:
|
||||
await self.irc_error(chan, 'error in play function', ex)
|
||||
except:
|
||||
error('error in play function', ex)
|
||||
finally:
|
||||
self.playing = False
|
||||
|
||||
async def listen(self):
|
||||
while True:
|
||||
try:
|
||||
if self.reader.at_eof():
|
||||
break
|
||||
data = await asyncio.wait_for(self.reader.readuntil(b'\r\n'), 200)
|
||||
line = data.decode('utf-8').strip()
|
||||
args = line.split()
|
||||
debug(line)
|
||||
if line.startswith('ERROR :Closing Link:'):
|
||||
raise Exception('Connection has closed.')
|
||||
elif args[0] == 'PING':
|
||||
await self.raw('PONG '+args[1][1:])
|
||||
elif args[1] == '001':
|
||||
if connection.modes:
|
||||
await self.raw(f'MODE {identity.nickname} +{connection.modes}')
|
||||
if identity.nickserv:
|
||||
await self.sendmsg('NickServ', f'IDENTIFY {identity.nickname} {identity.nickserv}')
|
||||
await self.raw(f'JOIN {connection.channel} {connection.key}') if connection.key else await self.raw('JOIN ' + connection.channel)
|
||||
await self.raw('JOIN #scroll')
|
||||
await self.sync()
|
||||
elif args[1] == '433':
|
||||
error('The bot is already running or nick is in use.')
|
||||
elif args[1] == 'INVITE' and len(args) == 4:
|
||||
invited = args[2]
|
||||
chan = args[3][1:]
|
||||
if invited == identity.nickname and chan in (connection.channel, '#scroll'):
|
||||
await self.raw(f'JOIN {connection.channel} {connection.key}') if connection.key else await self.raw('JOIN ' + connection.channel)
|
||||
elif args[1] == 'KICK' and len(args) >= 4:
|
||||
chan = args[2]
|
||||
kicked = args[3]
|
||||
if kicked == identity.nickname and chan in (connection.channel,'#scroll'):
|
||||
await asyncio.sleep(3)
|
||||
await self.raw(f'JOIN {connection.channel} {connection.key}') if connection.key else await self.raw('JOIN ' + connection.channel)
|
||||
elif args[1] == 'PRIVMSG' and len(args) >= 4:
|
||||
nick = args[0].split('!')[0][1:]
|
||||
chan = args[2]
|
||||
msg = ' '.join(args[3:])[1:]
|
||||
if chan in (connection.channel, '#scroll'):
|
||||
args = msg.split()
|
||||
if msg == '@cancer':
|
||||
await self.sendmsg(chan, bold + 'CANCER IRC Bot - Developed by acidvegas in Python - https://git.acid.vegas/cancer')
|
||||
elif args[0] == '.ascii':
|
||||
if msg == '.ascii stop' and self.playing:
|
||||
if chan in self.loops:
|
||||
self.loops[chan].cancel()
|
||||
elif time.time() - self.last < throttle.flood:
|
||||
if not self.slow:
|
||||
if not self.playing:
|
||||
await self.irc_error(chan, 'slow down nerd')
|
||||
self.slow = True
|
||||
elif len(args) >= 2 and not self.playing:
|
||||
self.slow = False
|
||||
if msg == '.ascii dirs':
|
||||
for dir in self.db:
|
||||
await self.sendmsg(chan, '[{0}] {1}{2}'.format(color(str(list(self.db).index(dir)+1).zfill(2), pink), dir.ljust(10), color('('+str(len(self.db[dir]))+')', grey)))
|
||||
await asyncio.sleep(throttle.message)
|
||||
elif msg == '.ascii list':
|
||||
await self.sendmsg(chan, underline + color('https://raw.githubusercontent.com/ircart/ircart/master/ircart/.list', light_blue))
|
||||
elif msg == '.ascii random':
|
||||
self.playing = True
|
||||
dir = random.choice(list(self.db))
|
||||
ascii = random.choice(self.db[dir]) if dir == 'root' else dir+'/'+random.choice(self.db[dir])
|
||||
self.loops[chan] = asyncio.create_task(self.play(chan, ascii))
|
||||
elif args[1] == 'random' and len(args) == 3:
|
||||
dir = args[2]
|
||||
if dir in self.db:
|
||||
self.playing = True
|
||||
ascii = random.choice(self.db[dir])
|
||||
self.loops[chan] = asyncio.create_task(self.play(chan, dir+'/'+ascii))
|
||||
else:
|
||||
await self.irc_error(chan, 'invalid directory name', dir)
|
||||
elif args[1] == 'search' and len(args) == 3:
|
||||
query = args[2]
|
||||
results = list()
|
||||
for dir in self.db:
|
||||
for ascii in self.db[dir]:
|
||||
if query in ascii:
|
||||
results.append({'dir':dir,'name':ascii})
|
||||
if results:
|
||||
for item in results[:throttle.results]:
|
||||
if item['dir'] == 'root':
|
||||
await self.sendmsg(chan, '[{0}] {1}'.format(color(str(results.index(item)+1).zfill(2), pink), item['name']))
|
||||
else:
|
||||
await self.sendmsg(chan, '[{0}] {1} {2}'.format(color(str(results.index(item)+1).zfill(2), pink), item['name'], color('('+item['dir']+')', grey)))
|
||||
await asyncio.sleep(throttle.message)
|
||||
else:
|
||||
await self.irc_error(chan, 'no results found', query)
|
||||
elif len(args) == 2:
|
||||
option = args[1]
|
||||
if [x for x in ('..','?','%','\\') if x in option]:
|
||||
await self.irc_error(chan, 'nice try nerd')
|
||||
elif option == 'random':
|
||||
self.playing = True
|
||||
self.loops[chan] = asyncio.create_task(self.play(chan, random.choice(self.db)))
|
||||
else:
|
||||
ascii = [dir+'/'+option for dir in self.db if option in self.db[dir]][0]
|
||||
if ascii:
|
||||
if ascii.startswith('root/'):
|
||||
ascii = ascii.split('/')[1]
|
||||
self.playing = True
|
||||
self.loops[chan] = asyncio.create_task(self.play(chan, ascii))
|
||||
else:
|
||||
await self.irc_error(chan, 'no results found', option)
|
||||
except (UnicodeDecodeError, UnicodeEncodeError):
|
||||
pass
|
||||
except Exception as ex:
|
||||
error('fatal error occured', ex)
|
||||
break
|
||||
finally:
|
||||
self.last = time.time()
|
||||
|
||||
# Main
|
||||
print('#'*56)
|
||||
print('#{:^54}#'.format(''))
|
||||
print('#{:^54}#'.format('Scroll IRC Art Bot'))
|
||||
print('#{:^54}#'.format('Developed by acidvegas in Python'))
|
||||
print('#{:^54}#'.format('https://git.acid.vegas/scroll'))
|
||||
print('#{:^54}#'.format(''))
|
||||
print('#'*56)
|
||||
try:
|
||||
import chardet
|
||||
except ImportError:
|
||||
raise SystemExit('missing required \'chardet\' library (https://pypi.org/project/chardet/)')
|
||||
else:
|
||||
asyncio.run(Bot().connect())
|
Loading…
Reference in New Issue
Block a user