[PYTHON] The story of making a Line Bot that tells us the schedule of competitive programming

Updated the Line Bot to notify you of competition programming dates. I also used DB etc. so I will summarize it a little.

What I made

Screen Shot 2020-05-16 at 16.42.23.png

If you send a sentence containing the word "contest" to the bot itself or the group that contains the bot, They will send you the Codeforces and AtCoder contest dates.

By the way, you can register as a friend from the QR code below. Please use it. Screen Shot 2020-05-15 at 23.28.15.png

What I used

How it works

Get AtCoder Contest Dates

AtCoder doesn't have an official contest schedule API, so I'm scraping the official page to schedule a Rated contest. I'm using Python's BeautifulSoup for scraping. First, get the URL information and scrape it. After that, only the necessary data is taken out and formatted.

utils.py


def get_upcoming_at_contests():
    r = get_data(AT_URL, True)
    soup = BeautifulSoup(r, 'html.parser')
    texts = soup.get_text()
    words = [line.strip() for line in texts.splitlines()]
    upcoming = False
    text = []
    for word in words:
        if word == '◉' or word == '':
            continue
        if word == 'Upcoming Contests':
            upcoming = True
            continue
        if word == 'Recent Contests':
            upcoming = False
        if upcoming:
            text.append(word)
    res = []
    for i in range(len(text)):
        if i < 4:
            continue
        if i % 4 == 0:
            text[i], text[i + 1] = text[i + 1], text[i]
        if i % 4 == 1:
            s = ''
            if i == 1:
                pass
            else:
                for t in text[i]:
                    if t == '+':
                        break
                    s += t
                start = datetime.datetime.strptime(s, '%Y-%m-%d %H:%M:%S')
                dur = datetime.datetime.strptime(text[i + 1], '%H:%M')
                end = start + datetime.timedelta(hours=int(dur.strftime('%H')), minutes=int(dur.strftime('%M')))
                s += ' - '
                s += end.strftime('%Y-%m-%d %H:%M:%S')
            text[i] = s
        if i % 4 != 2:
            res.append(text[i])

    return res

Get Codeforces dates

Codeforces has an Official API, so I hit the API to format it.

utils.py


def get_upcoming_cf_contests():
    JST = datetime.timezone(datetime.timedelta(hours=+9), 'JST')
    contents = get_data(CF_URL)
    if contents['status'] == 'FAILED':
        print('Failed to call CF API')
        return
    res = []
    for i in range(len(contents['result'])):
        if (contents['result'][i]['phase'] == 'FINISHED'):
            break
        res.insert(0, contents['result'][i]['name'])
        start = contents['result'][i]['startTimeSeconds']
        s = ''
        start_jst = datetime.datetime.fromtimestamp(start, JST)
        start_time = datetime.datetime.strftime(start_jst, '%Y-%m-%d %H:%M:%S')
        s += start_time
        dur_sec = contents['result'][i]['durationSeconds']
        dur = datetime.timedelta(seconds=dur_sec)
        end_time = start_jst + dur
        s += ' - '
        s += end_time.strftime('%Y-%m-%d %H:%M:%S')
        res.insert(1, s)
    
    return res

DB I'm using PostgreSQL, the official add-on for heroku. When inserting or retrieving a record, a function generates an SQL statement in advance and executes it. I am using a package called Psycopg2.

db.py


import psycopg2

def update_at_table():
    query = ''
    query += 'DELETE FROM {};'.format(AT_TABLE)
    data = utils.format_at_info()
    for i in range(len(data)):
        query += 'INSERT INTO {0} (name, time, range) VALUES (\'{1}\', \'{2}\', \'{3}\');'.format(AT_TABLE, data[i]['name'], data[i]['time'], data[i]['range'])
    execute(query)


def update_cf_table():
    query = ''
    query += 'DELETE FROM {};'.format(CF_TABLE)
    data = utils.format_cf_info()
    for i in range(len(data)):
        query += 'INSERT INTO {0} (name, time) VALUES (\'{1}\', \'{2}\');'.format(CF_TABLE, data[i]['name'], data[i]['time'])
    execute(query)


def get_records(table_name, range=True):
    query = ''
    if range:
        query += 'SELECT name, time, range FROM {};'.format(table_name)
    else:
        query += 'SELECT name, time FROM {};'.format(table_name)
    res = execute(query, False)

    return res


def execute(query, Insert=True):
    with get_connection() as conn:
        if Insert:
            with conn.cursor() as cur:
                cur.execute(query)
                conn.commit()
        else:
            with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
                cur.execute(query)
                res = cur.fetchall()
                return res

I also update the contents of the DB once an hour on heroku's scheduler.

Sending data to Line

