Exemple #1
0
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))
Exemple #2
0
 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()
Exemple #3
0
    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
Exemple #5
0
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()
Exemple #6
0
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")
Exemple #7
0
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("/")
Exemple #8
0
    # 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()
Exemple #9
0
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
Exemple #10
0
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('<', '&lt').replace('>', '&gt')
        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('&lt')
            sym_idx = args.find('&gt') if sym_idx < 0 else sym_idx
            if sym_idx < 0:
                args = '&lt' + 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 '&lt' not in args and '&gt' not in args:
                args = code + '&lt' + 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 '&lt' not in args and '&gt' not in args:
                args = args[:10] + ' &lt' + 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 ['&lt', '&gt']:
            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 == '&lt'
                  and current < amount) or (operators == '&gt'
                                            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 = {'&lt': 'lower', '&gt': '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)