cancer

- 🚬 bad habits 🍺 for internet relay chat
git clone git://git.acid.vegas/cancer.git
Log | Files | Refs | Archive | README | LICENSE

cancer.py (19530B)

      1 #!/usr/bin/env python
      2 # Cancer IRC Bot - Developed by acidvegas in Python (https://git.acid.vegas/cancer)
      3 
      4 '''
      5 WARNING: This bot highly encourages flooding!
      6 
      7 Commands:
      8 	@cancer       | Information about the bot
      9 	@cancer stats | Return bot statistics for the channel
     10 	!100          | 1 in 100 chance to get a 100 (big !smoke)
     11 	!beer [nick]  | Grab a beer or toss one to someone
     12 	!chainsmoke   | Start a game of Chain Smoke
     13 	!chug         | Sip beer
     14 	!dragrace     | Start a game of Drag Race
     15 	!extendo      | 1 in 100 chance to get an EXTENDO (big !toke)
     16 	!fatfuck      | 1 in 100 chance to get a  FATFUCK (fat !smoke/!toke)
     17 	!letschug     | LET'S FUCKING CHUG!
     18 	!letstoke     | LET'S FUCKING TOKE!
     19 	!toke         | Hit joint
     20 	!smoke        | Hit cigarette
     21 	!nosmoking    | Disable the bot for 30 seconds
     22 '''
     23 
     24 import asyncio
     25 import json
     26 import os
     27 import random
     28 import ssl
     29 import time
     30 
     31 # Connection
     32 server     = 'irc.supernets.org'
     33 port       = 6697
     34 use_ipv6   = False
     35 use_ssl    = True
     36 vhost      = None
     37 channel    = '#dev'
     38 key        = None
     39 
     40 # Identity
     41 nickname = '[DEV]CANCER'
     42 username = 'smokesome'
     43 realname = 'git.acid.vegas/cancer'
     44 
     45 # Login
     46 nickserv_password = None
     47 network_password  = None
     48 operator_password = None
     49 
     50 # Settings
     51 user_modes = 'BdDg' # +d requires additional ! and @ to be in your set::channel-command-prefix on UnrealIRCd
     52 
     53 # Formatting Control Characters / Color Codes
     54 bold        = '\x02'
     55 italic      = '\x1D'
     56 underline   = '\x1F'
     57 reverse     = '\x16'
     58 reset       = '\x0f'
     59 white       = '00'
     60 black       = '01'
     61 blue        = '02'
     62 green       = '03'
     63 red         = '04'
     64 brown       = '05'
     65 purple      = '06'
     66 orange      = '07'
     67 yellow      = '08'
     68 light_green = '09'
     69 cyan        = '10'
     70 light_cyan  = '11'
     71 light_blue  = '12'
     72 pink        = '13'
     73 grey        = '14'
     74 light_grey  = '15'
     75 
     76 def color(msg, foreground, background=None):
     77 	return f'\x03{foreground},{background}{msg}{reset}' if background else f'\x03{foreground}{msg}{reset}'
     78 
     79 def debug(data):
     80 	print('{0} | [~] - {1}'.format(time.strftime('%I:%M:%S'), data))
     81 
     82 def error(data, reason=None):
     83 	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))
     84 
     85 def luck(odds):
     86 	return True if random.randint(1,odds) == 1 else False
     87 
     88 def ssl_ctx():
     89 	ctx = ssl.create_default_context()
     90 	ctx.check_hostname = False
     91 	ctx.verify_mode = ssl.CERT_NONE
     92 	return ctx
     93 
     94 class Generate: # degenerate *
     95 	async def can(chan, target):
     96 		beer_choice = random.choice(['bud','modelo','ultra'])
     97 		beer_temp   = random.choice(['a piss warm','an ice cold','an empty'])
     98 		if beer_choice == 'bud':
     99 			beer = '{0}{1}{2}'.format(color(' ', white, white), color(' BUD ', white, random.choice((blue,brown))), color('c', grey, white))
    100 			await Cancer.action(chan, f'throws {color(target, white)} {beer_temp} {beer} =)')
    101 			if luck(100):
    102 				await asyncio.sleep(2)
    103 				await Cancer.action(chan, 'suddenly feels more gay...')
    104 		elif beer_choice == 'modelo':
    105 			beer = '{0}{1}{2}'.format(color(' ', orange, orange), color('Modelo', blue, yellow), color('c', grey, orange)) # props to opal
    106 			await Cancer.action(chan, f'throws {color(target, white)} {beer_temp} {beer} =)')
    107 		elif beer_choice == 'modelo':
    108 			beer = '{0}{1}'.format(color(' ULTRA ', blue, white), color('🬃', red, white)) # warm
    109 			await Cancer.action(chan, f'throws {color(target, white)} {beer_temp} {beer} =)')
    110 
    111 	def beer():
    112 		glass = color(' ', light_grey, light_grey)
    113 		return glass + color(''.join(random.choice(('       :.')) for _ in range(9)), orange, yellow) + glass
    114 
    115 	def cigarette(size):
    116 		filter    = color(';.`-,:.`;', yellow, orange)+color(' ', yellow, yellow)
    117 		cigarette = color('|'*size, light_grey, white)
    118 		cherry    = color('\u259A', random.choice((red,yellow,orange)), black)+color('\u259A', random.choice((red,yellow,orange)), grey)
    119 		smoke     = color('-' + ''.join(random.choice((';:-.,_`~\'')) for _ in range(random.randint(5,8))), grey)
    120 		return filter + cigarette + cherry + smoke
    121 
    122 	def joint(size):
    123 		joint    = color('/'*size, light_grey, white)
    124 		cherry   = color('\u259A', random.choice((red,yellow,orange)), black)+color('\u259A', random.choice((red,yellow,orange)), grey)
    125 		smoke    = color('-' + ''.join(random.choice((';:-.,_`~\'')) for _ in range(random.randint(5,8))), grey)
    126 		return joint + cherry + smoke
    127 
    128 	def mug(size):
    129 		glass  = color(' ', light_grey, light_grey)
    130 		empty  = f'{glass}         {glass}'
    131 		foam   = glass + color(':::::::::', light_grey, white) + glass
    132 		bottom = color('           ', light_grey, light_grey)
    133 		mug   = [foam,Generate.beer(),Generate.beer(),Generate.beer(),Generate.beer(),Generate.beer(),Generate.beer(),Generate.beer()]
    134 		for i in range(8-size):
    135 			mug.pop()
    136 			mug.insert(0, empty)
    137 		for i in range(len(mug)):
    138 			if i == 2 or i == 7:
    139 				mug[i] += glass + glass
    140 			elif i > 2 and i < 7:
    141 				mug[i] += '  ' + glass
    142 		mug.append(bottom)
    143 		return mug
    144 
    145 class Bot():
    146 	def __init__(self):
    147 		self.fat             = False
    148 		self.event           = None
    149 		self.nicks           = list()
    150 		self.stats           = {'hits':25,'sips':8,'chugged':0,'smoked':0,'toked':0,'chain':0,'drag':0}
    151 		self.loops           = {'chainsmoke':None,'dragrace':None,'letschug':None,'letstoke':None,'nosmoking':None,'timers':None}
    152 		self.status          = True
    153 		self.reader          = None
    154 		self.writer          = None
    155 
    156 	async def raw(self, data):
    157 		self.writer.write(data[:510].encode('utf-8') + b'\r\n')
    158 		await self.writer.drain()
    159 
    160 	async def action(self, chan, msg):
    161 		await self.sendmsg(chan, f'\x01ACTION {msg}\x01')
    162 
    163 	async def sendmsg(self, target, msg):
    164 		await self.raw(f'PRIVMSG {target} :{msg}')
    165 
    166 	async def notice(self, target, msg):
    167 		await self.raw(f'NOTICE {target} :{msg}')
    168 
    169 	async def connect(self):
    170 		while True:
    171 			try:
    172 				options = {
    173 					'host'       : server,
    174 					'port'       : port,
    175 					'limit'      : 1024,
    176 					'ssl'        : ssl_ctx() if use_ssl else None,
    177 					'family'     : 10 if use_ipv6 else 2,
    178 					'local_addr' : vhost
    179 				}
    180 				self.reader, self.writer = await asyncio.wait_for(asyncio.open_connection(**options), 15)
    181 				await self.raw(f'USER {username} 0 * :{realname}')
    182 				await self.raw('NICK ' + nickname)
    183 			except Exception as ex:
    184 				error('error: failed to connect to ' + server, ex)
    185 			else:
    186 				if os.path.isfile('stats.json'):
    187 					with open('stats.json') as stats_file:
    188 						self.stats = json.loads(stats_file.read())
    189 						debug('reloaded stats')
    190 				await self.listen()
    191 				for loop in self.loops:
    192 					if self.loops[loop]:
    193 						self.loops[loop].cancel()
    194 				self.stats['chain'] = 0
    195 				self.stats['drag']  = 0
    196 				self.event  = None
    197 				self.nicks  = list()
    198 				self.status = True
    199 			finally:
    200 				await asyncio.sleep(30)
    201 
    202 	async def loop_nosmoking(self):
    203 		await asyncio.sleep(30)
    204 		self.status = True
    205 
    206 	async def loop_timers(self):
    207 		while True:
    208 			try:
    209 				if time.strftime('%I:%M') == '04:20':
    210 					await self.sendmsg(channel, color('S M O K E W E E D E R R D A Y', light_green))
    211 					await self.sendmsg(channel, color('ITZ DAT MUTHA FUCKN 420 BITCH', yellow))
    212 					await self.sendmsg(channel, color('LIGHT UP A NICE GOOD FAT FUCK', red))
    213 					await asyncio.sleep(120)
    214 				elif time.strftime('%I:%M %p') == '02:00 AM':
    215 					await self.sendmsg(channel, '.ascii phish')
    216 					await asyncio.sleep(120)
    217 				elif time.strftime('%I:%M') == '12:00': # the biscuit hour..
    218 					with open('stats.json', 'w') as stats_file:
    219 						json.dump(self.stats, stats_file)
    220 					await asyncio.sleep(120)
    221 			except Exception as ex:
    222 				error('error: loop_timers failed', ex)
    223 			finally:
    224 				await asyncio.sleep(20)
    225 
    226 	async def loop_chainsmoke(self):
    227 		self.nicks = dict()
    228 		try:
    229 			await self.notice(channel, 'Starting a round of {0} in {1} seconds!'.format(color('ChainSmoke', red), color('10', white)))
    230 			await self.notice(channel, '[{0}] {1} {2} {3}'.format(color('How To Play', light_blue), color('Type', yellow), color('!smoke', light_green), color('to hit a cigarette. The cigarette goes down a little after each hit. Once you finish a cigarette, a new one will be lit for you. You will have 60 seconds to chain smoke as many cigarettes as possible.', yellow)))
    231 			await asyncio.sleep(10)
    232 			await self.action(channel, 'Round starts in 3...')
    233 			await asyncio.sleep(1)
    234 			await self.action(channel, '2...')
    235 			await asyncio.sleep(1)
    236 			await self.action(channel, '1...')
    237 			await asyncio.sleep(1)
    238 			await self.action(channel, color('GO', light_green))
    239 			self.status = True
    240 			await asyncio.sleep(60)
    241 			self.status = False
    242 			await self.sendmsg(channel, color('          CHAINSMOKE ROUND IS OVER          ', red, yellow))
    243 			await asyncio.sleep(1)
    244 			await self.sendmsg(channel, color('          CHAINSMOKE ROUND IS OVER          ', red, yellow))
    245 			await asyncio.sleep(1)
    246 			await self.sendmsg(channel, color('          CHAINSMOKE ROUND IS OVER          ', red, yellow))
    247 			await self.sendmsg(channel, color('Counting cigarette butts...', yellow))
    248 			await asyncio.sleep(10)
    249 			await self.sendmsg(channel, '{0} smoked {1} cigarettes!'.format(channel, color(str(self.stats['chain']), light_blue)))
    250 			if self.nicks:
    251 				guy = max(self.nicks, key=self.nicks.get)
    252 				await self.sendmsg(channel, '{0} smoked the most cigarettes... {1}'.format(guy, self.nicks[guy]))
    253 		except Exception as ex:
    254 			error('error: loop_chainsmoke failed', ex)
    255 		finally:
    256 			self.stats['chain'] = 0
    257 			self.nicks  = list()
    258 			self.event  = None
    259 			self.status = True
    260 
    261 	async def loop_dragrace(self):
    262 		self.hits   = 25
    263 		try:
    264 			await self.notice(channel, 'Starting a round of {0} in {1} seconds!'.format(color('DragRace', red), color('10', white)))
    265 			await self.notice(channel, '[{0}] {1} {2} {3}'.format(color('How To Play', light_blue), color('Type', yellow), color('!smoke', light_green), color('to hit a cigarette. The cigarette goes down a little after each hit. You will have 10 seconds to smoke as quickly as possible.', yellow)))
    266 			await asyncio.sleep(10)
    267 			await self.action(channel, 'Round starts in 3...')
    268 			await asyncio.sleep(1)
    269 			await self.action(channel, '2...')
    270 			await asyncio.sleep(1)
    271 			await self.action(channel, '1...')
    272 			await asyncio.sleep(1)
    273 			await self.action(channel, color('GO', light_green))
    274 			self.stats['drag'] = time.time()
    275 		except Exception as ex:
    276 			error('error: loop_dragrace failed', ex)
    277 		finally:
    278 			self.status = True
    279 
    280 	async def loop_letschug(self, nick):
    281 		self.nicks.append(nick)
    282 		try:
    283 			await self.sendmsg(channel, color(f'OH SHIT {nick} is drunk', light_green))
    284 			await self.notice(channel, color(f'Time to TOTALLY CHUG in {channel.upper()} in 30 seconds, type !chug to join', light_green))
    285 			await asyncio.sleep(10)
    286 			await self.sendmsg(channel, color('LOL we CHUG in 20 get ready ' + ' '.join(self.nicks), light_green))
    287 			await asyncio.sleep(10)
    288 			await self.sendmsg(channel, color('YO we CHUG in 10 get ready ' + ' '.join(self.nicks), light_green))
    289 			await asyncio.sleep(5)
    290 			await self.sendmsg(channel, color('alright CHUG in 5', light_green))
    291 			await asyncio.sleep(1)
    292 			await self.sendmsg(channel, color('4..', light_green))
    293 			await asyncio.sleep(1)
    294 			await self.sendmsg(channel, color('3..', light_green))
    295 			await asyncio.sleep(1)
    296 			await self.sendmsg(channel, color('2..', light_green))
    297 			await asyncio.sleep(1)
    298 			await self.sendmsg(channel, color('1..', light_green))
    299 			await asyncio.sleep(1)
    300 			await self.sendmsg(channel, color(' '.join(self.nicks) + ' .. CHUG!', light_green))
    301 		except Exception as ex:
    302 			error('error: loop_letschug failed', ex)
    303 		finally:
    304 			self.event = None
    305 			self.nicks = list()
    306 
    307 	async def loop_letstoke(self, nick):
    308 		self.nicks.append(nick)
    309 		try:
    310 			await self.sendmsg(channel, color(f'YO {nick} is high', light_green))
    311 			await self.notice(channel, color(f'Time to FUCKING toke in {channel.upper()}, type !toke to join', light_green))
    312 			await asyncio.sleep(10)
    313 			await self.sendmsg(channel, color('OH SHIT we toke in 20 get ready ' + ' '.join(self.nicks), light_green))
    314 			await asyncio.sleep(10)
    315 			await self.sendmsg(channel, color('OH SHIT we toke in 10 get ready ' + ' '.join(self.nicks), light_green))
    316 			await asyncio.sleep(5)
    317 			await self.sendmsg(channel, color('alright toke in 5', light_green))
    318 			await asyncio.sleep(1)
    319 			await self.sendmsg(channel, color('4..', light_green))
    320 			await asyncio.sleep(1)
    321 			await self.sendmsg(channel, color('3..', light_green))
    322 			await asyncio.sleep(1)
    323 			await self.sendmsg(channel, color('2..', light_green))
    324 			await asyncio.sleep(1)
    325 			await self.sendmsg(channel, color('1..', light_green))
    326 			await asyncio.sleep(1)
    327 			await self.sendmsg(channel, color(' '.join(self.nicks) + ' .. toke!', light_green))
    328 		except Exception as ex:
    329 			error('error: loop_letstoke failed', ex)
    330 		finally:
    331 			self.event = None
    332 			self.nicks = list()
    333 
    334 	async def listen(self):
    335 		while True:
    336 			try:
    337 				if self.reader.at_eof():
    338 					break
    339 				data = await asyncio.wait_for(self.reader.readuntil(b'\r\n'), 500)
    340 				line = data.decode('utf-8').strip()
    341 				args = line.split()
    342 				debug(line)
    343 				if line.startswith('ERROR :Closing Link:'):
    344 					raise Exception('Connection has closed.')
    345 				elif args[0] == 'PING':
    346 					await self.raw('PONG '+args[1][1:])
    347 				elif args[1] == '001':
    348 					if user_modes:
    349 						await self.raw(f'MODE {nickname} +{user_modes}')
    350 					if nickserv_password:
    351 						await self.sendmsg('NickServ', f'IDENTIFY {nickname} {nickserv_password}')
    352 					if operator_password:
    353 						await self.raw(f'OPER {username} {operator_password}')
    354 					await self.raw(f'JOIN {channel} {key}') if key else await self.raw('JOIN ' + channel)
    355 					self.loops['timers'] = asyncio.create_task(self.loop_timers())
    356 				elif args[1] == '433':
    357 					error('The bot is already running or nick is in use.') # nick change
    358 				elif args[1] == 'INVITE' and len(args) == 4:
    359 					invited = args[2]
    360 					chan    = args[3][1:]
    361 					if invited == nickname and chan == channel:
    362 						await self.raw(f'JOIN {channel} {key}') if key else await self.raw('JOIN ' + channel)
    363 				elif args[1] == 'KICK' and len(args) >= 4:
    364 					chan   = args[2]
    365 					kicked = args[3]
    366 					if kicked == nickname and chan == channel:
    367 						await asyncio.sleep(3)
    368 						await self.raw(f'JOIN {channel} {key}') if key else await self.raw('JOIN ' + channel)
    369 				elif args[1] == 'PART' and len(args) >= 3:
    370 					chan = args[2]
    371 					if chan == channel:
    372 						nick = args[0].split('!')[0][1:]
    373 						await self.action(nick, f'blows smoke in {nick}\'s face...')
    374 				elif args[1] == 'PRIVMSG' and len(args) >= 4:
    375 					nick = args[0].split('!')[0][1:]
    376 					chan = args[2]
    377 					msg  = ' '.join(args[3:])[1:]
    378 					if chan ==  channel:
    379 						if self.status:
    380 							args = msg.split()
    381 							if msg == '@cancer':
    382 								await self.sendmsg(chan, bold + 'CANCER IRC Bot - Developed by acidvegas in Python - https://git.acid.vegas/cancer')
    383 							elif msg == '@cancer stats':
    384 								await self.sendmsg(chan, 'Chugged : {0} beers      {1}'.format(color(self.stats['chugged'], light_blue), color('({0:,} cases)'.format(int(self.stats['chugged']/24)), grey)))
    385 								await self.sendmsg(chan, 'Smoked  : {0} cigarettes {1}'.format(color(self.stats['smoked'],  light_blue), color('({0:,} packs)'.format(int(self.stats['smoked']/24)),  grey)))
    386 								await self.sendmsg(chan, 'Toked   : {0} joints     {1}'.format(color(self.stats['toked'],   light_blue), color('({0:,} grams)'.format(int(self.stats['toked']/3)),    grey)))
    387 							elif msg in ('!100','!extendo','!fatfuck') and luck(100):
    388 								if msg == '!fatfuck':
    389 									self.fat = True
    390 									await self.sendmsg(chan, '{0}{1}{2}'.format(color(' !!! ', red, green), color('AWWW SHIT, IT\'S TIME FOR THAT MARLBORO FATFUCK', black, green), color(' !!! ', red, green)))
    391 								else:
    392 									self.stats['hits'] = 100
    393 									if msg == '!100':
    394 										await self.sendmsg(chan, '{0}{1}{2}'.format(color(' !!! ', white, red), color('AWWW SHIT, IT\'S TIME FOR THAT NEWPORT 100', red, white), color(' !!! ', white, red)))
    395 									else:
    396 										await self.sendmsg(chan, '{0}{1}{2}'.format(color(' !!! ', red, green), color('OHHH FUCK, IT\'S TIME FOR THAT 420 EXTENDO', yellow, green), color(' !!! ', red, green)))
    397 							elif args[0] == '!beer':
    398 								if len(args) == 1:
    399 									target = nick
    400 								elif len(args) == 2:
    401 									target = args[1]
    402 								await Generate.can(chan, target)
    403 							elif msg == '!chainsmoke' and not self.event:
    404 								self.status = False
    405 								self.event  = 'chainsmoke'
    406 								self.loops['chainsmoke'] = asyncio.create_task(self.loop_chainsmoke())
    407 							elif msg == '!chug':
    408 								if self.event == 'letschug':
    409 									if nick in self.nicks:
    410 										await self.sendmsg(chan, color(nick + ' you are already chuggin u wastoid!', light_green))
    411 									else:
    412 										self.nicks.append(nick)
    413 										await self.sendmsg(chan, color(nick + ' joined the CHUG session!', light_green))
    414 								else:
    415 									if self.stats['sips'] <= 0:
    416 										self.stats['sips'] = 8
    417 										self.stats['chugged'] += 1
    418 									for line in Generate.mug(self.stats['sips']):
    419 										await self.sendmsg(chan, line)
    420 									self.stats['sips'] -= random.choice((1,2))
    421 							elif msg == '!dragrace' and not self.event:
    422 								self.status = False
    423 								self.event  = 'dragrace'
    424 								self.loops['dragrace'] = asyncio.create_task(self.loop_dragrace())
    425 							elif msg == '!letschug' and not self.event:
    426 								self.event = 'letschug'
    427 								self.loops['letschug'] = asyncio.create_task(self.loop_letschug(nick))
    428 							elif msg == '!letstoke' and not self.event:
    429 								self.event = 'letstoke'
    430 								self.loops['letstoke'] = asyncio.create_task(self.loop_letstoke(nick))
    431 							elif msg == '!nosmoking':
    432 								self.status = False
    433 								self.loops['nosmoking'] = asyncio.create_task(self.loop_nosmoking())
    434 							elif msg in ('!smoke','!toke'):
    435 								option = 'smoked' if msg == '!smoke' else 'toked'
    436 								if self.event == 'letstoke' and msg == '!toke':
    437 									if nick in self.nicks:
    438 										await self.sendmsg(chan, color(nick + ' you are already toking u stoner!', light_green))
    439 									else:
    440 										self.nicks.append(nick)
    441 										await self.sendmsg(chan, color(nick + ' joined the TOKE session!', light_green))
    442 								else:
    443 									if self.stats['hits'] <= 0:
    444 										self.stats['hits'] = 25
    445 										self.stats[option] += 1
    446 										if self.fat:
    447 											self.fat = False
    448 										if self.event == 'chainsmoke' and msg == '!smoke':
    449 											self.nicks[nick] = self.nicks[nick]+1 if nick in self.nicks else 1
    450 											self.stats['chain'] += 1
    451 										elif self.event == 'dragrace' and msg == '!smoke':
    452 											await self.sendmsg(chan, 'It took {0} seconds for {1} to smoke a cigarette!'.format(color('{:.2f}'.format(time.time()-self.stats['drag']), light_blue), color(chan, white)))
    453 											self.event = None
    454 											self.stats['drag'] = 0
    455 										elif luck(25) and msg == '!smoke':
    456 											await self.raw(f'KILL {nick} CANCER KILLED {nick.upper()} - QUIT SMOKING TODAY! +1 800-QUIT-NOW')
    457 									else:
    458 										object = Generate.cigarette(self.stats['hits']) if msg == '!smoke' else Generate.joint(self.stats['hits'])
    459 										if self.fat:
    460 											for i in range(3):
    461 												await self.sendmsg(chan, object)
    462 										else:
    463 											await self.sendmsg(chan, object)
    464 										self.stats['hits'] -= random.choice((1,2))
    465 			except (UnicodeDecodeError, UnicodeEncodeError):
    466 				pass
    467 			except Exception as ex:
    468 				error(self.display + 'fatal error occured', ex)
    469 				break
    470 
    471 # Main
    472 Cancer = Bot()
    473 asyncio.run(Cancer.connect())