dickserv- irc bot with many useful commands |
git clone git://git.acid.vegas/dickserv.git |
Log | Files | Refs | Archive | README | LICENSE |
irc.py (20156B)
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # DickServ IRC Bot - Developed by acidvegas in Python (https://acid.vegas/dickserv) 4 # irc.py 5 6 import socket 7 import time 8 9 import config 10 import constants 11 import database 12 import debug 13 import functions 14 import httplib 15 16 from commands import * 17 18 # Load optional modules 19 if config.connection.ssl: 20 import ssl 21 if config.connection.proxy: 22 import sock 23 24 def color(msg, foreground, background=None): 25 if foreground == 'random': 26 foreground = '{0:0>2}'.format(functions.random_int(2,13)) 27 if background == 'random': 28 background = '{0:0>2}'.format(functions.random_int(2,13)) 29 if background: 30 return f'\x03{foreground},{background}{msg}{constants.reset}' 31 else: 32 return f'\x03{foreground}{msg}{constants.reset}' 33 34 class IRC(object): 35 def __init__(self): 36 self.last = 0 37 self.slow = False 38 self.sock = None 39 self.start = 0 40 self.status = True 41 42 def connect(self): 43 try: 44 self.create_socket() 45 self.sock.connect((config.connection.server, config.connection.port)) 46 self.register() 47 except socket.error as ex: 48 debug.error('Failed to connect to IRC server.', ex) 49 Events.disconnect() 50 else: 51 self.listen() 52 53 def create_socket(self): 54 family = socket.AF_INET6 if config.connection.ipv6 else socket.AF_INET 55 if config.connection.proxy: 56 proxy_server, proxy_port = config.connection.proxy.split(':') 57 self.sock = socks.socksocket(family, socket.SOCK_STREAM) 58 self.sock.setblocking(0) 59 self.sock.settimeout(15) 60 self.sock.setproxy(socks.PROXY_TYPE_SOCKS5, proxy_server, int(proxy_port)) 61 else: 62 self.sock = socket.socket(family, socket.SOCK_STREAM) 63 if config.connection.vhost: 64 self.sock.bind((config.connection.vhost, 0)) 65 if config.connection.ssl: 66 ctx = ssl.SSLContext() 67 if config.cert.file: 68 ctx.load_cert_chain(config.cert.file, config.cert.key, config.cert.password) 69 if config.connection.ssl_verify: 70 ctx.verify_mode = ssl.CERT_REQUIRED 71 ctx.load_default_certs() 72 else: 73 ctx.check_hostname = False 74 ctx.verify_mode = ssl.CERT_NONE 75 self.sock = ctx.wrap_socket(self.sock) 76 77 def listen(self): 78 while True: 79 try: 80 data = self.sock.recv(1024).decode('utf-8') 81 for line in (line for line in data.split('\r\n') if line): 82 debug.irc(line) 83 if len(line.split()) >= 2: 84 Events.handle(line) 85 except (UnicodeDecodeError,UnicodeEncodeError): 86 pass 87 except Exception as ex: 88 debug.error('Unexpected error occured.', ex) 89 break 90 Events.disconnect() 91 92 def register(self): 93 if config.login.network: 94 Commands.raw('PASS ' + config.login.network) 95 Commands.raw(f'USER {config.ident.username} 0 * :{config.ident.realname}') 96 Commands.nick(config.ident.nickname) 97 98 99 100 class Commands: 101 def action(chan, msg): 102 Commands.sendmsg(chan, f'\x01ACTION {msg}\x01') 103 104 def error(target, data, reason=None): 105 if reason: 106 Commands.sendmsg(target, '[{0}] {1} {2}'.format(color('!', constants.red), data, color('({0})'.format(reason), constants.grey))) 107 else: 108 Commands.sendmsg(target, '[{0}] {1}'.format(color('!', constants.red), data)) 109 110 def identify(nick, password): 111 Commands.sendmsg('nickserv', f'identify {nick} {password}') 112 113 def join_channel(chan, key=None): 114 Commands.raw(f'JOIN {chan} {key}') if key else Commands.raw('JOIN ' + chan) 115 116 def mode(target, mode): 117 Commands.raw(f'MODE {target} {mode}') 118 119 def nick(nick): 120 Commands.raw('NICK ' + nick) 121 122 def notice(target, msg): 123 Commands.raw(f'NOTICE {target} :{msg}') 124 125 def oper(user, password): 126 Commands.raw(f'OPER {user} {password}') 127 128 def raw(msg): 129 msg = msg.replace('\r','').replace('\n','')[:450] 130 DickServ.sock.send(bytes(msg + '\r\n', 'utf-8')) 131 132 def sendmsg(target, msg): 133 Commands.raw(f'PRIVMSG {target} :{msg}') 134 135 136 137 class Events: 138 def connect(): 139 DickServ.start = time.time() 140 if config.settings.modes: 141 Commands.mode(config.ident.nickname, '+' + config.settings.modes) 142 if config.login.nickserv: 143 Commands.identify(config.ident.nickname, config.login.nickserv) 144 if config.login.operator: 145 Commands.oper(config.ident.username, config.login.operator) 146 Commands.join_channel(config.connection.channel, config.connection.key) 147 148 def disconnect(): 149 DickServ.sock.close() 150 time.sleep(10) 151 DickServ.connect() 152 153 def kick(nick, chan, kicked): 154 if kicked == config.ident.nickname and chan == config.connection.channel: 155 time.sleep(3) 156 Commands.join_channel(chan, config.connection.key) 157 158 def message(nick, ident, chan, msg): 159 try: 160 if chan == config.connection.channel and (DickServ.status or ident == config.settings.admin): 161 if not msg.startswith(config.settings.cmd_char): 162 urls = httplib.parse_urls(msg) 163 if urls: 164 if time.time() - DickServ.last > 3: 165 DickServ.last = time.time() 166 Events.url(chan, urls[0]) 167 elif msg == '@dickserv': 168 Commands.sendmsg(chan, constants.bold + 'DickServ IRC Bot - Developed by acidvegas in Python - https://acid.vegas/dickserv') 169 elif msg == '@dickserv help': 170 Commands.sendmsg(chan, 'https://git.supernets.org/acidvegas/dickserv#commands') 171 elif msg == 'h' and functions.luck(4): 172 Commands.sendmsg(chan, 'h') 173 elif 'qhat' in msg: 174 Commands.sendmsg(chan, 'Q:)') 175 elif ident not in database.Ignore.idents(): 176 if time.time() - DickServ.last < 3 and ident != config.settings.admin: 177 if not DickServ.slow: 178 Commands.sendmsg(chan, color('Slow down nerd!', constants.red)) 179 DickServ.slow = True 180 else: 181 DickServ.slow = False 182 args = msg.split() 183 argz = msg[len(args[0])+1:] 184 cmd = args[0][1:] 185 if len(args) == 1: 186 if cmd == 'date': 187 Commands.sendmsg(chan, functions.current_date()) 188 elif cmd == 'talent': 189 if functions.luck(1000): 190 Commands.sendmsg(chan, color(f' !!! HOLY FUCKING SHIT {nick} ACHIEVED TALENT !!! ', 'random', 'random')) 191 Commands.sendmsg(chan, color(' !!! RIP DITTLE DIP DIP DIP DIP IT\'S YOUR BIRTHDAY !!! ', 'random', 'random')) 192 Commands.sendmsg(chan, color(f' !!! CAN WE HAVE A GOT DAMN MOMENT OF SILENCE FOR {nick} :) !!! ', 'random', 'random')) 193 Commands.sendmsg(chan, color(' !!! GOT DAMN XD THIS IS TOO CRAZY LIKE...DAMN HAHA. DAMN. !!! ', 'random', 'random')) 194 else: 195 Commands.sendmsg(chan, color('(^)', 'random')) 196 elif cmd == 'todo': 197 todos = database.Todo.read(ident) 198 if todos: 199 for item in todos: 200 Commands.notice(nick, '[{0}] {1}'.format(color(todos.index(item)+1, constants.pink), item)) 201 else: 202 Commands.notice(nick, 'You have no saved todos.') 203 elif cmd == 'uptime': 204 Commands.sendmsg(chan, functions.uptime(DickServ.start)) 205 elif len(args) == 2: 206 if cmd == 'coin': 207 api = cryptocurrency.get(args[1]) 208 if api: 209 Commands.sendmsg(chan, '{0} {1} - ${2:,.2f}'.format(color(api['name'], constants.white), color('({0})'.format(api['symbol']), constants.grey), float(api['price_usd']))) 210 else: 211 Commands.error(chan, 'Invalid cryptocurrency name!') 212 elif cmd == 'drug': 213 api = tripsit.drug(args[1]) 214 if api: 215 Commands.sendmsg(chan, '{0} - {1}'.format(color(api['name'], constants.yellow), api['desc'])) 216 else: 217 Commands.error(chan, 'No results found.') 218 elif cmd == 'define': 219 definition = dictionary.define(args[1]) 220 if definition: 221 Commands.sendmsg(chan, '{0} - {1}: {2}'.format(color('Definition', constants.white, constants.blue), args[1].lower(), definition)) 222 else: 223 Commands.error(chan, 'No results found.') 224 elif cmd == 'isup': 225 Commands.sendmsg(chan, '{0} is {1}'.format(args[1], isup.check(args[1]))) 226 elif cmd == 'r': 227 api = reddit.read(args[1]) 228 if api: 229 data = list(api.keys()) 230 for i in data: 231 count = str(data.index(i)+1) 232 Commands.sendmsg(chan, '[{0}] {1} [{2}|{3}/{4}|{5}]'.format(color(count, constants.pink), functions.trim(i, 100), color(str(api[i]['score']), constants.white), color('+' + str(api[i]['ups']), constants.green), color('-' + str(api[i]['downs']), constants.red), color(api[i]['comments'], constants.white))) 233 Commands.sendmsg(chan, ' - ' + color(api[i]['url'], constants.grey)) 234 else: 235 Commands.error(chan, 'No results found.') 236 elif cmd == 'w': 237 if args[1].isdigit(): 238 api = weather.lookup(args[1]) 239 if api: 240 Commands.sendmsg(chan, api) 241 else: 242 Commands.error(chan, 'No results found.') 243 else: 244 Commands.error(chan, 'Invalid arguments.') 245 if len(args) >= 2: 246 if cmd == 'g': 247 api = google.search(argz, database.Settings.get('max_results')) 248 if api: 249 for result in api: 250 count = api.index(result)+1 251 Commands.sendmsg(chan, '[{0}] {1}'.format(color(count, constants.pink), result['title'])) 252 Commands.sendmsg(chan, ' - ' + color(result['link'], constants.grey)) 253 else: 254 Commands.error(chan, 'No results found.') 255 elif cmd == 'imdb': 256 api = imdb.search(argz) 257 if api: 258 Commands.sendmsg(chan, '{0} {1} {2} {3}'.format(color('Title :', constants.white), api['Title'], api['Year'], color(api['Rated'], constants.grey))) 259 Commands.sendmsg(chan, '{0} {1}{2}'.format(color('Link :', constants.white), constants.underline, color('https://imdb.com/title/' + api['imdbID'], constants.light_blue))) 260 Commands.sendmsg(chan, '{0} {1}'.format(color('Genre :', constants.white), api['Genre'])) 261 if api['imdbRating'] == 'N/A': 262 Commands.sendmsg(chan, '{0} {1} 0/10'.format(color('Rating :', constants.white), color('★★★★★★★★★★', constants.grey))) 263 else: 264 Commands.sendmsg(chan, '{0} {1}{2} {3}/10'.format(color('Rating :', constants.white), color('★'*round(float(api['imdbRating'])), constants.yellow), color('★'*(10-round(float(api['imdbRating']))), constants.grey), api['imdbRating'])) 265 Commands.sendmsg(chan, '{0} {1}'.format(color('Plot :', constants.white), api['Plot'])) 266 else: 267 Commands.error(chan, 'No results found.') 268 elif cmd == 'netsplit': 269 api = netsplit.search(argz) 270 if api: 271 data = list(api.keys()) 272 for i in data: 273 count = str(data.index(i)+1) 274 Commands.sendmsg(chan, '[{0}] {1} {2} / {3}'.format(color(count, constants.pink), color(i, constants.light_blue), color('({0})'.format(api[i]['users']), constants.grey), color(api[i]['network'], constants.red))) 275 Commands.sendmsg(chan, color(' - ' + api[i]['topic'], constants.grey)) 276 else: 277 Commands.error(chan, 'No results found.') 278 elif cmd == 'todo' and len(args) >= 3: 279 if len(args) >= 3 and args[1] == 'add': 280 todos = database.Todo.read(ident) 281 if len(todos) <= database.Settings.get('max_todo_per') and len(database.Todo.read()) <= database.Settings.get('max_todo'): 282 argz = argz[4:] 283 if argz not in todos: 284 database.Todo.add(functions.get_date(), ident, argz) 285 Commands.notice(nick, 'Todo added to database!') 286 else: 287 Commands.notice(nick, 'Todo already in database!') 288 else: 289 Commands.notice(nick, 'Maximum todos reached!') 290 elif len(args) == 3 and args[1] == 'del': 291 num = args[2] 292 if num.isdigit(): 293 num = int(num) 294 todos = database.Todo.read(ident) 295 if todos: 296 if num <= len(todos): 297 for item in todos: 298 count = todos.index(item)+1 299 if count == num: 300 database.Todo.remove(ident, item) 301 break 302 Commands.notice(nick, 'Todo removed from database!') 303 else: 304 Commands.notice(nick, 'Invalid number.') 305 else: 306 Commands.notice(nick, 'No todos found.') 307 else: 308 Commands.notice(nick, 'Invalid number.') 309 else: 310 Commands.notice(nick, 'Invalid arguments.') 311 elif cmd == 'tpb': 312 api = tpb.search(argz, database.Settings.get('max_results')) 313 if api: 314 data = list(api.keys()) 315 for i in data: 316 count = str(data.index(i)+1) 317 Commands.sendmsg(chan, '[{0}] {1} [{2}/{3}]'.format(color(count, constants.pink), i, color(api[i]['seeders'], constants.green), color(api[i]['leechers'], constants.red))) 318 Commands.sendmsg(chan, ' - ' + color('http://thepiratebay.org' + api[i]['url'], constants.grey)) 319 else: 320 Commands.error(chan, 'No results found.') 321 elif cmd == 'ud': 322 definition = dictionary.urban(argz) 323 if definition: 324 Commands.sendmsg(chan, '{0}{1} - {2}: {3}'.format(color('urban', constants.white, constants.blue), color('DICTIONARY', constants.yellow, constants.black), argz, definition)) 325 else: 326 Commands.error(chan, 'No results found.') 327 elif cmd == 'wolfram': 328 results = wolfram.ask(argz) 329 if results: 330 Commands.sendmsg(chan, '{0}{1} - {2}'.format(color('Wolfram', constants.red), color('Alpha', constants.orange), results)) 331 else: 332 Commands.error(chan, 'No results found.') 333 elif cmd == 'yt': 334 api = youtube.search(argz, database.Settings.get('max_results')) 335 if api: 336 data = list(api.keys()) 337 for i in api.keys(): 338 count = str(data.index(i)+1) 339 Commands.sendmsg(chan, '[{0}] {1}'.format(color(count, constants.pink), functions.trim(i, 75))) 340 Commands.sendmsg(chan, ' - ' + color(api[i], constants.grey)) 341 else: 342 Commands.error(chan, 'No results found.') 343 DickServ.last = time.time() 344 except Exception as ex: 345 Commands.error(chan, 'Command threw an exception.', ex) 346 347 def nick_in_use(): 348 debug.error('DickServ is already running or nick is in use.') 349 350 def private(nick, ident, msg): 351 try: 352 if ident == config.settings.admin_host: 353 args = msg.split() 354 cmd = args[0][1:] 355 if len(args) == 1: 356 if cmd == 'config': 357 settings = database.Settings.read() 358 Commands.sendmsg(nick, '[{0}]'.format(color('Settings', constants.purple))) 359 for setting in settings: 360 Commands.sendmsg(nick, '{0} = {1}'.format(color(setting[0], constants.yellow), color(setting[1], constants.grey))) 361 elif cmd == 'ignore': 362 ignores = database.Ignore.idents() 363 if ignores: 364 Commands.sendmsg(nick, '[{0}]'.format(color('Ignore List', constants.purple))) 365 for user in ignores: 366 Commands.sendmsg(nick, color(user, constants.yellow)) 367 Commands.sendmsg(nick, '{0} {1}'.format(color('Total:', constants.light_blue), color(len(ignores), constants.grey))) 368 else: 369 Commands.error(nick, 'Ignore list is empty!') 370 elif cmd == 'off': 371 DickServ.status = False 372 Commands.sendmsg(nick, color('OFF', constants.red)) 373 elif cmd == 'on': 374 DickServ.status = True 375 Commands.sendmsg(nick, color('ON', constants.green)) 376 elif len(args) == 2: 377 if cmd == 'ignore' and args[1] == 'reset': 378 database.Ignore.reset() 379 elif cmd == 'todo' and args[1] == 'expire': 380 database.Todo.expire_check() 381 elif cmd == 'todo' and args[1] == 'reset': 382 database.Todo.reset() 383 elif len(args) == 3: 384 if cmd == 'config': 385 setting, value = args[1], args[2] 386 if functions.CheckString.number(value): 387 value = functions.floatint(value) 388 if value >= 0: 389 if setting in database.Settings.settings(): 390 database.Settings.update(setting, value) 391 Commands.sendmsg(nick, 'Change setting for {0} to {1}.'.format(color(setting, constants.yellow), color(value, constants.grey))) 392 else: 393 Commands.error(nick, 'Invalid config variable.') 394 else: 395 Commands.error(nick, 'Value must be greater than or equal to zero.') 396 else: 397 Commands.error(nick, 'Value must be an integer or float.') 398 elif cmd == 'ignore': 399 if args[1] == 'add': 400 user_ident = args[2] 401 if user_ident not in database.Ignore.idents(): 402 database.Ignore.add(nickname, user_ident) 403 Commands.sendmsg(nick, 'Ident {0} to the ignore list.'.format(color('added', constants.green))) 404 else: 405 Commands.error(nick, 'Ident is already on the ignore list.') 406 elif cmd == 'del': 407 user_ident = args[2] 408 if user_ident in database.Ignore.idents(): 409 database.Ignore.remove(user_ident) 410 Commands.sendmsg(nick, 'Ident {0} from the ignore list.'.format(color('removed', constants.red))) 411 else: 412 Commands.error(nick, 'Ident does not exist in the ignore list.') 413 except Exception as ex: 414 Commands.error(nick, 'Command threw an exception.', ex) 415 416 def url(chan, url): 417 try: 418 if imdb.check(url): 419 id = imdb.check(url) 420 api = imdb.search(id) 421 if api: 422 Commands.sendmsg(chan, '{0} {1} {2} {3}'.format(color('Title :', constants.white), api['Title'], api['Year'], color(api['Rated'], constants.grey))) 423 Commands.sendmsg(chan, '{0} {1}{2}'.format(color('Link :', constants.white), constants.underline, color('https://imdb.com/title/' + api['imdbID'], constants.light_blue))) 424 Commands.sendmsg(chan, '{0} {1}'.format(color('Genre :', constants.white), api['Genre'])) 425 if api['imdbRating'] == 'N/A': 426 Commands.sendmsg(chan, '{0} {1} 0/10'.format(color('Rating :', constants.white), color('★★★★★★★★★★', constants.grey))) 427 else: 428 Commands.sendmsg(chan, '{0} {1}{2} {3}/10'.format(color('Rating :', constants.white), color('★'*round(float(api['imdbRating'])), constants.yellow), color('★'*(10-round(float(api['imdbRating']))), constants.grey), api['imdbRating'])) 429 Commands.sendmsg(chan, '{0} {1}'.format(color('Plot :', constants.white), api['Plot'])) 430 elif reddit.check(url): 431 subreddit = reddit.check(url)[0] 432 post_id = reddit.check(url)[1] 433 api = reddit.post_info(subreddit, post_id) 434 if api: 435 Commands.sendmsg(chan, '[{0}] - {1} [{2}|{3}/{4}|{5}]'.format(color('reddit', constants.cyan), color(functions.trim(api['title'], 75), constants.white), color(api['score'], constants.white), color('+' + api['ups'], constants.green), color('-' + api['downs'], constants.red), color(api['num_comments'], constants.white))) 436 elif youtube.check(url): 437 api = youtube.video_info(youtube.check(url)) 438 if api: 439 Commands.sendmsg(chan, '{0}{1} - {2} [{3}|{4}/{5}]'.format(color('You', constants.black, constants.white), color('Tube', constants.white, constants.red), functions.trim(api['title'], 75), color(api['views'], constants.white), color('+' + api['likes'], constants.green), color('-' + api['dislikes'], constants.red))) 440 Commands.sendmsg(chan, color(api['description'], constants.grey)) 441 else: 442 url_type = httplib.get_type(url) 443 if url_type == 'text/html': 444 title = httplib.get_title(url) 445 Commands.sendmsg(chan, '[{0}] {1}'.format(color(url_type, constants.pink), color(title, constants.white))) 446 else: 447 file_name = httplib.get_file(url) 448 if file_name: 449 file_size = httplib.get_size(url) 450 Commands.sendmsg(chan, '[{0}] {1} [{2}]'.format(color(url_type, constants.pink), color(file_name, constants.white), color(file_size, constants.blue))) 451 except Exception as ex: 452 debug.error('Title Error', ex) 453 454 def handle(data): 455 args = data.split() 456 if data.startswith('ERROR :Closing Link:'): 457 raise Exception('Connection has closed.') 458 elif args[0] == 'PING': 459 Commands.raw('PONG ' + args[1][1:]) 460 elif args[1] == constants.RPL_WELCOME: 461 Events.connect() 462 elif args[1] == constants.ERR_NICKNAMEINUSE: 463 Events.nick_in_use() 464 elif args[1] == constants.KICK: 465 nick = args[0].split('!')[0][1:] 466 chan = args[2] 467 kicked = args[3] 468 Events.kick(nick, chan, kicked) 469 elif args[1] == constants.PRIVMSG: 470 nick = args[0].split('!')[0][1:] 471 ident = args[0].split('!')[1] 472 chan = args[2] 473 msg = ' '.join(args[3:])[1:] 474 if chan == config.ident.nickname: 475 Events.private(nick, ident, msg) 476 else: 477 Events.message(nick, ident, chan, msg) 478 479 DickServ = IRC()