I used Flask to create a web application, as provided by the official SDK. The callback function is first called when the application starts. The handle_message function is called inside the callback function to send the message. Flex Message is used to send the message. Since Flex Message is generated in Json format, stock the Json template in advance and use it to send the message.

main.py


@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']
    body = request.get_data(as_text=True)
    app.logger.info('Request body: ' + body)

    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        print('Invalid signature. Please check your channel access token/channel secret.')
        abort(400)
    return 'OK'

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    user_id = event.source.user_id
    to = user_id
    if hasattr(event.source, "group_id"):
        to = event.source.group_id
    TARGET = 'Contest' 
    if not TARGET in event.message.text:
        return
    cf_data = utils.send_cf_info()
    cf_message = FlexSendMessage(
        alt_text='hello',
        contents=cf_data
    )
    at_data = utils.send_at_info()
    at_message = FlexSendMessage(
        alt_text='hello',
        contents=at_data
    )

    try:
        line_bot_api.push_message(
                to,
                messages=cf_message)
        line_bot_api.push_message(
                to,
                messages=at_message)
    except LineBotApiError as e:
        print('Failed to Send Contests Information')

Deploy to heroku

First, create a Procfile in your project. This tells heroku what type of project it is and how it works. Describe the following.

web: python /app/src/main.py

I'm teaching heroku that the process type is Web and the program that actually runs it.

I also used the heroku CLI for deployment. This

$ git push heroku master 

Will allow you to deploy to heroku.

the end

If you have any questions, questions, improvement requests, or mistakes, please let us know via Twitter.

Also, all the source code is on GitHub. Source code

Recommended Posts

The story of making a Line Bot that tells us the schedule of competitive programming
The story of making a university 100 yen breakfast LINE bot with Python
The story of making a question box bot with discord.py
The story of making a module that skips mail with python
The story of making a package that speeds up the operation of Juman (Juman ++) & KNP
The story of making a lie news generator
The story of making a mel icon generator
The story of making a box that interconnects Pepper's AL Memory and MQTT
The story of making a web application that records extensive reading with Django
[Discode Bot] I created a bot that tells me the race value of Pokemon
A story that reduces the effort of operation / maintenance
Make a BOT that shortens the URL of Discord
LINE Bot that notifies you of the stocks of interest
A story that analyzed the delivery of Nico Nama.
Story of trying competitive programming 2
I want a Slack bot that calculates and tells me the salary of a part-time job from the schedule of Google Calendar!
Story of trying competitive programming Part 1
The story of writing a program
A story that struggled to handle the Python package of PocketSphinx
The story of making a standard driver for db with python.
The story of creating a site that lists the release dates of books
I made a slack bot that notifies me of the temperature
I made a Line bot that guesses the gender and age of a person from an image
The story of making a tool that runs on Mac and Windows at the game development site
The story of creating a bot that displays active members in a specific channel of slack with python
A story that visualizes the present of Qiita with Qiita API + Elasticsearch + Kibana
I made a calendar that automatically updates the distribution schedule of Vtuber
The story of making Python an exe
[Python] I made a bot that tells me the current temperature when I enter a place name on LINE
The story of making an immutable mold
The story of developing a web application that automatically generates catchphrases [MeCab]
The story of making a slackbot that outputs as gif or png when you send the processing code
The story of making a sound camera with Touch Designer and ReSpeaker
The story of blackjack A processing (python)
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 creating a store search BOT (AI LINE BOT) for Go To EAT in Chiba Prefecture (2) [Overview]
Competitive programming solution using that the product of the least common multiple of a and b and the greatest common divisor is ab
I made a LINE BOT that returns a terrorist image using the Flickr API
Create a BOT that displays the number of infected people in the new corona
The story of Django creating a library that might be a little more useful
The story of making the Mel Icon Generator version2
The story of misreading the swap line of the top command
[Ansible] Example of playbook that adds a character string to the first line of the file
The story of Linux that I want to teach myself half a year ago
The world's most easy-to-understand explanation of how to make a LINE BOT (1) [Account preparation]
I made a calendar that automatically updates the distribution schedule of Vtuber (Google Calendar edition)
A programming language that protects the people from NHK
# Function that returns the character code of a string
A story that struggled with the common set HTTP_PROXY = ~
Generate that shape of the bottom of a PET bottle
A story about changing the master name of BlueZ
The story that the return value of tape.gradient () was None
Zip 4 Gbyte problem is a story of the past
[Python] A program that compares the positions of kangaroos.
Create a bot that only returns the result of morphological analysis with MeCab on Discord
How to make an interactive LINE BOT 004 (answer the closing date of a listed company)
The story of sys.path.append ()
A tool that automatically turns the gacha of a social game
The story of creating a VIP channel for in-house chatwork
I made a LINE BOT that returns parrots with Go
The story of implementing the popular Facebook Messenger Bot with python