[PYTHON] I want a Slack bot that calculates and tells me the salary of a part-time job from the schedule of Google Calendar!

Introduction

Google Calendar is very convenient, isn't it? It can be linked with Gmail, can be opened from a PC, and is simple. However, there are some dissatisfaction points. That is that it does not calculate the salary of the part-time job. I wish it would work with the shift management app. .. .. With that said, I tried to make it while using various APIs and AWS that I was interested in as a beginner.

What I used

Overall architecture

I created a bot with the following configuration.

仕組み

procedure

  1. Create an app for creating a bot on the Slack API site
  2. Create an endpoint on AWS that receives Slack events
  3. Create a program to calculate the salary of bytes from Google Calendar using Google API
  4. Introduced to AWS lambda

[Procedure 1] [Procedure 2] Create a Slack bot and create an endpoint on AWS

This procedure was very helpful to the following article. If you follow the article, you can do it without any problems. Even AWS beginners can understand! Completed on the browser! Super Introduction to Slack Bot Using AWS + Slack Event API

[Procedure 3] Create a program to calculate the salary of bytes from Google Calendar

I referred to the following article for how to get the schedule of Google Calendar using Python. Get Google Calender appointments from Python 3

Next, refer to Official Sample and Official Reference. , We will make a program to calculate salary.

The program is roughly divided into the following.

The full code can be found on github, so please refer to that.

handle_slack_event()

This is the entry point. It parses the text sent by the user and posts a message based on it.

# -----Entry point-----
def handle_slack_event(slack_event, context):

    #Output received event information to Cloud Watch log
    logging.info(json.dumps(slack_event))

    #Event API authentication
    if "challenge" in slack_event:
        return slack_event.get("challenge")

    #Other than bot events or message posting events
    #Return as it is to not react
    #I need to return some response to Slack, so I return OK
    #(If not returned, it will be considered a failure and the same request will be sent several times)
    if is_bot(slack_event) or not is_message_event(slack_event):
        return "OK"

    #Extract message text from user
    text = slack_event.get("event").get("text")

    #Payroll Class Declaration
    pay_msg = MakePayMsg()
    
    #Compose a message by parsing text from the user
    if 'help' in text:
        msg = 'Enter the number corresponding to the information you want to know!\n'
        msg += '(1)Next month's salary\n'
        msg += '(2)This year's salary\n'
        msg += '(3)Salary log\n'
    elif text == '1':
        msg = 'Next month's salary is ¥{}is!'.format(pay_msg.monthpay())
    elif text == '2':
        msg = '{}'.format(pay_msg.yearpay())
    elif text == '3':
        msg = 'Salary log\n{}'.format(pay_msg.paylog())
    else:
        msg = '\\Quay/'
    
    #Posting a message
    post_message_to_slack_channel(msg, slack_event.get("event").get("channel"))

    #As a result of a request by the Event API, apart from posting the message
    #I need to return some response to Slack, so I return OK
    #(If not returned, it will be considered a failure and the same request will be sent several times)
    return "OK"

MakePayMsg() Calculate the salary corresponding to the user text and create a message.

