2021年12月5日 星期日

Flask MVC 架構的應用 (二)

學習目標:
  • Flask MVC 架構的應用 -- 建立樣板目錄 templates
  • 專案目錄結構:
    flaskproject
    |- app
    |--- templates
    |------ index.html
    |--- __init__.py
    |--- router.py
    |main.py
    

Flask MVC 架構實作
  1. 將專案 flaskproject/app 目錄下,新增 templates 資料夾:
    mkdir flaskproject/app/templates
    
  2. 將專案 flaskproject/app/templates 目錄下,新增 index.html 檔案:
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        This is my web site!!
    </body>
    </html>
    
  3. 修改 flaskproject/app/router.py 檔案:
    from flask import render_template
    from app import app
    
    @app.route("/")
    def index():
        #return 'Hello World'
        return render_template('index.html')
    
  4. 重新啟動 Flask :
    flask run
    
  5. 開啟瀏覽器,網址: http://127.0.0.1:5000

Flask MVC 架構的應用 (一)

學習目標:
  • Flask MVC 架構的應用 -- 獨立出 router 的部份
  • 專案目錄結構:
    flaskproject
    |- app
    |--- __init__.py
    |--- router.py
    |main.py
    

Flask MVC 架構實作
  1. 將專案 flaskproject 目錄下,新增 app 資料夾:
    mkdir flaskproject/app
    
  2. 將專案 flaskproject 目錄下,新增 main.py :
    from app import app
    
    if __name__ == "__main__":
        app.run(debug=False)
    
  3. 將專案 flaskproject 目錄下,將 app.py 移至 app 資料夾下並改名稱為 __init__.py :
    mv app.py app/__init__.py
    
  4. 修改在 app 資料夾下的 __init__.py 檔案 :
    from flask import Flask
    
    app = Flask(__name__)
    
    # 導入其他的程式模組
    from app import router
    
  5. 在 app 資料夾下,新增 router.py 檔案 :
    from app import app
    
    @app.route('/')
    def index():
        return 'Hello World'
    
  6. 在 Terminal 視窗下,執行 flask :
    set FLASK_APP=main.py
    flask run
    
  7. 開啟瀏覽器,網址: http://127.0.0.1:5000

Flask 齊步走

學習目標:
  • 安裝 Flask 模組套件!
  • 執行第一支 Flask 程式!

安裝與使用 Flask 模組
  1. 使用 pip 安裝 Flask 模組:
    pip install flask
    
  2. 建立專案資料夾,方便開發專案:
    mkdir flaskproject
    cd flaskproject
    
  3. 建立第一個 flask 專案的檔案 app.py:
    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route('/')
    def index():
      return 'Hello World'
    
  4. 啟動 Flask :
    flask run
    
  5. 開啟瀏覽器,網址: http://127.0.0.1:5000

2021年8月17日 星期二

使用 Flask Login 模組

學習目標:
  • 利用 Flask Login 模組,控制站台運作!

使用 Flask Login 模組
  1. 新增 app/templates/login.html 檔案:
    {% extends "base.html" %}
    {% block title %}大學麵店系統管理區{% endblock %}


    {% block main %}
    <div class="container">
    <div class="row">
    <div class="col">
    <h1>後台管理</h1>
    <form action="/logins" method="post">
    <label for="loginname">登入帳號</label>
    <input type="text" id="loginname" placeholder="請輸入帳號" name="loginname">
    <br>
    <br>
    <label for="passwrod">登入密碼</label>
    <input type="password" id="password" placeholder="請輸入密碼" name="password">
    <br>
    <input type="submit" value="送出">
    </form>
    </div>
    </div> {% endblock %}
  2. 修改 requirements.txt
    (前面略過....)
    Flask-Login>=0.4.1
    
  3. 修改 app/__init__.py
    (前面略過....)
    from flask_login import LoginManager
    (中間略過....)
    # 導入加密 key
    app.secret_key = config.get('Flask','SecretKey')
    
    lm = LoginManager()
    lm.init_app(app)
    lm.session_protection = "strong"
    lm.login_view = 'login'
    lm.login_message = '請輸入帳號密碼'
    
    # 導入其他的程式模組
    from app import router, linebotmodules, loginmodels
    
  4. 使用 terminal 介面,產生 secret_key 內容:
    C:\workspace\LineBot> python
    >>> import os
    >>> os.urandom(16).hex()
    (產生一堆編碼)
    
  5. 修改 config.ini 檔案內容:
    [Flask]
    SecretKey = 上個步驟產生的一堆編碼
    
  6. 新增 app/loginmodels.py 檔案內容:
    from flask_login import UserMixin
    from app import lm
    
    class User(UserMixin):
        pass
    
    @lm.user_loader
    def user_loader(users):
        if users not in users_dict:
            return
    
        user = User()
        user.id = users
        return user
    
    @lm.request_loader
    def request_loader(request):
        user = request.form.get('user_id')
        if user not in users_dict:
            return
        
        user = User()
        user.id = user
    
        user.is_authenticated = request.form['password'] == users_dict[user]['password']
    
        return user
    
    users_dict = {'Owner': {'password': 'HelloWorld'}}
    
  7. 修改路由檔案 app/router.py
    (前面略過....)
    from flask import request, render_template, url_for, redirect, flash
    from flask_login import login_user, logout_user, login_required, current_user
    from app.loginmodels import User, users_dict
    
    @app.route('/logins', methods=['GET','POST'])
    def login():
        if request.method == 'GET':
            return render_template("login.html")
    
        user = request.form['loginname']
        conditionA = user in users_dict
        conditionB = request.form['password'] == users_dict[user]['password']
    
        if conditionA and conditionB:
            user1 = User()
            user1.id = user
            login_user(user1)
            flash(f'{user}!Welcome home!')
            return redirect(url_for('home'))
    
        flash('login failed...')
        return render_template('login.html')
    
    @app.route('/logout')
    def logout():
        user = current_user.get_id()
        logout_user()
        flash(f'{user}!logout!')
        return render_template('login.html')
    
    @app.route('/show_records')
    @login_required
    def show_records():
        python_records =web_select_overall()
        return render_template('show_records.html', html_records=python_records)
    (後面略過....)
    
  8. 修改 app/templates/base.html
    {% if current_user.is_authenticated %}
    <li class="nav-item">
    <a class="nav-link" href="http://mylinebothellotux.herokuapp.com/showmenu">麵店菜單</a>
    </li>
    <li class="nav-item">
    <a class="nav-link" href="http://mylinebothellotux.herokuapp.com/showads">活動訊息</a>
    </li>
    <li class="nav-item">
    <a class="nav-link" href="http://mylinebothellotux.herokuapp.com/logout">登出</a>
    </li>
    {% else %}
    <li class="nav-item">
    <a class="nav-link" href="http://mylinebothellotux.herokuapp.com/logins">登入</a>
    </li>
    {% endif %}
  9. 將專案送上 Heroku 主機!

2021年8月15日 星期日

使用 Line Bot 圖文選單

學習目標:
  • 利用 Line Bot 圖文選單,進行互動!

使用 Line Developer 工具建立圖文選單
  1. 登入 Line Developer,選用 Line Official Account Manager :
  2. 在主頁選項,選擇「聊天室相關」的圖文選單:
  3. 閱讀注意事項:
  4. 建立圖文選單:
  5. 輸入需要的項目內容:
  6. 在內容設定中,選擇版型:
  7. 選擇需要的版型,按下「確定」!
  8. 按下「建立圖片」:
  9. 為每個對達框選擇圖片檔案:
    PS: 可按「Ctrl」+ 三個圖片框,同時上傳三個圖檔,進行設定!
  10. 指定「A」區塊類型為:文字,並輸入文字內容:
  11. 完成後,可以按下「儲存」,完成編輯!
  12. 打開 line 聊天室,可看到該圖文框!

