# result_dir = r'H:\\fc\\TestReport' # lists = os.listdir(result_dir) # lists.sort() # file_new = os.path.join(lists[-1]) # source_path = result_dir + '\\' + file_new email_host = "smtp.qq.com" host_port = 465 from_addr = "*****@*****.**" pwd = "ytrrkmtghhckbegf" # 获取最新生成的测试报告附件 result_dir = r'H:\\fc\\TestReport' # result_dir = r'H:\\fc\\test_case' lists = os.listdir(result_dir) lists.sort() file_new = os.path.join(lists[-1]) source_path = result_dir + '\\' + file_new to_addr_list = ["[email protected],[email protected]"] email_content = "t_report" email_content_html = "tttt" email_subject = "t_report" email_from = "12332" part_name = "test.html" email_obj = Email.get_email_obj(email_subject, email_from, to_addr_list) Email.attach_content(email_obj, email_content) Email.attach_part(email_obj, source_path, part_name) Email.send_email(email_obj, email_host, host_port, from_addr, pwd, to_addr_list) fp.close()
class FxStock: def __init__(self, lambda_mode=False, send_email=False): self.email_service = Email() if send_email else None self.cmds = { 's': self.get_stk, 'c': self.get_fx, 'i': self.get_idx, 'alert': self.alert, 'reset': self.reset, 'check': self.check, 'us': self.get_us_stk, 'query': self.query } self.desc = { 's': 'get stock price', 'c': 'get currency exchange rate', 'i': 'get index', 'alert': 'view/set alert', 'reset': 'clear alert', 'us': 'get us stock price' } self.examples = { 's': Constant.S_EXAMPLE, 'c': Constant.C_EXAMPLE, 'i': Constant.I_EXAMPLE, 'alert': Constant.ALERT_EXAMPLE, 'reset': Constant.RESET_EXAMPLE, 'us': Constant.US_EXAMPLE } if lambda_mode: cmd_lst = ['s', 'c', 'i', 'us', 'query'] self.cmds = {cmd: self.cmds[cmd] for cmd in cmd_lst} self.desc = { cmd: self.desc[cmd] for cmd in cmd_lst if cmd in self.desc } self.examples = { cmd: self.examples[cmd] for cmd in cmd_lst if cmd in self.examples } else: self.db = DB() # query command def query(self, data): data['method'] = 'answerInlineQuery' args = data['args'].strip() if 0 < len(args) < 5 and args.isnumeric() and int(args) > 0: stock_info = self.get_stock_info(args) if len(stock_info) != 0: s = { i: stock_info.get(i, 'NA') for i in ['company', 'price', 'change'] } btn_lst = [{ 'text': 'View details', "callback_data": "/s " + args }] data['results'] = [{ "type": "article", "id": "unique", "title": '{company}'.format(**s), "description": '{price} {change}'.format(**s), "reply_markup": { "inline_keyboard": [btn_lst] }, "input_message_content": { "parse_mode": "HTML", "message_text": '<b><u>{company}</u>\n{price} {change}</b>'.format( **s) } }] # s command def get_stk(self, data): if data.get('callback_query_id', -1) != -1: data['method'] = ['editMessageText', 'answerCallbackQuery'] data['text'] = self.get_stock_detail(data['args']) return data['method'] = 'sendMessage' code = self.get_stock_code(data) if code != '': stock_info = self.get_stock_info(code) if len(stock_info) == 0: data['text'] = 'Please enter a valid stock code.' else: data['text'] = '<b><u>{}</u>\n{} {}</b>'.format( stock_info.get('company', 'NA'), stock_info.get('price', 'NA'), stock_info.get('change', 'NA')) btn_lst = [{ 'text': 'View details', "callback_data": "/s " + code }] data['reply_markup'] = {"inline_keyboard": [btn_lst]} def get_stock_code(self, data): args = data['args'].strip() if len(args) == 0: return '5' elif len(args) > 4: data[ 'text'] = 'Please use the following format.\ne.g. /s 5\ne.g. /s 0005' return '' elif not (args.isnumeric()) or int(args) == 0: data['text'] = '"{}" is not a valid stock code.'.format(args) return '' return args def get_stock_info(self, code): url = 'https://finance.yahoo.com/quote/{}.HK/'.format(code.zfill(4)) headers = {'User-Agent': ''} page = HttpService.get(url, headers) soup = BeautifulSoup(page.content, 'html.parser') stock_info = {} price_ele = soup.find( 'span', {'class': 'Trsdu(0.3s) Fw(b) Fz(36px) Mb(-4px) D(ib)'}) if price_ele is None or price_ele.get_text(strip=True) == '': logger.error('yahoo finance stock price is not available!') else: stock_info['price'] = price_ele.get_text(strip=True).replace( ',', '') company_ele = soup.find('h1', {'class': 'D(ib) Fz(18px)'}) if company_ele is None or company_ele.get_text(strip=True) == '': logger.error('yahoo finance company name is not available!') else: company = company_ele.get_text(strip=True) company = company[:company.find('(')].strip() stock_info['company'] = company change_ele = soup.find_all( 'span', { 'class': [ 'Trsdu(0.3s) Fw(500) Pstart(10px) Fz(24px) C($negativeColor)', 'Trsdu(0.3s) Fw(500) Pstart(10px) Fz(24px) C($positiveColor)', 'Trsdu(0.3s) Fw(500) Pstart(10px) Fz(24px)' ] }) if change_ele is None or len( change_ele) == 0 or change_ele[0].get_text(strip=True) == '': logger.error('yahoo finance change is not available!') else: stock_info['change'] = change_ele[0].get_text(strip=True) return stock_info def get_stock_detail(self, code): url = 'http://realtime-money18-cdn.on.cc/securityQuote/genStockDetailHKJSON.php?stockcode={}' json_resp = HttpService.cf_get_json(url.format(code.zfill(5))) json_obj = JsonUtil.to_json_obj(json_resp) change = json_obj.calculation.change percent_change = json_obj.calculation.pctChange json_obj.change = '+' + str(change) if change > 0 else str(change) json_obj.pctChange = '+' + str( percent_change) if percent_change > 0 else str(percent_change) json_obj.real.vol = NumberUtil.format_num(json_obj.real.vol) json_obj.real.tvr = NumberUtil.format_num(json_obj.real.tvr) json_obj.calculation.marketValue = NumberUtil.format_num( json_obj.calculation.marketValue) json_obj.daily.issuedShare = NumberUtil.format_num( json_obj.daily.issuedShare) json_obj.shortPut.Trade[0].Qty = NumberUtil.format_num( json_obj.shortPut.Trade[0].Qty) json_obj.shortPut.Trade[0].Amount = NumberUtil.format_num( json_obj.shortPut.Trade[0].Amount) np = float(json_obj.real.np) if NumberUtil.is_float( json_obj.real.np) else 0 eps = float(json_obj.daily.eps) if NumberUtil.is_float( json_obj.daily.eps) else 0 lot_size = float(json_obj.daily.lotSize) if NumberUtil.is_float( json_obj.daily.lotSize) else 0 dividend = float(json_obj.daily.dividend) if NumberUtil.is_float( json_obj.daily.dividend) else 0 json_obj.peRatio = 0 if eps == 0 else (np / eps) json_obj.dividendYield = 0 if np == 0 else (dividend * 100 / np) json_obj.minSubscriptionFee = np * lot_size json_obj.daily.lotSize = int(lot_size) qty_price = json_obj.bulkTrade.Trade[0].Transaction[0].Qty_price json_obj.bulkTradeQty = '' if str( qty_price) == '' else qty_price.split('-')[0] json_obj.bulkTradePrice = '' if str( qty_price) == '' else qty_price.split('-')[1] return Constant.STK_DETAIL_TEMPLATE.format(s=json_obj) # c command def get_fx(self, data): if data.get('callback_query_id', -1) != -1: data['method'] = ['editMessageText', 'answerCallbackQuery'] data['text'] = ''.join([ '{} - {}\n'.format(k, v) for k, v in Constant.CCY_DCT.items() ]) return data['method'] = 'sendMessage' ccy_param = self.get_ccy_param(data) if len(ccy_param) != 0: current = self.get_xrates(ccy_param[0], ccy_param[1], 1) current = 'NA' if current == '-1' else current data['text'] = '<b>{} to {}\n{}</b>'.format( ccy_param[0].upper(), ccy_param[1].upper(), current) def get_ccy_param(self, data): args = data['args'].strip() if args == '': args = 'jpy' elif (' to ' in args and len(args) != 10) or (' to ' not in args and len(args) != 3): data[ 'text'] = 'Please use the following format.\ne.g. /c jpy\ne.g. /c jpy to hkd' return [] args = (args + ' to HKD') if len(args) == 3 else args ccys = args.split(' to ') ccy = ccys[0] ccy2 = ccys[1] if ccy.upper() not in Constant.CCY_DCT.keys(): data[ 'text'] = '"{}" is not a valid currency code. Please retry.'.format( ccy) btn_lst = [{'text': 'View currency code', "callback_data": "/c"}] data['reply_markup'] = {"inline_keyboard": [btn_lst]} return [] if ccy2.upper() not in Constant.CCY_DCT.keys(): data[ 'text'] = '"{}" is not a valid currency code. Please retry.'.format( ccy2) btn_lst = [{'text': 'View currency code', "callback_data": "/c"}] data['reply_markup'] = {"inline_keyboard": [btn_lst]} return [] return [ccy, ccy2] def get_xrates(self, ccy, ccy2, amount): url = 'https://www.x-rates.com/calculator/?from={}&to={}&amount={}'.format( ccy.upper(), ccy2.upper(), amount) page = HttpService.get(url) soup = BeautifulSoup(page.content, 'html.parser') ele = soup.find('span', {'class': 'ccOutputTrail'}) if ele is None: logger.error('xrates is not available!') return '-1' return ele.previous_sibling.replace(',', '') + ele.get_text(strip=True) # i command def get_idx(self, data): if data.get('callback_query_id', -1) != -1: data['method'] = ['editMessageText', 'answerCallbackQuery'] data['text'] = ''.join([ '{} - {}\n'.format(k, v) for k, v in Constant.IDX_DCT_NO_PREFIX.items() ]) return data['method'] = 'sendMessage' args = data['args'].strip() if args == '': idx_info = self.get_idx_info('%5EHSI') data['text'] = '<b><u>{}</u>\n{} {}</b>'.format( idx_info.get('name', 'NA'), idx_info.get('price', 'NA'), idx_info.get('change', 'NA')) elif args.upper() not in Constant.IDX_DCT_NO_PREFIX.keys(): data[ 'text'] = '"{}" is not a valid index code. Please retry.'.format( args) btn_lst = [{'text': 'View index code', "callback_data": "/i"}] data['reply_markup'] = {"inline_keyboard": [btn_lst]} else: code = args.upper() if '%5E' + code in Constant.IDX_DCT.keys(): code = '%5E' + code idx_info = self.get_idx_info(code) data['text'] = '<b><u>{}</u>\n{} {}</b>'.format( idx_info.get('name', 'NA'), idx_info.get('price', 'NA'), idx_info.get('change', 'NA')) def get_idx_info(self, code): url = 'https://finance.yahoo.com/quote/{0}?p={0}'.format(code) headers = {'User-Agent': ''} page = HttpService.get(url, headers) soup = BeautifulSoup(page.content, 'html.parser') idx_info = {} price_ele = soup.find( 'span', {'class': 'Trsdu(0.3s) Fw(b) Fz(36px) Mb(-4px) D(ib)'}) if price_ele is None or price_ele.get_text(strip=True) == '': logger.error('yahoo finance index is not available!') else: idx_info['price'] = price_ele.get_text(strip=True).replace(',', '') name_ele = soup.find('h1', {'class': 'D(ib) Fz(18px)'}) if name_ele is None or name_ele.get_text(strip=True) == '': logger.error('yahoo finance index name is not available!') else: name = name_ele.get_text(strip=True) name = name[:name.find('(')].strip() idx_info['name'] = name change_ele = soup.find_all( 'span', { 'class': [ 'Trsdu(0.3s) Fw(500) Pstart(10px) Fz(24px) C($negativeColor)', 'Trsdu(0.3s) Fw(500) Pstart(10px) Fz(24px) C($positiveColor)', 'Trsdu(0.3s) Fw(500) Pstart(10px) Fz(24px)' ] }) if change_ele is None or len( change_ele) == 0 or change_ele[0].get_text(strip=True) == '': logger.error('yahoo finance index change is not available!') else: idx_info['change'] = change_ele[0].get_text(strip=True) return idx_info # alert command def alert(self, data): args = data['args'] data['method'] = 'sendMessage' if args.strip() == '': data['text'] = self.view_alert(data) return alert_param = self.verify_alert(data) if len(alert_param) != 0: self.set_alert(data, alert_param) data['text'] = self.view_alert(data) def view_alert(self, data): from_id = data['from_id'] sql = 'SELECT * FROM alerts WHERE fromid=?' param = (from_id, ) rows = self.db.execute(sql, param) if len(rows) == 0: return '<b>No alert set.</b>' else: msg = '' for row in rows: current = 'NA' types, code, operators, amount = row[2], row[3], row[4], row[5] code_text = code if types == 'STK': current = self.get_stock_info(code).get('price', 'NA') elif types == 'FX': ccy = code.split(':')[0] ccy2 = code.split(':')[1] current = self.get_xrates(ccy, ccy2, 1) current = 'NA' if current == '-1' else current code_text = code_text.replace(':', ' to ') elif types == 'IDX': current = self.get_idx_info(code).get('price', 'NA') code_text = code_text.replace('%5E', '') msg += '{} {} {} ({})\n'.format(code_text, operators, amount, current) return '<b>' + msg + '</b>' def verify_alert(self, data): args = data['args'].strip().replace('<', '<').replace('>', '>') err_msg = Constant.ALERT_ERR_MSG end_idx = args.find(' ') end_idx = len(args) if end_idx < 0 else end_idx code = args[:end_idx].upper() if code in Constant.IDX_DCT_NO_PREFIX.keys(): # verify index code types = 'IDX' sym_idx = args.find('<') sym_idx = args.find('>') if sym_idx < 0 else sym_idx if sym_idx < 0: args = '<' + args[end_idx:].strip() else: args = args[sym_idx:].strip() operators = args[:3] amount = args[3:].strip() if '%5E' + code in Constant.IDX_DCT.keys(): code = '%5E' + code elif args[0].isnumeric(): # verify stock code types = 'STK' if len(code) > 4 or not (code.isnumeric()) or \ int(code) == 0 or len(self.get_stock_info(code)) == 0: data[ 'text'] = '"{}" is not a valid stock code. Please retry.'.format( code) return {} code = code.zfill(4) if '<' not in args and '>' not in args: args = code + '<' + args[end_idx:].strip() else: args = code + args[end_idx:].strip() operators = args[4:7] amount = args[7:].strip() else: # verify currency types = 'FX' if len(args) < 5: data['text'] = err_msg return {} if ' to ' not in args: args = args[:3] + ' to HKD' + args[3:] if '<' not in args and '>' not in args: args = args[:10] + ' <' + args[10:] ccy = args[:3] ccy2 = args[7:10] operators = args[11:14] amount = args[14:].strip() if ccy.upper() not in Constant.CCY_DCT.keys(): data[ 'text'] = '"{}" is not a valid currency code. Please retry.'.format( ccy) return {} elif ccy2.upper() not in Constant.CCY_DCT.keys(): data[ 'text'] = '"{}" is not a valid currency code. Please retry.'.format( ccy2) return {} code = ccy.upper() + ':' + ccy2.upper() if operators not in ['<', '>']: data['text'] = err_msg return {} if amount.strip() == '': data['text'] = 'Please enter an amount.' return {} elif not (NumberUtil.is_float(amount)) or float(amount) <= 0: data['text'] = '"{}" is not a valid amount. Please retry.'.format( amount) return {} elif len(str(float(amount)).replace('.', '')) > 9: data['text'] = 'Maximum length of amount is 9. Please retry.' return {} return { 'types': types, 'code': code, 'operators': operators, 'amount': str(float(amount)) } def set_alert(self, data, param): sql = 'REPLACE INTO alerts(fromid,name,types,code,operators,amount,chatid) VALUES(?,?,?,?,?,?,?)' param = (data['from_id'], data['sender_name'], param['types'], param['code'], param['operators'], param['amount'], data['chat_id']) self.db.execute(sql, param) # reset command def reset(self, data): data['method'] = 'sendMessage' args = data['args'].strip() if args == '': data['text'] = self.reset_all(data) return reset_param = self.verify_reset(data) if len(reset_param) != 0: data['text'] = self.reset_alert(data, reset_param) def reset_all(self, data): from_id = data['from_id'] sql = 'DELETE FROM alerts WHERE fromid=?' param = (from_id, ) self.db.execute(sql, param) return '<b>Hi {}, you have cleared all alerts.</b>'.format( data.get('sender_name', '')) def verify_reset(self, data): args = data['args'].strip() err_msg = Constant.RESET_ERR_MSG if args.upper() in Constant.IDX_DCT_NO_PREFIX.keys(): # verify index code types = 'IDX' if '%5E' + args.upper() in Constant.IDX_DCT.keys(): code = '%5E' + args.upper() else: code = args.upper() elif args[0].isnumeric(): # verify stock code types = 'STK' if len(args) > 4 or not (args.isnumeric()) or int(args) == 0: data[ 'text'] = '"{}" is not a valid stock code. Please retry.'.format( args) return {} code = args.zfill(4) else: # verify currency types = 'FX' if (' to ' not in args and len(args) != 3) or (' to ' in args and len(args) != 10): data['text'] = err_msg return {} if len(args) == 3: args = args[:3] + ' to HKD' ccy = args[:3] ccy2 = args[7:10] if ccy.upper() not in Constant.CCY_DCT.keys(): data[ 'text'] = '"{}" is not a valid currency code. Please retry.'.format( ccy) return {} elif ccy2.upper() not in Constant.CCY_DCT.keys(): data[ 'text'] = '"{}" is not a valid currency code. Please retry.'.format( ccy2) return {} code = ccy.upper() + ':' + ccy2.upper() return {'types': types, 'code': code} def reset_alert(self, data, reset_param): from_id = data['from_id'] types = reset_param['types'] code = reset_param['code'] select_sql = 'SELECT * FROM alerts WHERE fromid=? and types=? and code=?' delete_sql = 'DELETE FROM alerts WHERE fromid=? and types=? and code=?' param = (from_id, types, code) sender_name = data.get('sender_name', '') desc = {'STK': 'stock', 'FX': 'exchange rate', 'IDX': 'index'}[types] code_text = code if types == 'FX': code_text = code_text.replace(':', ' to ') elif types == 'IDX': code_text = code_text.replace('%5E', '') rows = self.db.execute(select_sql, param) if len(rows) == 0: return '<b>Hi {}, you do not have alerts on {} {}</b>'.format( sender_name, desc, code_text) else: self.db.execute(delete_sql, param) return '<b>Hi {}, you have cleared alerts on {} {}</b>'.format( sender_name, desc, code_text) # check command def check(self, data): select_sql = 'SELECT * FROM alerts' rows = self.db.execute(select_sql) msgs = [] for row in rows: from_id, name, types, code, operators, amount, chat_id = row amount = float(amount) current = -1 code_text = code if types == 'STK': current = float(self.get_stock_info(code).get('price', -1)) elif types == 'FX': ccy = code.split(':')[0] ccy2 = code.split(':')[1] current = float(self.get_xrates(ccy, ccy2, 1)) code_text = code_text.replace(':', ' to ') elif types == 'IDX': current = float(self.get_idx_info(code).get('price', -1)) code_text = code_text.replace('%5E', '') if current <= 0: continue elif (operators == '<' and current < amount) or (operators == '>' and current > amount): logger.info( 'name: [{}], code: [{}], current: [{}], amount: [{}]'. format(name, code, current, amount)) msg = {} desc = { 'STK': 'stock price', 'FX': 'rate', 'IDX': 'index' }[types] word = {'<': 'lower', '>': 'higher'}[operators] msg_text = '<b>Hi {},\n{} {} is now {},\n{} than {}</b>'.format( name, code_text, desc, current, word, amount) msg['text'] = msg_text msg['chat_id'] = chat_id msgs.append(msg) delete_sql = 'DELETE FROM alerts WHERE fromid=? AND types=? AND code=? AND operators=?' param = (from_id, types, code, operators) self.db.execute(delete_sql, param) if self.email_service: subject = '{} {} notice to {}'.format( code_text, desc, name) self.email_service.send_email(subject, msg_text) if len(msgs) != 0: data['method'] = 'sendMultiMessage' data['msgs'] = msgs # us command def get_us_stk(self, data): data['method'] = 'sendMessage' code = self.get_us_stock_code(data) if code != '': stock_info = self.get_us_stock_info(code) if len(stock_info) == 0 or stock_info.get('price', '') == '': data['text'] = 'Please enter a valid US stock code.' else: data['text'] = '<b><u>{}</u>\n{} {}</b>'.format( stock_info.get('company', 'NA'), stock_info.get('price', 'NA'), stock_info.get('change', 'NA')) def get_us_stock_code(self, data): args = data['args'].strip() if len(args) == 0: return 'BTC-USD' elif not (args.replace('.', '').replace('-', '').isalnum()): data['text'] = '"{}" is not a valid US stock code.'.format(args) return '' return args.upper() def get_us_stock_info(self, code): url = 'https://finance.yahoo.com/quote/{}/'.format(code) headers = {'User-Agent': ''} page = HttpService.get(url, headers) soup = BeautifulSoup(page.content, 'html.parser') stock_info = {} price_ele = soup.find( 'span', {'class': 'Trsdu(0.3s) Fw(b) Fz(36px) Mb(-4px) D(ib)'}) if price_ele is None: logger.error('yahoo finance US stock price is not available!') else: stock_info['price'] = price_ele.get_text(strip=True).replace( ',', '') company_ele = soup.find('h1', {'class': 'D(ib) Fz(18px)'}) if company_ele is None: logger.error('yahoo finance US company name is not available!') else: company = company_ele.get_text(strip=True) company = company[:company.find('(')].strip() stock_info['company'] = company change_ele = soup.find_all( 'span', { 'class': [ 'Trsdu(0.3s) Fw(500) Pstart(10px) Fz(24px) C($negativeColor)', 'Trsdu(0.3s) Fw(500) Pstart(10px) Fz(24px) C($positiveColor)', 'Trsdu(0.3s) Fw(500) Pstart(10px) Fz(24px)' ] }) if change_ele is None or len(change_ele) == 0: logger.error('yahoo finance US stock change is not available!') else: stock_info['change'] = change_ele[0].get_text(strip=True) return stock_info