---
author: Willis Chen
slideOptions:
transition: slide
slideNumber: true
type: slide
GA: G-CH7FZ71WRC
---
# 佈署雲端服務
----
https://drive.google.com/file/d/1aNk3KHI6EVUwxMCS_TiK9yBRPLyucx63/view?usp=sharing
<iframe src="https://drive.google.com/file/d/1aNk3KHI6EVUwxMCS_TiK9yBRPLyucx63/preview
"
frameborder="0"
width="100%"
height="600" >
</iframe>
---

----
[Iaas、Pass、Saas](https://dotblogs.com.tw/007_Lawrence/2017/08/21/155203)
----
:::spoiler **Function as a Service, FaaS**:arrow_forward:

:::
- 就是個函式
- 無狀態(stateless)
- FaaS 隨叫隨開
- 事件驅動(event-triggered)
----
御三家FaaS
- [Amazon Lambda](https://aws.amazon.com/tw/lambda/)
- [Google Cloud Functions](https://cloud.google.com/functions/)
- [Azure Functions](https://azure.microsoft.com/en-us/services/functions/)
----
### 以[Google Cloud Functions](https://cloud.google.com/functions/)為例
----

----

----

---
[🤖 客製化你的AI教學助手-蘇格拉底引導教學法](
https://willismax.github.io/my-site/blog/Customize%20Your%20AI%20Teaching%20Assistant%20-%20A%20Socratic%20Approach)

---
# [Vercel](https://vercel.com/)

---
# [fly.io](https://fly.io/) * LINE
----
## 為何用 [fly.io](https://fly.io/)
[Heroku](https://id.heroku.com/)宣布2022.11起開始收費

----
- [fly.io](https://fly.io/)可部署Python服務,也可以直接自Heroku免費遷移專案

----
### [fly.io 收費標準](https://fly.io/docs/about/pricing/)
:::spoiler 免費方案Hobbi :arrow_forward:
```
Free Allowances
Resources included for free on all plans:
Up to 3 shared-cpu-1x 256mb VMs
3GB persistent volume storage (total)
160GB outbound data transfer
```
:::

---
## [用HackMD API打造個人專屬LINE BOT助手](https://willismax.github.io/my-site/blog/%E7%94%A8HackMD%20API%E6%89%93%E9%80%A0%E5%80%8B%E4%BA%BA%E5%B0%88%E5%B1%ACLINE%20BOT%E5%8A%A9%E6%89%8B)
---
## 流程
- 完成Fly.io基本專案
- 完成LINE Developer基本設定
- 修改Fly.io程式碼
- 連結LINE與Fly.io專案
---
## 1.完成Fly.io基本專案
----
### 在 Fly.io 建一個新的 Flask APP 專案
:::spoiler Lanch an APP with Python:arrow_forward:

:::
:::spoiler 以 Python 部署 Flask APP :arrow_forward:

:::
----
在終端機執行以建立專案
(要先裝[Git](https://git-scm.com/downloads))
```
# clone基本專案
git clone https://github.com/fly-apps/python-hellofly-flask
cd .\python-hellofly-flask\
# 安裝python相依套件
python -m pip install -r requirements.txt
```
----
安裝flyctl
```
## mac
brew install flyctl
## Linux
curl -L https://fly.io/install.sh | sh
## Windows PowerShell
iwr https://fly.io/install.ps1 -useb | iex
```
----
```
# 登入 fly.io
flyctl auth login
# 啟動一個 fly App 專案
flyctl launch
```
----
:::spoiler 設定專案:arrow_forward:

:::
- 可能會要同意覆蓋專案(Y/n)
- 選擇伺服器區域,選附近的(香港或東京)
```
? Overwrite "D:\dev\NUTC-CSIE-MS\python-hellofly-flask\Procfile"? Yes
? Choose an app name (leave blank to generate one):
? Choose a region for deployment: Tokyo, Japan (nrt)
? Would you like to set up a Postgresql database now? No
? Would you like to set up an Upstash Redis database now? No
We have generated a simple Procfile for you. Modify it to fit your needs and run "fly deploy" to deploy your application.
```
----
- 修改`Procfile`的程式碼,`hellofly`為主程式
```
# file name: Procfile
web: gunicorn hellofly:app
^^^^^^^^
```
----
- 檢查完程式後,一鍵佈署。
```
flyctl deploy
```

----
:::spoiler 查看狀態與部署結果:arrow_forward:

:::
```
flyctl status
```
```
flyctl open
```

----
(最後)設定後續本機測試及開發環境
```
//設置環境變數`FLASK_APP`
#linux
export FLASK_APP=hellofly.py
#windows
set FLASK_APP=hellofly.py
//日後啟動flask的指令
flask run
```
```
//如果用環境變數都跑不出來,退而求其次,在`hellofly.py`最後加入
if __name__ == '__main__':
app.run(debug=True)
//日後啟動flask的指令
python hello.py #但[這不是flask建議的啟用方式https://www.maxlist.xyz/2020/04/30/flask-helloworld/)。
```
----
[設定secrets](https://fly.io/docs/reference/secrets/)
```
#新增 secrets
flyctl secrets set DATABASE_URL= ....
#取消 secrets
flyctl secrets unset DATABASE_URL= ....
#列出 secrets list
flyctl secrets list
```
---
## 2.完成LINE Developer基本設定
(建立程式可控的 LINE@ 服務)
----
- 目標為建立 LINE Developer Channel
- 並取得 `Channel ID`、 `Channel Secret` 、 `Channel Access Token` 。
----
## [LINE Developer](https://developers.line.biz/zh-hant/)
- 右上方登入(LINE帳號即可)
[](https://developers.line.biz/zh-hant/)
----
:::spoiler 1.創Provider:arrow_forward:

:::
:::spoiler 2.創Channel:arrow_forward:

:::
:::spoiler 3.選MessageAPI:arrow_forward:

:::
----
:::spoiler 4.建立基本資訊:arrow_forward:
||
|:-:|:-:|
:::
:::spoiler 4.建立基本資訊:arrow_forward:
||
|:-:|:-:|
:::
----
- 同意創立後,取得**Channel ID**、**Channel Secret**
(在Basic Setting )
||
|:-:|:-:
----
- 取得**Channel access token**
(在 Messaging API 分頁,按issue產生)
||
|:-:|:-:
----
- 現在有了`Channel ID`、 `Channel Secret` 、 `Channel Access Token` 。
- 等伺服器程式處理好,用webhook連結就完成了
----
- 通常會把系統預設回應關掉
- 通常會加入圖文選單
---
## 3.修改Fly.io程式碼
----
## <i class="fa fa-github" aria-hidden="true"></i> [Line-bot-fly-flask](https://github.com/willismax/MediaSystem-Python-Course/tree/main/line-bot-fly-flask)
----
在hellofly修改專案檔案
:::spoiler 修改部分檔案 :arrow_forward:

:::
----
:::spoiler my_moduls/: 自製模組 :arrow_forward:

:::
----
:::spoiler app.py: 主程式 :arrow_forward:
```python=
from flask import Flask, request, abort
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import (
MessageEvent, TextMessage, ImageMessage, TextSendMessage
)
import my_moduls.hackmd_bot as hb
import my_moduls.my_functions as mf
from my_moduls.openai_bot import OpenAIBot
from config import (
CHANNEL_ACCESS_TOKEN, CHANNEL_SECRET, LINE_USER_ID, TEMP_NOTE_ID
)
import os
app = Flask(__name__)
line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(CHANNEL_SECRET)
chatgpt = OpenAIBot()
# Messages on start and restart
line_bot_api.push_message(
LINE_USER_ID,
TextSendMessage(text='HackMD Bot Starting')
)
# Listen for all Post Requests from /callback
@app.route("/callback", methods=['POST'])
def callback():
# get X-Line-Signature header value
signature = request.headers['X-Line-Signature']
# get request body as text
body = request.get_data(as_text=True)
app.logger.info("Request body: " + body)
# handle webhook body
try:
handler.handle(body, signature)
except InvalidSignatureError:
abort(400)
return 'OK'
@handler.add(MessageEvent, message=(TextMessage, ImageMessage))
def handle_message(event):
"""LINE MessageAPI message processing"""
if event.source.user_id =='Udeadbeefdeadbeefdeadbeefdeadbeef':
return 'OK'
if event.message.type=='image':
image = line_bot_api.get_message_content(event.message.id)
path = hb.get_user_image(image)
link = hb.upload_img_link(path)
content = hb.add_temp_note(content = f"")
message = TextSendMessage(text=content)
line_bot_api.reply_message(event.reply_token, message)
if event.message.type=='text':
word = str(event.message.text)
if word[:3] == "@fletting":
content = hb.creat_fletting_note(word[3:])
message = TextSendMessage(text=content)
line_bot_api.reply_message(event.reply_token, message)
elif word[:5] == "@todo":
content = hb.update_todo_note(word[5:])
message = TextSendMessage(text=content)
line_bot_api.reply_message(event.reply_token, message)
elif word[:3] == "@ai":
content = event.message.text
chatgpt.add_msg(f"HUMAN:{content}?\n")
reply_msg = chatgpt.get_response()
message = TextSendMessage(text=reply_msg)
line_bot_api.reply_message(event.reply_token, message)
elif event.message.text[:3] == "@翻英":
content = mf.translate_text(event.message.text[3:], "en")
message = TextSendMessage(text=content)
line_bot_api.reply_message(event.reply_token, message)
elif event.message.text[:3] == "@翻日":
content = mf.translate_text(event.message.text[3:] , "ja")
message = TextSendMessage(text=content)
line_bot_api.reply_message(event.reply_token, message)
elif event.message.text[:3] == "@翻中":
content = mf.translate_text(event.message.text[3:] , "zh-tw")
message = TextSendMessage(text=content)
line_bot_api.reply_message(event.reply_token, message)
elif event.message.text[:3] == "@違法":
content = mf.query_illegal_announcement(event.message.text[3:])
message = TextSendMessage(text=content)
line_bot_api.reply_message(event.reply_token, message)
elif event.message.text[:3] == "@職務":
content = mf.search_jobbooks(event.message.text[3:])
message = TextSendMessage(text=content)
line_bot_api.reply_message(event.reply_token, message)
elif event.message.text[:3] == "@選單":
content = f"@翻英、@翻日、@翻中、@違法、@職務、@ai,或存https://hackmd.io/{TEMP_NOTE_ID}"
message = TextSendMessage(text=content)
line_bot_api.reply_message(event.reply_token, message)
else:
content = hb.add_temp_note(word)
message = TextSendMessage(text=content)
line_bot_api.reply_message(event.reply_token, message)
# if __name__ == "__main__":
# port = int(os.environ.get('PORT', 5000))
# app.run(host='0.0.0.0', port=port)
:::
----
:::spoiler Procfile: 設定wsgi用 :arrow_forward:
```
# Modify this Procfile to fit your needs
# web: gunicornapp-hellofly:app
# web: gunicorn app_for_tools:app
# web: gunicorn app_for_openaibot:app
# web: gunicorn app_for_hackmdbot:app
web: gunicorn app:app
```
:::
----
:::spoiler config.py: 各種Token :arrow_forward:
```
CHANNEL_ACCESS_TOKEN = ""
CHANNEL_SECRET = ""
LINE_USER_ID = ''
OPENAI_API_KEY = ''
HACKMD_USER_NAME = ''
IMGUR_CLIENT_ID = ''
HACKMD_API_TOKEN = ''
TODO_NOTE_ID = '' # paste your fixed TODO note id
TEMP_NOTE_ID = '' # paste your fixed TEMP note id
```
:::
----
:::spoiler config.py: 各種Token :arrow_forward:
```
click
Flask
gunicorn
itsdangerous
Jinja2
MarkupSafe
Werkzeug
googletrans==3.1.0a0
line-bot-sdk
requests
pandas
lxml
bs4
PyHackMD
pyimgur
openai
```
----
```
flyctl deploy
```
```
flyctl logs
```
---
## 4.連結LINE與Fly.io專案
----

----

---
## [流程整理](https://hackmd.io/@wiimax/hackmd_line_chat_bot)