試寫自己的訂購單
  1. 在 app/router.py 中,加入下列內容:
    @app.route('/orderMenu/<nodes>')
    def orders(nodes):
    return render_template("orderMenu.html",nodeslist=nodes)

    @app.route('/orderMenu',methods = ['GET','POST'])
    def addorders():
    if request.method == 'POST':
    print(request.form)
    data = ""
    for key,value in request.form.items():
    data += value
    data += ','
    data = data[:-1]
    connectDB.addOrders(data)
    return render_template("showOrders.html")
    else:
    return render_template("showOrders.html")

    # 顯示訂單列表
    @app.route("/showOrders")
    def showOrders():
    orderslists = connectDB.showOrders()
    return render_template('showOrders.html',menulist=orderslists)
  2. 在 app/templates/orderMenu.html
    {% extends "base.html" %}
    {% block title %}My Web Site{% endblock %}
    {% block main %}
    <div class="container my-5 py-5">
    <div class="row">
    <div class="rol">
    <h1>{{ nodeslist }}訂購單</h1>
    <form action="/orderMenu" method="post">
    <label for="nos">{{ nodeslist }}數量</label>
    <input type="text" id="nos" placeholder="請輸入數量" name="nos">
    <br>
    <label for="cusphone">手機號碼</label>
    <input type="text" id="cusphone" placeholder="請輸入手機號碼" name="cusphone">
    <br>
    <input type="hidden" name="nodes" value="{{ nodeslist }}">
    <input type="submit" value="送出">
    </form>
    </div>
    </div>

    </div>
    {% endblock %}
  3. 新增訂單資料表:
    C:\workspace\LineBot> heroku login -i
    C:\workspace\LineBot> python
    >>> import os
    >>> import psycopg2
    >>> heroku_pgCLI = 'heroku config:get DATABASE_URL -a 你的APP名稱'
    >>> DATABASE_URL = os.popen(heroku_pgCLI).read()[:-1]
    >>> print(DATABASE_URL)
    >>> connection_db=psycopg2.connect(DATABASE_URL, sslmode='require')
    >>> cursor = connection_db.cursor()
    >>> SQL_cmd = '''CREATE TABLE ordermenu(
    ... order_id serial PRIMARY KEY,
    ... nodes VARCHAR (50) NOT NULL,
    ... nos INT NOT NULL,
    ... cusphone VARCHAR (20) NOT NULL
    ... );'''
    >>> cursor.execute(SQL_cmd)
    >>> connection_db.commit()
    >>> cursor.close()
    >>> connection_db.close()
    >>>
    
  4. 新增訂單列表:
    {% extends "base.html" %}
    {% block title %}大學麵店訂單系統{% endblock %}

    {% block main %}

    <!-- 宣告巨集 -->
    {% macro show_row(data, tag) -%}
    {% for menu in data %}
    <div class="row">
    {% if menu[4] == 'Action' %}
    {% for item in menu %}
    <div class="col">
    <{{ tag }}>{{ item }}</{{ tag }}>
    </div>
    {% endfor %}
    {% else %}
    {% for item in menu %}
    <div class="col">
    <{{ tag }}>{{ item }}</{{ tag }}>
    </div>
    {% endfor %}
    <div class="col">
    <button type="submit" value="{{ menu[0],menu[1] }}" name="deldata">刪除</button>
    </div>
    {% endif %}
    </div>
    {% endfor %}
    {%- endmacro %}
    <!-- 宣告結束 -->

    <div class="container my-5 py-5">
    <div class="row">
    <div class="rol">
    <h1>訂單列表</h1>
    </div>
    </div>
    <!-- 顯示菜單用 -->
    {% set cols = (("項次","菜單","數量","手機號碼","Action"),)%}

    {{ show_row(cols, "h2") }}
    <hr class="my-4">
    <form action="/delads" method="post">
    {{ show_row(menulist,"p") }}
    </form>
    <form action="/addOrders" method="get">
    <input type="submit" value="新增訂單">
    </form>
    </div>

    {% endblock %}
  5. 修改 app/dataSQL/connectDB:
    (前面略過....)
    def addOrders(text):
        DATABASE_URL = os.environ['DATABASE_URL']
        connection_db = psycopg2.connect(DATABASE_URL,sslmode='require')
        cursor = connection_db.cursor()
        data = text.split(',')
        table_columns = '(nos,cusphone,nodes)'
        SQL_cmd = f"""INSERT INTO ordermenu { table_columns } VALUES(%s,%s,%s);"""
        cursor.execute(SQL_cmd,(int(data[0]),str(data[1]),str(data[2])))
        connection_db.commit()
        cursor.close()
        connection_db.close()
        return text
    
    def showOrders():
        DATABASE_URL = os.environ['DATABASE_URL']
        connection_db = psycopg2.connect(DATABASE_URL,sslmode='require')
        cursor = connection_db.cursor()
        SQL_cmd = f"""SELECT * FROM ordermenu ORDER by order_id DESC limit 10;"""
        cursor.execute(SQL_cmd)
        connection_db.commit()
        raws = cursor.fetchall()
        data = []
        for raw in raws:
            data.append(raw)
        
        cursor.close()
        connection_db.close()
        return data
    
  6. 將資料表送上 Heroku 主機,進行測試!

2021年8月10日 星期二

推送廣告活動訊息

學習目標:
  • 利用己學過的知識,進行商業化活動!

