-
Notifications
You must be signed in to change notification settings - Fork 53
Adds opengraph example #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+278
−0
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| # OpenGraph Meta Tag Injection Example | ||
|
|
||
| This example demonstrates how to build a Python Worker that dynamically injects OpenGraph meta tags into web pages based on the request path. This is perfect for controlling how your content appears when shared on social media platforms like Facebook, Twitter, LinkedIn, and Slack. | ||
|
|
||
| ## What It Does | ||
|
|
||
| The Worker: | ||
| 1. **Receives a request** for a specific URL path (e.g., `/blog/my-article`) | ||
| 2. **Generates OpenGraph metadata** dynamically based on the path | ||
| 3. **Fetches the original HTML** from your target website | ||
| 4. **Uses Cloudflare's HTMLRewriter** to inject OpenGraph meta tags into the HTML `<head>` section | ||
| 5. **Returns the enhanced HTML** with proper social media preview tags | ||
|
|
||
| This example showcases how to use Cloudflare's powerful HTMLRewriter API from Python Workers via the `js` module interop. | ||
|
|
||
| ## How to Run | ||
|
|
||
| First ensure that `uv` is installed: | ||
| https://docs.astral.sh/uv/getting-started/installation/#standalone-installer | ||
|
|
||
| Now, if you run `uv run pywrangler dev` within this directory, it should use the config | ||
| in `wrangler.jsonc` to run the example. | ||
|
|
||
| ```bash | ||
| uv run pywrangler dev | ||
| ``` | ||
|
|
||
| Then visit: | ||
| - `http://localhost:8787/` - Home page with default metadata | ||
| - `http://localhost:8787/blog/python-workers-intro` - Blog post example | ||
| - `http://localhost:8787/products/awesome-widget` - Product page example | ||
| - `http://localhost:8787/about` - About page example | ||
|
|
||
| ## Deployment | ||
|
|
||
| Deploy to Cloudflare Workers: | ||
|
|
||
| ```bash | ||
| uv run pywrangler deploy | ||
| ``` | ||
|
|
||
| ## Customization | ||
|
|
||
| To adapt this example for your own website: | ||
|
|
||
| 1. **Update the target URL** in `src/entry.py`: | ||
| ```python | ||
| target_url = f"https://your-website.com{path}" | ||
| ``` | ||
|
|
||
| 2. **Customize metadata patterns** in the `get_opengraph_data()` method: | ||
| ```python | ||
| if path.startswith("/your-section/"): | ||
| og_data.update({ | ||
| "title": "Your Custom Title", | ||
| "description": "Your custom description", | ||
| "image": "https://your-image-url.com/image.jpg" | ||
| }) | ||
| ``` | ||
|
|
||
| 3. **Add more URL patterns** to match your site structure | ||
|
|
||
| ## Testing Your OpenGraph Tags | ||
|
|
||
| Use these tools to validate your OpenGraph tags: | ||
| - [Facebook Sharing Debugger](https://developers.facebook.com/tools/debug/) | ||
| - [X Card Validator](https://cards-dev.x.com/validator) | ||
| - [LinkedIn Post Inspector](https://www.linkedin.com/post-inspector/) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "name": "python-opengraph", | ||
| "version": "0.0.0", | ||
| "private": true, | ||
| "scripts": { | ||
| "deploy": "uv run pywrangler deploy", | ||
| "dev": "uv run pywrangler dev", | ||
| "start": "uv run pywrangler dev" | ||
| }, | ||
| "devDependencies": { | ||
| "wrangler": "^4.46.0" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| [project] | ||
| name = "python-opengraph" | ||
| version = "0.1.0" | ||
| description = "Python opengraph example" | ||
| readme = "README.md" | ||
| requires-python = ">=3.12" | ||
| dependencies = [ | ||
| "webtypy>=0.1.7", | ||
| ] | ||
|
|
||
| [dependency-groups] | ||
| dev = [ | ||
| "workers-py", | ||
| "workers-runtime-sdk" | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| from workers import WorkerEntrypoint, Request, fetch | ||
| from js import HTMLRewriter | ||
| from urllib.parse import urlparse | ||
| from html import escape | ||
|
|
||
| from pyodide.ffi import create_proxy | ||
|
|
||
|
|
||
| class MetaTagInjector: | ||
| """ | ||
| Element handler for HTMLRewriter that injects OpenGraph meta tags. | ||
| Uses Python's html.escape() for proper HTML escaping. | ||
| """ | ||
|
|
||
| def __init__(self, og_data: dict): | ||
| self.og_data = og_data | ||
| self.injected = False | ||
|
|
||
| def element(self, element): | ||
| """Called when the <head> element is encountered.""" | ||
| if not self.injected: | ||
| # Create and inject meta tags | ||
| self._inject_meta_tags(element) | ||
| self.injected = True | ||
|
|
||
| def _inject_meta_tags(self, head_element): | ||
| """Inject OpenGraph and Twitter Card meta tags.""" | ||
| # OpenGraph tags | ||
| self._create_meta(head_element, "property", "og:title", self.og_data["title"]) | ||
| self._create_meta( | ||
| head_element, "property", "og:description", self.og_data["description"] | ||
| ) | ||
| self._create_meta(head_element, "property", "og:image", self.og_data["image"]) | ||
| self._create_meta(head_element, "property", "og:url", self.og_data["url"]) | ||
| self._create_meta(head_element, "property", "og:type", self.og_data["type"]) | ||
| self._create_meta( | ||
| head_element, "property", "og:site_name", self.og_data["site_name"] | ||
| ) | ||
|
|
||
| # Twitter Card tags | ||
| self._create_meta(head_element, "name", "twitter:card", "summary_large_image") | ||
| self._create_meta(head_element, "name", "twitter:title", self.og_data["title"]) | ||
| self._create_meta( | ||
| head_element, "name", "twitter:description", self.og_data["description"] | ||
| ) | ||
| self._create_meta(head_element, "name", "twitter:image", self.og_data["image"]) | ||
|
|
||
| def _create_meta(self, head_element, attr_name: str, attr_value: str, content: str): | ||
| """ | ||
| Create a meta tag and prepend it to the head element. | ||
| Uses Python's html.escape() for proper attribute escaping. | ||
| """ | ||
| # Use Python's built-in html.escape() which handles all necessary escaping | ||
| escaped_attr_value = escape(attr_value, quote=True) | ||
| escaped_content = escape(content, quote=True) | ||
| meta_html = ( | ||
| f'<meta {attr_name}="{escaped_attr_value}" content="{escaped_content}" />' | ||
| ) | ||
| head_element.prepend(meta_html, html=True) | ||
|
|
||
|
|
||
| class ExistingMetaRemover: | ||
| """ | ||
| Element handler that removes existing OpenGraph and Twitter meta tags. | ||
| """ | ||
|
|
||
| def element(self, element): | ||
| """Remove the element by calling remove().""" | ||
| element.remove() | ||
|
|
||
|
|
||
| class Default(WorkerEntrypoint): | ||
| """ | ||
| OpenGraph Meta Tag Injection Example | ||
|
|
||
| This Worker fetches a web page and injects OpenGraph meta tags | ||
| based on the request path using Cloudflare's HTMLRewriter API. | ||
| """ | ||
|
|
||
| async def fetch(self, request: Request): | ||
| # Parse the request path to determine which page we're serving | ||
| url = urlparse(request.url) | ||
| path = url.path | ||
|
|
||
| # Define OpenGraph metadata based on the path | ||
| og_data = self.get_opengraph_data(path) | ||
|
|
||
| # Fetch the original HTML from a target website | ||
| # In this example, we'll use example.com, but you can replace this | ||
| # with your actual website URL | ||
| # | ||
| # Note that this isn't necessary if your worker will also be serving | ||
| # content of your website, in that case you should already have the HTML | ||
| # you're returning ready to go here. | ||
| target_url = f"https://example.com{path}" | ||
|
|
||
| # Fetch the original page | ||
| response = await fetch(target_url) | ||
|
|
||
| # Use HTMLRewriter to inject OpenGraph meta tags | ||
| rewritten_response = self.inject_opengraph_tags(response, og_data) | ||
|
|
||
| return rewritten_response | ||
|
|
||
| def get_opengraph_data(self, path: str) -> dict: | ||
| """ | ||
| Generate OpenGraph metadata based on the request path. | ||
| Customize this function to match your site's structure. | ||
| """ | ||
| # Default metadata | ||
| og_data = { | ||
| "title": "My Awesome Website", | ||
| "description": "Welcome to my website built with Python Workers!", | ||
| "image": "https://images.unsplash.com/photo-1518770660439-4636190af475", | ||
| "url": f"https://yoursite.com{path}", | ||
| "type": "website", | ||
| "site_name": "Python Workers Demo", | ||
| } | ||
|
|
||
| # Customize based on path | ||
| if path.startswith("/blog/"): | ||
| article_slug = path.replace("/blog/", "").strip("/") | ||
| og_data.update( | ||
| { | ||
| "title": f"Blog Post: {article_slug.replace('-', ' ').title()}", | ||
| "description": f"Read our latest article about {article_slug.replace('-', ' ')}", | ||
| "image": "https://images.unsplash.com/photo-1499750310107-5fef28a66643", | ||
| "type": "article", | ||
| } | ||
| ) | ||
| elif path.startswith("/products/"): | ||
| product_slug = path.replace("/products/", "").strip("/") | ||
| og_data.update( | ||
| { | ||
| "title": f"Product: {product_slug.replace('-', ' ').title()}", | ||
| "description": f"Check out our amazing {product_slug.replace('-', ' ')} product", | ||
| "image": "https://images.unsplash.com/photo-1505740420928-5e560c06d30e", | ||
| "type": "product", | ||
| } | ||
| ) | ||
| elif path == "/about": | ||
| og_data.update( | ||
| { | ||
| "title": "About Us - Python Workers", | ||
| "description": "Learn more about our team and what we do with Python Workers", | ||
| "image": "https://images.unsplash.com/photo-1522071820081-009f0129c71c", | ||
| } | ||
| ) | ||
|
|
||
| return og_data | ||
|
|
||
| def inject_opengraph_tags(self, response, og_data: dict): | ||
| """ | ||
| Use HTMLRewriter to inject OpenGraph meta tags into the HTML response. | ||
| Removes existing OG tags first to avoid duplicates. | ||
| """ | ||
| # Create an HTMLRewriter instance | ||
| rewriter = HTMLRewriter.new() | ||
|
|
||
| meta_remover = create_proxy(ExistingMetaRemover()) | ||
| meta_injector = create_proxy(MetaTagInjector(og_data)) | ||
|
|
||
| rewriter = HTMLRewriter.new() | ||
| # Remove existing OpenGraph and Twitter meta tags to avoid duplicates | ||
| rewriter.on('meta[property^="og:"]', meta_remover) | ||
| rewriter.on('meta[name^="twitter:"]', meta_remover) | ||
| # Inject new OpenGraph meta tags into the <head> element | ||
| rewriter.on("head", meta_injector) | ||
|
|
||
| return rewriter.transform(response.js_object) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| { | ||
| "$schema": "node_modules/wrangler/config-schema.json", | ||
| "name": "python-opengraph", | ||
| "main": "src/entry.py", | ||
| "compatibility_date": "2025-11-02", | ||
| "compatibility_flags": [ | ||
| "python_workers" | ||
| ], | ||
| "observability": { | ||
| "enabled": true | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.