# -----Calculate salary and compose a message-----
class MakePayMsg():
    def __init__(self):
    
        self.SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
        self.now = datetime.datetime.now()
        self.events = self.get_event() #Events retrieved from Google Calendar
        self.pay_log = self.make_paylog() #This year's salary log

    # ---Retrieving events from Google Calendar---
    def get_event(self):
        creds = None
        if os.path.exists('token.pickle'):
            with open('token.pickle', 'rb') as token:
                creds = pickle.load(token)
        # If there are no (valid) credentials available, let the user log in.
        if not creds or not creds.valid:
            if creds and creds.expired and creds.refresh_token:
                creds.refresh(Request())
            else:
                flow = InstalledAppFlow.from_client_secrets_file(
                    'credentials.json', self.SCOPES)
                creds = flow.run_local_server(port=0)
            # Save the credentials for the next run
            with open('/tmp/token.pickle', 'wb') as token:
                pickle.dump(creds, token)

        service = build('calendar', 'v3', credentials=creds)

        #Select the calendar that has the byte shift registered
        calender_id = os.environ['CALENDER_ID']

        page_token = None
        events = service.events().list(calendarId=calender_id, pageToken=page_token).execute()
        return events

    # ---Create a salary log for this year---
    def make_paylog(self):
        pay_log = []
        cal = CalculatePay(1013, 1063, 1.25, 22) #Enter hourly wage information
        
        #Extract the start time and end time of the byte from event and calculate the salary
        for event in self.events['items']:
            #Transform start time and end time into datetime
            stime = event['start']['dateTime']
            stime = datetime.datetime(
                int(stime[0:4]), int(stime[5:7]), int(stime[8:10]),
                int(stime[11:13]), int(stime[14:16]))
            etime = event['end']['dateTime']
            etime = datetime.datetime(
                int(etime[0:4]), int(etime[5:7]), int(etime[8:10]),
                int(etime[11:13]), int(etime[14:16]))
            
            #Period to calculate salary
            # (x-1)December~The salary for x years is the amount worked in November x
            if self.now.month != 12:
                sdate = datetime.date(self.now.year-1, 12, 1)
                edate = datetime.date(self.now.year, 11, 30)
            else:
                sdate = datetime.date(self.now.year, 12, 1)
                edate = datetime.date(self.now.year+1, 11, 30)

            #Record one year's salary as a log
            if (stime.date() >= sdate) and (etime.date() <= edate):
                #One day salary calculation from start time and end time
                daypay = cal.calculate(stime, etime)
                #Adjust so that the amount of work is the salary of the next month
                if stime.month==12:
                    daypay_dir = {'date':stime.date(), 'month':1, 'pay':daypay}
                else:
                    daypay_dir = {'date':stime.date(), 'month':stime.month+1, 'pay':daypay}
                pay_log += [daypay_dir]
        pay_log = sorted(pay_log, key=lambda x:x['date'])
        return pay_log
            
    # ---Create a message to show your salary for next month---
    def monthpay(self):
        mpay = 0
        for i in self.pay_log:
            if self.now.month!=12:
                if i['month'] == (self.now.month+1):
                    mpay += i['pay']
            else:
                if i['month'] == 1:
                    mpay += i['pay']
        return mpay

    # ---Create a message to show your salary for one year---
    def yearpay(self):
        mpay_list = [0] * 12
        for i in self.pay_log:
            mpay_list[i['month']-1] += i['pay']
        msg = ''
        for i, mpay in enumerate(mpay_list):
            msg += '{}Month ¥{:,}\n'.format(i+1, mpay)
        msg += '\n total ¥{}'.format(sum(mpay_list))
        return msg

    # ---Create a message to display one year's log---
    def paylog(self):
        msg = ''
        month = 0
        for i in self.pay_log:
            while i['month'] != month:
                msg += '\n{}Month\n'.format(month+1)
                month += 1
            msg += '{} ¥{:,}\n'.format(i['date'], i['pay'])
        return msg

Please note that Lambda can only write files under / tmp. Therefore, when writing to a certain file, it may be said that [Errno 30] Read-only file system occurs when executed with Lambda, even though no error occurred when executed locally. Since an error occurred in this program as well, I changed it as follows.

Change before


with open('token.pickle', 'wb') as token:
    pickle.dump(creds, token)

After change


with open('/tmp/token.pickle', 'wb') as token:
    pickle.dump(creds, token)

CalculatePay() Calculate the daily salary.


