걸어서 개발속으로

1.약 먹었는지 물어보고 체크해주는 기능을 만들어 보자! (2) 본문

Side Project/트위터 봇 제작기

1.약 먹었는지 물어보고 체크해주는 기능을 만들어 보자! (2)

티샤 2022. 8. 26. 01:10

 

사용언어 : Python

사용툴 : Pycharm

사용DB : 일단은 구글 스프레드시트..이나 추후 변경될수도.


<지난 이야기>

 

(1)매일 일정 시간 되면 약 먹었냐고 봇이 물어봄

(2)[명령어] 약 먹었다! 하면 잘했다고 답변하기◀ 여기까지 함

(3)연동db에 약 먹은거 자동으로 기재함

(4) 매달 자동으로 얼마나 잘 챙겨먹었는지 그래프(?)화 하기 (여기까지 할 수 있는가?)

 

스프레드시트에 연동하기는 다음시간에 해보기로 하며 미루어버리고 말았는데..

과연 나는 해낼 수 있을 것인가?

 


(3)[명령어] 약 먹었다! 하면 반응하기 - 답변을 하며 동시에 스프레드 시트에 기재 (2/2)

 

무조건 어떻게 작동 시킬지를 고민해봐야만 했다.

3-1.명령어가 들어온 날짜 [월 / 일 ] 구분해서 시트 이동

그렇다면, 일단 날짜를 받아보기로 했다.

for i in api.mentions_timeline():
    create_time = i.created_at

가장 최근에 받은 멘션 결과값을 하나 뽑으면..

[created_at': 'Tue Aug 23 13:20:31 +0000 2022]

 

나는 2022년 8월 23일 22시 20분에 답을 받은건데,

2022년 8월 23일 13시 20분으로 답을 받은걸로 설정된것.

 

이거..표준시간대(UTC) 기준으로 저장되는 값 아님?

 

그럼 저 created_at 을 어떻게든 처리해야하는 것이다...

 

맨처음 나는 created_at 이 아무리봐도... string으로 보였다.

Tue Aug 23 13:20:31 +0000 2022 이걸 잘 쪼개서..

2022-8-23 13:20:31 이렇게 바꾸고...

근데 이게 문자열 상태니까 데이터 타입을 날짜로 바꿔야하고..

(날짜와 시간(datetime)을 문자열로 출력하려면 strftime을 이용한다)

이걸 .... UTC 기준으로 해서 .... 바꾸고.....(아이고 길다)

..

그래서 이것저것 검색해보다가,

정말 가장 기본적인 것을 놓쳤다.

가장 간단한 방법.

...타입, 그냥 알아보면 되잖아..

 

.....

....데이터 타입이 datetime인데 나는 대체 무엇을 위해 고민하였나..

 

그렇다면 이 created_at 을 datatime 라이브러리를 이용한다면 한국 시간대로 손쉽게 변경할 수 있지 않을까?

 

일단 이 포스팅을 보며 얼레벌레 코딩해보았음

https://velog.io/@hongcoilhouse/Python-time-zone-aware-datetime-%EB%A7%8C%EB%93%A4%EA%B8%B0

import datetime
from pytz import timezone

KST = timezone('Asia/Seoul')

create_time = i.created_at
korea_time = KST.localize(create_time)

 

그랬더니..

Not naive datetime (tzinfo is already set)

 

그니까 naive 객체로 바꿔주란 건가...? tzinfo 는 이미 set 되어있고.

이게 뭔소리임? 


https://docs.python.org/ko/3.10/library/datetime.html

 

검색해보니

날짜와 시간 객체는 시간대 정보를 포함하는지에 따라

“어웨어(aware)”와 “나이브(naive)”로 분류된다고 한다.

 

나이브(naive) datetime naive 객체는 날짜와 시간 정보만을 가지며,

어웨어(aware) 객체는 '시간대' 정보인 tzinfo까지 포함한다!

 

다시 정리해보자면

지금 내가 받은 날짜값은 aware 타입인거고 (시간대까지 포함됨)

공식문서를 보면 tzinfo(시간대)를 'Asia/Seoul'로 바꿔주고 출력하면 되는 거다.

import pytz
import tweepy

for i in api.mentions_timeline():
    create_time = i.created_at
    print("기존시간 : " + str(create_time))
    print(type(create_time))

    KST = pytz.timezone('Asia/Seoul')
    dt_korea = create_time.astimezone(KST)
    print("한국시간 : " + str(dt_korea))
    print("월 : " + str(dt_korea.month))
    print("일 : " + str(dt_korea.day))
    print("시간 :" + str(dt_korea.hour))
    print("-----------------")

