創作內容

30 GP

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

作者:熾炎之翼│2022-06-11 00:55:11│巴幣:2,056│人氣:1185
期末考完終於有空做點個人的研究
(資料庫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)

總之如果我直接等於賦值的話
我所有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

而這不是想用就用的
他會要求你填一份問卷 需要有一定英文能力去描述你的用途以及相關事項

總之搞定完之後
就能透過好用的套件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

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

以上
感謝觀看
引用網址:https://home.gamer.com.tw/TrackBack.php?sn=5482544
All rights reserved. 版權所有,保留一切權利

相關創作

留言共 6 篇留言

娃娃音
所以我說那個.gif呢?

06-11 01:03

熾炎之翼
欸恭喜 line API不讓bot發gif檔
最多只能貼連結ㄟ06-11 01:05
村村
好讚喔!

06-11 04:00

熾炎之翼
讚!06-11 10:56
樂小呈
俗投.jpg

06-11 08:57

熾炎之翼
https://media.discordapp.net/attachments/856516846144192543/985014446466408448/IMG_8040.jpg06-11 10:56
無鹽粄條
信不信我放一個石頭在這裡.jpg

06-11 22:13

歿莫
感謝,依照文章修修改改完成了,但是有遇到一個小問題,如果是推特內容是一張圖片跟影片,我看回傳的type都會變成photo,導致無法正確的把兩個資料傳到LINE上,這個問題請問大大有遇過嗎?

06-28 15:19

熾炎之翼
我當時寫這篇文章的時候 推特還沒有更新可以同時發圖片跟影片的功能 所以就我這版本是沒辦法的06-28 15:24
熾炎之翼
我後來因為Twitter API要收費 所以改用了另一個依靠外部網站架設的動態爬蟲爬取媒體內容的方案 所以目前對Twitter API回傳的格式沒什麼研究 所以很抱歉QQ

但是基本上你只要看他整個回傳對那一坨裡面 一定會有mp4結尾的連結 找到以後就可以從結構裡把他套出來用喔06-28 15:27
歿莫
大大你好,我後來也是跟您有一樣想法,想說不管他type本身,直接依照格式的方式來做發送
但是還是不行,後來就放棄了,感謝大大的回應

07-06 13:16

我要留言提醒:您尚未登入,請先登入再留言

30喜歡★h32902227 可決定是否刪除您的留言,請勿發表違反站規文字。

前一篇:2022/06/09 期... 後一篇:2022/06/11 生...

追蹤私訊切換新版閱覽

作品資料夾

aaa1357932大家
各位有空可以來我家看看畫作或聽聽我的全創作專輯!看更多我要大聲說14小時前


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

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