الموضوع

صور OG في بايثون

يشرح هذا الدليل كيفية إنشاء صور OG في بايثون باستخدام FastOG — سواء مع Django أو FastAPI أو Flask — من خلال توقيع رابط HMAC وعرض صور Open Graph ديناميكية عند الطلب. يعتمد النظام على الاشتراك مع حساب مجاني (اختر خطة Dynamic API؛ وأنشئ مفتاح API الخاص بك في لوحة التحكم)؛ وجرّب أداة اختبار صور OG المجانية لرؤية قالب قبل الدمج.

دليل شامل لتنفيذ روابط صور OG الموقّعة بـ HMAC في بايثون.

بدء الاستخدام السريع

1. إنشاء وحدة الموقّع (signer module)

python
# utils/fastog.py
import hmac
import hashlib
import os
from urllib.parse import quote, urlencode

def _enc(s):
    return quote(str(s), safe='-._~')  # RFC-3986: only unreserved chars

def generate_signed_og_url(params: dict, options: dict = None) -> str:
    options = options or {}
    base_url = options.get('base_url', os.environ.get('FASTOG_BASE_URL', 'https://fastog.com/api/v1/og'))
    api_key = options.get('api_key', os.environ.get('FASTOG_API_KEY'))
    hmac_secret = options.get('hmac_secret', os.environ.get('FASTOG_HMAC_SECRET'))

    if not api_key or not hmac_secret:
        raise ValueError("Missing FASTOG_API_KEY or FASTOG_HMAC_SECRET")

    all_params = {**params, 'key': api_key}
    pairs = []
    for k, v in all_params.items():
        for vv in sorted(v if isinstance(v, list) else [v]):
            pairs.append(f"{_enc(k)}={_enc(vv)}")
    canonical = '&'.join(sorted(pairs))
    signature = hmac.new(
        hmac_secret.encode(),
        f"GET/api/v1/og\n{canonical}".encode(),
        hashlib.sha256
    ).hexdigest()

    return f"{base_url}?{urlencode({**all_params, 's': signature})}"

2. متغيرات البيئة

bash
# .env
FASTOG_API_KEY=sk_your_full_api_key_here
FASTOG_HMAC_SECRET=your_64_char_hex_secret_here
bash
pip install python-dotenv
python
# config.py
from dotenv import load_dotenv
load_dotenv()

Django

خدمة التوقيع (Signer Service)

python
# services/fastog.py
from utils.fastog import generate_signed_og_url

class QuickogService:
    @staticmethod
    def get_og_url(template: str, **params) -> str:
        return generate_signed_og_url({
            'template': template,
            **params,
        })

العرض (View)

python
# views.py
from django.shortcuts import render
from services.fastog import QuickogService

def blog_post(request, slug):
    post = get_post(slug)

    og_image_url = QuickogService.get_og_url(
        template='blog',
        title=post.title,
        subtitle=post.excerpt,
        author=post.author.name,
    )

    return render(request, 'blog/post.html', {
        'post': post,
        'og_image_url': og_image_url,
    })

القالب (Template)

