Skip to content

Commit 82498ad

Browse files
committed
feat: 内置 1333 个技术图标走本地优先,CDN 仅兜底缺失
新增 extract-wappalyzer-icons 抽取脚本扫 public/rules 里所有 name、按 slug 匹配本地 Wappalyzer 图标库、抽出 1247 个 SVG + 86 个 PNG 到 public/icons/tech、同步写出 local-icon-manifest.json 给 TechChip 查表;TechChip 改三档链式 fallback:本地命中 → cdn.simpleicons.org → 2 秒超时回落首字母色块,避免本地有图标却走 CDN 浪费请求、也保住国内访问可用性;PRIVACY.md 同步说明本地优先策略;将版本号提升到 1.3.67。
1 parent 0dd3760 commit 82498ad

1,338 files changed

Lines changed: 37405 additions & 16 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

PRIVACY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ StackPrism / 栈棱镜(以下简称「本扩展」)是一款基于 Chrome / Edge
4242
- 没有遥测、没有广告 SDK、没有第三方分析、没有错误上报到云端
4343
- 扩展只发起两类网络请求,**均不**携带您的任何身份信息或浏览数据:
4444
1. 异步抓取**您当前访问页面自身已经加载的**少量 JS 文件首段(走浏览器缓存,不增加额外网络流量),用于在本地扫描该 JS 中的版权注释和 OAuth 入口 URL
45-
2. `cdn.simpleicons.org` 拉取技术品牌图标(请求体只包含一个公开技术名 slug,例如 react / vuedotjs / docker,不含您正在访问的页面 URL、识别结果或任何个人信息);图标如果 2 秒内未加载成功,扩展会自动回落到本地生成的首字母色块,绝不阻塞识别流程
45+
2. 技术品牌图标**优先从本扩展内置的图标库读取**(已离线打包 1300+ 主流技术的 SVG / PNG,不发起任何网络请求);仅当某项技术不在内置图标库中时,才会从 `cdn.simpleicons.org` 拉取兜底(请求体只包含一个公开技术名 slug,例如 react / vuedotjs / docker,不含您正在访问的页面 URL、识别结果或任何个人信息);CDN 如果 2 秒内未加载成功,扩展会自动回落到本地生成的首字母色块,绝不阻塞识别流程
4646

