chore: 初始提交 - 移除所有硬编码凭据,统一从环境变量读取

This commit is contained in:
yangxiangyuan
2026-06-11 17:10:58 +08:00
commit c223c79b36
1699 changed files with 313940 additions and 0 deletions
+187
View File
@@ -0,0 +1,187 @@
const axios = require('axios')
const fs = require('fs')
const path = require('path')
const Database = require('better-sqlite3')
const { logJSON } = require('./logger')
const SH_LAT = 31.2304
const SH_LON = 121.4737
const fetchWeather = async (lat, lon) => {
const latitude = Number(lat) || SH_LAT
const longitude = Number(lon) || SH_LON
try {
const urlWeather = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current=temperature_2m,apparent_temperature,precipitation&hourly=precipitation_probability&daily=temperature_2m_max,temperature_2m_min&timezone=auto`
const urlAir = `https://air-quality-api.open-meteo.com/v1/air-quality?latitude=${latitude}&longitude=${longitude}&hourly=us_aqi,pm2_5,european_aqi&timezone=auto`
const [wRes, aRes] = await Promise.all([
axios.get(urlWeather, { timeout: 8000 }),
axios.get(urlAir, { timeout: 8000 })
])
const w = wRes.data || {}
const a = aRes.data || {}
const cur = (w.current || {})
let prob = null
try {
const times = (w.hourly && w.hourly.time) || []
const probs = (w.hourly && w.hourly.precipitation_probability) || []
const nowIso = new Date().toISOString().slice(0,13)
const idx = times.findIndex(t => String(t).slice(0,13) === nowIso)
if (idx >= 0) prob = probs[idx]
} catch {}
const hAqi = (a.hourly || {})
let tmin = null, tmax = null
try {
const dTimes = (w.daily && w.daily.time) || []
const dMin = (w.daily && w.daily.temperature_2m_min) || []
const dMax = (w.daily && w.daily.temperature_2m_max) || []
const today = new Date().toISOString().slice(0,10)
const di = dTimes.findIndex(t => String(t).slice(0,10) === today)
if (di >= 0) { tmin = dMin[di]; tmax = dMax[di] }
} catch {}
const info = {
source: 'open-meteo',
city: '上海',
temperature: cur.temperature_2m,
apparent_temperature: cur.apparent_temperature,
precipitation: cur.precipitation,
precipitation_probability: prob,
us_aqi: hAqi.us_aqi ? hAqi.us_aqi[0] : undefined,
european_aqi: hAqi.european_aqi ? hAqi.european_aqi[0] : undefined,
pm2_5: hAqi.pm2_5 ? hAqi.pm2_5[0] : undefined,
temperature_min: tmin,
temperature_max: tmax
}
logJSON('wall.weather', { latitude, longitude, info }, 'wall')
return info
} catch (e) {
logJSON('wall.weather.error', { error: String(e.message || e) }, 'wall')
return { source: 'open-meteo', city: '上海', temperature: null, apparent_temperature: null, precipitation: null, precipitation_probability: null, us_aqi: null, pm2_5: null }
}
}
module.exports = { fetchWeather }
const cfgPath = path.join(process.cwd(), 'config', 'wall.json')
if (!fs.existsSync(cfgPath)) fs.writeFileSync(cfgPath, JSON.stringify({
autoplay_ms: 45000,
swipe_pause_ms: 5 * 60 * 1000,
max_cards_on_screen: 2,
background: { url: "", mode: "tile", size: 120, opacity: 0.08 },
cards: [
{ title: '轻断食 · 吃什么', items: ['清汤/水', '清蒸蛋/少量蛋白', '蔬菜少油', '避免高糖与精制', '避免冷饮'], meta: '场景:晨间/午后 · 时长≤3秒读' },
{ title: '冷水澡 · 准备步骤', items: ['热身2–3分钟', '先温水→冷水渐进', '重点避头部直冲', '备干毛巾与保暖', '注意心率与眩晕'], meta: '场景:沐浴前 · 安全优先' },
{ title: '跑步 · 前置准备', items: ['热身动态拉伸', '检查鞋与袜', '补水少量', '路线与天气确认', '佩戴反光/夜跑灯'], meta: '场景:运动前 · 目标安全' }
]
}, null, 2))
const getConfig = () => JSON.parse(fs.readFileSync(cfgPath, 'utf-8'))
const setConfig = cfg => fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2))
module.exports.getConfig = getConfig
module.exports.setConfig = setConfig
const dataDir = path.join(process.cwd(), 'data')
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true })
const wallDbPath = path.join(dataDir, 'call.db')
const wallDb = new Database(wallDbPath)
wallDb.exec(`CREATE TABLE IF NOT EXISTS wall_cards (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
items_json TEXT NOT NULL,
meta TEXT,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
);`)
const countCardsRow = () => {
try {
const row = wallDb.prepare('SELECT COUNT(*) AS c FROM wall_cards').get()
if (row && typeof row.c === 'number') return row.c
return 0
} catch {
return 0
}
}
const ensureInitialCardsFromConfig = () => {
try {
if (countCardsRow() > 0) return
const cfg = getConfig()
const cards = Array.isArray(cfg.cards) ? cfg.cards : []
if (!cards.length) return
const insert = wallDb.prepare(`INSERT INTO wall_cards (title, items_json, meta) VALUES (@title, @items_json, @meta)`)
const tx = wallDb.transaction((list) => {
for (const c of list) {
const items = Array.isArray(c.items) ? c.items : []
insert.run({ title: String(c.title || ''), items_json: JSON.stringify(items), meta: String(c.meta || '') })
}
})
tx(cards)
try { logJSON('wall.cards.migrate_from_config', { count: cards.length }, 'wall') } catch {}
} catch (e) {
try { logJSON('wall.cards.migrate_from_config.error', { error: String(e.message || e) }, 'wall') } catch {}
}
}
ensureInitialCardsFromConfig()
const listCardsStmt = wallDb.prepare(`SELECT id, title, items_json, meta, created_at, updated_at FROM wall_cards ORDER BY id ASC`)
const insertCardStmt = wallDb.prepare(`INSERT INTO wall_cards (title, items_json, meta) VALUES (@title, @items_json, @meta)`)
const updateCardStmt = wallDb.prepare(`UPDATE wall_cards SET title=@title, items_json=@items_json, meta=@meta, updated_at=datetime('now') WHERE id=@id`)
const deleteCardStmt = wallDb.prepare(`DELETE FROM wall_cards WHERE id=?`)
const normalizeItems = (items) => {
if (Array.isArray(items)) return items.map(x => String(x || ''))
const text = String(items || '')
if (!text.trim()) return []
return text.split(/\r?\n/).map(s => s.trim()).filter(Boolean)
}
const listCards = () => {
const rows = listCardsStmt.all()
return rows.map(r => {
let items = []
try {
const parsed = JSON.parse(r.items_json || '[]')
if (Array.isArray(parsed)) items = parsed.map(x => String(x || ''))
} catch {}
return {
id: r.id,
title: r.title || '',
items,
meta: r.meta || '',
created_at: r.created_at,
updated_at: r.updated_at
}
})
}
const createCard = (payload) => {
const items = normalizeItems(payload.items)
const items_json = JSON.stringify(items)
const title = String(payload.title || '')
const meta = String(payload.meta || '')
const info = insertCardStmt.run({ title, items_json, meta })
try { logJSON('wall.card.create', { id: info.lastInsertRowid, title }, 'wall') } catch {}
return info.lastInsertRowid
}
const updateCardById = (id, payload) => {
const items = normalizeItems(payload.items)
const items_json = JSON.stringify(items)
const title = String(payload.title || '')
const meta = String(payload.meta || '')
updateCardStmt.run({ id, title, items_json, meta })
try { logJSON('wall.card.update', { id, title }, 'wall') } catch {}
}
const deleteCardById = (id) => {
deleteCardStmt.run(id)
try { logJSON('wall.card.delete', { id }, 'wall') } catch {}
}
module.exports.listCards = listCards
module.exports.createCard = createCard
module.exports.updateCardById = updateCardById
module.exports.deleteCardById = deleteCardById