免责声明:本文章的所有内容具有时效性和道德争议性,请谨慎使用!
重要提示:本方案依赖 不是辣么稳定的 第三方服务,可能随时失效,请谨慎使用!
前情提要
背景(废话)
本人因受 二叉树树 影响,考虑写博客记录生活(实则早已把QQ空间当成博客 /雾)。但因一些限制,本人用不了赛博菩萨 Cloudflare 的 R2 存储(其实就是没有银行卡),这就导致博客的图片传输成了一大问题。作为 Bilibili API Collect 的长期潜在共享者之一,我的潜意识告诉我,相比于 可能被刷且过于昂贵的国内对象存储 和 毫不稳定的第三方图站,允许空 Referer 的哔哩哔哩图站 hdslb.com 显然是个更优的选择**(并不**
注意事项
本方法不适用于有以下需求的大佬
-
需要图片无损的(哔哩哔哩常规方式上传图片会转码,略微压缩)
-
有除了图片以外其他存储需求的
-
博客有极大量访问的,且内容及其重要的
实现方法
哔哩哔哩端
每次写完博客,前往 哔哩哔哩动态 上传博客所需图片,并点击发表动态(可以修改可见范围为“仅自己可见”),右键复制图像链接,拷贝到形如 https://i0.hdslb.com/bfs/new_dyn/3c56f49711ce5d0ef6a03a67994166e2325903362.jpg@784w_1044h.webp 的链接后,去除 @ 及后面的所有字符(如果没有就不用去)
也可保留为 https://i0.hdslb.com/bfs/new_dyn/3c56f49711ce5d0ef6a03a67994166e2325903362.jpg@.webp,有损压缩,大幅减少传输占用(3.22 MB -> 241 KB)。
如果你不想看下文长篇大论,对你的 Fuwari 自身进行修改,你可以在Markdown中,直接使用形如下方的格式来代替 。
<img src='你的链接' alt="图片说明" referrerpolicy='no-referrer' />Fuwari端
仅仅解决了文章中的图片还不够,我们还有封面图,而封面图不能用 HTML格式 来规避 Referer问题。就真的毫无解决方法了吗?
通过本地调试发现,处理封面图片的程序就在 Fuwari 项目的 ./src/components/misc/ImageWrapper.astro 的最后一段(当前版本第53行)
{!isLocal && <img src={isPublic ? url(src) : src} alt={alt || ""} class={imageClass} style={imageStyle} }只需将其略作修改,改为如下即可
{!isLocal && <img src={isPublic ? url(src) : src} alt={alt || ""} class={imageClass} style={imageStyle} referrerpolicy="no-referrer" />}这样,就可以解决封面图带 Referer 的问题了。
可是如果每次写博客都要用 <img> 标签去写图片,可就比较复杂了。有没有什么一劳永逸的方式呢?
当然有! 而且并不复杂。
剪水不才,但通过仔细的代码分析(当然也只是走马观花),终于发现,Fuwari 本身好像并没有对图片的特殊处理。所以,要对 <img> 标签添加 referrerpolicy 就只能自己写中间键了。
在利用 AI 与自身的认知调用之后,我成功了!
以下是详细的修改流程:
-
添加文件
./src/plugins/rehype-img-no-referrer.js,并写入以下代码./src/plugins/rehype-img-no-referrer.js import { visit } from 'unist-util-visit';/*** 一个简单的 Rehype 插件,为所有 img 标签添加 referrerpolicy="no-referrer" 属性,注释都是 AI 写的*/export function rehypeImgNoReferrer() {return (tree) => {visit(tree, (node) => {// 检查节点是否为 HTML 元素且标签名是 'img'if (node.type === 'element' && node.tagName === 'img') {// 确保 properties 对象存在node.properties = node.properties || {};// 添加或覆盖 referrerpolicy 属性node.properties.referrerpolicy = 'no-referrer';}});};} -
修改根目录下的文件
astro.config.mjs,在开头的一堆import ...后面加上一行./astro.config.mjs import { rehypeImgNoReferrer } from "./src/plugins/rehype-img-no-referrer.js";并在后面的
defineConfig中使用你的中间件./astro.config.mjs export default defineConfig({// ... 其他配置markdown: {rehypePlugins: [// ... 其他 rehype 插件rehypeImgNoReferrer // [[[这一行添加我们的插件]]]],// ...}});
这里呢,也怕我写得不太清楚,所以在这里也贴出我的 astro.config.mjs 全部内容以供参考。
import sitemap from "@astrojs/sitemap";import svelte from "@astrojs/svelte";import tailwind from "@astrojs/tailwind";import { pluginCollapsibleSections } from "@expressive-code/plugin-collapsible-sections";import { pluginLineNumbers } from "@expressive-code/plugin-line-numbers";import swup from "@swup/astro";import expressiveCode from "astro-expressive-code";import icon from "astro-icon";import { defineConfig } from "astro/config";import rehypeAutolinkHeadings from "rehype-autolink-headings";import rehypeComponents from "rehype-components"; /* Render the custom directive content */import rehypeKatex from "rehype-katex";import rehypeSlug from "rehype-slug";import remarkDirective from "remark-directive"; /* Handle directives */import remarkGithubAdmonitionsToDirectives from "remark-github-admonitions-to-directives";import remarkMath from "remark-math";import remarkSectionize from "remark-sectionize";import { expressiveCodeConfig } from "./src/config.ts";import { pluginLanguageBadge } from "./src/plugins/expressive-code/language-badge.ts";import { AdmonitionComponent } from "./src/plugins/rehype-component-admonition.mjs";import { GithubCardComponent } from "./src/plugins/rehype-component-github-card.mjs";import { parseDirectiveNode } from "./src/plugins/remark-directive-rehype.js";import { remarkExcerpt } from "./src/plugins/remark-excerpt.js";import { remarkReadingTime } from "./src/plugins/remark-reading-time.mjs";import { pluginCustomCopyButton } from "./src/plugins/expressive-code/custom-copy-button.js";// 导入自定义的rehype插件import { rehypeImgNoReferrer } from "./src/plugins/rehype-img-no-referrer.js";
// https://astro.build/configexport default defineConfig({ site: "https://blog.evira.top", base: "/", trailingSlash: "always", integrations: [ tailwind({ nesting: true, }), swup({ theme: false, animationClass: "transition-swup-", // see https://swup.js.org/options/#animationselector // the default value `transition-` cause transition delay // when the Tailwind class `transition-all` is used containers: ["main", "#toc"], smoothScrolling: true, cache: true, preload: true, accessibility: true, updateHead: true, updateBodyClass: false, globalInstance: true, }), icon({ include: { "preprocess: vitePreprocess(),": ["*"], "fa6-brands": ["*"], "fa6-regular": ["*"], "fa6-solid": ["*"], }, }), expressiveCode({ themes: [expressiveCodeConfig.theme, expressiveCodeConfig.theme], plugins: [ pluginCollapsibleSections(), pluginLineNumbers(), pluginLanguageBadge(), pluginCustomCopyButton() ], defaultProps: { wrap: true, overridesByLang: { 'shellsession': { showLineNumbers: false, }, }, }, styleOverrides: { codeBackground: "var(--codeblock-bg)", borderRadius: "0.75rem", borderColor: "none", codeFontSize: "0.875rem", codeFontFamily: "'JetBrains Mono Variable', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace", codeLineHeight: "1.5rem", frames: { editorBackground: "var(--codeblock-bg)", terminalBackground: "var(--codeblock-bg)", terminalTitlebarBackground: "var(--codeblock-topbar-bg)", editorTabBarBackground: "var(--codeblock-topbar-bg)", editorActiveTabBackground: "none", editorActiveTabIndicatorBottomColor: "var(--primary)", editorActiveTabIndicatorTopColor: "none", editorTabBarBorderBottomColor: "var(--codeblock-topbar-bg)", terminalTitlebarBorderBottomColor: "none" }, textMarkers: { delHue: 0, insHue: 180, markHue: 250 } }, frames: { showCopyToClipboardButton: false, } }), svelte(), sitemap(), ], markdown: { remarkPlugins: [ remarkMath, remarkReadingTime, remarkExcerpt, remarkGithubAdmonitionsToDirectives, remarkDirective, remarkSectionize, parseDirectiveNode, ], rehypePlugins: [ rehypeKatex, rehypeSlug, [ rehypeComponents, { components: { github: GithubCardComponent, note: (x, y) => AdmonitionComponent(x, y, "note"), tip: (x, y) => AdmonitionComponent(x, y, "tip"), important: (x, y) => AdmonitionComponent(x, y, "important"), caution: (x, y) => AdmonitionComponent(x, y, "caution"), warning: (x, y) => AdmonitionComponent(x, y, "warning"), }, }, ], [ rehypeAutolinkHeadings, { behavior: "append", properties: { className: ["anchor"], }, content: { type: "element", tagName: "span", properties: { className: ["anchor-icon"], "data-pagefind-ignore": true, }, children: [ { type: "text", value: "#", }, ], }, }, ], rehypeImgNoReferrer, ], }, vite: { build: { rollupOptions: { onwarn(warning, warn) { // temporarily suppress this warning if ( warning.message.includes("is dynamically imported by") && warning.message.includes("but also statically imported by") ) { return; } warn(warning); }, }, }, },});最后
该方法不保证强可用性,说不定哪天就不让空 Referer 访问了。
另外,强烈建议有条件的佬使用 Cloudflare 的 R2 存储 + avif图片压缩以获得最优体验。