4747
## 第三方共享
4848

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#!/usr/bin/env node
2+
// 从本地安装的 Wappalyzer 扩展抽取与我们 rules name 命中的图标,放到 public/icons/tech/<slug>.{svg,png}
3+
// 用法:WAPPALYZER_ICON_DIR=<wappalyzer 安装目录的 images/icons> node build-scripts/extract-wappalyzer-icons.mjs
4+
// 默认到百分浏览器(Cent Browser)的 Wappalyzer 6.12.2 安装路径找
5+
import fs from 'node:fs'
6+
import path from 'node:path'
7+
import { fileURLToPath } from 'node:url'
8+
9+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
10+
const repoRoot = path.resolve(__dirname, '..')
11+
12+
const DEFAULT_DIR =
13+
'C:/Users/19622/AppData/Local/CentBrowser/User Data/Default/Extensions/gppongmhjkpfnbhagpmjfkannfbllamg/6.12.2_0/images/icons'
14+
const ICON_DIR = process.env.WAPPALYZER_ICON_DIR || DEFAULT_DIR
15+
const RULES_DIR = path.join(repoRoot, 'public', 'rules')
16+
const OUTPUT_DIR = path.join(repoRoot, 'public', 'icons', 'tech')
17+
18+
if (!fs.existsSync(ICON_DIR)) {
19+
console.error(`找不到 Wappalyzer 图标目录:${ICON_DIR}`)
20+
console.error('请设置环境变量 WAPPALYZER_ICON_DIR 指向本地安装的 images/icons 目录')
21+
process.exit(1)
22+
}
23+
24+
const collectNames = (node, out) => {
25+
if (!node || typeof node !== 'object') return
26+
if (Array.isArray(node)) {
27+
for (const i of node) collectNames(i, out)
28+
return
29+
}
30+
if (typeof node.name === 'string') out.add(node.name.trim())
31+
for (const v of Object.values(node)) collectNames(v, out)
32+
}
33+
34+
const walk = (dir, files = []) => {
35+
for (const e of fs.readdirSync(dir)) {
36+
const p = path.join(dir, e)
37+
if (fs.statSync(p).isDirectory()) walk(p, files)
38+
else if (e.endsWith('.json')) files.push(p)
39+
}
40+
return files
41+
}
42+
43+
const slugify = raw =>
44+
String(raw || '')
45+
.toLowerCase()
46+
.replace(/[^a-z0-9]/g, '')
47+
48+
// 收 Wappalyzer 图标库:filename → [候选文件名(可能多种扩展名)]
49+
const iconBySlug = new Map()
50+
for (const f of fs.readdirSync(ICON_DIR)) {
51+
if (!f.endsWith('.svg') && !f.endsWith('.png')) continue
52+
const slug = slugify(f.replace(/\.(svg|png)$/i, ''))
53+
if (!slug) continue
54+
if (!iconBySlug.has(slug)) iconBySlug.set(slug, [])
55+
iconBySlug.get(slug).push(f)
56+
}
57+
58+
// 收我们 rules 里所有 name
59+
const ruleNames = new Set()
60+
for (const f of walk(RULES_DIR)) {
61+
try {
62+
collectNames(JSON.parse(fs.readFileSync(f, 'utf8')), ruleNames)
63+
} catch {
64+
// ignore parse errors
65+
}
66+
}
67+
68+
// 清掉旧的输出目录,重新抽
69+
if (fs.existsSync(OUTPUT_DIR)) fs.rmSync(OUTPUT_DIR, { recursive: true, force: true })
70+
fs.mkdirSync(OUTPUT_DIR, { recursive: true })
71+
72+
let copied = 0
73+
let svgCount = 0
74+
let pngCount = 0
75+
let totalBytes = 0
76+
const manifest = {}
77+
const seenSlugs = new Set()
78+
for (const name of ruleNames) {
79+
// 跟我们 TechChip 里的 slugify 算法保持一致:取 / 之前的部分,小写 + 去掉所有非字母数字
80+
const slug = slugify(name.split('/')[0])
81+
if (!slug || seenSlugs.has(slug)) continue
82+
const candidates = iconBySlug.get(slug)
83+
if (!candidates) continue
84+
85+
// 优先 svg
86+
const file = candidates.find(c => c.endsWith('.svg')) || candidates[0]
87+
const ext = path.extname(file).toLowerCase().slice(1)
88+
const dst = path.join(OUTPUT_DIR, slug + '.' + ext)
89+
fs.copyFileSync(path.join(ICON_DIR, file), dst)
90+
seenSlugs.add(slug)
91+
manifest[slug] = ext
92+
copied++
93+
totalBytes += fs.statSync(dst).size
94+
if (ext === 'svg') svgCount++
95+
else pngCount++
96+
}
97+
98+
// 写出 manifest,运行时 TechChip 用它判断本地是否有图标、是 svg 还是 png,避免无意义 404
99+
const sortedManifest = {}
100+
for (const slug of Object.keys(manifest).sort()) sortedManifest[slug] = manifest[slug]
101+
const manifestPath = path.join(repoRoot, 'src', 'ui', 'components', 'local-icon-manifest.json')
102+
fs.writeFileSync(manifestPath, JSON.stringify(sortedManifest) + '\n', 'utf8')
103+
104+
console.log(`抽取完成:${copied} 个图标 (svg: ${svgCount}, png: ${pngCount})`)
105+
console.log(`输出目录:${OUTPUT_DIR}`)
106+
console.log(`总大小:${(totalBytes / 1024 / 1024).toFixed(2)} MB`)
107+
console.log(`manifest:${manifestPath}`)

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "stackprism",
33
"private": true,
4-
"version": "1.3.66",
4+
"version": "1.3.67",
55
"type": "module",
66
"description": "StackPrism 用于检测网页前端、后端、CDN、SaaS、广告营销、统计、登录、支付、网站程序和主题模板线索。",
77
"scripts": {
@@ -10,6 +10,7 @@
1010
"build:injected": "node build-scripts/build-injected.mjs",
1111
"build": "pnpm run build:injected && vite build",
1212
"check:links": "node build-scripts/check-tech-links.mjs",
13+
"extract:icons": "node build-scripts/extract-wappalyzer-icons.mjs",
1314
"dev": "pnpm run build:injected && vite",
1415
"docs:assets": "node build-scripts/sync-docs-assets.mjs",
1516
"docs:dev": "pnpm run docs:assets && vitepress dev docs",

public/icons/tech/51la.png

672 Bytes
Loading

public/icons/tech/abantecart.svg

Lines changed: 62 additions & 0 deletions
Loading

public/icons/tech/abtasty.svg

Lines changed: 22 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)