명령어 [약먹기]를 받은 멘션의 시간대를 한국타임으로 바꾸는데 성공했다!

 

하지만 이 모든 것을 한 이유는 그다음을 위한 빌드업이다...

 

(3)연동db에 약 먹은거 자동으로 기재함

3-2.시트의 A행에 적힌 숫자와 날짜의 [일]을 비교해서 동일하면 그 [열]을 사용함

다시 어제의 스프레드 시트를 째려본다.

일단.. 년도와 월에 따라 입력되는 시트가 달라질테니까,

①조건에 부합하는 시트로 이동 ②조건에 부합하는 열을 찾아 DB입력

 

3-2-1. 멘션받은 시간대를 년도, 월, 일, 시간으로 받는다.

mention_year = dt_korea.year
mention_month = dt_korea.month
mention_day = dt_korea.day
mention_hour = dt_korea.hour #type은 int(정수)형이다

3-2-2.방금받은 시간대를 워크시트의 제목의 형태(년도-월)로 만든다.

making_title = str(mention_year)+"-"+str(mention_month) #type은 문자열(str)

3-2-3.[티샤 약복용 테스트] 파일의 워크시트 목록을 불러온 다음,

title(제목) 속성만 따로 빼내서 리스트 worksheet_title을 만든다.

worksheet = wks.worksheets() # 워크시트 리스트를 얻음
worksheet_title = [] #워크시트 제목만 얻으려고 리스트 만듬
for i in worksheet:
    worksheet_title.append(i.title) #리스트에 워크시트 제목 넣음
    
    
#['2022-8', '2022-9', '2022-10', '2022-11', '2022-12', '2023-1'] 이렇게 넣어짐
#리스트 각 요소의 type은 string(문자열)

3-2-4. title이 들어있는 리스트인 worksheet_title에 making_title이 있는지 체크하고

동일하다면 그 시트로 이동한다!

즉, 

making_title = '2022-8'

worksheet_title = '2022-8'

이렇게 같은지 비교 하고, 같으면 title의 워크시트로 이동한다는 것.

for title in worksheet_title:
    if making_title == title: #워크시트 목록에서 일치하는지 체크하면 되는거지?
        print("워크시트 : "+str(title))

3-2-5.②조건에 부합하는 열을 찾아 DB입력.. 해야하는데,

이제 이동한 워크시트를 보면 A열이 년월일 중 '일'에 해당된다.

즉 시트의 A열에 적힌 숫자와 날짜의 [일]을 비교해서 동일하면 그 [열]을 이용하는거다.

 

그렇다면..

for title in worksheet_title:
    if making_title == title: #워크시트 목록에서 일치하는지 체크하면 되는거지?
        print("워크시트 : "+str(title))
        print("!"+title+"시트로 이동합니다!")
        this_wks = wks.worksheet(title) #(1)시트 선택함
        print("!이동완료. 선택된 시트는 "+this_wks.title)

1)A행의 리스트(day_list)를 출력하고

2)[날짜의 일]과 A열에 적힌 [숫자]가 일치한다면,

(예를들어 26일이면, A열의 26이 적힌 셀을 찾는 것.)

3) 같은 행에 있는 B열 내지 C열에 입력하기 

가 순서가 된다.

for title in worksheet_title:
    if making_title == title: #워크시트 목록에서 일치하는지 체크하면 되는거지?
        print("워크시트 : "+str(title))
        print("!"+title+"시트로 이동합니다!")
        this_wks = wks.worksheet(title) #(1)시트 선택함
        print("!이동완료. 선택된 시트는 "+this_wks.title)
        #(2)시트의 A행에 적힌 숫자와 날짜의 [일]을 비교해서 동일하면 그 [열]을 사용함
        day_list = this_wks.col_values(1)
        # print("day list를 뽑았음")
        # print("멘션들어온 일" + str(mention_day))
        if str(mention_day) in day_list: #day_list에 mention_day가 있다면
            cell = this_wks.findall(str(mention_day)) #mention_day가 있는 셀을 찾는다.

그렇게 cell을 출력하면..

[<Cell R28C1 '26'>]

..일단, R28C1이 28행(row) 1열(col). 위치인 것은 알겠다.

 

이제 이 녀석을 출력할 차례다.

어쨌든 저 녀석의 타입을 string으로 바꾸고,

공백으로 구분하여 list를 생성.

그렇게 해서 위치값을 따로 뽑아낸다.