建立廣告活動訊息資料庫
  1. 增加一個資料表 ads 存放訊息:
    C:\workspace\LineBot> heroku login -i
    C:\workspace\LineBot> python
    >>> import os
    >>> import psycopg2
    >>> heroku_pgCLI = 'heroku config:get DATABASE_URL -a 你的APP名稱'
    >>> DATABASE_URL = os.popen(heroku_pgCLI).read()[:-1]
    >>> print(DATABASE_URL)
    >>> connection_db=psycopg2.connect(DATABASE_URL, sslmode='require')
    >>> cursor = connection_db.cursor()
    >>> SQL_cmd = '''CREATE TABLE ads(
    ... ads_id serial PRIMARY KEY,
    ... adstitle VARCHAR (150) NOT NULL,
    ... adscontent TEXT NOT NULL,
    ... adspicname VARCHAR (150) NOT NULL
    ... );'''
    >>> cursor.execute(SQL_cmd)
    >>> connection_db.commit()
    >>> cursor.close()
    >>> connection_db.close()
    >>>
    
  2. 修改 app/dataSQL/connectDB.py,寫入一段顯示活動資料的SQL語法:
    def showAds():
        DATABASE_URL = os.environ['DATABASE_URL']
        connection_db = psycopg2.connect(DATABASE_URL,sslmode='require')
        cursor = connection_db.cursor()
        SQL_cmd = f"""SELECT * FROM ads ORDER by ads_id DESC limit 5;"""
        cursor.execute(SQL_cmd)
        connection_db.commit()
        raws = cursor.fetchall()
        data = []
        for raw in raws:
            data.append(raw)
        
        cursor.close()
        connection_db.close()
        return data  
     
  3. 修改 app/router.py 檔案,加入一段引導到活動列表的程式:
    # 顯示活動清單列表
    @app.route("/showads")
    def showads():
        adslists = connectDB.showAds()
        return render_template('showads.html',menulist=adslists)
    
  4. 增加一個顯示活動清單的 html 程式碼 app/templates/showads.html:
    {% extends "base.html" %}
    {% block title %}大學麵店活動通知系統{% endblock %}

    {% block main %}

    <!-- 宣告巨集 -->
    {% macro show_row(data, tag) -%}
    {% for menu in data %}
    <div class="row">
    {% if menu[4] == 'Action' %}
    {% for item in menu %}
    <div class="col">
    <{{ tag }}>{{ item }}</{{ tag }}>
    </div>
    {% endfor %}
    {% else %}
    {% for item in menu %}
    <div class="col">
    <{{ tag }}>{{ item }}</{{ tag }}>
    </div>
    {% endfor %}
    <div class="col">
    <button type="submit" value="{{ menu[0],menu[1] }}" name="deldata">刪除</button>
    </div>
    {% endif %}
    </div>
    {% endfor %}
    {%- endmacro %}
    <!-- 宣告結束 -->

    <div class="container my-5 py-5">
    <div class="row">
    <div class="rol">
    <h1>活動列表</h1>
    </div>
    </div>
    <!-- 顯示菜單用 -->
    {% set cols = (("項次","活動標題","活動內容","參考圖檔","Action"),)%}

    {{ show_row(cols, "h2") }}
    <hr class="my-4">
    <form action="/delmenu" method="post">
    {{ show_row(menulist,"p") }}
    </form>
    <form action="/addmenu" method="get">
    <input type="submit" value="新增活動">
    </form>
    </div>

    {% endblock %}
  5. 推送上 Heroku 專案!檢視一下成果!
  6. 修改 app/templates/showads.html 檔案內容,確定新增/刪改的路徑:
    {{ show_row(cols, "h2") }}
    <hr class="my-4">
    <form action="/delads" method="post">
    {{ show_row(menulist,"p") }}
    </form>
    <form action="/addads" method="get">
    <input type="submit" value="新增活動">
    </form>
  7. 修改 app/router.py 檔案內容,加上新增與刪除的程式碼:
    # 新增活動清單
    @app.route("/addads",methods = ['GET','POST'])
    def addads():
        if request.method == 'POST':
            return showads()
        else:
            return showads()
    # 刪除活動清單
    @app.route("/delads",methods = ['GET','POST'])
    def delads():
        if request.method == 'POST':
            print(request.form)
            data = ""
            data1 = ""
            for key, value in request.form.items():
                data = eval(''.join(value))
                data1 = str(data[0])+','+str(data[1])
                print(data1)
                connectDB.deleteAds(data1)
            return showads()
        else:
            return render_template("showads.html")
    
  8. 修改 app/dataSQL/connectDB.py 程式,加入下列程式碼:
    (前面略過....)
    def deleteAds(text):
        DATABASE_URL = os.environ['DATABASE_URL']
        connection_db = psycopg2.connect(DATABASE_URL,sslmode='require')
        cursor = connection_db.cursor()
        data = text.split(',')
        SQL_cmd = f"""DELETE FROM ads WHERE ads_id = %s;"""
        print(int(data[0]))
        cursor.execute(SQL_cmd,[int(data[0])])
        connection_db.commit()
        cursor.close()
        connection_db.close()
        return text
    
  9. 推送上 Heroku 專案!檢視一下成果!
  10. 增加一個填寫資料內容的檔案 app/templates/addads.html:
    {% extends "base.html" %}
    {% block title %}大學麵店活動訊息系統{% endblock %}
    {% block main %}
    <div class="container">
    <div class="row">
    <div class="col">
    <h1>新增活動訊息</h1>
    <form action="/addads" method="post">
    <label for="adstitle">訊息標題</label>
    <input type="text" id="adstitle" placeholder="請輸入訊息標題" name="adstitle">
    <br>
    <br>
    <label for="adscontent">訊息內容</label>
    <input type="text" id="adscontent" placeholder="請輸入訊息內容" name="adscontent">
    <br>
    <br>
    <label for="adspicname">圖片名稱</label>
    <input type="text" id="adspicname" placeholder="請輸入圖片名稱" name="adspicname">
    <br>
    <input type="submit" value="送出">
    </form>
    </div>
    </div>
    </div>
    {% endblock %}
  11. 修改檔案 app/timer.py:
    (前面略過....)
    @scheduleEvent.scheduled_job('interval',minutes=1)
    def timed_job():
        msg = connectDB.showAds()
        print(msg)
        line_bot_api.push_message("你的ID",TextSendMessage(text='test'))
    
  12. 修改檔案 app/dataSQL/connectDB.py:
    (前面略過....)
    def addads(text):
        DATABASE_URL = os.environ['DATABASE_URL']
        connection_db = psycopg2.connect(DATABASE_URL,sslmode='require')
        cursor = connection_db.cursor()
        data = text.split(',')
        table_columns = '(adsname,adscontent,adspicname)'
        SQL_cmd = f"""INSERT INTO menu { table_columns } VALUES(%s,%s,%s);"""
        cursor.execute(SQL_cmd,(str(data[0]),str(data[1]),str(data[2])))
        connection_db.commit()
        cursor.close()
        connection_db.close()
        return text
    
  13. 修改 add/router.py 檔案內容:
    (前面略過....)
    # 新增活動清單
    @app.route("/addads",methods = ['GET','POST'])
    def addads():
        if request.method == 'POST':
            print(request.form)
            data = ""
            for key,value in request.form.items():
                data += value
                data += ','
            data = data[:-1]
            connectDB.addads(data)
            return showads()
        else:
            return render_template("addads.html")
    (後面略過....)
    
  14. 推送上 Heroku 專案!

2021年8月8日 星期日

實作 Flask 框架連結資料庫的網站

學習目標:
  • 了解 Flask 框架連結資料庫的用法!
  • 實作網站CRUD功能!

Flask 框架連結資料庫
  1. 修改 app/router.py 內容,在檔尾增加下列程式內容:
    (前方略過....)
    # 導向查詢菜單內容的網頁
    @app.route("/showmenu")
    def showmenu():
        menulists = connectDB.showallMenu()
        return render_template("menu.html", menulist=menulists)
    
  2. 新增 app/templates/menu.html 檔案,內容如下:
    {% extends "base.html" %}
    {% block title %}大學麵店菜單系統{% endblock %}

    {% block main %}
    <div class="container my-5 py-5">
    <div class="row">
    <div class="rol">
    <h1>菜單列表</h1>
    </div>
    </div>

    {% for menu in menulist %}
    <div class="row">
    {% for item in menu %}
    <div class="col">
    <p>{{ item }}</p>
    </div>
    {% endfor %}
    </div>
    {% endfor %}
    </div>
    {% endblock %}
  3. 推送上 Heroku 專案!利用瀏覽器查看站台: https://你的app.herokuapp.com/showmenu
  4. 修改 app/templates/menu.html 檔案,增加巨集內容(macro)如下:
    {% extends "base.html" %}
    {% block title %}大學麵店菜單系統{% endblock %}

    {% block main %}

    <!-- 宣告巨集 -->
    {% macro show_row(data, tag) -%}
    {% for menu in data %}
    <div class="row">
    {% for item in menu %}
    <div class="col">
    <{{ tag }}>{{ item }}</{{ tag }}>
    </div>
    {% endfor %}
    </div>
    {% endfor %}
    {%- endmacro %}
    <!-- 宣告結束 -->

    <div class="container my-5 py-5">
    <div class="row">
    <div class="rol">
    <h1>菜單列表</h1>
    </div>
    </div>
    <!-- 顯示菜單用 -->
    {% set cols = (("菜單項次","菜單名稱","價錢/每碗"),)%}

    {{ show_row(cols, "h2") }}
    <hr class="my-4">
    {{ show_row(menulist,"p") }}
    </div>

    {% endblock %}
  5. 推送上 Heroku 專案!利用瀏覽器查看站台: https://你的app.herokuapp.com/showmenu

實作 CRUD 連結資料庫
  1. 新增 app/templates/addmenu.html 檔案,內容如下:
    <form action="/addmenu" method="post">
    <label for="menuname">菜單名稱</label>
    <input type="text" id="menuname" placeholder="請輸入菜單名稱" name="menuname">
    <br>
    <label for="menuprize">菜單價錢</label>
    <input type="text" id="menuprize" placeholder="請輸入價格" name="menuprize">
    <br>
    <input type="submit" value="送出">
    </form>
  2. 修改 app/router.py 內容,在檔尾增加下列程式內容:
    (前方略過....)
    # 新增菜單內容的網頁
    @app.route("/addmenu", methods=['GET', 'POST'])
    def addmenu():
        if request.method == 'POST':
            data = ""
            for key, value in request.form.items():
                print(value)
                data += value
                data += ','
            data = data[:-1]
            connectDB.addMenu(data)
            print(data)
            menulists = connectDB.showallMenu()
            return render_template("menu.html", menulist=menulists)
        else:
            return render_template("addmenu.html")
    
  3. 推送上 Heroku 專案!利用瀏覽器查看站台: https://你的app.herokuapp.com/showmenu
  4. 修改 app/router.py 內容,在檔尾增加下列程式內容:
    (前方略過....)
    # 刪除菜單內容的網頁
    @app.route("/delmenu", methods=['GET', 'POST'])
    def delmenu():
        if request.method == 'POST':
            print(request.form)
            data = ""
            data1 = ""
            for key, value in request.form.items():
                data = eval(''.join(value))
                data1 = str(data[0])+','+str(data[1])
                print(data1)
                connectDB.deleteMenu(data1)
    
        menulists = connectDB.showallMenu()
        return render_template("menu.html", menulist=menulists)
    
  5. 修改 app/templates/menu.html 檔案,內容如下:
    {% extends "base.html" %}
    {% block title %}大學麵店菜單系統{% endblock %}

    {% block main %}

    <!-- 宣告巨集 -->
    {% macro show_row(data, tag) -%}
    {% for menu in data %}
    <div class="row">
    {% if menu[3] == 'Action' %}
    {% for item in menu %}
    <div class="col">
    <{{ tag }}>{{ item }}</{{ tag }}>
    </div>
    {% endfor %}
    {% else %}
    {% for item in menu %}
    <div class="col">
    <{{ tag }}>{{ item }}</{{ tag }}>
    </div>
    {% endfor %}
    <div class="col">
    <button type="submit" value="{{ menu[1],menu[2] }}" name="deldata">刪除</button>
    </div>
    {% endif %}
    </div>
    {% endfor %}
    {%- endmacro %}
    <!-- 宣告結束 -->

    <div class="container my-5 py-5">
    <div class="row">
    <div class="rol">
    <h1>菜單列表</h1>
    </div>
    </div>
    <!-- 顯示菜單用 -->
    {% set cols = (("菜單項次","菜單名稱","價錢/每碗","Action"),)%}

    {{ show_row(cols, "h2") }}
    <hr class="my-4">
    <form action="/delmenu" method="post">
    {{ show_row(menulist,"p") }}
    </form>
    <form action="/addmenu" method="get">
    <input type="submit" value="新增菜單">
    </form>
    </div>

    {% endblock %}
  6. 推送上 Heroku 專案!利用瀏覽器查看站台: https://你的app.herokuapp.com/showmenu

