星期三, 8月 19, 2026

[Gemini] 媒體浮水印

Gemini 官方有開放選項,讓使用者自行選擇是否要有浮水印,個人是還蠻喜歡浮水印,要不然還要特別打上由 AI 生成之類的警語

星期四, 7月 30, 2026

[SQL] 隱含轉換 - 彙總函數回傳值資料型態

一段 sum 並把結果 insert 進 Table TSQL 語法,拋出下面錯誤訊息
結果的空間不足,無法將 money (貨幣) 值轉換成 smallmoney。
該欄位資料型態是 smallmoeny,很明顯是 sum 結果超過 smallmoney 資料範圍導致的異常,samllint 資料範圍在 -214,748.3648 到 214,748.3647 之間,但意外的是為什麼針對 smallmoney 欄位進行 sum 時是回傳 moeny 資料型態
官方文件 - sum return type 內有說明,彙總函式 sum 結果會發生資料型態隱含轉換,以該例來看就是 sum 把 smallmoney 資料彙總時,因為彙總結果超過 smallmoney 資料範圍,所以隱含轉換為 money,insert 回 smallmoney 欄位時拋出錯誤
簡易範例驗證
use tempdb
GO

DROP TABLE IF EXISTS tblDemo

CREATE TABLE tblDemo
(
	ID int identity(1,1) ,
	ColSmallMoney smallmoney
)

INSERT INTO tblDemo (ColSmallMoney) VALUES(214748)
INSERT INTO tblDemo (ColSmallMoney) VALUES(214748)
GO

SELECT 
	SQL_VARIANT_PROPERTY(SUM(ColSmallMoney) , 'BaseType') -- 會得到 money 資料型態
FROM tblDemo

星期三, 7月 22, 2026

[GAS] 使用 JavaScript Library 來產生 QRCode

在 GAS 上使用 JavaScript Linrary - Kjua 來產生 QRCode

code.gs
function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index')
    .setTitle('kjua QRCode 產生器');
}

Index.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <!-- 引入 kjua CDN -->
    <script src="https://cdn.jsdelivr.net/npm/kjua@0.10.0/dist/kjua.min.js"></script>
    <style>
      body {
        font-family: Arial, sans-serif;
        text-align: center;
        padding: 40px 20px;
      }
      input {
        padding: 10px;
        font-size: 16px;
        width: 80%;
        max-width: 300px;
        margin-bottom: 10px;
      }
      button {
        padding: 10px 20px;
        font-size: 16px;
        background-color: #4CAF50;
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
      }
      button:hover {
        background-color: #45a049;
      }
      #qrcode-container {
        margin-top: 30px;
      }
    </style>
  </head>
  <body>
    
    <h2>QRCode 產生器</h2>
    <input type="text" id="text-input" value="https:/www.google.com" placeholder="請輸入要轉換的網址或文字" />
    <br>
    <button onclick="generateQRCode()">產生 QRCode</button>
    
    <!-- QRCode 渲染區域 -->
    <div id="qrcode-container"></div>

    <script>
      function generateQRCode() {
        const textToEncode = document.getElementById('text-input').value;
        const container = document.getElementById('qrcode-container');
        
        // 避免重覆產生 QRCode,每次都先清空
        container.innerHTML = ""; 

        if (!textToEncode) {
          alert("請輸入內容!");
          return;
        }

        // 呼叫 kjua 並產生 QRCode
        const qrCodeElement = kjua({
          render: 'image',   // 渲染為 <img> 標籤
          text: textToEncode,
          size: 250,         // 尺寸設定為 250px
          fill: '#000000',   // QRCode 顏色
          back: '#ffffff',   // 背景顏色
          rounded: 10        // 模組圓角 (0-100),讓外觀看起來更柔和
        });

        container.appendChild(qrCodeElement);
      }
    </script>
    
  </body>
</html>

星期一, 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 內資料