星期一, 7月 20, 2026

[GAS] 透過第三方 API 產生 QRCode

在 GAS 上使用 goQR.me 第三方服務,該服務只有兩個 API 可以使用,分別為
該筆記只會紀錄使用 create-qr-code 來產生 QRCode 而已
/**
 * 測試與執行範例:呼叫共用模組並儲存到 Google Drive
 */
function testGenerateQRCode() {
  const myData = "https://workspace.google.com/";
  const mySize = 250; // 將會產生 250x250 的 QR Code
    
  const qrBlob = getQRCodeBlob(myData, mySize);
  
  if (qrBlob) {
    const file = DriveApp.getRootFolder().createFile(qrBlob);
    Logger.log(`✅ QR Code 建立成功!檔案連結:${file.getUrl()}`);
  } else {
    Logger.log("❌ 流程中斷:無法取得 QR Code 圖片。");
  }
}

/**
 * 透過 goQR.me (api.qrserver.com) 產生 QR Code 的共用模組
 * 
 * @param {string} textToEncode - 必填:要轉換為 QR Code 的內容(網址或純文字)
 * @param {number} size - 選填:QR Code 的尺寸,預設為 300 (即 300x300 像素)
 * @returns {GoogleAppsScript.Base.Blob|null} 成功時回傳圖片 Blob,失敗則回傳 null
 */
function getQRCodeBlob(textToEncode, size = 300) {
  if (!textToEncode) {
    Logger.log("❌ 錯誤:未傳入 textToEncode 參數");
    return null;
  }

  // 組合 API 網址 (將單一 size 數值轉換為 api.qrserver.com 所需的 "寬x高" 格式)
  const apiUrl = `https://api.qrserver.com/v1/create-qr-code/?size=${size}x${size}&data=${encodeURIComponent(textToEncode)}`;
  
  try {
    const response = UrlFetchApp.fetch(apiUrl, {
      muteHttpExceptions: true
    });
    
    // 驗證並回傳 Blob
    if (response.getResponseCode() === 200) {
      const fileName = `QRCode_${new Date().getTime()}.png`;
      return response.getBlob().setName(fileName);
    } else {
      Logger.log(`❌ 產生失敗,API 伺服器回應狀態碼:${response.getResponseCode()}`);
      return null;
    }
  } catch (error) {
    Logger.log(`❌ 執行階段發生例外狀況:${error.message}`);
    return null;
  }
}


星期六, 7月 11, 2026

[GAS] API 執行檔

根據官方文章-使用 Google Apps Script API 執行函式 的實作筆記,部屬 API 執行檔後在 C# 控制台上使用 OAuth2.0 來存取該 API,該 API 會 insert 資料進入 Google Sheet 內

說明
Apps Script API 提供 scripts.run 方法,可遠端執行指定的 Google Apps Script 函式。您可以在呼叫應用程式中使用這個方法,從遠端執行其中一個指令碼專案中的函式,並接收回應。
需求條件

必須先完成下列事項,才能使用 scripts.run 方法:
  • 將 GAS 部署為 API 執行檔
  • 設定 OAuth Scopes 且必須涵蓋 GAS 使用相關服務,不能只是呼叫函式使用的服務,EX:https://www.googleapis.com/auth/spreadsheets
  • 確認 GAS 和呼叫應用程式的 OAuth2 用戶端共用使用同一個 GCP 專案。GAS 專案必須是標準 GCP 專案,GAS 專案預設 GCP 專案無法使用
  • 在 GCP 專案中啟用 Google Apps Script API。
GAS Code

該 code.gs 單純是 insert 資料進入 Google Sheet 內,可以透過 testAddCheckIn() 進行測試
const SPREADSHEET_ID = '填入 Google Sheet ID';
const SHEET_NAME = 'CheckIn';

/**
 * 本機測試用:可先在 Apps Script 編輯器內執行,確認可正常寫入資料
 */
function testAddCheckIn() {
  return addCheckIn({
    identifier: 'U001',
    meetingCode: 'MEET-001',
    deviceInfo: 'Apps Script Editor Test'
  });
}

/**
 * 給外部 API Executable 呼叫的主函式
 */
function addCheckIn(payload) {
  payload = payload || {};

  const identifier = String(payload.identifier || '').trim();
  const meetingCode = String(payload.meetingCode || '').trim();
  const deviceInfo = String(payload.deviceInfo || '').trim();

  if (!identifier || !meetingCode) {
    return {
      success: false,
      status: 'NO_REQUIRED_DATA',
      message: 'identifier 和 meetingCode 為必填'
    };
  }

  const now = new Date();
  const checkedAt = Utilities.formatDate(
    now,
    'Asia/Taipei',
    'yyyy-MM-dd HH:mm:ss'
  );

  const sheet = getCheckInSheet_();
  sheet.appendRow([
    checkedAt,
    identifier,
    meetingCode,
    deviceInfo,
    'API_EXECUTABLE'
  ]);

  return {
    success: true,
    status: 'CHECK_IN_SUCCESS',
    checkedAt: checkedAt,
    identifier: identifier,
    meetingCode: meetingCode
  };
}

function getCheckInSheet_() {
  const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
  let sheet = ss.getSheetByName(SHEET_NAME);

  if (!sheet) {
    sheet = ss.insertSheet(SHEET_NAME);
    sheet.appendRow(['時間', '識別碼', '會議代碼', '裝置資訊', '來源']);
  }

  return sheet;
}
設定 appsscript.json

