小屋創作

日誌2022-06-11 00:55

Line Bot研究 - Flex Message應用&結合twitter API

作者:熾炎之翼

期末考完終於有空做點個人的研究
(資料庫final project、AI專題:?)

總之昨晚在快入眠的時候突然想到可以運用flex message來實現一些不錯的功能
於是今天就來實踐看看了

首先正文開始前必須先科普一下何謂flex message
要技術一點的解釋可以去官網參考
而簡而言之就是類似這種訊息

基本上這種橫向可滑動,非傳統文字、照片、影片都是flex message
其中每一個方塊又被稱作Bubble 複數Bubbles組合起來就稱作Carousel
如果各位平時有加一些商家的line bot應該不陌生

對功能爛到哭死板的line來說
flex message無疑是最有彈性也最富可塑性的載體了

而且Line有提供線上捏flex message的網站:https://developers.line.biz/flex-simulator/
版型還能用json匯入匯出
如果有要研究的人務必要上去摸索一下


簡單介紹完flex message以後
該來講講我今夜做出或者說改進的兩大功能了
1.自動搜圖改進(改進之前的作法)
2.twitter預覽(結合twitter API)

先來講自動搜圖
原先的版本我其實一直苦惱一件事
那就是line bot基本上是一問一答形式
超出使用者發言數的回應訊息有次數限制
一個免費bot帳號一個月只有500次訊息的流量
基本上大概算夠  但我個人還是覺得這不是好方法

但是用flex message可以一定程度解決這問題而且更美觀


一則flex message最多可以有10個bubbles
換言之就是最多可以放十張圖片
而且橫向排列對於版面來說更友善

先上個code
if '.jpg' in get_message.lower() or '.png' in get_message.lower():
        if split[-1].isdigit():
            n = int(split[-1])
            if(n > 10):
                n = 10
        else:
            n = 1
        URL_list = []
        params = {
            "engine": "google",
            "tbm": "isch"
        }
        try:
            params['q'] = get_message
            params['api_key'] = random.choice(jdata['serpapi_key'])
            client = GoogleSearch(params)
            data = client.get_dict()
            while('error' in data.keys()):
                params['api_key'] = random.choice(jdata['serpapi_key'])
                client = GoogleSearch(params)
                data = client.get_dict()
            imgs = data['images_results']
            x = 0
            if(n > len(imgs)):
                n = len(imgs)
            for img in imgs:
                if x < n and img['original'][-4:].lower() in ['.jpg', '.png', 'jpeg'] and img['original'][:5] == 'https':
                    URL_list.append(img['original'])
                    x += 1
            with open('json/imgBubble.json', 'r', encoding='utf8') as jfile:
                jdata
= json.load(jfile)
            ctn = []
            for i in range(n):
                tmp
= copy.deepcopy(jdata)
                tmp['hero']['url'] = tmp['hero']['action']['uri'] = URL_list[i]
                ctn.append(tmp)

            if len(ctn) > 1:
                with open('json/carousel.json', 'r', encoding='utf8') as jfile:
                    jdataCtn
= json.load(jfile)
                jdataCtn['contents'] = ctn
                reply = jdataCtn
                line_bot_api.reply_message(event.reply_token,FlexSendMessage('imgs',reply))
            else:
                img_reply(ctn[0][
'hero']['url'])
        except:
            url = 'https://www.google.com.tw/search?q=' + get_message + '&tbm=isch'
            request = requests.get(url=url)
            html = request.content
            bsObj = BeautifulSoup(html, 'html.parser')
            content = bsObj.findAll('img', {'class': 't0fcAb'})
            for i in content:
                URL_list.append(i['src'])
            url = random.choice(URL_list)
            img_reply(url)


跟之前一致的地方就不贅述
有疑問請參考之前這篇

基本上改變也只有把圖片URL套在flex message的格式
我這裡叫出了純圖片的Bubble模板,也就是imgBubble.json
{
    "type": "bubble",
    "hero": {
        "type": "image",
        "url": "",
        "action": {
            "type": "uri",
            "uri": ""
        },
        "size": "full"
    }
}

我們需要的是把圖片url填入顯示圖片點擊連結
也就是我這段:
tmp['hero']['url'] = tmp['hero']['action']['uri'] = URL_list[i]
如此一來 圖片就能正確顯示並點擊即可連結到圖片網址

這裡可能有人發現了
我為何要用變數tmp而不直接等於jdata
還要再套一個copy.deepcopy()?(需import copy)
tmp = copy.deepcopy(jdata)
原因涉及到淺複製(shallow copy)與深複製(deep copy)
可以參考到這篇文:https://ithelp.ithome.com.tw/articles/10221255

總之如果我直接等於賦值的話
我所有Bubble都將套用最後一個Bubble的變更
也就是所有照片都會跟最後一張照片一樣