# -----Calculate daily wage-----
class CalculatePay():
    def __init__(
        self, basic_pay, irregular_pay, night_rate, night_time):

        self.basic_pay = basic_pay #Weekday hourly wage
        self.irregular_pay = irregular_pay #Hourly wages on Saturdays, Sundays, and holidays
        self.night_rate = night_rate #Late night salary increase rate
        self.night_time = night_time #Time to get midnight salary

    # ---Calculate daily wage---
    def calculate(self, stime, etime):
        night_time = datetime.datetime(stime.year, stime.month, stime.day, self.night_time)
        
        if stime.weekday() >= 5 or jpholiday.is_holiday(stime.date()):
            pay = self.irregular_pay
        else:
            pay = self.basic_pay

        if etime >= night_time:
            normal_time = self.get_h(night_time - stime)
            night_time = self.get_h(etime - night_time)
            daypay = normal_time * pay + night_time * (pay * self.night_rate)
        else:
            normal_time = self.get_h(etime - stime)
            daypay = normal_time * pay

        return round(daypay)

    # ---Convert from x hours y minutes to h hours display---
    def get_h(self, delta_time):
        h = delta_time.seconds / 3600
        return h

[Procedure 4] Introduced to AWS lambda

I have installed some modules to write a program to calculate salary from Google Calendar. If you run this program on AWS Lambda without doing anything, you will get a ModuleNotFoundError. Create an AWS Lambda deployment package for Python so that various modules can be used on AWS Lambda. Simply put, install all the dependent modules in your project directory and zip them up with the executable. This can be done according to the following site. [Python] Using an external module with AWS Lambda

Execution result

I recently started recording byte shifts in Google Calendar, so there isn't much data. Since I introduced this bot, I would like to manage shifts with Google Calendar in the future. (It's an easy-to-understand icon where the byte destination is)

Show help

スクリーンショット 2019-11-28 0.37.29.png

Display of next month's salary

スクリーンショット 2019-11-28 0.36.52.png

This year's salary

スクリーンショット 2019-11-28 0.37.05.png

Salary log

スクリーンショット 2019-11-28 0.42.47.png

Other

スクリーンショット 2019-11-28 0.45.04.png

Recommended Posts

I want a Slack bot that calculates and tells me the salary of a part-time job from the schedule of Google Calendar!
I made a slack bot that notifies me of the temperature
[Discode Bot] I created a bot that tells me the race value of Pokemon
I made a calendar that automatically updates the distribution schedule of Vtuber (Google Calendar edition)
I made a calendar that automatically updates the distribution schedule of Vtuber
I made a LINE bot that tells me the type and strength of Pokemon in the Galar region with Heroku + Flask + PostgreSQL (Heroku Postgres)
The story of making a Line Bot that tells us the schedule of competitive programming
I created a Slack bot that confirms and notifies AWS Lambda of the expiration date of an SSL certificate
Scraping the schedule of Hinatazaka46 and reflecting it in Google Calendar
A formula that simply calculates the age from the date of birth
I started to work at different times, so I made a bot that tells me the time to leave
Get the average salary of a job with specified conditions from indeed.com
The story of IPv6 address that I want to keep at a minimum
[Python] A program that calculates the number of updates of the highest and lowest records
I made a github action that notifies Slack of the visual regression test
The story of Linux that I want to teach myself half a year ago
Import the schedule obtained from "Schedule-kun" into Google Calendar
Make a BOT that shortens the URL of Discord
I want to find the intersection of a Bezier curve and a straight line (Bezier Clipping method)
I want to start a lot of processes from python
I want to send a message from Python to LINE Bot
I want to know the features of Python and pip
I want to get information from fstab at the ssh connection destination and execute a command
I analyzed the image of the Kyudo scoring book (a booklet that records the results of the hits). (Google Colaboratory)
I want to clear up the question of the "__init__" method and the "self" argument of a Python class.
[Google Photo & Slack Photo Bot] A story about making a bot that acquires photos in Google Photos and sends them to Slack.
I want to extract the tag information (title and artist) of a music file (flac, wav).
The story of creating a bot that displays active members in a specific channel of slack with python