การผสานรวม Agent และ LLM — FastOG
คู่มือฉบับสมบูรณ์แบบครบวงจรสำหรับการผสานรวม FastOG จากภาษาใดก็ได้ฝั่งเซิร์ฟเวอร์ — เขียนขึ้นสำหรับทั้งนักพัฒนาที่เป็นมนุษย์และเอเจนต์เขียนโค้ด AI พื้นผิวไคลเอนต์ทั้งหมดคือฟังก์ชันเดียว: signedUrl(params, key, secret) → URL <meta property="og:image"> ที่พร้อมฝังใช้งาน
ดาวน์โหลดสกิล
นำสกิลทั้งหมดติดตัวไปด้วย — วางไฟล์ทั้งสองลงในไดเรกทอรี agents/skills/fastog-integration/ เพื่อโหลดใน Claude Code, Codex หรือเอเจนต์ใดก็ได้:
SKILL.md—curl -O https://fastog.com/skill/SKILL.mdimplementations.md—curl -O https://fastog.com/skill/implementations.md
ต้องการไฟล์รวมไฟล์เดียวหรือไม่? หน้านี้คือสกิล (สัญญา + การใช้งานแบบอินไลน์): curl -O https://fastog.com/docs-files/agent-integration.md
สัญญา (สัญญาเดียวเท่านั้น)
URL = https://fastog.com/api/v1/og?<content>&key=<apiKey>&s=<signature>
canonical = every query param except `s` (INCLUDING `key`), keys sorted,
array values sorted, each pair enc(key)=enc(value) RFC-3986,
joined with "&"
message = "GET/api/v1/og" + "\n" + canonical
signature = hex(HMAC-SHA256(message, hmac_secret))
เงื่อนไขคงที่ (การละเมิดข้อใดข้อหนึ่ง → 401)
- การเข้ารหัส RFC-3986 — เปอร์เซ็นต์-เอนโค้ดทุกอย่างยกเว้น
A-Za-z0-9-._~; ช่องว่าง →%20, ห้ามใช้+. PHPrawurlencode; JSencodeURIComponent+ เข้ารหัสใหม่!'()*; Pythonquote(safe='-._~'); Ruby/Go ตัวเข้ารหัสไบต์แบบกำหนดเอง keyต้องถูกเซ็น — มันเป็นส่วนหนึ่งของคิวรีแบบคาโนนิคัล ดังนั้นลายเซ็นจึงผูกกับคีย์- อาร์เรย์ต้องใช้คีย์แบบวงเล็บเหลี่ยมใน URL — เซ็นคาโนนิคัลด้วยคีย์ธรรมดา (
features=…&features=…) แต่ URL ต้องใช้features[0]=…หรือfeatures[]=…. แบ็กเอนด์เป็น PHP: คีย์ที่ซ้ำกันแบบไม่มีวงเล็บ (features=a&features=b) จะดีโค้ดเป็น สเกลาร์ (ค่าสุดท้ายชนะ) → คาโนนิคัลไม่ตรงกัน → 401 - ไม่มี timestamp — อ้างอิงตามเนื้อหา. พารามิเตอร์เหมือนกัน → URL เหมือนกัน → แคชได้ตลอดไป; การเปลี่ยนแปลงใดๆ → 401 → ไม่มีการเรนเดอร์, ไม่มีการใช้โควตา
พารามิเตอร์ที่สงวนไว้: s (ลายเซ็น — ไม่รวมในคาโนนิคัล), key (รวมอยู่), t (สงวนไว้ — ห้ามใช้)
ความปลอดภัย (ไม่สามารถต่อรองได้)
hmac_secretเป็น ความลับ — ใช้ได้เฉพาะฝั่งเซิร์ฟเวอร์เท่านั้น. ห้ามอยู่ในเบราว์เซอร์, ห้ามอยู่ในสคริปต์ CDN สาธารณะ. การเซ็นฝั่งไคลเอนต์/เบราว์เซอร์ไม่สามารถทำงานได้keyเป็น สาธารณะ (ปรากฏในทุก URL) — ปลอดภัยและคาดหวังได้ เพราะการปลอมแปลงต้องใช้คีย์ลับ- ใช้ GET เท่านั้น (ครอว์เลอร์ไม่เคย POST). เซ็นบนเซิร์ฟเวอร์ ณ เวลาที่ร้องขอ; URL ที่เซ็นแล้วคือสิ่งที่คุณฝัง
ตัวอย่างการทำงาน
params = {template:"blog", title:"Hello World", features:["Free shipping","30-day returns"], key:"sk_test123"}
secret = 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
canonical = features=30-day%20returns&features=Free%20shipping&key=sk_test123&template=blog&title=Hello%20World
signature = a79232b15b45002aee76174f542e0a661b37cb1259df01e263c952ca4052a54d
url = https://fastog.com/api/v1/og?template=blog&title=Hello%20World&features%5B0%5D=Free%20shipping&features%5B1%5D=30-day%20returns&key=sk_test123&s=a79232b15b45002aee76174f542e0a661b37cb1259df01e263c952ca4052a54d
ตัวลงนามแบบไฟล์เดียว (ไม่พึ่งพาแพ็กเกจ ไม่ต้องใช้ตัวจัดการแพ็กเกจ)
ยังไม่มีแพ็กเกจ npm i/pip install — สิ่งเหล่านี้คือ ตัวลงนามแบบไฟล์เดียว: แต่ละไฟล์ดาวน์โหลดเป็นไฟล์เดียวและสร้างเฉพาะ URL ของรูปภาพ OG ที่ลงนามแล้วเท่านั้น (รวมถึงตรรกะ HMAC + RFC-3986 ทั้งหมด ไม่มีรันไทม์) ดาวน์โหลดและลงนามบนเซิร์ฟเวอร์ของลูกค้า:
| ภาษา/เฟรมเวิร์ก | ไฟล์ | คำสั่งบรรทัดเดียว |
|---|---|---|
| Node.js (CJS) / Express | https://fastog.com/sdk/node.js · /sdk/express/fastog.js |
ogImageUrl(params, { key, secret }) |
| ESM (SvelteKit / Next / Astro / Vue-Nuxt) | /sdk/svelte/fastog.js · /sdk/next/fastog.js · /sdk/astro/fastog.js · /sdk/vue/fastog.js |
ogImageUrl(params, { key, secret }) |
| PHP | https://fastog.com/sdk/php/fastog.php.txt |
FastOg::signedUrl($params, $key, $secret) |
| Ruby / Rails | https://fastog.com/sdk/ruby/fastog.rb |
FastOg.signed_url(params, key, secret) |
| Python | https://fastog.com/sdk/python/fastog.py |
fastog.signed_url(params, key, secret) |
| Go | https://fastog.com/sdk/go/fastog.go |
fastog.SignedURL(params, key, secret) |
ฝังผลลัพธ์:
<meta property="og:image" content="https://fastog.com/api/v1/og?…&key=…&s=…" />
โกลเดนเวกเตอร์ (ตรวจสอบการใช้งานใดๆ ได้ด้วยตนเอง)
params = {template:"blog", title:"Hello World", features:["Free shipping","30-day returns"], key:"sk_test123"}
secret = 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
canonical = features=30-day%20returns&features=Free%20shipping&key=sk_test123&template=blog&title=Hello%20World
signature = a79232b15b45002aee76174f542e0a661b37cb1259df01e263c952ca4052a54d
การใช้งานจะถือว่าถูกต้อง ก็ต่อเมื่อ ให้ค่าแคนอนิคัล (canonical) และลายเซ็น (signature) นั้นสำหรับอินพุตเหล่านั้น — และพารามิเตอร์หรือคีย์ที่ต่างกันต้องเปลี่ยนลายเซ็น โกลเดนเวกเตอร์ถูกสร้างโดยเซิร์ฟเวอร์ FastOG เอง ดังนั้นจึงเป็นแหล่งความจริงสูงสุด
ตัวอย่างการใช้งานอ้างอิง (ไม่ใช้ SDK / ภาษาใดก็ได้)
JavaScript (Node.js, CommonJS)
const crypto = require("crypto");
const enc = (s) =>
encodeURIComponent(String(s)).replace(
/[!'()*]/g,
(c) => "%" + c.charCodeAt(0).toString(16).toUpperCase(),
);
function canonicalize(params, apiKey) {
const all = { ...params, key: apiKey };
const pairs = [];
for (const [k, v] of Object.entries(all)) {
for (const vv of Array.isArray(v) ? [...v].sort() : [v]) {
pairs.push(`${enc(k)}=${enc(vv)}`);
}
}
return pairs.sort().join("&");
}
function sign(params, apiKey, secret) {
return crypto
.createHmac("sha256", secret)
.update(`GET/api/v1/og\n${canonicalize(params, apiKey)}`)
.digest("hex");
}
function signedUrl(
params,
apiKey,
secret,
baseUrl = "https://fastog.com/api/v1/og",
) {
const url = new URL(baseUrl);
for (const [k, v] of Object.entries({ ...params, key: apiKey })) {
const isArray = Array.isArray(v);
for (const vv of isArray ? v : [v]) {
url.searchParams.append(isArray ? `${k}[]` : k, vv);
}
}
url.searchParams.set("s", sign(params, apiKey, secret));
return url.toString();
}
module.exports = { signedUrl, sign, canonicalize };
PHP
function fastog_canonicalize(array $params, string $apiKey): string
{
$pairs = [];
foreach (array_merge($params, ['key' => $apiKey]) as $k => $v) {
$values = is_array($v) ? $v : [$v];
sort($values, SORT_STRING);
foreach ($values as $vv) {
$pairs[] = rawurlencode((string) $k) . '=' . rawurlencode((string) $vv);
}
}
sort($pairs, SORT_STRING);
return implode('&', $pairs);
}
function fastog_sign(array $params, string $apiKey, string $secret): string
{
return hash_hmac('sha256', 'GET/api/v1/og' . "\n" . fastog_canonicalize($params, $apiKey), $secret);
}
function fastog_signed_url(array $params, string $apiKey, string $secret, string $baseUrl = 'https://fastog.com/api/v1/og'): string
{
$all = array_merge($params, ['key' => $apiKey, 's' => fastog_sign($params, $apiKey, $secret)]);
return $baseUrl . '?' . http_build_query($all, '', '&', PHP_QUERY_RFC3986);
}
Python
import hashlib, hmac
from urllib.parse import quote
_enc = lambda s: quote(str(s), safe="-._~")
def canonicalize(params, api_key):
pairs = []
for k, v in {**params, "key": api_key}.items():
for vv in sorted(v) if isinstance(v, list) else [v]:
pairs.append(f"{_enc(k)}={_enc(vv)}")
return "&".join(sorted(pairs))
def sign(params, api_key, secret):
return hmac.new(secret.encode(), f"GET/api/v1/og\n{canonicalize(params, api_key)}".encode(), hashlib.sha256).hexdigest()
def signed_url(params, api_key, secret, base_url="https://fastog.com/api/v1/og"):
all_p = {**params, "key": api_key, "s": sign(params, api_key, secret)}
pairs = []
for k, v in all_p.items():
if isinstance(v, list):
for i, vv in enumerate(v):
pairs.append(f"{_enc(f'{k}[{i}]')}={_enc(vv)}")
else:
pairs.append(f"{_enc(k)}={_enc(v)}")
return f"{base_url}?{'&'.join(pairs)}"
Ruby
require "openssl"
def enc(s)
s.to_s.gsub(/[^A-Za-z0-9\-._~]/) { |c| c.bytes.map { |b| "%%%02X" % b }.join }
end
def canonicalize(params, api_key)
pairs = params.merge("key" => api_key).flat_map do |k, v|
values = v.is_a?(Array) ? v.sort : [v]
values.map { |vv| "#{enc(k)}=#{enc(vv)}" }
end
pairs.sort.join("&")
end
def sign(params, api_key, secret)
OpenSSL::HMAC.hexdigest("SHA256", secret, "GET/api/v1/og\n#{canonicalize(params, api_key)}")
end
def signed_url(params, api_key, secret, base_url = "https://fastog.com/api/v1/og")
all = params.merge("key" => api_key, "s" => sign(params, api_key, secret))
pairs = all.flat_map do |k, v|
if v.is_a?(Array)
v.each_with_index.map { |vv, i| "#{enc("#{k}[#{i}]")}=#{enc(vv)}" }
else
["#{enc(k)}=#{enc(v)}"]
end
end
"#{base_url}?#{pairs.join("&")}"
end
Go
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"sort"
"strings"
)
func enc(s string) string {
const hex = "0123456789ABCDEF"
var b strings.Builder
for i := 0; i < len(s); i++ {
c := s[i]
if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || c == '~' {
b.WriteByte(c)
} else {
b.WriteByte('%')
b.WriteByte(hex[c>>4])
b.WriteByte(hex[c&0x0f])
}
}
return b.String()
}
func canonicalize(params map[string]any, apiKey string) string {
all := map[string]any{}
for k, v := range params {
all[k] = v
}
all["key"] = apiKey
keys := make([]string, 0, len(all))
for k := range all {
keys = append(keys, k)
}
sort.Strings(keys)
var pairs []string
for _, k := range keys {
vals := []string{fmt.Sprint(all[k])}
if arr, ok := all[k].([]string); ok {
vals = arr
sort.Strings(vals)
}
for _, v := range vals {
pairs = append(pairs, enc(k)+"="+enc(v))
}
}
sort.Strings(pairs)
return strings.Join(pairs, "&")
}
func sign(params map[string]any, apiKey, secret string) string {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte("GET/api/v1/og\n" + canonicalize(params, apiKey)))
return hex.EncodeToString(mac.Sum(nil))
}
func signedURL(params map[string]any, apiKey, secret, baseURL string) string {
all := map[string]any{}
for k, v := range params {
all[k] = v
}
all["key"] = apiKey
all["s"] = sign(params, apiKey, secret)
keys := make([]string, 0, len(all))
for k := range all {
keys = append(keys, k)
}
sort.Strings(keys)
var pairs []string
for _, k := range keys {
if arr, ok := all[k].([]string); ok {
for i, v := range arr {
pairs = append(pairs, enc(k+"["+fmt.Sprint(i)+"]")+"="+enc(v))
}
} else {
pairs = append(pairs, enc(k)+"="+enc(fmt.Sprint(all[k])))
}
}
return baseURL + "?" + strings.Join(pairs, "&")
}
การดีบักข้อผิดพลาด 401
| รหัส | ความหมาย | วิธีแก้ไข |
|---|---|---|
missing_signature |
URL ขาด key หรือ s |
สร้างใหม่ด้วยฟังก์ชันเซ็นชื่อ (signer) ตรวจสอบให้แน่ใจว่ามีพารามิเตอร์ทั้งสองอยู่ |
invalid_signature |
canonical ไม่ตรงกันหรือ secret ผิด | เซ็นชื่อใหม่หลังการเปลี่ยนแปลงเนื้อหาใดๆ ยืนยันว่าใช้ hmac_secret เดียวกันทั้งสองฝั่ง ตรวจสอบว่า space เป็น %20 (ไม่ใช่ +) ตรวจสอบว่า array ใช้คีย์ในรูปแบบวงเล็บเหลี่ยมใน URL และยืนยันว่า key อยู่ใน canonical |