2021年8月3日 星期二

利用 Flask 建立支援 Line Bot 的後勤網站

學習目標:
  • 了解 Flask 的用法!
  • 建立支援 Line Bot 機器人的網站!

利用 Bootstrap 製作 Flask Web 站台網頁
  1. 開啟 templates/home.html 檔案,導入 Bootstrap 連結:
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <title>MySite</title>
    </head>
    <body>
    Hello , Hacker !!
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
    </body>
    </html>
  2. 使用導航列!
    (前方略過....)
    <body>
    <nav class="navbar navbar-expand-sm navbar-dark bg-primary">
    <ul class="navbar-nav">
    <li class="nav-item">
    <a class="nav-link" href="#">Home</a>
    </li>
    <li class="nav-item">
    <a class="nav-link" href="#">Link</a>
    </li>
    </ul>
    </nav> (後方略過....)
  3. 使用拆疊式按鍵,方便做成 RWD 網頁
    (前方略過....)
    <body>
    <nav class="navbar navbar-expand-sm navbar-dark bg-primary">

    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavBar">
    <span class="navbar-toggler-icon"></span>
    </button>

    <div class="collapse navbar-collapse" id="collapsibleNavBar">
    <ul class="navbar-nav"> (後方略過....)
  4. 使用 Jumbotron
    (前方略過....)
    </nav>
    <div class="jumbotron">
    <h1>Hello , Hacker !!</h1>
    <p>這是測試頁...
    </div> (後方略過....)
  5. 使用 Google fonts :
    (前方略過....)
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@300&display=swap" rel="stylesheet">

    <title>MySite</title>
    <style> body {font-family: 'Noto Sans TC', sans-serif;}</style>
    </head> (後方略過....)
利用 Jinja2 模板引擎打造網頁
  1. 將 home.html 檔案,複製到 base.html 檔案
  2. 修改 base.html 檔案中,可能異動的部份:
    (前方略過....)
    <title>{% block title %}{% endblock %}</title>
    (中間略過....) 
    <!--取代 jumbotron 那一段-->
    <main>
    {% block main %}{% endblock %}
    </main>

    <footer>
    </footer> (中間略過....) {% block script %}{% endblock %}
    </body>
  3. 修改 home.html 檔案,去掉重複的部份,並加入 base.html 樣板檔:
    {% extends "base.html" %}
    {% block title %}My Web Site{% endblock %}
    {% block main %}
    <div class="jumbotron">
    <h1>Hello , Hacker !!</h1>
    <p>這是測試頁...</p>
    </div>
    <div class="container my-5 py-5">
    <div class="row">
    <div class="col">
    <h2>左側</h2>
    </div>
    <div class="col">
    <h2>中間</h2>
    </div>
    <div class="col">
    <h2>右側</h2>
    </div>
    </div>
    </div>
    {% endblock %}
  4. 送上 Heroku 主機,測試看看內容!
  5. 修改 app/router.py 檔案內容,加入導向查詢菜單的網頁位置:
    # 導向查詢菜單內容的網頁
    app.route("/showmenu")
    def showmenu():
        return render_template("menu.html")
    
  6. 新增菜單的網頁 app/templates/menu.html:
    {% extends "base.html" %}
    {% block title %}大學麵店菜單系統{% endblock %}

    {% block main %}
    <div class="container my-5 py-5">
    <div class="row">
    <div class="rol">
    <h1>菜單列表</h1>
    </div>
    </div>

    {% for menu in menulist %}
    <div class="row">
    <div class="col">
    <p>{{ menu[0] }}</p>
    </div>
    <div class="col">
    <p>{{ menu[1] }}</p>
    </div>
    <div class="col">
    <p>{{ menu[2] }}</p>
    </div>
    </div>
    {% endfor %}
    </div>
    {% endblock %}
  7. 送上 Heroku 主機,測試看看內容!

2021年7月30日 星期五

Line Bot 上的 FlexMessage 用法

學習目標:
  • 了解 Line Bot 上的 FlexMessage 用法!
  • 設定 FlexMessage 連結 Line Bot 機器人資料庫!

產生 FlexMessage 程式碼
  1. 登入 Line Developers ,查看 Flex Message Simulator
  2. 利用「Showcase」選擇自己想要的範例!利用「View as JSON」可以查看檔案內容!
  3. 新增 app/flexmodules 子目錄,方便存放Flex Message 程式!
  4. 利用 Python 程式,產生所需要的 JSON 檔案格式,傳送給 Line 平台!例:flexmessages.py
    def fleximage(url):
        return { "type": "image",
                 "url": url,
                 "size": "full",
                 "aspectRatio": "20:13",
                 "aspectMode": "cover"}
                 
    def flextext(text,size,color,weight='regular',wrap=False):
        return { "type": "text",
                 "text": text,
                 "size": size,
                 "color": color,
                 "weight": weight,
                 "wrap": wrap }
                 
    def flexlogo(text='大學麵店'):
        return flextext(text, size='md',color='#066BAF', weight='bold')
    
    def flextitle(text):
        return flextext(text, size='xl',color='#066BAF', weight='bold')
        
    def flexhead(text):
        return flextext(text, size='xl',color='#555555')
        
    def flexnote(text):
        return flextext(text, size='md',color='#AAAAAA', wrap=True)
        
    def flexseparator(margin='xl'):
        return { "type": "separator", "margin": margin }
        
    def flexbutton(label, data, display_text):
        return { "type": "button",
                 "action": {
                     "type": "postback",
                     "label": label,
                     "data": data,
                     "display_text": display_text },
                     "style": "link",
                     "color": "#066BAF",
                     "height": "sm" }
                     
    def flex_index():
        hero_url = "https://scdn.line-apps.com/n/channel_devcenter/img/fx/01_2_restaurant.png"
        bodys = [flexlogo(),
        		 flextitle('歡迎光臨'),
                 flexseparator(),
                 flexhead('菜單列表'),
                 flexnote('# 查詢所有資料'),
                 flexseparator()]
        footers = [flexbutton('菜單列表','菜單','菜單列表'),
                   flexbutton('單項查詢','單項','單項菜單查詢') ]
    
        FlexMessage = {'type': 'bubble',
                        'hero': fleximage(hero_url),
                        'body': {
                            'type': 'box',
                            'layout': 'vertical',
                            'spacing': 'md',
                            'contents': bodys},
                        'footer': {
                            'type': 'box',
                            'layout': 'vertical',
                            'contents': footers}}
        return FlexMessage
    
  5. 新增程式 app/flexmodules/flextalks.py ,回應 line bot 平台的訊息!
    from app.flexmodules import flexmessages
    from linebot.models import FlexSendMessage
    from app import line_bot_api, handler
    
    def query_menu(event):
        if '菜單查詢' in event.message.text:
            line_bot_api.reply_message(
                event.reply_token,
                FlexSendMessage(alt_text='query record: index',contents=flexmessages.flex_index())
            )
            return True
        else:
            return False
    
  6. 修改 app/linebotmodules.py 裡的程式,加上呼叫 flextalks.py 的程式!
    (前面略過....)
            replay = False
    
            if not replay:
                reply = flextalks.query_menu(event)
    (後面略過....)
    
  7. 上傳至 Heroky 專案內!進行測試!
