Dungyichao / Google-Cloud-Storage-and-Line-API

The main goal is to use C# program to send image or text message to LINE app chat room. Firebase and Google Cloud Storage will be involved to achieve the goal.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Sending LINE message with Google Cloud Storage and Line API

  1. What is the Goal: Overview, Steps
  2. LINE Developer Console Configuration
  3. Firebase Functions and Firestore Database: Firestore, Functions, LINE Webhook settings
  4. LINE Chat and Postman Testing
  5. Google Cloud Storage and Platform Configuration: Service Account, Bucket, Permission
  6. C# Program Send Image and Text Message to LINE App

1. What is the Goal

The main goal is to use C# program to send image or text message to LINE app chat room. Firebase and Google Cloud Storage will also be involved to achieve the goal. Some useful official documentary links are listed below

1.1 Program Overview

System structure

As you can see from the above image of the structure of our program, there are many elements. Have you ever think why do we need so many parts to achieve such simple goal? The reason are as following

  • LINE user ID is not the same one you usually used to add new friend. The ID contains numeric and alphabetic characters such as "U9d70e010e48a1t93634a60cf1a5y9a46". You will need a function running on the cloud to serve as a Webhook for LINE to communicate with and then retrieve the ID. So here comes the Firebase function. It will serve as the API for LINE to call and provide LINE user ID to our function. The LINE user ID then be stored in Firestore (a database) for later usage.
  • You may use C# program on your local PC to send pure text message to users via LINE API, however, image cannot. You need to upload your image to a cloud and expose it with URL, and then send this URL via LINE API to the users. So here comes the Google Cloud Storage to server as a online storage for your image and provide the accessability with URL.

1.2 Step to the Goal

Steps to the goal

2. LINE Developer Console Configuration

Please follow the steps in the following LINE official link to configure your developer console. https://developers.line.biz/en/docs/messaging-api/getting-started/

I would set the Auto-response messages status (In Response settings) to Off.

3. Firebase Functions and Firestore Database

Firebase functions will serve as Webhook of LINE (LINE explain: When an event occurs, such as when a user adds your LINE Official Account as a friend or sends a message, the LINE Platform sends an HTTPS POST request to the webhook URL). Notice that using Firestore database is free while Functions requires your project to be upgraded to Blaze plan (pay by your usage)

Please go through following two link

  1. How to start Firebase Function project: https://firebase.google.com/docs/functions/get-started
  2. How to make Firebase Functions work with LINE PDF

3.1 Firestore Database

Firestore database

3.2 Firebase Functions

Use the following command to create Functions folder and related documents

$npm install firebase-functions@latest firebase-admin@latest --save
$npm install -g firebase-tools
$firebase init functions

Some sample code would look like the following

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.addMessage = functions.https.onRequest(async (req, res) => {
  const original = req.query.text;
  const writeResult = await admin.firestore().collection('messages').add({original: original});
  res.json({result: `Message with ID: ${writeResult.id} added.`});
});

In .eslintrc.js which check your code rule, we need some modification to save our life. https://www.programmersought.com/article/46885832344/

module.exports = {
  root: true,
  env: {
    es6: true,
    node: true,
  },
  extends: [
    "eslint:recommended",
  ],
  rules: {
    quotes: ["error", "double"],
    "no-unused-vars":"off",
  },
};

Now you can deploy your function onto Firebase

$firebase deploy --only functions

or

$firebase deploy --only "functions:addMessage"