點選專案設定並勾選「在編輯器中顯示 appsscript.json 資訊清單檔案」
開啟 appsscript.json 並把 oauthScopes 範圍加進去
{
  "timeZone": "Asia/Taipei",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/spreadsheets"
  ]
}

GCP 標準專案

建立標準專案 gas-api-executable-demo 並啟用 Google Apps Script API
利用搜尋功能尋找 Google Apps Script API 服務
在 Google Enterprise API 和 Google Workspace 分類內都可以找到
啟用 Apps Script API 服務
設定 OAuth 同意畫面
GAS 綁定 GCP 標準專案

在標準專案上取得專案編號 (如下圖)
在 GAS 專案上綁定專案標號
綁定完成

該筆記 [GAS] Goolge Cloud Logging 內有紀錄不同順序的設定流程,也可以參考看看

設定用戶端
  • 應用程式類別:選擇電腦版應用程式
  • 名稱:C# Console
按下 [下載 json] 按鈕後,可以下載 credentials.json 檔案,檔案名稱為 client_secret_用戶端ID.apps.googleusercontent.com
部署 API 執行檔

部屬 => 新增部屬 => API 執行檔,誰可以存取」選項有 「只有我自己」、「所以已登入 Google 帳號使用者」
部屬完成
部屬時假如是預設專案的話,會出現下圖並引導至設定內,要求設定為標準專案
C# Console App 呼叫

首先要先確認部屬 ID,下圖為部屬時畫面上的部屬 ID,事後要取得的話,請從管理部屬進入取的
credentials.json 檔案放在 EXE 執行檔案資料夾內
安裝 NuGet
Install-Package Google.Apis.Script.v1
Install-Package Google.Apis.Auth
第一次執行 C# 程式時,瀏覽器會跳出 Google 授權畫面,看要選擇哪一個帳號來執行,該帳號必須是在 GCP 目標對象內
執行結果
token 會儲存在本機 token.json 資料夾,後續執行就不須再次授權
C# Code,必須填入 deploymentId 和 credentialsFileName
using Google.Apis.Auth.OAuth2;
using Google.Apis.Script.v1;
using Google.Apis.Script.v1.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;

internal class Program
{
    private static async Task Main()
    {
        string deploymentId = "請填入 API Executable 的 Deployment ID";
        string credentialsFileName = "請填入 credentials.json 檔案名稱或是完整檔案路徑";

        string[] scopes =
        {
            "https://www.googleapis.com/auth/spreadsheets"
        };

        UserCredential credential;

        using (var stream = new FileStream(credentialsFileName, FileMode.Open, FileAccess.Read))
        {
            credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
                GoogleClientSecrets.FromStream(stream).Secrets,
                scopes,
                "user",
                CancellationToken.None,
                new FileDataStore("token.json", true)
            );
        }

        var service = new ScriptService(new BaseClientService.Initializer
        {
            HttpClientInitializer = credential,
            ApplicationName = "GAS API Executable Demo"
        });

        var payload = new Dictionary<string, object>
        {
            { "identifier", "U001" },
            { "meetingCode", "MEET-001" },
            { "deviceInfo", "C# Console App" }
        };

        var request = new ExecutionRequest
        {
            Function = "addCheckIn",
            Parameters = new List<object> { payload }
        };

        Operation operation = await service.Scripts.Run(request, deploymentId).ExecuteAsync();

        if (operation.Error != null)
        {
            Console.WriteLine("GAS 執行失敗:");
            Console.WriteLine(operation.Error.Message);
            return;
        }

        if (operation.Response != null && operation.Response.ContainsKey("result"))
        {
            Console.WriteLine("GAS 回傳結果:");
            Console.WriteLine(operation.Response["result"]);
        }
        else
        {
            Console.WriteLine("GAS 執行完成,但沒有回傳 result。");
        }
    }
}
Google Sheet 內資料

星期六, 7月 04, 2026

[GAS] 服務配額-MailApp.getRemainingDailyQuota

之前以為 Google Apps Script 服務配額是沒有內建函數可以查詢,上課時老師提到可寄發 mail 數量是可以查詢的,查發現是透過 getRemainingDailyQuota() 來取得,官方文件說明
Returns the number of recipients you can send emails to for the rest of the day. The returned value is valid for the current execution and might vary between executions.
function sendEmailQuotaQuery()
{
  console.log(MailApp.getRemainingDailyQuota());
}

星期三, 7月 01, 2026

[Forms] 顯示連結以傳送更多回覆

上課時聽到老師介紹才發現原來表單送出後,點選 [提交其他回應] 連結可以重新開始,該連結是可以取消,一直以為是常駐功能

設定 => 簡報 => 顯示連結以傳送更多回覆

表單送出後畫面,畫面上會有 [提交其他回應] 連結

星期日, 6月 28, 2026

[SSRS] 框線

之前都是在屬性視窗內設定框線,這次無意中進入 IDE 內進行設定,發現原來框線 IDE 設定是有操作順序,以矩形為例來記錄

框線 IDE 設定

矩形 => 滑鼠右鍵 => 矩形屬性 => 框線
上圖橘框內的樣式、寬度、色彩,要先設定後,選擇黃框外框會套用該設定,而黃框外框選擇 [無] 後離開的話,會一併恢復橘框內的三個設定預設值。

屬性視窗設定

點選控件後理論上右側屬性視窗會自動出現,或是按 F4 快捷鍵
屬性視窗內設定會比較直覺,設定就是直接套用該效果