最後的判斷式這裡就是來看Bubble是否多於一個
if len(ctn) > 1:
    with open('json/carousel.json', 'r', encoding='utf8') as jfile:
        jdataCtn = json.load(jfile)
    jdataCtn['contents'] = ctn
    reply = jdataCtn
else:
    
img_reply(ctn[0]['hero']['url'])
如果有複數Bubbles就要用Carousel格式包起來才能發出來
一樣需要叫出Carousel模板,也就是carousel.json
{
    "type": "carousel",
    "contents": []
}
並且把Bubbles存成的list存進contents

如果只有一個那其實也不用flex message了
直接發成圖片就好
(之所以不一開始就偵測指令是否要求1張
是因為也預防指令要求多張結果套件只抓到一張的罕見情況)



基本上就這樣
算是一個很簡單的更動
而接下來twitter預覽就要講比較久了


首先twitter具有蠻強的擋爬蟲機制
想要穩定又輕鬆的獲取推文(tweet)的訊息
要去使用由官方推出的twitter API

而這不是想用就用的
你需要去這裡申請https://developer.twitter.com/en/portal/
他會要求你填一份問卷 需要有一定英文能力去描述你的用途以及相關事項

總之搞定完之後
就能透過好用的套件tweepy
tweepy本質是封裝過後的twitter API
只要有申請到twitter API就能使用 並提供更好的操作性
透過python在推特上自由爬取資料甚至操控帳號發文等等

於是搭配line bot即可實現這樣的功能
推文有影片時直接把影片發出來
(這裡也順便成為了簡易的twitter影片下載器)
而沒有影片時把推文本身以及所有圖片(如果有)用flex message發出來當預覽

至於為何不把影片放入flex message
因為bubble不支援影片line不意外


這邊就來講解一下code
    if 'twitter.com' in get_message:
        urlElement = get_message.split('/')
        auth = tweepy.OAuthHandler(os.environ.get(
            "TWITTER_APP_KEY"), os.environ.get("TWITTER_APP_SECRET"))
        auth.set_access_token(os.environ.get(
            "TWITTER_ACCESS_TOKEN"), os.environ.get("TWITTER_ACCESS_TOKEN_SECRET"))
        api = tweepy.API(auth)

        try:
            tweet = api.get_status(urlElement[-1], tweet_mode="extended")
            url = tweet.extended_entities["media"][0]["video_info"]["variants"][0]["url"].split('?')[
                0]
            url2 = tweet.extended_entities["media"][0]['media_url']
            if('https' not in url2):
                url2 = url2.replace('http', 'https')
            video_reply(url, url2)
        except:
            tweet = api.get_status(urlElement[-1])
            with open('json/carousel.json', 'r', encoding='utf8') as jfile:
                jdata
= json.load(jfile)
            with open('json/twitterBubble.json', 'r', encoding='utf8') as jfile:
                jdata1
= json.load(jfile)
            with open('json/imgBubble.json', 'r', encoding='utf8') as jfile:
                jdata2
= json.load(jfile)
            ctn = []
            jdata1['body']['contents'][0]['url'] = tweet.user.profile_image_url.replace(
                'http', 'https')
            jdata1['body']['contents'][1]['text'] = tweet.user.name
            jdata1['body']['contents'][2]['text'] = '@'+tweet.user.screen_name
            jdata1['body']['contents'][3]['contents'][1]['text'] = str(
                tweet.user.followers_count)
            jdata1['body']['contents'][5]['contents'][0]['text'] = tweet.text
            jdata1['body']['contents'][5]['contents'][2]['contents'][1]['text'] = str(
                tweet.retweet_count)
            jdata1['body']['contents'][5]['contents'][3]['contents'][1]['text'] = str(
                tweet.favorite_count)
            ctn.append(jdata1)
            tweet = api.get_status(urlElement[-1], tweet_mode="extended")
            if 'media' in tweet.entities:
                for media in tweet.extended_entities['media']:
                    tmp = copy.deepcopy(jdata2)
                    if('https' not in media['media_url']):
                        tmp['hero']['url'] = tmp['hero']['action']['uri'] = media['media_url'].replace(
                            'http', 'https')
                    else:
                        tmp['hero']['url'] = tmp['hero']['action']['uri'] = media['media_url']
                    ctn.append(tmp)
            if len(ctn) > 1:
                with open('json/carousel.json', 'r', encoding='utf8') as jfile:
                    jdataCtn
= json.load(jfile)
                jdataCtn['contents'] = ctn
                reply = jdataCtn
            else:
                reply = jdata1
            line_bot_api.reply_message(
                event.reply_token, FlexSendMessage('tweet', reply))


至於一開始這裡就是申請twitter API的目的了
auth = tweepy.OAuthHandler(os.environ.get("TWITTER_APP_KEY"), os.environ.get("TWITTER_APP_SECRET"))
auth.set_access_token(os.environ.get("TWITTER_ACCESS_TOKEN"), os.environ.get("TWITTER_ACCESS_TOKEN_SECRET"))
api = tweepy.API(auth)
經過這段認證以後才能暢遊推特