製作 PostbackEvent 程式碼
  1. 修改 app/linebotmodules.py 裡的程式!在最後一行加上下列程式碼:
    (前面略過....)
    # line 回應的訊息
    @handler.add(PostbackEvent)
    def handle_postback(event):
        print(event)
        flextalks.query_menu_back(event)
    
  2. 修改程式 app/flexmodules/flextalks.py ,import 資料庫連結程式,並加入回應 line bot 平台的訊息!
    (前面略過....)
    from app import line_bot_api, handler
    from app.dataSQL import connectDB
    (中間略過....)
    def query_menu_back(event):
        query = event.postback.data
        print(query)
        if '=' in query:
            print(query.split('=')[1])
            data = connectDB.queryItem(query.split('=')[1])
            menu_name = [i[2] for i in data]
            line_bot_api.reply_message(
                event.reply_token,
                FlexSendMessage(
                    alt_text=f"query record: column {query}",
                    contents= flexmessages.flex_menu_prize(query,menu_name)
                )
            )
            return True
        elif '菜單' in query:
            data = connectDB.showallMenu()
            menu_name = [i[1] for i in data]
            line_bot_api.reply_message(
                event.reply_token,
                FlexSendMessage(
                    alt_text=f"query record: column {query}",
                    contents= flexmessages.flex_menu(query,menu_name)
                )
            )
            return True
        else:
            return False
    
  3. 修改程式 app/flexmodules/fflexmessages.py,在檔案最後面,加上下列程式:
    (前面略過....)
    def flex_menu(keywords, data):
        bodys = [flexlogo(),
        		 flextitle(f'{keywords}'),
                 flexseparator()]
    
        footers = [flexbutton(f"{i}",f"{keywords}={i}",f"查詢 {i}") for i in data]
    
        FlexMessage = {'type': 'bubble',
                        'body': {
                            'type': 'box',
                            'layout': 'vertical',
                            'spacing': 'md',
                            'contents': bodys},
                        'footer': {
                            'type': 'box',
                            'layout': 'vertical',
                            'contents': footers}}
        return FlexMessage
    
    def flex_menu_prize(keywords, data):
        keyword = keywords.split('=')[1]
        bodys = [flexlogo(),
        		 flextitle(f'{keyword}'),
                 flexseparator(),
                 flexnote(f'價格:{data}'),
                 flexseparator()]
    
        footers = [flexbutton("菜單查詢","菜單查詢","菜單列表")]
    
        FlexMessage = {'type': 'bubble',
                        'body': {
                            'type': 'box',
                            'layout': 'vertical',
                            'spacing': 'md',
                            'contents': bodys},
                        'footer': {
                            'type': 'box',
                            'layout': 'vertical',
                            'contents': footers}}
        return FlexMessage
    
  4. 修改 app/dataSQL/connectDB.py,加入單項查詢的資料庫語法:
    (前面略過....)
    def queryItem(keyword):
        DATABASE_URL = os.environ['DATABASE_URL']
        connection_db = psycopg2.connect(DATABASE_URL,sslmode='require')
        cursor = connection_db.cursor()
        SQL_cmd = f"""SELECT * FROM menu WHERE menuname = %s;"""
        cursor.execute(SQL_cmd,[keyword])
        connection_db.commit()
        raws = cursor.fetchall()
        data = []
        for raw in raws:
            data.append(raw)
        
        cursor.close()
        connection_db.close()
        return data
    (後面略過....)
    
  5. 上傳 Heroku 專案後,進行測試!

2021年7月24日 星期六

連結與使用資料庫

學習目標:
  • 了解 Python 與資料庫連結方式!
  • 設定 Line Bot 機器人讀寫資料庫!

Python 與資料庫連結
  1. 在 Heroku 上,新增一個資料庫軟體:
    • 使用「Configure Add-ons」選項!
    • 選擇「Heroku Postgres」項目!
    • 「Plan Name」選用 Free 項目!再按下「Submit Order Form」!
    • 按下「Heroku Postgres」可查看使用情形!
  2. 使用文字介面視窗,安裝 psycopg2 套件,並進行資料庫的建立!
    C:\workspace\LineBot> heroku login -i
    C:\workspace\LineBot> pip install psycopg2
    C:\workspace\LineBot> python
    >>> import os
    >>> import psycopg2
    >>> heroku_pgCLI = 'heroku config:get DATABASE_URL -a 你的APP名稱'
    >>> DATABASE_URL = os.popen(heroku_pgCLI).read()[:-1]
    >>> print(DATABASE_URL)
    >>> connection_db=psycopg2.connect(DATABASE_URL, sslmode='require')
    >>> cursor = connection_db.cursor()
    >>>
    (要注意一下網址!)
    
  3. 建立一張表格,可以放入麵店的點餐單資料!
    >>> SQL_cmd = '''CREATE TABLE menu(
    ... menu_id serial PRIMARY KEY,
    ... menuname VARCHAR (150) UNIQUE NOT NULL,
    ... menuprize Integer NOT NULL
    ... );'''
    >>> cursor.execute(SQL_cmd)
    >>> connection_db.commit()
    >>> cursor.close()
    >>> connection_db.close()
    >>>
    
  4. 查看一下 Heroku 內的資料!
  5. 利用指令,查看資料庫表格相關訊息!
    >>> import os
    >>> import psycopg2
    >>> heroku_pgCLI = 'heroku config:get DATABASE_URL -a 你的APP名稱'
    >>> DATABASE_URL = os.popen(heroku_pgCLI).read()[:-1]
    >>> connection_db=psycopg2.connect(DATABASE_URL, sslmode='require')
    >>> cursor = connection_db.cursor()
    >>> SQL_cmd = '''SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'menu';'''
    >>> cursor.execute(SQL_cmd)
    >>> connection_db.commit()
    >>> data = []
    >>> while True:
    ...   temp = cursor.fetchone()
    ...   if temp:
    ...      data.append(temp)
    ...   else:
    ...      break
    ... 
    >>> print(data)
    [('menu_id', 'integer'), ('menuname', 'character varying'), ('menuprize', 'integer')]
    >>> cursor.close()
    >>> connection_db.close()
    
    PS:刪除資料的方式:SQL_cmd = '''DROP TABLE IF EXISTS menu;''' ,再重複執行上列工作即可!
  6. 將一筆資料輸入資料表內!
    >>> import os
    >>> import psycopg2
    >>> heroku_pgCLI = 'heroku config:get DATABASE_URL -a 你的APP名稱'
    >>> DATABASE_URL = os.popen(heroku_pgCLI).read()[:-1]
    >>> connection_db=psycopg2.connect(DATABASE_URL, sslmode='require')
    >>> cursor = connection_db.cursor()
    >>> record = ('牛肉麵',170)
    >>> table_columns = '(menuname,menuprize)'
    >>> SQL_cmd = f"""INSERT INTO menu {table_columns} VALUES(%s,%s);"""
    >>> cursor.execute(SQL_cmd,record)
    >>> connection_db.commit()
    >>> cursor.close()
    >>> connection_db.close()
    
  7. 將多筆資料輸入資料表內!
    >>> import os
    >>> import psycopg2
    >>> heroku_pgCLI = 'heroku config:get DATABASE_URL -a 你的APP名稱'
    >>> DATABASE_URL = os.popen(heroku_pgCLI).read()[:-1]
    >>> connection_db=psycopg2.connect(DATABASE_URL, sslmode='require')
    >>> cursor = connection_db.cursor()
    >>> record = [('餛飩麵',100),('湯麵',80)]
    >>> table_columns = '(menuname,menuprize)'
    >>> SQL_cmd = f"""INSERT INTO menu {table_columns} VALUES(%s,%s);"""
    >>> cursor.executemany(SQL_cmd,record)
    >>> connection_db.commit()
    >>> count = cursor.rowcount
    >>> print(count," records have inserted!!")
    >>> cursor.close()
    >>> connection_db.close()
    
  8. 列出資料庫的資料表內容
    >>> import os
    >>> import psycopg2
    >>> heroku_pgCLI = 'heroku config:get DATABASE_URL -a 你的APP名稱'
    >>> DATABASE_URL = os.popen(heroku_pgCLI).read()[:-1]
    >>> connection_db=psycopg2.connect(DATABASE_URL, sslmode='require')
    >>> cursor = connection_db.cursor()
    >>> SQL_cmd = f"""SELECT * FROM menu ;"""
    >>> cursor.execute(SQL_cmd)
    >>> connection_db.commit()
    >>> temp = cursor.fetchall()
    >>> print(temp)
    >>> count = cursor.rowcount
    >>> print(count," records have geted!!")
    >>> cursor.close()
    >>> connection_db.close()
    
  9. 有條件的列出資料庫的資料表內容:
    >>> import os
    >>> import psycopg2
    >>> heroku_pgCLI = 'heroku config:get DATABASE_URL -a 你的APP名稱'
    >>> DATABASE_URL = os.popen(heroku_pgCLI).read()[:-1]
    >>> connection_db=psycopg2.connect(DATABASE_URL, sslmode='require')
    >>> cursor = connection_db.cursor()
    >>> prize = 100
    >>> SQL_cmd = f"""SELECT * FROM menu WHERE menuprize < %s;"""
    >>> cursor.execute(SQL_cmd,[prize])
    >>> connection_db.commit()
    >>> temp = cursor.fetchall()
    >>> print(temp)
    >>> count = cursor.rowcount
    >>> print(count," records have geted!!")
    >>> cursor.close()
    >>> connection_db.close()
    
  10. 更新資料庫的資料表內容:
    >>> import os
    >>> import psycopg2
    >>> heroku_pgCLI = 'heroku config:get DATABASE_URL -a 你的APP名稱'
    >>> DATABASE_URL = os.popen(heroku_pgCLI).read()[:-1]
    >>> connection_db=psycopg2.connect(DATABASE_URL, sslmode='require')
    >>> cursor = connection_db.cursor()
    >>> name = "湯麵"
    >>> prize = 50
    >>> SQL_cmd = f"""UPDATE menu SET menuprize = %s WHERE menuname = %s;"""
    >>> cursor.execute(SQL_cmd,(prize,name))
    >>> connection_db.commit()
    >>> count = cursor.rowcount
    >>> print(count," records have updated!!")
    >>> cursor.close()
    >>> connection_db.close()
    
  11. 刪除資料庫內的資料表內容:"DELETE FROM menu WHERE menuname = '';"