django
{# templates/blog/post.html #}
<!DOCTYPE html>
<html>
<head>
    <meta property="og:image" content="{{ og_image_url }}" />
    <meta property="og:title" content="{{ post.title }}" />
    <meta property="og:description" content="{{ post.excerpt }}" />
</head>
<body>
    <h1>{{ post.title }}</h1>
    <p>{{ post.excerpt }}</p>
</body>
</html>

وسم القالب المخصص (Custom Template Tag)

python
# templatetags/fastog_tags.py
from django import template
from services.fastog import QuickogService

register = template.Library()

@register.simple_tag
def og_image_url(template_name, **params):
    return QuickogService.get_og_url(template_name, **params)
django
{# In template #}
{% load fastog_tags %}
<meta property="og:image" content="{% og_image_url 'blog' title=post.title subtitle=post.excerpt %}" />

FastAPI

python
# main.py
from fastapi import FastAPI, Query
from pydantic import BaseModel
from utils.fastog import generate_signed_og_url

app = FastAPI()

class OgRequest(BaseModel):
    title: str
    template: str = "default"
    subtitle: str | None = None

@app.get("/api/og")
def generate_og(title: str = Query(...), template: str = Query("default")):
    og_url = generate_signed_og_url({
        'template': template,
        'title': title,
    })
    return {"ogImageUrl": og_url}

@app.get("/blog/{slug}")
async def blog_post(slug: str):
    post = await get_post(slug)

    og_url = generate_signed_og_url({
        'template': 'blog',
        'title': post.title,
        'subtitle': post.excerpt,
    })

    return {"post": post, "ogImageUrl": og_url}

Flask

python
# app.py
from flask import Flask, render_template, request, jsonify
from utils.fastog import generate_signed_og_url

app = Flask(__name__)

@app.route("/blog/<slug>")
def blog_post(slug):
    post = get_post(slug)

    og_image_url = generate_signed_og_url({
        'template': 'blog',
        'title': post['title'],
        'subtitle': post['excerpt'],
    })

    return render_template('blog/post.html', post=post, og_image_url=og_image_url)

@app.route("/api/og")
def api_og():
    title = request.args.get('title')
    template = request.args.get('template', 'default')

    if not title:
        return jsonify({'error': 'Missing title'}), 400

    og_url = generate_signed_og_url({
        'template': template,
        'title': title,
    })

    return jsonify({'ogImageUrl': og_url})

مثال التجارة الإلكترونية

python
# views.py
def product_detail(request, product_id):
    product = get_product(product_id)

    og_image_url = generate_signed_og_url({
        'template': 'ecommerce',
        'title': product.name,
        'price': product.formatted_price,
        'image': product.image_url,
    })

    return render(request, 'products/detail.html', {
        'product': product,
        'og_image_url': og_image_url,
    })

مثال على منشور مدونة

python
def user_profile(request, username):
    user = get_user(username)

    og_image_url = generate_signed_og_url({
        'template': 'blog',
        'title': user.name,
        'subtitle': user.bio,
        'author': user.name,
    })

    return render(request, 'profile/show.html', {
        'user': user,
        'og_image_url': og_image_url,
    })

الأخطاء الشائعة

توقيع الرسالة الخاطئة

❌ خطأ: f"GET/api/v1/og{canonical}" — فقدان فاصل السطر الجديد. ✅ صحيح: f"GET/api/v1/og\n{canonical}" — الطريقة والمسار، ثم سطر جديد، ثم الاستعلام الأساسي (canonical query).

استبعاد key من التوقيع

❌ خطأ: توقيع معاملات المحتوى فقط وإلحاق key بعنوان URL لاحقًا. ✅ صحيح: يتم دمج key في المعاملات قبل بناء الاستعلام الأساسي، ويظهر في عنوان URL النهائي.

نسيان ترميز المفاتيح والقيم بعنوان URL

❌ خطأ: بناء الاستعلام الأساسي بقيم خام غير مشفرة. ✅ صحيح: قم بترميز النسبة المئوية (percent-encode) لكل من المفاتيح والقيم باستخدام RFC-3986 (safe='-._~') قبل التوقيع — استخدم quote، وليس quote_plus (الذي يُصدر + للمسافات ولن يطابق الخادم).

401 missing_signature / invalid_signature

missing_signature (401) تعني أن key أو s مفقود من عنوان URL. invalid_signature (401) تعني أن الاستعلام الأساسي لم يطابق — تحقق من السطر الجديد في الرسالة الموقعة، ومن استخدام نفس hmac_secret على كلا الجانبين، ومن استخدام quote(safe='-._~') (وليس quote_plus) لكل مفتاح وقيمة.

مرجع API الكامل

جرّبه مجانًا — لا حاجة للتسجيل

معاينة واختبار صور OG الديناميكية في ثوانٍ.

افتح أداة اختبار OG المجانية

A BeCaSimulations site