README file from
GithubHotlink Media
Play hotlink-protected videos, audio and images in your notes.
The problem
Many sites only serve their media to requests that come from their own pages: the server checks the Referer header. In a browser, reading the original article, the Referer is the site itself, so the video plays. Put the same URL in an Obsidian note and the Referer becomes app://obsidian.md. The server answers HTTP 403 and you get a black box that never plays.
A typical case is a WeChat Official Account article (mp.weixin.qq.com) clipped into Obsidian: the note keeps the <video> tags, but every video is blocked.
Obsidian does not let plugins rewrite request headers globally, so the usual fix is not available.
What this plugin does
For each <video>, <audio>, <img> or <source> in a rendered note whose URL matches one of its rules, it:
- Takes over the element: moves the URL aside so Obsidian doesn't request it and fail.
- Fetches the file itself when the element scrolls near the viewport, sending the headers the source site expects.
- Hands the file to the element as a
blob:URL. Nothing is written to your vault; the file lives in memory until the note is closed. - If a site's rule knows how, gets a fresh URL when the old one is rejected and retries.
- If it still fails, shows a short note under the media with a link to the original page.
URLs that match no rule are never touched. Your markdown is never modified.
Works in Reading view and Live Preview.
Supported sites
| Rule | Handles | How |
|---|---|---|
| WeChat Official Account videos | mpvideo.qpic.cn |
Sends Referer: https://mp.weixin.qq.com/ and a browser User-Agent. When rejected, re-fetches the original article (the note's source frontmatter field), finds the same video's fresh URL, and retries, up to 4 attempts. |
Measured on 2026-10-04: the same WeChat video URL works only part of the time (3 out of 8 checks over two hours, one every 15 minutes), while re-fetching the article always yields working URLs. That is why the rule refreshes and retries rather than giving up on the first 403.
Network use
This plugin makes network requests, and only for media that match a rule:
- The media file itself, from the media's own host (for WeChat:
mpvideo.qpic.cn), with the headers listed in the rule. - The original article page, only when the media URL was rejected and the rule can refresh it (for WeChat: the
mp.weixin.qq.comURL in the note'ssourcefrontmatter field).
No other requests are made. No data about you or your vault is sent anywhere. There is no telemetry.
Requests use Node's built-in http/https modules instead of fetch or requestUrl: browsers forbid setting Referer by hand, and Electron's network stack does not reliably send it either. That is why the plugin is desktop only.
Adding a site
Add an entry to RULES in src/rules.ts:
{
id: "example",
name: "Example images",
match: /^https:\/\/img\.example\.com\//, // which media URLs this rule handles
headers: { Referer: "https://www.example.com/" }, // headers to send
// refresh: optional, how to get a fresh URL when the old one is rejected
}
Notes
- A file is downloaded in full before it plays, so long videos take a few seconds to start.
- The same file shown in both Reading view and Live Preview is downloaded once.
- Files are fetched only when they scroll within 400px of the viewport, so a note with many videos does not download them all on open.
Development
npm install
npm test # rules, retry and DOM takeover logic, including real WeChat article data in tests/fixtures/
npm run build # produces main.js
To install manually, copy main.js, manifest.json and styles.css into <vault>/.obsidian/plugins/hotlink-media/.
License
MIT