Python 與資料庫連結
  1. 修改 LineBot 專案的 requirements.txt:
    Flask>=1.1.1
    gunicorn>=20.0.4
    line-bot-sdk>=1.15.0
    APScheduler>=3.6.1
    psycopg2>=2.0.0
    
  2. 修改 LineBot 專案的程式碼:app/linebotmodules.py
    import app.dataSQL import callData
    (中間略過....)
    # 靈活展現文字
    @handler.add(MessageEvent, message=TextMessage)
    def replyText(event):
        if event.source.user_id == "Uf4a596a6eb65eabf52c003ffe325a21d":
           reply = False
           if not reply:
               reply = callData.showData(event)
    (接下來的程式碼,請先註解....)
    
  3. 在 app 資料夾新增 dataSQL 資料夾!
  4. 在資料夾 dataSQL 中,新增檔案 connectDB.py:
    import os
    import psycopg2
    
    def showallMenu():
        DATABASE_URL = os.environ['DATABASE_URL']
        connection_db = psycopg2.connect(DATABASE_URL,sslmode='require')
        cursor = connection_db.cursor()
        SQL_cmd = f"""SELECT * FROM menu ;"""
        cursor.execute(SQL_cmd)
        raws = cursor.fetchall()
        data = []
        for raw in raws:
            data.append(raw)
    
        return data
    
  5. 在資料夾 dataSQL 中,新增檔案 callData.py:
    from app import line_bot_api
    from linebot.models import TextSendMessage, ImageSendMessage, TemplateSendMessage, messages
    from linebot.models import ImageCarouselTemplate, ImageCarouselColumn, URIAction
    from app.dataSQL import connectDB
    
    import random
    
    def showData(event):
        if '菜單' in event.message.text:
            data = connectDB.showallMenu()
            print_text = ""
            for i in data:
                print_text += str(i[1])
                print_text += str(i[2])
                print_text += "\n"
    
            line_bot_api.reply_message(
                event.reply_token,
                TextSendMessage(text=print_text)
            )
    
            return True
        else:
            return False
    

2021年7月22日 星期四

機器人主動推送通知

學習目標:
  • 在 Flask 上,寫一個網頁,防止 Heroku 主機休眠!
  • 寫一個定時推送通知的 Line Bot 機器人程式!

寫個簡易的 Flask 小網頁
  1. 修改 app/router.py 的內容
    (前方省略....)
    from flask import render_template
    
    # 設定預設網頁
    @app.route("/")
    def home():
        return render_template("home.html")
    (後方省略....)
    
  2. 在專案目錄 LineBot 下,建立 app/templates/home.html 的 HTML 檔案:
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <title>MyLineBot</title>
    </head>
    <body>
    <h1>Hello, World</h1>
    </body>
    </html>
  3. 將專案推向 Heroku 主機....
  4. 打開瀏覽器,輸入http://你的專案.herokuapp.com/

定時推送通知的機器人程式
  1. 安裝 APScheduler
    C:\> pip install APScheduler
    
  2. 在專案目錄中,寫一個 timer.py 的時鐘程式
    from apscheduler.schedulers.blocking import BlockingScheduler
    import urllib.request
    
    scheduleEvent = BlockingScheduler()
    
    @scheduleEvent.scheduled_job('cron',day_of_week='mon-fri',minute='*/20')
    def scheduled_job():
        website = "https://mylinebothellotux.herokuapp.com/"
        connection = urllib.request.urlopen(website)
    
        for i, value in connection.getheaders():
            print(i,value)
    
    scheduleEvent.start()
    
  3. 修改 Procfile 檔案內容
    web: gunicorn main:app --preload
    clock: python timer.py
    
  4. 修改 requirements.txt 檔案內容
    Flask>=1.1.1
    gunicorn>=20.0.4
    line-bot-sdk>=1.15.0
    APScheduler>=3.6.1
    
  5. 上傳專案到 heroku
  6. 修改 Heroku 的設定值
  7. 建立主動推送通知的方式:
    line_bot_api.push_message(to, TextSendMessage(text='Morning Sir'))
    
  8. 修改成需要的樣子:linebotmodules.py
     (前方省略....)
     except:
              #hello_text = echo(event.message.text)
              line_bot_api.push_message(
                '你的 User ID',
                TextSendMessage(text='沒找到')
              )
    
  9. 推送上 Heroku !
  10. 在手機 line 上,輸入任何一個句子!

2021年7月20日 星期二

到網路上爬圖片吧!

學習目標:
  • 寫出網路爬蟲程式,將 Google 上的圖檔回傳!