而後續我進行了一次try except
用途在於識別推文是否還有影片
如果有的話就透過tweepy爬出影片URL與預覽圖URL
然後再讓line bot發出來
video_reply()是我封裝後的VideoSendMessage()
def video_reply(URL, URL2):
    reply = VideoSendMessage(
        original_content_url=URL, preview_image_url=URL2)
    line_bot_api.reply_message(event.reply_token, reply)

值得注意的是  line API要求使用者必須提供VideoSendMessage()影片的預覽圖URL
而且如同其他功能 所有URL必須為https

這邊我就有被陰過 = =
因為好死不死twitter API提供的URL都是http開頭
如果沒有用replace('http', 'https')把http換成https就會報錯


要是推文沒有含影片
那這裡就要把推文以及所有圖片裝進flex message了

這裡推文本體的bubble是我自己在網站上手捏出來的 花費了一點時間
之後作為模板存成twitterBubble.json
{
    "type": "bubble",
    "body": {
        "type": "box",
        "layout": "vertical",
        "contents": [{
                "type": "image",
                "url": "",
                "position": "absolute",
                "size": "xxs",
                "offsetEnd": "xxl",
                "offsetTop": "xxl"
            }, {
                "type": "text",
                "text": "",
                "weight": "bold",
                "size": "xl",
                "margin": "md",
                "color": "#00acee"
            },
            {
                "type": "text",
                "text": "",
                "weight": "bold",
                "color": "#aaaaaa",
                "size": "xs"
            },
            {
                "type": "box",
                "layout": "horizontal",
                "contents": [{
                        "type": "text",
                        "text": "followers:",
                        "size": "xxs",
                        "color": "#aaaaaa"
                    },
                    {
                        "type": "text",
                        "text": "",
                        "size": "xxs"
                    }
                ],
                "spacing": "none",
                "margin": "none",
                "width": "100px"
            },
            {
                "type": "separator",
                "margin": "xxl"
            },
            {
                "type": "box",
                "layout": "vertical",
                "margin": "xxl",
                "spacing": "sm",
                "contents": [{
                        "type": "text",
                        "text": "",
                        "size": "md",
                        "wrap": true
                    },
                    {
                        "type": "separator",
                        "margin": "xxl"
                    },
                    {
                        "type": "box",
                        "layout": "horizontal",
                        "margin": "xxl",
                        "contents": [{
                                "type": "text",
                                "text": "RETWEETS:",
                                "size": "xxs",
                                "color": "#555555"
                            },
                            {
                                "type": "text",
                                "text": "",
                                "size": "sm",
                                "color": "#111111",
                                "align": "end"
                            }
                        ]
                    },
                    {
                        "type": "box",
                        "layout": "horizontal",
                        "contents": [{
                                "type": "text",
                                "text": "LIKES",
                                "size": "xxs",
                                "color": "#555555"
                            },
                            {
                                "type": "text",
                                "text": "",
                                "size": "sm",
                                "color": "#111111",
                                "align": "end"
                            }
                        ]
                    }
                ]
            },
            {
                "type": "separator",
                "margin": "xxl"
            }
        ]
    },
    "styles": {
        "footer": {
            "separator": true
        }
    }
}


作為感覺還行(?
但相信各位美感更好的大觸一定能捏得更好

然後一大串code就只是把API爬下來的各項資訊放進來而已
後面塞圖片則跟前面自動搜圖原理相同
只是URL來源換成tweepy提供的而已

最後面判斷也是看要不要套進Carousel
與前文相同不贅述


差不多就這樣了
這篇到這裡也打了一個小時多
真的是有點累Orz

雖然文章長但內容不到很多
本來還想套進YouTube API來更新之前的預覽功能
但一個晚上也做不了太多東西XD(尤其Bubble版面設計真的很花時間)

不過這次至少把一些基本模板都定好了
之後對flex message的應用也會更加游刃有餘
有空有想法再繼續寫一些酷功能吧
但現在最好還是先滾去寫課業的code


最後再說一下
自從我開始寫這系列就開始收到很多對line bot有興趣的朋友
說用我的code無法在自己的bot上運行的私訊
照抄code不能運行是非常合理且正常的
首先我們bot的架構沒人能保證相同
而且我自己在展示code時也因為隱私或者篇幅而省略某些外部部分
而這些通常都是非常重要的 你的檔案跟我長不一樣就是0
正確的做法應該是參考本人不專業也不好看的code以後
再把核心概念轉置成能運行在自己bot上的code

當然有問題問我十分歡迎 我也樂於回答
在底下留言我就會回覆

以上
感謝觀看

30

6

LINE 分享

相關創作

【奏起惡夢歌謠(第三階段以後)】蒙大拿無腦速刷

【開箱】KAWASAKI NINJA400 我的第一台重機!

【職業&生活】2024/06/13(四)、車維修&晚餐

留言

開啟 APP

face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】