├── .env.sample ├── test_data └── dog.png ├── requirements.txt ├── docker-compose.yml ├── Dockerfile ├── .gitignore ├── .dockerignore ├── templates ├── notify_index.html └── notify_confirm.html ├── fly.toml.sample ├── MIT-LICENSE ├── README.md └── app.py /.env.sample: -------------------------------------------------------------------------------- 1 | LINE_CLIENT_ID= 2 | LINE_CLIENT_SECRET= 3 | LINE_REDIRECT_URI= 4 | -------------------------------------------------------------------------------- /test_data/dog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/louis70109/flask-line-notify/HEAD/test_data/dog.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==2.0.3 2 | requests==2.23.0 3 | lotify 4 | gunicorn 5 | Jinja2==3.1.1 6 | MarkupSafe==2.1.1 7 | Werkzeug==2.0.3 8 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | test_url_for: 5 | image: flask-line-notify 6 | build: . 7 | ports: 8 | - 8000:8000 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7 2 | LABEL Name=flask-line-notify Version=0.0.1 3 | WORKDIR /app 4 | COPY ["requirements.txt", "/app/"] 5 | RUN python3 -m pip install -r requirements.txt 6 | ADD . /app 7 | ENV FLASK_APP=app.py 8 | EXPOSE 8000 9 | # CMD ["gunicorn", "api:app", "--log-file=-"] 10 | CMD ["flask", "run", "--host", "0.0.0.0", "--port", "8000"] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Distribution / packaging 2 | .Python 3 | *.pyc 4 | env/ 5 | build/ 6 | develop-eggs/ 7 | dist/ 8 | downloads/ 9 | eggs/ 10 | .eggs/ 11 | lib/ 12 | lib64/ 13 | parts/ 14 | sdist/ 15 | var/ 16 | *.egg-info/ 17 | .installed.cfg 18 | *.egg 19 | node_modules/ 20 | # Serverless directories 21 | .serverless 22 | .vscode/ 23 | .package-lock.json 24 | .env 25 | __pycache__ 26 | .idea 27 | venv 28 | fly.toml -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | README.md -------------------------------------------------------------------------------- /templates/notify_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | LINE Notify 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /fly.toml.sample: -------------------------------------------------------------------------------- 1 | # fly.toml app configuration file generated for flask-line-notify on 2024-04-04T01:11:08+08:00 2 | # 3 | # See https://fly.io/docs/reference/configuration/ for information about how to use this file. 4 | # 5 | 6 | app = 'flask-line-notify' 7 | primary_region = 'nrt' 8 | 9 | [build] 10 | 11 | [env] 12 | LINE_CLIENT_ID = '' 13 | LINE_CLIENT_SECRET = '' 14 | LINE_REDIRECT_URI = 'https://DOMAIN/callback' 15 | 16 | [http_service] 17 | internal_port = 8000 18 | force_https = true 19 | auto_stop_machines = true 20 | auto_start_machines = true 21 | min_machines_running = 0 22 | processes = ['app'] 23 | 24 | [[vm]] 25 | memory = '256mb' 26 | cpu_kind = 'shared' 27 | cpus = 1 28 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 nijia lin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask-LINE-notify 2 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 3 | [![Python Version](https://img.shields.io/badge/Python-%3E%3D%203.5-blue.svg)](https://badge.fury.io/py/lotify) 4 | 5 | 前一陣子看到保哥寫了一篇 [LINE Notify 的文章](https://blog.miniasp.com/post/2020/02/17/Go-Through-LINE-Notify-Without-Any-Code),詳細的介紹整個操作流程,這個專案則是去實作整個流程的範例, 6 | 7 | 同時也是 [Lotify](https://github.com/louis70109/lotify) 的範例程式,歡迎大家取用試玩。 8 | 9 | # LINE Notify 註冊 10 | 11 | 可以參考我之前[鐵人賽的文章](https://nijialin.com/2019/09/20/Day5-%E5%81%9A%E4%B8%80%E5%80%8B%E8%88%87-LINE-Notify-%E9%80%A3%E5%8B%95%E7%9A%84%E6%9C%8D%E5%8B%99/)。 12 | 13 | 設定的 Callback Url 為 `http://YOUR_DOMAIN/callback`,本地端測試網址就為 `http://localhost:5000/callback` 14 | 15 | # GCP 一鍵佈署 16 | 17 | [![Run on Google Cloud](https://deploy.cloud.run/button.svg)](https://deploy.cloud.run) 18 | 19 | 按下上面部署按鈕之後需要設定 LINE Notify 所需三個`環境變數` 20 | 21 | ![](https://i.imgur.com/jtw8KgI.png) 22 | 23 | 24 | # 本地端測試 25 | 26 | ```sh 27 | cp .env.sample .env 28 | python api.py 29 | ``` 30 | 31 | 或是 32 | 33 | ```dockerfile 34 | cp .env.sample .env 35 | docker-compose up 36 | ``` 37 | 38 | > 兩個方法擇一 39 | 40 | # 步驟 41 | 42 | ### [LINE Notify](https://notify-bot.line.me/zh_TW/) 基本設定 43 | ![](https://i.imgur.com/cqmi2x0l.png) 44 | 45 | --- 46 | 47 | ### 初始頁面 48 | 49 | 開啟瀏覽器後輸入 `http://localhost:5000` 後就會看到一個輸入按鈕 50 | 51 | ![](https://i.imgur.com/u3W3jOil.png) 52 | 53 | --- 54 | 55 | ### 綁定通知 - 選擇`1對1聊天接收` 56 | ![](https://i.imgur.com/bdGHOqbl.png) 57 | 58 | --- 59 | 60 | ### 連動完成 61 | 這時候 LINE Notify 就會推播一個綁定成功的通知 62 | 63 | ![](https://i.imgur.com/veLmsRkl.png) 64 | 65 | --- 66 | 67 | ### 網頁範例 68 | 同時瀏覽器會被導到`/notify/check` 並帶上 code & state 的資訊 69 | ![](https://i.imgur.com/XlkhJwM.png) 70 | 71 | --- 72 | 73 | ### 實測內容 74 | ![](https://i.imgur.com/jf1HUqEl.png) 75 | --- 76 | 77 | # 路由 78 | 79 | - GET / 80 | - 使用者點選綁定的畫面 81 | - GET /callback 82 | - LINE Notify 的設定以及認證完後的 callback 路由 83 | - 幫忙發送推播的路由(因為有[ CORS 問題](https://developer.mozilla.org/zh-TW/docs/Web/HTTP/CORS)所以需要一個 api 來幫忙轉發) 84 | - POST /notify/send 85 | - POST /notify/send_sticker 86 | - POST /notify/send_url 87 | - POST /notify/send_path 88 | - POST /notify/revoke 89 | 90 | # 授權 91 | 92 | [MIT](https://github.com/louis70109/flask-line-notify/blob/master/MIT-LICENSE) 93 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from flask import Flask, render_template, request, jsonify 3 | import os 4 | 5 | from lotify.client import Client 6 | 7 | app = Flask(__name__) 8 | 9 | CLIENT_ID = os.getenv("LINE_CLIENT_ID") 10 | SECRET = os.getenv("LINE_CLIENT_SECRET") 11 | URI = os.getenv("LINE_REDIRECT_URI") 12 | lotify = Client(client_id=CLIENT_ID, client_secret=SECRET, redirect_uri=URI) 13 | 14 | 15 | @app.route("/") 16 | def home(): 17 | link = lotify.get_auth_link(state=uuid.uuid4()) 18 | return render_template("notify_index.html", auth_url=link) 19 | 20 | 21 | @app.route("/callback") 22 | def confirm(): 23 | token = lotify.get_access_token(code=request.args.get("code")) 24 | return render_template("notify_confirm.html", token=token) 25 | 26 | 27 | @app.route("/notify/send", methods=["POST"]) 28 | def send(): 29 | payload = request.get_json() 30 | response = lotify.send_message( 31 | access_token=payload.get("token"), message=payload.get("message") 32 | ) 33 | return jsonify(result=response.get("message")), response.get("status") 34 | 35 | 36 | @app.route("/notify/send/sticker", methods=["POST"]) 37 | def send_sticker(): 38 | payload = request.get_json() 39 | response = lotify.send_message_with_sticker( 40 | access_token=payload.get("token"), 41 | message=payload.get("message"), 42 | sticker_id=630, 43 | sticker_package_id=4, 44 | ) 45 | return jsonify(result=response.get("message")), response.get("status") 46 | 47 | 48 | @app.route("/notify/send/url", methods=["POST"]) 49 | def send_url(): 50 | payload = request.get_json() 51 | response = lotify.send_message_with_image_url( 52 | access_token=payload.get("token"), 53 | message=payload.get("message"), 54 | image_fullsize=payload.get("url"), 55 | image_thumbnail=payload.get("url"), 56 | ) 57 | return jsonify(result=response.get("message")), response.get("status") 58 | 59 | 60 | @app.route("/notify/send/path", methods=["POST"]) 61 | def send_file(): 62 | payload = request.get_json() 63 | response = lotify.send_message_with_image_file( 64 | access_token=payload.get("token"), 65 | message=payload.get("message"), 66 | file=open("./test_data/dog.png", "rb"), 67 | ) 68 | return jsonify(result=response.get("message")), response.get("status") 69 | 70 | 71 | @app.route("/notify/revoke", methods=["POST"]) 72 | def revoke(): 73 | payload = request.get_json() 74 | response = lotify.revoke(access_token=payload.get("token")) 75 | return jsonify(result=response.get("message")), response.get("status") 76 | 77 | 78 | if __name__ == "__main__": 79 | port = os.environ.get("PORT", 8000) 80 | app.run(host="0.0.0.0", port=port, debug=True) 81 | -------------------------------------------------------------------------------- /templates/notify_confirm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 驗證來源以及發送範例 8 | 9 | 10 |

Token: {{token}}

11 |

送純文字 or 貼圖

12 | 請輸入文字: 13 | 14 | 15 | 16 |
17 |

發送圖片網址 (建議有 SSL)

18 | 請輸入文字: 19 | 20 | 請輸入網址: 21 | 22 | 23 |
24 |

發送靜態圖片範例

25 | 請輸入文字: 26 | 27 | 28 |
29 |

註銷 token

30 | 31 | 32 | 33 | 94 | 95 | --------------------------------------------------------------------------------