寫出網路爬蟲程式
  1. 開啟瀏覽器,利用 Google 進行查詢!按下「F12」進行網址的觀察!
  2. 在 python 文字介面中,進行分析與測試:
    C:\workspace\LineBot> python
    >>>
    
  3. 導入 urllib ,對 google 進行網站的連結:
    >>> import urllib.request
    >>> url = "https://www.google.com/"
    >>> conn = urllib.request.urlopen(url)
    >>> print(conn)
    <http.client.HTTPResponse object at 0x7f0d76035550>
    
  4. 將接收的物件,轉成資料印出來:
    >>> data = conn.read()
    >>> print(data)
    (印出的資料太多了,省略一下...)
    
  5. 修正一下,將 headers 的參數加上,限制資料印出的數量:
    >>> header = { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0' }
    >>> req = urllib.request.Request(url,headers=header)
    >>> conn = urllib.request.urlopen(req)
    >>> data = conn.read()
    >>> print(data)
    (印出的資料太多了,省略一下...)
    
    PS: 你有個網頁好幫手「F12」!
  6. 回瀏覽器,在 google 查詢一下某家書商的名稱,再切換成圖片,觀察一下網址變化
    PS: 注意其網址的組成!
  7. 上圖中,按下滑鼠右鍵,可以「複製網址」
  8. 將網址複製後,貼至 python 的文字介面視窗內,進行分析!
    PS:觀察之後,可以猜測:
    • q=%E5%8D%9A%E7%A2%A9: 代表查詢字串
    • tbm=isch :指的是查詢圖片
  9. 使用分析函式,進行相關網址分析:
    >>> u = urllib.request.urlparse(search_url)
    >>> print(u)
    
  10. 進行下一步的分析!
    >>> u[4]
    >>> urllib.parse.parse_qs(u[4])
    
  11. URL 分析列表,有助於組合回原來的查詢字串:
     Attribute   Index   Value   Value if not present 
    scheme 0 URL scheme specifier scheme parameter
    netloc 1 Network location part empty string
    path 2 Hierarchical path empty string
    params 3 Parameters for last path element empty string
    query 4 Query component empty string
    fragment 5 Fragment identifier empty string
    username User name None
    password Password None
    hostname Host name (lower case) None
    port Port number as integer,if present None
  12. 大致上了解其組成結構後,可以進行測試:
    >>> test = {'tbm': 'isch', 'q': '博碩'}
    >>> urllib.parse.urlencode(test)
    'tbm=isch&q=%E5%8D%9A%E7%A2%A9'
    
  13. 將下列字串,放回瀏覽器的網址列,觀查結果是否相同:
    https://www.google.com/search?tbm=isch&q=%E5%8D%9A%E7%A2%A9
    
  14. 回到文字介面中,持續進行測試:
    >>> url = f"https://www.google.com/search?{urllib.parse.urlencode(test)}/"
    >>> req = urllib.request.Request(url, headers=header)
    >>> conn = urllib.request.urlopen(req)
    >>> data = conn.read()
    >>> print(data)
    (資料出現太多,省略過去....)
    
  15. 從瀏覽器中,分析圖片位於 HTML 語法中的何處!提示:在「檢視原始碼中」,查詢關鍵字詞:"img data-src"
  16. 切回文字介面,設定關鍵字詞的樣板:正規化設定
    >>> import re
    >>> template = '"(https://encrypted-tbn0.gstatic.com[\S]*)"'
    >>> image_list = []
    >>> for i in re.finditer(template,str(data,'utf-8')):
    ...     image_list.append(i.group(1))
    >>> image_list[:5]
    
    PS: 語法注意事項
    • [\S]: 空白字元除外
    • * : 任意字數的字元
    • .group(1): 只頡取 template 字串中的有()號的內容資料
    • [:5] : 取回前五行資料!
  17. 整理下過的指令,可容易形成程式檔案:
    import urllib.request
    import re
    import random
    
    search_key_word = {'tbm': 'isch', 'q': event.message.text}
    url = f"https://www.google.com/search?{urllib.parse.urlencode(search_key_word)}/"
    header = { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0' }
    req = urllib.request.Request(url, headers=header)
    conn = urllib.request.urlopen(req)
    data = conn.read()
    template = '"(https://encrypted-tbn0.gstatic.com[\S]*)"'
    image_list = []
    for i in re.finditer(template,str(data,'utf-8')):
      image_list.append(i.group(1))
    
    random_image_url = image_list[random.randint(0, len(image_list)-1)]
    
    line_bot_api.reply_message(
      event.reply_token,
      ImageSendMessage(
        original_content_url=random_image_url,
        preview_image_url=random_image_url
      )
    )
    
  18. 利用 line-bot-sdk-python 提供的 TemplateSendMessage 可以一次取得多張圖片:
    TemplateSendMessage(
      alt_text=alt_text
      template=ImageCarouselTemplate(
        columns=[ImageCarouselColumn(
          image_url='https://website/image.jpg',
          action=URIAction(uri='https://website',label='label'))]
      )
    )
    
  19. 修改 LineBot/app/linebotmodules.py 檔案,將上面試過的指令,一一寫入檔案內!
    from linebot.models.send_messages import ImageSendMessage
    from app import line_bot_api, handler
    from linebot.models import MessageEvent, TextMessage, TextSendMessage
    
    import urllib.request
    import re
    import random
    
    # 查詢 google
    @handler.add(MessageEvent, message=TextMessage)
    def replyText(event):
        if event.source.user_id == "Uf4a596a6eb65eabf52c003ffe325a21d":
    
                search_key_word = {'tbm': 'isch', 'q': event.message.text}
                url = f"https://www.google.com/search?{urllib.parse.urlencode(search_key_word)}/"
                header = { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0' }
                req = urllib.request.Request(url, headers=header)
                conn = urllib.request.urlopen(req)
                data = conn.read()
                template = '"(https://encrypted-tbn0.gstatic.com[\S]*)"'
                image_list = []
                
                for i in re.finditer(template,str(data,'utf-8')):
                    image_list.append(re.sub(r'\\u003d','=',i.group(1)))
    
                random_image_url = image_list[random.randint(0, len(image_list)+1)]
    
                line_bot_api.reply_message(
                    event.reply_token,
                    ImageSendMessage(
                        original_content_url=random_image_url,
                        preview_image_url=random_image_url
                    )
                )
    
  20. 將程式推上 Heroku 主機,並且進行測試!
  21. 修改 LineBot/app/linebotmodules.py 檔案,加入 TemplateSendMessage 模組!
    from linebot.models.send_messages import ImageSendMessage
    from app import line_bot_api, handler
    from linebot.models import MessageEvent, TextMessage, TextSendMessage
    
    import urllib.request
    import re
    import random
    
    # 查詢 google
    @handler.add(MessageEvent, message=TextMessage)
    def replyText(event):
        if event.source.user_id == "Uf4a596a6eb65eabf52c003ffe325a21d":
                search_key_word = {'tbm': 'isch', 'q': event.message.text, 'client': 'img'}
                url = f"https://www.google.com/search?{urllib.parse.urlencode(search_key_word)}/"
                header = { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0' }
                req = urllib.request.Request(url, headers=header)
                conn = urllib.request.urlopen(req)
                data = conn.read()
                template = '"(https://encrypted-tbn0.gstatic.com[\S]*)"'
                image_list = []
                for i in re.finditer(template,str(data,'utf-8')):
                    image_list.append(re.sub(r'\\u003d','=',i.group(1)))
    
                #random_image_url = image_list[random.randint(0, len(image_list)-1)]
                random_image_list = random.sample(image_list,k=3)
    
                image_template = ImageCarouselTemplate(
                    columns=[ImageCarouselColumn(image_url=urx,action=URIAction(label=f'image{j}',
                    uri=urx)) for j,urx in enumerate(random_image_list)]
                )
    
                line_bot_api.reply_message(
                    event.reply_token,
                    TemplateSendMessage(
                        alt_text='Hello World',
                        template=image_template
                    )
                )
    
  22. PS: heroku 可能會當機,膽小者勿試!

重新設計應用程式

學習目標:
  • 將檔案分解,藏暱重要的 Line Bot 聊天機器人機密
  • 結構化開發的專案,以利將來的維護與發展

藏暱重要的機密
  1. 在專案資料夾 LineBot下,新增 config.ini 檔案,寫人重要資訊
    [LineBot]
    channel_access_token = Channel access token
    channel_secret = Channel secret
    
  2. 修改主要程式檔:main.py
    (前面略過...)
    import configparser
    
    app = Flask(__name__)
    
    # 導入 config.ini 檔案
    config = configparser.ConfigParser()
    config.read('config.ini')
    
    # Line 聊天機器人的基本資料
    line_bot_api = LineBotApi(config.get('LineBot','channel_access_token'))
    handler = WebhookHandler(config.get('LineBot','channel_secret'))
    (以下略過...)
    
  3. 將程式碼送上 Heroku 進行測試,應不會出現錯誤訊息
    C:\LineBot>git add .
    C:\LineBot>git commit -m "Modify Main.py"
    C:\LineBot>git push heroku main
    
  4. MessageEvent主要分類:
    • MessageEvent:訊息事件
      • TextMessage:文字訊息
      • ImageMessage:圖片訊息
      • VideoMessage:影音訊息
      • StickerMessage:貼圖訊息
      • FileMessage:檔案訊息
    • FollowEvent:加入好友事件
    • UnfollowEvent:刪除好友事件
    • JoinEvent:加入聊天室事件
    • LeaveEvent:離開聊天室事件
    • MemberJoinedEvent:加入群組事件
    • MemberLeftEvent:離開群組事件
  5. 試著加上一些程式碼,例:main.py
    (前面的程式略過....)
    # 鸚鵡學說話
    @handler.add(MessageEvent, message=TextMessage)
    def echo(event):
        # user id 在 Line Developers / Basic Setting 下
        if event.source.user_id == "Uf4a596a6eb65eabf52c003ffe325a21d":
            line_bot_api.reply_message(
                event.reply_token,
                TextSendMessage(text=event.message.text)
            )
    
    if __name__ == "__main__":
        app.run()
    
    PS:將程式推上 Heroku ,再測看看!
學會查看重要記錄檔
  1. 在 main.py 檔案中,寫入一小斷程式碼:
    (前面略過....)
    print(body)
    try:
    (後面略過....)
    
  2. 利用 Heroku 的 app dashboard ,可查看 Server 上運作的訊息:
關閉自動訊息的回應
  1. 如果不喜歡這個自動回應的訊息,可以關閉:
  2. 靈活的改一段程式,顯現想要回應的訊息:main.py
    (前面略過....)
    # 靈活展現文字
    @handler.add(MessageEvent, message=TextMessage)
    def echo(event):
        # user id 在 Line Developers / Basic Setting 下
        if event.source.user_id == "Uf4a596a6eb65eabf52c003ffe325a21d":
    
            display_message = '你後面那位也想聽'
    
            if event.message.text == "tux來一個鬼故事":
                response_message = display_message
    
            line_bot_api.reply_message(
                event.reply_token,
                TextSendMessage(text=response_message)
            )
    
    if __name__ == "__main__":
        app.run()
    (後面略過....)
    
結構化開發專案的目錄設計
  1. 將目錄結構化成下列形式,儘量把程式分門別類分開:
    LineBot
      |-- Procfile
      |-- requirements.txt
      |-- runtime.txt
      |-- config.ini
      |-- main.py
      |-- app
           |-- __init__.py
           |-- router.py
           |-- linebotmodules.py
    
  2. 修改 main.py ,將內容儘量清空:
    from app import app
    
    if __name__ == "__main__":
       app.run()
    
  3. 新增 __init__.py 內容:
    from flask import Flask, request, abort
    from linebot import LineBotApi, WebhookHandler
    import configparser
    
    app = Flask(__name__)
    
    # 導入 config.ini 檔案
    config = configparser.ConfigParser()
    config.read('config.ini')
    
    # Line 聊天機器人的基本資料
    line_bot_api = LineBotApi(config.get('LineBot','channel_access_token'))
    handler = WebhookHandler(config.get('LineBot','channel_secret'))
    
    # 導入其他的程式模組
    from app import router, linebotmodules
    
  4. 新增 router.py 內容:
    from app import app, handler, request, abort
    from linebot.exceptions import InvalidSignatureError
    
    # 接收 Line 平台來的「通知」
    @app.route("/callback", methods=['POST'])
    def callback():
        signature = request.headers['X-Line-Signature']
        body = request.get_data(as_text=True)
        app.logger.info("Request body: " + body)
    
        print(body)
        try:
            handler.handle(body, signature)
        except InvalidSignatureError:
            abort(400)
    
        return 'OK'
    
  5. 新增 linebotmodules.py 內容:
    from app import line_bot_api, handler
    from linebot.models import MessageEvent, TextMessage, TextSendMessage
    
    # 靈活展現文字
    @handler.add(MessageEvent, message=TextMessage)
    def echo(event):
        # user id 在 Line Developers / Basic Setting 下
        if event.source.user_id == "Uf4a596a6eb65eabf52c003ffe325a21d":
    
            display_message = '你後面那位也想聽'
    
            if event.message.text == "tux來一個鬼故事":
                response_message = display_message
    
            line_bot_api.reply_message(
                event.reply_token,
                TextSendMessage(text=response_message)
            )
    
    
  6. 將程式推上 Heroku ,再測看看!

2021年7月18日 星期日

初始化 LineBot 專案

學習目標:
  • 了解 Line Bot 聊天機器人專案所需使用的設定
  • 了解每項開發工具的配合項目與使用方式

開始建立 Line Bot 專案
  1. 在 Workspace 目錄下,建立一個新的專案資料夾:LineBot
  2. 在 LineBot 資料匣下,先行建立 Python 主要程式檔:main.py
    import os
    from flask import Flask, request, abort
    from linebot import LineBotApi, WebhookHandler
    from linebot.exceptions import InvalidSignatureError
    
    app = Flask(__name__)
    
    # Line 聊天機器人的基本資料
    line_bot_api = LineBotApi('Channel access token')
    handler = WebhookHandler('Channel secret')
    
    # 接收 line 平台送來的「通知」
    @app.route("/callback", methods=['POST'])
    def callback():
        signature = request.headers['X-Line-Signature']
        body = request.get_data(as_text=True)
        app.logger.info("Request body: " + body)
    
        try:
            handler.handle(body, signature)
        except InvalidSignatureError:
            abort(400)
    
        return 'OK'
    
    if __name__ == "__main__":
        app.run()
    
    PS:先別急著執行測試該檔案
  3. 到 Line Developers 申請 access token 以及 secret key:
    • 選擇 Basic setting
    • 下接網頁至底,可以看見 Channel secret 項目!將資料複製下來,貼至程式 main.py 中,取代 Channel secret 字樣!
    • 選擇 Messaging API :
    • 下接頁面至底,可以看到 Channel access token!按下 issue ,可以產生access token !!
      複製 token 後,取代程式 main.py 內的 Channel access token 字樣!
  4. 在 Heroku 中建立專案!
    • 登入 Heroku 首頁中,選擇「Create new app」
  5. 自行輸入專案名稱,只要是出現綠色框,表示 Heroku 同意建立!
  6. 建立的主畫面如下:
  7. 在主畫面往下拉,可看見 Heroku cli 的 git 教學
    PS:等一下將會使用到該教學
  8. 在專案目錄底下建立三個檔案,分別為 Procfile、requirements.txt、runtime.txt
    • Procfile 檔案內容
      web: gunicorn main:app --preload
      
    • requirements.txt
      Flask>=1.1.1
      gunicorn>=20.0.4
      line-bot-sdk>=1.15.0
      
    • runtime.txt
      python-3.9.6
      
  9. 將工作目錄切換至專案目錄內,將檔案推送至 Heroku 的專案內!
    heroku login
    git init
    heroku git:remote -a 你的app名稱
    git add .
    git commit -m "Add new App"
    git branch
    git checkout -b main
    git branch -D master
    git push heroku main
     
    PS:出現以下畫面,就算成功了!
  10. 設定 Line Developers 的 Webhook :
    • 在 Line Developers 的 Messaging API 中,下拉網頁至 Webhook site
    • 在 Webhook URL 的欄位中,輸入 https://你的app名稱.herokuapp.com/callback
    • 按下「Verify」可以驗證結果,並且要記得推開 Use webhook 的功能
  11. 修改 main.py 檔案後,推送至 Heroku 專案,即可進行測試
    import os
    from typing import Text
    from flask import Flask, request, abort
    from linebot import LineBotApi, WebhookHandler
    from linebot.exceptions import InvalidSignatureError
    
    from linebot.models import MessageEvent, TextMessage, TextSendMessage
    
    app = Flask(__name__)
    
    # Line 聊天機器人的基本資料
    line_bot_api = LineBotApi('Channel access token')
    handler = WebhookHandler('Channel secret')
    
    # 接收 line 平台送來的「通知」
    @app.route("/callback", methods=['POST'])
    def callback():
        signature = request.headers['X-Line-Signature']
        body = request.get_data(as_text=True)
        app.logger.info("Request body: " + body)
    
        try:
            handler.handle(body, signature)
        except InvalidSignatureError:
            abort(400)
    
        return 'OK'
    
    if __name__ == "__main__":
        app.run()
    
參考文獻: