archive

- Random tools & helpful resources for IRC
git clone git://git.acid.vegas/archive.git
Log | Files | Refs | Archive

iex.py (11213B)

      1 #!/usr/bin/env python
      2 # IExTrading IRC Bot - Developed by acidvegas in Python (https://acid.vegas/iex)
      3 
      4 import http.client
      5 import json
      6 import random
      7 import ssl
      8 import socket
      9 import time
     10 
     11 # Connection
     12 server  = 'irc.supernets.org'
     13 channel = '#dev'
     14 
     15 # Identity
     16 nickname = 'StockMarket'
     17 username = 'iex'
     18 realname = 'acid.vegas/iex'
     19 
     20 # Login
     21 nickserv_password = None
     22 network_password  = None
     23 
     24 # Settings
     25 throttle_cmd = 3
     26 throttle_msg = 0.5
     27 user_modes   = None
     28 
     29 # Formatting Control Characters / Color Codes
     30 bold        = '\x02'
     31 italic      = '\x1D'
     32 underline   = '\x1F'
     33 reverse     = '\x16'
     34 reset       = '\x0f'
     35 white       = '00'
     36 black       = '01'
     37 blue        = '02'
     38 green       = '03'
     39 red         = '04'
     40 brown       = '05'
     41 purple      = '06'
     42 orange      = '07'
     43 yellow      = '08'
     44 light_green = '09'
     45 cyan        = '10'
     46 light_cyan  = '11'
     47 light_blue  = '12'
     48 pink        = '13'
     49 grey        = '14'
     50 light_grey  = '15'
     51 
     52 def condense_value(value):
     53 	value = float(value)
     54 	if value < 0.01:
     55 		return '${0:,.8f}'.format(value)
     56 	elif value < 24.99:
     57 		return '${0:,.2f}'.format(value)
     58 	else:
     59 		return '${:,}'.format(int(value))
     60 
     61 def debug(msg):
     62 	print(f'{get_time()} | [~] - {msg}')
     63 
     64 def error(msg, reason=None):
     65 	if reason:
     66 		print(f'{get_time()} | [!] - {msg} ({reason})')
     67 	else:
     68 		print(f'{get_time()} | [!] - {msg}')
     69 
     70 def error_exit(msg):
     71 	raise SystemExit(f'{get_time()} | [!] - {msg}')
     72 
     73 def get_float(data):
     74 	try:
     75 		float(data)
     76 		return True
     77 	except ValueError:
     78 		return False
     79 
     80 def get_time():
     81 	return time.strftime('%I:%M:%S')
     82 
     83 def percent_color(percent):
     84 	percent = float(percent)
     85 	if percent == 0.0:
     86 		return grey
     87 	elif percent < 0.0:
     88 		if percent > -10.0:
     89 			return brown
     90 		else:
     91 			return red
     92 	else:
     93 		if percent < 10.0:
     94 			return green
     95 		else:
     96 			return light_green
     97 
     98 def random_int(min, max):
     99 	return random.randint(min, max)
    100 
    101 class IEX:
    102 	def api(api_data):
    103 		conn = http.client.HTTPSConnection('api.iextrading.com', timeout=15)
    104 		conn.request('GET', '/1.0/' + api_data)
    105 		response = conn.getresponse().read().decode('utf-8')
    106 		data = json.loads(response)
    107 		conn.close()
    108 		return data
    109 
    110 	def company(symbol):
    111 		return IEX.api(f'stock/{symbol}/company')
    112 
    113 	def lists(list_type):
    114 		return IEX.api('stock/market/list/' + list_type)
    115 
    116 	def news(symbol):
    117 		return IEX.api(f'stock/{symbol}/news')
    118 
    119 	def quote(symbols):
    120 		data = IEX.api(f'stock/market/batch?symbols={symbols}&types=quote')
    121 		if len(data) == 1:
    122 			return data[next(iter(data))]['quote']
    123 		else:
    124 			return [data[item]['quote'] for item in data]
    125 
    126 	def stats(symbol):
    127 		return IEX.api(f'stock/{symbol}/stats')
    128 
    129 	def symbols():
    130 		return IEX.api('ref-data/symbols')
    131 
    132 class IRC(object):
    133 	def __init__(self):
    134 		self.last = 0
    135 		self.slow = False
    136 		self.sock = None
    137 
    138 	def stock_info(self, data):
    139 		sep      = self.color('|', grey)
    140 		sep2     = self.color('/', grey)
    141 		name     = '{0} ({1})'.format(self.color(data['companyName'], white), data['symbol'])
    142 		value    = condense_value(data['latestPrice'])
    143 		percent  = self.color('{:,.2f}%'.format(float(data['change'])), percent_color(data['change']))
    144 		volume   = '{0} {1}'.format(self.color('Volume:', white), '${:,}'.format(data['avgTotalVolume']))
    145 		cap      = '{0} {1}'.format(self.color('Market Cap:', white), '${:,}'.format(data['marketCap']))
    146 		return f'{name} {sep} {value} ({percent}) {sep} {volume} {sep} {cap}'
    147 
    148 	def stock_matrix(self, data): # very retarded way of calculating the longest strings per-column
    149 		results = {'symbol':list(),'value':list(),'percent':list(),'volume':list(),'cap':list()}
    150 		for item in data:
    151 			results['symbol'].append(item['symbol'])
    152 			results['value'].append(condense_value(item['latestPrice']))
    153 			results['percent'].append('{:,.2f}%'.format(float(item['change'])))
    154 			results['volume'].append('${:,}'.format(item['avgTotalVolume']))
    155 			results['cap'].append('${:,}'.format(item['marketCap']))
    156 		for item in results:
    157 			results[item] = len(max(results[item], key=len))
    158 		if results['symbol'] < len('Symbol'):
    159 			results['symbol'] = len('Symbol')
    160 		if results['value'] < len('Value'):
    161 			results['value'] = len('Value')
    162 		if results['percent'] < len('Change'):
    163 			results['percent'] = len('Change')
    164 		if results['volume'] < len('Volume'):
    165 			results['volume'] = len('Volume')
    166 		if results['cap'] < len('Market Cap'):
    167 			results['cap'] = len('Market Cap')
    168 		return results
    169 
    170 	def stock_table(self, data):
    171 		matrix = self.stock_matrix(data)
    172 		header = self.color(' {0}   {1}   {2}   {3}   {4}'.format('Symbol'.center(matrix['symbol']), 'Value'.center(matrix['value']), 'Percent'.center(matrix['percent']),  'Volume'.center(matrix['volume']), 'Market Cap'.center(matrix['cap'])), black, light_grey)
    173 		lines  = [header,]
    174 		for item in data:
    175 			symbol   = item['symbol'].ljust(matrix['symbol'])
    176 			value    = condense_value(item['latestPrice']).rjust(matrix['value'])
    177 			percent  = self.color('{:,.2f}%'.format(float(item['change'])).rjust(matrix['percent']), percent_color(item['change']))
    178 			volume   = '${:,}'.format(item['avgTotalVolume']).rjust(matrix['volume'])
    179 			cap      = '${:,}'.format(item['marketCap']).rjust(matrix['cap'])
    180 			lines.append(' {0} | {1} | {2} | {3} | {4} '.format(symbol,value,percent,volume,cap))
    181 		return lines
    182 
    183 	def color(self, msg, foreground, background=None):
    184 		if background:
    185 			return f'\x03{foreground},{background}{msg}{reset}'
    186 		else:
    187 			return f'\x03{foreground}{msg}{reset}'
    188 
    189 	def connect(self):
    190 		try:
    191 			self.sock = ssl.wrap_socket(socket.socket(socket.AF_INET, socket.SOCK_STREAM))
    192 			self.sock.connect((server, 6697))
    193 			self.raw(f'USER {username} 0 * :{realname}')
    194 			self.nick(nickname)
    195 		except socket.error as ex:
    196 			error('Failed to connect to IRC server.', ex)
    197 			self.event_disconnect()
    198 		else:
    199 			self.listen()
    200 
    201 	def error(self, chan, msg, reason=None):
    202 		if reason:
    203 			self.sendmsg(chan, '[{0}] {1} {2}'.format(self.color('!', red), msg, self.color('({0})'.format(reason), grey)))
    204 		else:
    205 			self.sendmsg(chan, '[{0}] {1}'.format(self.color('!', red), msg))
    206 
    207 	def event_connect(self):
    208 		if user_modes:
    209 			self.mode(nickname, '+' + user_modes)
    210 		if nickserv_password:
    211 			self.identify(nickname, nickserv_password)
    212 		self.join_channel(channel)
    213 
    214 	def event_disconnect(self):
    215 		self.sock.close()
    216 		time.sleep(10)
    217 		self.connect()
    218 
    219 	def event_kick(self, chan, kicked):
    220 		if chan == channel and kicked == nickname:
    221 			time.sleep(3)
    222 			self.join_channel(channel, key)
    223 
    224 	def event_message(self, nick, chan, msg):
    225 		#try:
    226 			if msg[:1] in '@!':
    227 				if time.time() - self.last < throttle_cmd:
    228 					if not self.slow:
    229 						self.error(chan, 'Slow down nerd!')
    230 						self.slow = True
    231 				else:
    232 					args = msg.split()
    233 					if msg == '@iex':
    234 						self.sendmsg(chan, bold + 'IExTrading IRC Bot - Developed by acidvegas in Python - https://acid.vegas/iex')
    235 					elif args[0] == '!stock':
    236 						if len(args) == 2:
    237 							symbols = args[1].upper()
    238 							if ',' in symbols:
    239 								symbols = ','.join(list(symbols.split(','))[:10])
    240 								data    = IEX.quote(symbols)
    241 								if type(data) == dict:
    242 									self.sendmsg(chan, self.stock_info(data))
    243 								elif type(data) == list:
    244 									for line in self.stock_table(data):
    245 										self.sendmsg(chan, line)
    246 										time.sleep(throttle_msg)
    247 								else:
    248 									self.error(chan, 'Invalid stock names!')
    249 							else:
    250 								symbol = args[1]
    251 								data = IEX.quote(symbol)
    252 								if data:
    253 									self.sendmsg(chan, self.stock_info(data))
    254 								else:
    255 									self.error(chan, 'Invalid stock name!')
    256 						elif len(args) == 3:
    257 							if args[1] == 'company':
    258 								symbol = args[2]
    259 								data = IEX.company(symbol)
    260 								if data:
    261 									self.sendmsg(chan, '{0} {1} ({2}) {3} {4} {5} {6} {7} {8}'.format(self.color('Company:', white), data['companyName'], data['symbol'], self.color('|', grey), data['website'], self.color('|', grey), data['industry'], self.color('|', grey), data['CEO']))
    262 									self.sendmsg(chan, '{0} {1}'.format(self.color('Description:', white), data['description']))
    263 								else:
    264 									self.error('Invalid stock name!')
    265 							elif args[1] == 'search':
    266 								query = args[2].lower()
    267 								data = [{'symbol':item['symbol'],'name':item['name']} for item in IEX.symbols() if query in item['name'].lower()]
    268 								if data:
    269 									count = 1
    270 									max_length = len(max([item['name'] for item in data], key=len))
    271 									for item in data[:10]:
    272 										self.sendmsg(chan, '[{0}] {1} {2} {3}'.format(self.color(str(count), pink), item['name'].ljust(max_length), self.color('|', grey), item['symbol']))
    273 										count += 1
    274 										time.sleep(throttle_msg)
    275 								else:
    276 									self.error(chan, 'No results found.')
    277 							elif args[1] == 'list':
    278 								options = {'active':'mostactive','gainers':'gainers','losers':'losers','volume':'iexvolume','percent':'iexpercent'}
    279 								option = args[2]
    280 								try:
    281 									option = options[option]
    282 								except KeyError:
    283 									self.error(chan, 'Invalid option!', 'Valid options are active, gainers, losers, volume, & percent')
    284 								else:
    285 									data = IEX.lists(option)
    286 									for line in self.stock_table(data):
    287 										self.sendmsg(chan, line)
    288 										time.sleep(throttle_msg)
    289 							elif args[1] == 'news':
    290 								symbol = args[2]
    291 								data   = IEX.news(symbol)
    292 								if data:
    293 									count  = 1
    294 									for item in data:
    295 										self.sendmsg(chan, '[{0}] {1}'.format(self.color(str(count), pink), item['headline']))
    296 										self.sendmsg(chan, ' - ' + self.color(item['url'], grey))
    297 										count += 1
    298 										time.sleep(throttle_msg)
    299 								else:
    300 									self.error(chan, 'Invalid stock name!')
    301 				self.last = time.time()
    302 		#except Exception as ex:
    303 		#	self.error(chan, 'Unknown error occured!', ex)
    304 
    305 	def event_nick_in_use(self):
    306 		self.nick('IEX_' + str(random_int(10,99)))
    307 
    308 	def handle_events(self, data):
    309 		args = data.split()
    310 		if data.startswith('ERROR :Closing Link:'):
    311 			raise Exception('Connection has closed.')
    312 		elif args[0] == 'PING':
    313 			self.raw('PONG ' + args[1][1:])
    314 		elif args[1] == '001':
    315 			self.event_connect()
    316 		elif args[1] == '433':
    317 			self.event_nick_in_use()
    318 		elif args[1] == 'KICK':
    319 			chan   = args[2]
    320 			kicked = args[3]
    321 			self.event_kick(nick, chan, kicked)
    322 		elif args[1] == 'PRIVMSG':
    323 			nick = args[0].split('!')[0][1:]
    324 			chan = args[2]
    325 			msg  = ' '.join(args[3:])[1:]
    326 			if chan == channel:
    327 				self.event_message(nick, chan, msg)
    328 
    329 	def identify(self, nick, passwd):
    330 		self.sendmsg('nickserv', f'identify {nick} {passwd}')
    331 
    332 	def join_channel(self, chan, key=None):
    333 		self.raw(f'JOIN {chan} {key}') if key else self.raw('JOIN ' + chan)
    334 
    335 	def listen(self):
    336 		while True:
    337 			try:
    338 				data = self.sock.recv(1024).decode('utf-8')
    339 				for line in (line for line in data.split('\r\n') if line):
    340 					debug(line)
    341 					if len(line.split()) >= 2:
    342 						self.handle_events(line)
    343 			except (UnicodeDecodeError,UnicodeEncodeError):
    344 				pass
    345 			#except Exception as ex:
    346 			#	error('Unexpected error occured.', ex)
    347 			#	break
    348 		self.event_disconnect()
    349 
    350 	def mode(self, target, mode):
    351 		self.raw(f'MODE {target} {mode}')
    352 
    353 	def nick(self, nick):
    354 		self.raw('NICK ' + nick)
    355 
    356 	def raw(self, msg):
    357 		self.sock.send(bytes(msg + '\r\n', 'utf-8'))
    358 
    359 	def sendmsg(self, target, msg):
    360 		self.raw(f'PRIVMSG {target} :{msg}')
    361 
    362 IRC().connect()