Where addMessage is your function name. You can then find the function trigger URL in your Firebase Console (in tab Functions. URL would look like https://us-central1-projectname-3f2d0.cloudfunctions.net/addMessage)

However, after you deploy, you might encounter error: Forbidden....when you try to call the URL of your functions in the browser. Please follow the following link to solve the problem https://lukestoolkit.blogspot.com/2020/06/google-cloud-functions-error-forbidden.html Go to the following link: https://console.cloud.google.com/functions/list . Select your project. Check the check box of the function which you encounter error. Click on ADD MEMBER. In the new members field, type in "allUsers" and select the "allUsers" option. In the "Select a role" dropdown, select Cloud Functions then Cloud Functions Invoker.

If you get everything right with above addMessage function, you can then deploy the following code onto Firebase Function.

const functions = require("firebase-functions");
const fetch = require("node-fetch");
var admin = require("firebase-admin");

admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
//var serviceAccount = require("path/to/serviceAccountKey.json");
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//

 exports.helloWorld = functions.https.onRequest((request, response) => {
   functions.logger.info("Hello logs!", {structuredData: true});
   response.send("Hello from Firebase Line!");
 });

 exports.LineMessAPI = functions.https.onRequest((request, respond) => {
    var event = request.body.events[0]
    functions.logger.log(JSON.stringify(event));
    var userId = event.source.userId;
    var timestamp = event.timestamp;
    var replyToken = event.replyToken;
    var userText = ""
    if (event.type === "message" && event.message.type === "text"){
        userText = event.message.text
    } else {
        userText = "(Message type is not text)";
    }
    db.collection("chat-history").doc(timestamp.toString()).set({
        "userId": userId,
        "Message": userText,
        "timestamp": timestamp
    })

    db.collection("Customer").doc(userId).get().then( returnData =>{
        if (returnData.exists){
          var name = returnData.data().name
          var surname = returnData.data().surname
          var nickname = returnData.data().nickname
          reply_message(replyToken, `Hello ${nickname}(${name} ${surname})`)
        } else {
          reply_message(replyToken, "You are not the customer, Register?")
        }
        return null
    }).catch(err => {
        console.log(err)
    })

    return respond.status(200).send(request.method);
});

const LINE_HEADER = {
    "Content-Type": "application/json",
    "Authorization": "Bearer pb2iNzDae3dfP5igReOzv8Rpcdsgrahnw0eH2LAe4/WLXuvJrgN/VcOGLAe69wDiaHL7wPvFfsda35ldsasdfqCaXjs4wB04t89/1O/w1cDnyilFU="
  }

function reply_message(replytoken,textfrom){
    fetch("https://api.line.me/v2/bot/message/reply",{
        method: "post",
        body:    JSON.stringify({
            replyToken: replytoken,
            messages: [
              {
                type: "text",
                text: textfrom
              }
            ]
          }),
        headers: LINE_HEADER,
    }).then(res => res.json())
    .then(json => functions.logger.log(JSON.stringify(json)))
}

Note that in "Authorization": "Bearer XXOOXX where XXOOXX is Channel access token configured in LINE Developer Console mentioned in previous section. You should keep the Channel access token secure.

Make sure that the above function LineMessAPI be accessible to public (we did'nt put much effort on the security here, so make sure you know how to configure rule when deploy in real business). Put the LineMessAPI URL into Webhook URL in LINE Developer Console under the channel you just created.

Webhook settings in LINE Developer Console

4. LINE Chat and Postman Testing

After you complete above section, use your LINE app on your cellphone and add (Home --> Add Friend --> QR code --> Scan the channel QR code) the newly created channel. You will then receive a auto reply message from the channel. At the same time, LINE also trigger the Webhook URL, so our function LineMessAPI should be recording the LINE user ID (for example: U4d709010e49a0f83634p70cf1a0e0a76)who just add the channel to friend.

In LINE Developers documents about Message API --> Message --> Send push message (link). A example request looks like the following

curl -v -X POST https://api.line.me/v2/bot/message/push \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {channel access token}' \
-d '{
    "to": "U4d709010e49a0f83634p70cf1a0e0a76",
    "messages":[
        {
            "type":"text",
            "text":"Hello, world1"
        },
        {
            "type":"text",
            "text":"Hello, world2"
        }
    ]
}'

We will use Postman to send the above request for testing if everything working great. LINE user should receive Hello, world1 from the channel.

Postman send http request

Sending text message is simple, however, sending image should go through image URL. The request looks like following (doc).

{
    "type": "image",
    "originalContentUrl": "https://example.com/original.jpg",
    "previewImageUrl": "https://example.com/preview.jpg"
}

Our next task is to upload some image to the cloud and generate an URL of the image so that we can send the URL to LINE user.

5. Google Cloud Storage and Platform Configuration

Please go through the following link first to setup the Google Cloud Storage. (Document link)

You will be overwhelm by many terms in the Google Cloud Platform. The following image will show you the overview of relationship of bucket, service account, key file

Overview of the Google Cloud Platform and its elements relationship

5.1 Service Account

You are required to create a Service account in Google Cloud Platform --> IAM & Admin --> Service Accounts (Google explain: A service account represents a Google Cloud service identity, such as code running on Compute Engine VMs, App Engine apps, or systems running outside Google). In the DETAILS of the service account, click on SHOW DOMAIN-WIDE DELEGATION --> check the Enable Google Workspace Domain-wide Delegation and SAVE. You will be assigned with a email for this service account like some_service_account_name@gteshwa-3d9870.iam.gserviceaccount.com.

Then, create a service account key, a JSON key file will be downloaded to your computer. Put that JSON key file into your C# project solution folder. Put the following code in your c# program (at the place after InitializeComponent). I didn't use PowerShell nor Command Prompt because they are not working for my case.

Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", @"C:\Users\admin\Desktop\SomeProjectSolutionFolder\JsonkeyName-3d9f0-8b3859shyd480.json");
// this means the json key will represent its identity to access the Google Cloud Storage bucket

5.2 Bucket

On the left panel of Google Cloud Platform, you can find Cloud Storage in the Storage section. Then click CREATE BUCKET to create a bucket which will be the place to store objects which you uploaded. In the Location type, I would choose Region because my target LINE user are near me. Then choose Standard for default storage class. After you create a bucket, now comes the permssions part.

5.2.1 Permission for Service Account

First, Remove public access prevention. Second, add the service account you created (use service account email address some_service_account_name@gteshwa-3d9870.iam.gserviceaccount.com) to this bucket permission new Member (so that your C# program can access and upload to this bucket) and make the role to Storage Object Admin.

5.2.2 Permission for Public Viewer

Add allUsers as new member and make the role to Storage Object Viewer so that every LINE user can see the image you send via URL.

6. C# Program Send Image and Text Message to LINE App

We will now demonstrate how to upload image to Google cloud storage and send text and image message to LINE app. Your C# progrm should include following library

using System.IO;
using Google.Apis.Storage.v1.Data;
using Google.Cloud.Storage.V1;
using RestSharp;

6.1 Upload Object to Google Cloud Storage Bucket

The following C# function will upload object to the new created bucket

// Call the function to upload image in local PC C:\Users\admin\Desktop\LINE\ATT00001.JPG, name the object ATT00001.JPG
UploadGoogleDrive("some_service_account_name", @"C:\Users\admin\Desktop\LINE\ATT00001.JPG", "ATT00001.JPG");

//Functions which upload object to bucket
public void UploadGoogleDrive(string bucketName, string localPath, string objectName)
{
     var storage = StorageClient.Create();
     using (var fileStream = File.OpenRead(localPath))
     {
        storage.UploadObject(bucketName, objectName, null, fileStream);
     }
     Console.WriteLine($"Uploaded {objectName}.");
}

After you successfully uploaded the object, the URL for this object for public to access will look like https://storage.googleapis.com/your_bucket_name/ATT00001.JPG

6.2 Send Text Message in LINE App

public void LINE_Send_Text()
{
   string send_string = "Some string";
   var client = new RestClient("https://api.line.me/v2/bot/message/multicast");
   client.Timeout = -1;
   var request = new RestRequest(Method.POST);
   request.AddHeader("Content-Type", "application/json");
   request.AddHeader("Authorization", "Bearer pb2iNzDae3dfP5igReOzv8Rpcdsgrahnw0eH2LAe4/WLXuvJrgN/VcOGLAe69wDiaHL7wPvFfsda35ldsasdfqCaXjs4wB04t89/1O/w1cDnyilFU=");
   string body2 = String.Format("{{\"to\": [\"U4d709010e49a0f83634p70cf1a0e0a76\"], \"messages\":[{{\"type\":\"text\",\"text\":\"{0}\"}}]}}", send_string);
   request.AddParameter("application/json", body2, ParameterType.RequestBody);
   IRestResponse response = client.Execute(request);
   Console.WriteLine(response.Content);
}

6.3 Send Image Message in LINE App

public void Postman_img()
{
   string send_string = "https://storage.googleapis.com/your_bucket_name/ATT00001.JPG";
   var client = new RestClient("https://api.line.me/v2/bot/message/multicast");
   client.Timeout = -1;
   var request = new RestRequest(Method.POST);
   request.AddHeader("Content-Type", "application/json");
   request.AddHeader("Authorization", "Bearer pb2iNzDae3dfP5igReOzv8Rpcdsgrahnw0eH2LAe4/WLXuvJrgN/VcOGLAe69wDiaHL7wPvFfsda35ldsasdfqCaXjs4wB04t89/1O/w1cDnyilFU=");           
   string body2 = String.Format("{{\"to\": [\"U4d709010e49a0f83634p70cf1a0e0a76\"], \"messages\":[{{\"type\":\"image\",\"originalContentUrl\":\"{0}\",\"previewImageUrl\":\"{0}\"}}]}}", send_string);
   request.AddParameter("application/json", body2, ParameterType.RequestBody);
   IRestResponse response = client.Execute(request);
   Console.WriteLine(response.Content);
}

About

The main goal is to use C# program to send image or text message to LINE app chat room. Firebase and Google Cloud Storage will be involved to achieve the goal.