if cell is not None: #cell값이 있으면
    cell_site = str(cell).split()[1]
    # print(cell_site)
    # print(type(cell_site)) #타입은 string R27C1
    cell_row = int(cell_site.split("R")[1].split("C")[0])  # m행의 m 28행
    cell_col = int(cell_site.split("R")[1].split("C")[1])  # n열의 n 1열

이후 '28'행과 '1'열을 구분하여 각각 값을 저장해준다.

 

3-3.약 먹었냐고 물어본 트윗의 시간이 오전이면 [아침 약], 오후면 [저녁 약]으로 구분

[아침 약]은 2열, [저녁약]은 3열에 입력해야한다.

그럼, 오전과 오후를 구분하는 법은?

받은 멘션의 시간 mention_hour의 범위만 보면 된다.

 

시간이 0~11이면 오전,

12~23이면 오후.

 

if 0<=mention_hour<=11 :
    print("! 오전 시간대입니다")
    cell_col = cell_col+1
    print("! 열주소 +1 이동")
    this_wks.update_cell(cell_row,cell_col,"약먹음")
    print("!DB 업데이트 완료")
    print("!문제 없이 작동했어요")

elif 12<=mention_hour<=23:
    print("! 오후 시간대입니다")
    cell_col = cell_col+2
    print("! 열주소 +2 이동")
    this_wks.update_cell(cell_row, cell_col, "약먹음")
    print("!DB 업데이트 완료")
    print("!문제 없이 작동했어요")
else:
    print("!시간이 잘못되었음 ")

 

3-4.그렇게 해서.. 체크하고 칭찬과 함께 DB갱신했다는 답멘 보내기

이건.. 첫날했던 것에 추가만 하면 되니, 크게 문제될 것이 없다.

 

 

그렇게 나온 결과!

 

전체적인 알고리즘은 다음과 같다.

 

1.특정시간이되면 나한테 약 먹었냐고 물어봄

2.[약먹기] 명령어로 답멘을 함

3.명령어 확인하면 멘션의 시간을 확인해서 기록을 추가할 시트(달력) 찾고

4.멘션의 날짜와 행을 비교해서 해당 일(day)에 '약복용'을 체크

5.정상적으로 되면 답멘으로 칭찬과 db업데이트 완료! 라고 보냄

 

전체 코드는 다음과 같다.

(임의로 생략한 코드들도 있음)

import pytz
import tweepy
import datetime
import time
import schedule
import gspread

#스프레드 시트
gc = gspread.service_account(filename='json파일')
wks = gc.open_by_url("오픈한 스프레드시트 url")

#트위터 연동
API_KEY = 'api key'
API_KEY_SECRET = 'api token 값'

BOT_ACCESS_TOKEN = '봇 액세스토큰'
BOT_ACCESS_SECRET = '봇 액세스토큰 secret key'

bot = api.verify_credentials()  # 봇의 user obj 반환 / 감로테스트봇
bot_id = bot.id  # 봇 id (고유값, 주소)
timeline_list = api.user_timeline(user_id=bot_id)
last_reply_id = timeline_list[0].id_str

def check_new_mention():
    print("!멘션을 확인하는 함수입니다")
    #이전과 동일하기 때문에 생략함


def check_keyword(mention_timeline_length, mention_return):
    global last_reply_id
    print("!키워드 체크 함수입니다")
    keyword = "[약먹기]"
    for i in range(mention_timeline_length - 1, -1, -1):
        mention = mention_return[i]
        mention_text = mention.text
        mention_time = mention.created_at
        KST = pytz.timezone('Asia/Seoul')
        dt_korea = mention_time.astimezone(KST)
        # print(mention_text, mention_time,dt_korea)
        try:
            if mention.author.id != bot_id:  # 내가 보낸 멘션이 아닐때만 답변하기
                if keyword in mention_text:
                    print("!약을 먹었어요")
                    #멘션 받은 시간을 이용해서 스프레드 시트 이동하기
                    mention_year = dt_korea.year
                    mention_month = dt_korea.month
                    mention_day = dt_korea.day
                    mention_hour = dt_korea.hour
                    making_title = str(mention_year)+"-"+str(mention_month) #2022-8 만듬, 타입은 str
                    worksheet = wks.worksheets() # 워크시트 리스트를 얻음
                    worksheet_title = [] #워크시트 제목만 얻으려고 리스트 만듬
                    for i in worksheet:
                        worksheet_title.append(i.title) #리스트에 워크시트 제목 넣음
                    for title in worksheet_title:
                        if making_title == title: #워크시트 목록에서 일치하는지 체크하면 되는거지?
                            print("워크시트 : "+str(title))
                            print("!"+title+"시트로 이동합니다!")
                            this_wks = wks.worksheet(title) #(1)시트 선택함
                            print("!이동완료. 선택된 시트는 "+this_wks.title)
                            #(2)시트의 A행에 적힌 숫자와 날짜의 [일]을 비교해서 동일하면 그 [열]을 사용함
                            day_list = this_wks.col_values(1)
                            # print("day list를 뽑았음")
                            if str(mention_day) in day_list: #day_list에 mention_day가 있다면
                                cell = this_wks.findall(str(mention_day)) #mention_day가 있는 셀을 찾고
                                print(cell)
                                if cell is not None: #cell값이 있으면
                                    # print("!mention_day를 찾음")
                                    # print(cell)
                                    cell_site = str(cell).split()[1]
                                    cell_row = int(cell_site.split("R")[1].split("C")[0])  # m행의 m 3행
                                    cell_col = int(cell_site.split("R")[1].split("C")[1])  # n열의 n 1열
                                    # print("행과 열을 뽑아냄")
                                    if 0<=mention_hour<=11 :
                                        print("! 오전 시간대입니다")
                                        cell_col = cell_col+1
                                        print("! 열주소 +1 이동")
                                        this_wks.update_cell(cell_row,cell_col,"약먹음")
                                        print("!DB 업데이트 완료")
                                        reply = "@" + mention.user.screen_name + " "
                                        reply_fir = "약을 잘 챙겼군요, 훌륭해요!" + "\n" + "DB 업데이트 완료"
                                        reply_result = reply + reply_fir + "\n\n"
                                        result = reply_result + formtime()
                                        api.update_status(result, in_reply_to_status_id=mention.id_str)
                                        last_reply_id = mention.id_str
                                        print("!문제 없이 작동했어요")

                                    elif 12<=mention_hour<=23:
                                        print("! 오후 시간대입니다")
                                        cell_col = cell_col+2
                                        print("! 열주소 +2 이동")
                                        this_wks.update_cell(cell_row, cell_col, "약먹음")
                                        print("!DB 업데이트 완료")
                                        reply = "@" + mention.user.screen_name + " "
                                        reply_fir = "약을 잘 챙겼군요, 훌륭해요!" + "\n" + "DB 업데이트 완료"
                                        reply_result = reply + reply_fir + "\n\n"
                                        result = reply_result + formtime()
                                        api.update_status(result, in_reply_to_status_id=mention.id_str)
                                        last_reply_id = mention.id_str
                                        print("!문제 없이 작동했어요")
                                    else:
                                        print("!시간이 잘못되었음 ")
                                else:
                                    print("!해당되는 값이 없네요")
                            else :
                                print("!day리스트에 해당되는 값이 없어요")
                else:
                    print("키워드가 없어용")
            else:
                print("오류래용!!")
        except:
            print("!오류래용")
            pass


def formtime():
    now = datetime.datetime.now()
    nowDatetime = now.strftime('%Y-%m-%d %H:%M:%S')
    return nowDatetime
    

def alarm_8am():
    print("!아침 약 먹을 시간이에요!")
    reply = "@" + "트위터아이디" + " "
    reply_contents = "아침 약 챙겼나요?" + "\n\n" + formtime()
    reply_result = reply + reply_contents
    api.update_status(reply_result)


def alarm_11pm():
    print("!저녁 약 먹을 시간이에요!")
    reply = "@" + "트위터아이디" + " "
    reply_contents = "저녁 약 챙겼나요?" + "\n\n" + formtime()
    reply_result = reply + reply_contents
    api.update_status(reply_result)


schedule.every().day.at("08:00").do(alarm_8am)  # 매일 오전 8시에 알람이 울린다
schedule.every().day.at("23:00").do(alarm_11pm)  # 매일 오후 11시에 알람이 울린다

while True:
    schedule.run_pending()
    check_new_mention()
    time.sleep(30)

 

 

...하.. 이 과정이 이렇게만 적으면 평화로워 보이는데..

더보기

정말 많은 일이 있었다...

정말 고통받으며 했다.

계속 type을 적으며 코딩한 이유는 단순하다..

생각없이 코딩했다가 오류가 우수수 났던 것이다..

 코드를 보면 알겠지만 ...

 ... 많은 일이 있었다..

고통의 흔적

 쉬지 않고 빡집중 코딩한 흔적을 남김..

하얗게 불태웠다. 

 

 

 

 


다음 예고편..

서버에 계속 돌리기! 같은걸 시도,

시트 자동생성..할 수 있으면 해보기..

Comments