dsh-xueqiu 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +91 -0
- package/cordis.patch.yml +7 -0
- package/dynamic/client.js +849 -0
- package/dynamic/host.js +426 -0
- package/package.json +50 -0
- package/src/client/index.js +854 -0
- package/src/index.js +434 -0
package/dynamic/host.js
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
return {
|
|
2
|
+
apply(ctx) {
|
|
3
|
+
const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
4
|
+
const BASE = 'https://stock.xueqiu.com'
|
|
5
|
+
const SITE = 'https://xueqiu.com'
|
|
6
|
+
const DEFAULT_WATCHLIST = ['SH600519', 'SZ300750', 'SZ002594', 'SH601318', '00700', 'AAPL']
|
|
7
|
+
|
|
8
|
+
const state = { cookie: '' }
|
|
9
|
+
let watchlist = null
|
|
10
|
+
let uiState = null
|
|
11
|
+
|
|
12
|
+
function enc(s) {
|
|
13
|
+
try { return encodeURIComponent(String(s)) } catch (e) { return String(s) }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function toQuery(params) {
|
|
17
|
+
const parts = []
|
|
18
|
+
for (const k in params) {
|
|
19
|
+
const v = params[k]
|
|
20
|
+
if (v === undefined || v === null || v === '') continue
|
|
21
|
+
parts.push(enc(k) + '=' + enc(v))
|
|
22
|
+
}
|
|
23
|
+
return parts.length ? '?' + parts.join('&') : ''
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getShell() { return ctx.get('shell') }
|
|
27
|
+
|
|
28
|
+
async function ensureCookie(force) {
|
|
29
|
+
if (state.cookie && !force) return state.cookie
|
|
30
|
+
const shell = getShell()
|
|
31
|
+
if (!shell) return ''
|
|
32
|
+
const cmd = "curl -s --max-time 12 -D - -o /dev/null 'https://xueqiu.com/' -H 'User-Agent: " + UA + "'"
|
|
33
|
+
const spec = shell.resolve({ command: cmd, timeoutMs: 15000, stdoutMaxBytes: 65536 })
|
|
34
|
+
const res = await shell.run(spec)
|
|
35
|
+
if (res.exitCode !== 0) return ''
|
|
36
|
+
const pairs = []
|
|
37
|
+
const lines = String(res.stdout.text || '').split('\n')
|
|
38
|
+
for (let i = 0; i < lines.length; i++) {
|
|
39
|
+
const m = /^set-cookie:\s*([^=;\s]+)=([^;]*)/i.exec(lines[i].trim())
|
|
40
|
+
if (m) pairs.push(m[1] + '=' + m[2])
|
|
41
|
+
}
|
|
42
|
+
state.cookie = pairs.join('; ')
|
|
43
|
+
return state.cookie
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function curl(url) {
|
|
47
|
+
const shell = getShell()
|
|
48
|
+
if (!shell) throw new Error('shell 服务不可用,无法访问雪球')
|
|
49
|
+
const cookie = await ensureCookie(false)
|
|
50
|
+
let cmd = "curl -s --max-time 12 '" + url + "'"
|
|
51
|
+
cmd += " -H 'User-Agent: " + UA + "'"
|
|
52
|
+
cmd += " -H 'Referer: https://xueqiu.com/'"
|
|
53
|
+
cmd += " -H 'Accept: application/json'"
|
|
54
|
+
if (cookie) cmd += " -H 'Cookie: " + cookie + "'"
|
|
55
|
+
const spec = shell.resolve({ command: cmd, timeoutMs: 15000, stdoutMaxBytes: 4194304 })
|
|
56
|
+
const result = await shell.run(spec)
|
|
57
|
+
if (result.exitCode !== 0) {
|
|
58
|
+
const err = ((result.stderr && result.stderr.text) || '').slice(0, 200)
|
|
59
|
+
throw new Error('curl 失败 (' + result.exitCode + '): ' + err)
|
|
60
|
+
}
|
|
61
|
+
return String(result.stdout.text || '')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function getJSON(path, params, base, depth) {
|
|
65
|
+
const url = (base || BASE) + path + toQuery(params || {})
|
|
66
|
+
let text = await curl(url)
|
|
67
|
+
if (!text && state.cookie) {
|
|
68
|
+
await ensureCookie(true)
|
|
69
|
+
text = await curl(url)
|
|
70
|
+
}
|
|
71
|
+
if (!text) throw new Error('雪球返回空响应(可能被风控,稍后再试)')
|
|
72
|
+
let data
|
|
73
|
+
try { data = JSON.parse(text) } catch (e) { throw new Error('雪球响应解析失败: ' + text.slice(0, 120)) }
|
|
74
|
+
if (data && data.error_code && data.error_code !== 0) {
|
|
75
|
+
const code = String(data.error_code)
|
|
76
|
+
const desc = (data.error_description || ('雪球错误 ' + code)).slice(0, 120)
|
|
77
|
+
if (code === '400016' && depth < 2) {
|
|
78
|
+
await ensureCookie(true)
|
|
79
|
+
return getJSON(path, params, base, (depth || 0) + 1)
|
|
80
|
+
}
|
|
81
|
+
throw new Error(desc)
|
|
82
|
+
}
|
|
83
|
+
return data
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ---- data normalizers ----
|
|
87
|
+
function nz(v) { return v === undefined ? null : v }
|
|
88
|
+
|
|
89
|
+
function sanitize(v) {
|
|
90
|
+
if (v === undefined) return null
|
|
91
|
+
if (v === null || typeof v !== 'object') return v
|
|
92
|
+
if (Array.isArray(v)) {
|
|
93
|
+
const out = []
|
|
94
|
+
for (let i = 0; i < v.length; i++) out.push(sanitize(v[i]))
|
|
95
|
+
return out
|
|
96
|
+
}
|
|
97
|
+
const out = {}
|
|
98
|
+
for (const k in v) {
|
|
99
|
+
if (Object.prototype.hasOwnProperty.call(v, k)) out[k] = sanitize(v[k])
|
|
100
|
+
}
|
|
101
|
+
return out
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function pickQuote(q) {
|
|
105
|
+
if (!q) return null
|
|
106
|
+
return {
|
|
107
|
+
symbol: nz(q.symbol), name: nz(q.name), code: nz(q.code), exchange: nz(q.exchange), type: nz(q.type),
|
|
108
|
+
current: nz(q.current), percent: nz(q.percent), chg: nz(q.chg),
|
|
109
|
+
open: nz(q.open), last_close: nz(q.last_close), high: nz(q.high), low: nz(q.low),
|
|
110
|
+
volume: nz(q.volume), amount: nz(q.amount), turnover_rate: nz(q.turnover_rate),
|
|
111
|
+
market_capital: nz(q.market_capital), float_market_capital: nz(q.float_market_capital),
|
|
112
|
+
volume_ratio: nz(q.volume_ratio), amplitude: nz(q.amplitude), current_year_percent: nz(q.current_year_percent),
|
|
113
|
+
pe_ttm: nz(q.pe_ttm), pe_lyr: nz(q.pe_lyr), pe_forecast: nz(q.pe_forecast), pb: nz(q.pb), eps: nz(q.eps),
|
|
114
|
+
dividend_yield: nz(q.dividend_yield), high52w: nz(q.high52w), low52w: nz(q.low52w),
|
|
115
|
+
limit_up: nz(q.limit_up), limit_down: nz(q.limit_down), status: nz(q.status)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function mapKline(data) {
|
|
120
|
+
const d = data && data.data ? data.data : null
|
|
121
|
+
if (!d || !d.column || !d.item) return { period: null, rows: [] }
|
|
122
|
+
const idx = {}
|
|
123
|
+
for (let i = 0; i < d.column.length; i++) idx[d.column[i]] = i
|
|
124
|
+
const rows = d.item.map(function (it) {
|
|
125
|
+
return {
|
|
126
|
+
timestamp: it[idx.timestamp], open: it[idx.open], high: it[idx.high],
|
|
127
|
+
low: it[idx.low], close: it[idx.close], volume: it[idx.volume],
|
|
128
|
+
amount: it[idx.amount], percent: it[idx.percent], chg: it[idx.chg],
|
|
129
|
+
turnoverrate: it[idx.turnoverrate], pe: it[idx.pe], pb: it[idx.pb],
|
|
130
|
+
market_capital: it[idx.market_capital]
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
return { period: null, rows: rows }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function mapPosts(list) {
|
|
137
|
+
if (!Array.isArray(list)) return []
|
|
138
|
+
return list.map(function (p) {
|
|
139
|
+
let text = p.description || p.text || ''
|
|
140
|
+
text = String(text).replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').slice(0, 200)
|
|
141
|
+
return {
|
|
142
|
+
id: p.id, title: p.title || '', text: text,
|
|
143
|
+
created_at: p.created_at, reply_count: p.reply_count, fav_count: p.fav_count,
|
|
144
|
+
view_count: p.view_count, target: p.target,
|
|
145
|
+
user: p.user ? { id: p.user.id, screen_name: p.user.screen_name, followers_count: p.user.followers_count } : null
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function mapKOL(arr) {
|
|
151
|
+
if (!Array.isArray(arr)) return []
|
|
152
|
+
return arr.map(function (u) {
|
|
153
|
+
return {
|
|
154
|
+
id: u.id, screen_name: u.screen_name, description: u.description || '',
|
|
155
|
+
followers_count: u.followers_count, status_count: u.status_count,
|
|
156
|
+
verified: u.verified, verified_description: u.verified_description,
|
|
157
|
+
profile: u.profile, profile_image_url: u.profile_image_url
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function mapFinance(list) {
|
|
163
|
+
if (!Array.isArray(list)) return []
|
|
164
|
+
return list.map(function (r) {
|
|
165
|
+
function v(key) { return (r[key] && r[key][0] !== undefined) ? r[key][0] : null }
|
|
166
|
+
return {
|
|
167
|
+
report_name: r.report_name, report_date: r.report_date,
|
|
168
|
+
avg_roe: v('avg_roe'), basic_eps: v('basic_eps'),
|
|
169
|
+
gross_selling_rate: v('gross_selling_rate'), net_selling_rate: v('net_selling_rate'),
|
|
170
|
+
operating_income_yoy: v('operating_income_yoy'), net_profit_atsopc_yoy: v('net_profit_atsopc_yoy'),
|
|
171
|
+
asset_liab_ratio: v('asset_liab_ratio'), current_ratio: v('current_ratio'),
|
|
172
|
+
total_revenue: v('total_revenue'), net_profit_atsopc: v('net_profit_atsopc'),
|
|
173
|
+
np_per_share: v('np_per_share'), operate_cash_flow_ps: v('operate_cash_flow_ps')
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ---- actions ----
|
|
179
|
+
async function actQuote(args) {
|
|
180
|
+
const symbols = Array.isArray(args.symbols) ? args.symbols : [String(args.symbols || '')]
|
|
181
|
+
const list = symbols.filter(function (s) { return /^[A-Za-z0-9_.]+$/.test(s) })
|
|
182
|
+
if (!list.length) return { list: [], status: null }
|
|
183
|
+
const data = await getJSON('/v5/stock/batch/quote.json', { symbol: list.join(',') })
|
|
184
|
+
const items = (data && data.data && data.data.items) || []
|
|
185
|
+
const first = items[0]
|
|
186
|
+
const status = first && first.market ? first.market.status_id : null
|
|
187
|
+
return {
|
|
188
|
+
status: nz(status),
|
|
189
|
+
list: items.map(function (it) { return pickQuote(it.quote) }).filter(Boolean)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function actQuoteDetail(args) {
|
|
194
|
+
const symbol = String(args.symbol || '')
|
|
195
|
+
const data = await getJSON('/v5/stock/quote.json', { symbol: symbol, extend: 'detail' })
|
|
196
|
+
return { quote: pickQuote(data.data && data.data.quote), market: data.data && data.data.market }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function actKline(args) {
|
|
200
|
+
const period = String(args.period || 'day')
|
|
201
|
+
const count = Math.min(Math.max(parseInt(args.count, 10) || 120, 5), 500)
|
|
202
|
+
const symbol = String(args.symbol || '')
|
|
203
|
+
const data = await getJSON('/v5/stock/chart/kline.json', {
|
|
204
|
+
symbol: symbol, period: period, type: 'before', begin: Date.now(),
|
|
205
|
+
count: -count, indicator: 'kline,pe,pb,ps,pcf,market_capital'
|
|
206
|
+
})
|
|
207
|
+
return mapKline(data)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function actMinute(args) {
|
|
211
|
+
const symbol = String(args.symbol || '')
|
|
212
|
+
const data = await getJSON('/v5/stock/chart/minute.json', { symbol: symbol, period: '1d' })
|
|
213
|
+
const d = data && data.data ? data.data : null
|
|
214
|
+
if (!d || !d.items) return { last_close: null, items: [] }
|
|
215
|
+
return {
|
|
216
|
+
last_close: d.last_close,
|
|
217
|
+
items: d.items.map(function (it) {
|
|
218
|
+
return { timestamp: it.timestamp, current: it.current, avg_price: it.avg_price, percent: it.percent, volume: it.volume, high: it.high, low: it.low }
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function actHot(args) {
|
|
224
|
+
const market = String(args.market || 'cn')
|
|
225
|
+
const typeMap = { global: 10, us: 11, cn: 12, hk: 13 }
|
|
226
|
+
const size = Math.min(Math.max(parseInt(args.size, 10) || 10, 1), 30)
|
|
227
|
+
const data = await getJSON('/v5/stock/hot_stock/list.json', { type: typeMap[market] || 12, size: size })
|
|
228
|
+
const items = (data && data.data && data.data.items) || []
|
|
229
|
+
return {
|
|
230
|
+
list: items.map(function (it) {
|
|
231
|
+
return { symbol: it.symbol, code: it.code, name: it.name, current: it.current, percent: it.percent, chg: it.chg, exchange: it.exchange, value: it.value, rank_change: it.rank_change, increment: it.increment }
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function actSearch(args) {
|
|
237
|
+
const q = String(args.q || '')
|
|
238
|
+
if (!q) return { list: [] }
|
|
239
|
+
const data = await getJSON('/query/v1/suggest_stock.json', { q: q, count: Math.min(parseInt(args.count, 10) || 8, 20) }, SITE)
|
|
240
|
+
const list = (data && data.data) || []
|
|
241
|
+
return {
|
|
242
|
+
list: list.map(function (it) {
|
|
243
|
+
return { code: it.code, name: it.query || it.name, type: it.type, stock_type: it.stock_type, label: it.label }
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function actSearchPosts(args) {
|
|
249
|
+
const q = String(args.q || '')
|
|
250
|
+
if (!q) return { list: [] }
|
|
251
|
+
const data = await getJSON('/query/v1/search/status.json', { q: q, count: Math.min(parseInt(args.count, 10) || 10, 30) }, SITE)
|
|
252
|
+
return { list: mapPosts(data && data.list) }
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function actNews(args) {
|
|
256
|
+
const count = Math.min(Math.max(parseInt(args.count, 10) || 20, 1), 50)
|
|
257
|
+
const data = await getJSON('/statuses/livenews/list.json', { since_id: -1, max_id: -1, count: count }, SITE)
|
|
258
|
+
const items = (data && data.items) || []
|
|
259
|
+
return {
|
|
260
|
+
items: items.map(function (it) {
|
|
261
|
+
return { id: it.id, text: it.text, mark: it.mark, created_at: it.created_at, target: it.target, view_count: it.view_count, reply_count: it.reply_count }
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function actKOL(args) {
|
|
267
|
+
const symbol = String(args.symbol || '')
|
|
268
|
+
const count = Math.min(Math.max(parseInt(args.count, 10) || 8, 1), 20)
|
|
269
|
+
const data = await getJSON('/recommend/user/stock_hot_user.json', { symbol: symbol, start: 0, count: count }, SITE)
|
|
270
|
+
return { list: mapKOL(data) }
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function actUser(args) {
|
|
274
|
+
const userId = String(args.userId || args.id || '')
|
|
275
|
+
if (!userId) return { user: null, posts: [] }
|
|
276
|
+
const data = await getJSON('/statuses/user_timeline.json', { user_id: userId, page: 1, count: Math.min(parseInt(args.count, 10) || 10, 30) }, SITE)
|
|
277
|
+
const statuses = (data && data.statuses) || []
|
|
278
|
+
const first = statuses[0]
|
|
279
|
+
return {
|
|
280
|
+
user: first && first.user ? {
|
|
281
|
+
id: first.user.id, screen_name: first.user.screen_name,
|
|
282
|
+
followers_count: first.user.followers_count, status_count: first.user.status_count,
|
|
283
|
+
verified: first.user.verified, description: first.user.description, profile: first.user.profile
|
|
284
|
+
} : null,
|
|
285
|
+
posts: mapPosts(statuses)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function actFinance(args) {
|
|
290
|
+
const symbol = String(args.symbol || '')
|
|
291
|
+
const data = await getJSON('/v5/stock/finance/cn/indicator.json', { symbol: symbol, type: 'all', is_detail: 'true', count: 4 })
|
|
292
|
+
const d = data && data.data ? data.data : null
|
|
293
|
+
return {
|
|
294
|
+
quote_name: d ? d.quote_name : null, last_report_name: d ? d.last_report_name : null,
|
|
295
|
+
list: mapFinance(d ? d.list : null)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ---- watchlist ----
|
|
300
|
+
async function loadWatchlist() {
|
|
301
|
+
if (watchlist) return watchlist
|
|
302
|
+
watchlist = { symbols: DEFAULT_WATCHLIST.slice() }
|
|
303
|
+
const fs = ctx.get('fs')
|
|
304
|
+
const sp = ctx.get('sandboxPolicy')
|
|
305
|
+
const root = sp && sp.workspaceRoot ? sp.workspaceRoot : null
|
|
306
|
+
if (fs && root) {
|
|
307
|
+
try {
|
|
308
|
+
const target = await fs.resolve('.xueqiu-watchlist.json', { cwd: root })
|
|
309
|
+
const text = await fs.readText(target)
|
|
310
|
+
const parsed = JSON.parse(text)
|
|
311
|
+
if (parsed && Array.isArray(parsed.symbols) && parsed.symbols.length) {
|
|
312
|
+
watchlist = { symbols: parsed.symbols.slice(0, 50) }
|
|
313
|
+
}
|
|
314
|
+
} catch (e) { /* 首次运行或无写权限时使用默认列表 */ }
|
|
315
|
+
}
|
|
316
|
+
return watchlist
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function saveWatchlist() {
|
|
320
|
+
const fs = ctx.get('fs')
|
|
321
|
+
const sp = ctx.get('sandboxPolicy')
|
|
322
|
+
const root = sp && sp.workspaceRoot ? sp.workspaceRoot : null
|
|
323
|
+
if (!fs || !root) return
|
|
324
|
+
try {
|
|
325
|
+
const target = await fs.resolve('.xueqiu-watchlist.json', { cwd: root })
|
|
326
|
+
await fs.writeText(target, JSON.stringify(watchlist))
|
|
327
|
+
} catch (e) { /* 保持内存态 */ }
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function actWatchlistGet() {
|
|
331
|
+
const wl = await loadWatchlist()
|
|
332
|
+
return { symbols: wl.symbols }
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function actWatchlistAdd(args) {
|
|
336
|
+
const symbol = String(args.symbol || '')
|
|
337
|
+
if (!/^[A-Za-z0-9_.]+$/.test(symbol)) throw new Error('无效代码: ' + symbol)
|
|
338
|
+
const wl = await loadWatchlist()
|
|
339
|
+
if (wl.symbols.indexOf(symbol) === -1) {
|
|
340
|
+
wl.symbols.push(symbol)
|
|
341
|
+
await saveWatchlist()
|
|
342
|
+
}
|
|
343
|
+
return { symbols: wl.symbols }
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async function actWatchlistRemove(args) {
|
|
347
|
+
const symbol = String(args.symbol || '')
|
|
348
|
+
const wl = await loadWatchlist()
|
|
349
|
+
const i = wl.symbols.indexOf(symbol)
|
|
350
|
+
if (i !== -1) {
|
|
351
|
+
wl.symbols.splice(i, 1)
|
|
352
|
+
await saveWatchlist()
|
|
353
|
+
}
|
|
354
|
+
return { symbols: wl.symbols }
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ---- UI 状态持久化 ----
|
|
358
|
+
async function loadUiState() {
|
|
359
|
+
if (uiState) return uiState
|
|
360
|
+
uiState = { pos: null, tab: 'market', minimized: false }
|
|
361
|
+
const fs = ctx.get('fs')
|
|
362
|
+
const sp = ctx.get('sandboxPolicy')
|
|
363
|
+
const root = sp && sp.workspaceRoot ? sp.workspaceRoot : null
|
|
364
|
+
if (fs && root) {
|
|
365
|
+
try {
|
|
366
|
+
const target = await fs.resolve('.xueqiu-ui-state.json', { cwd: root })
|
|
367
|
+
const text = await fs.readText(target)
|
|
368
|
+
const parsed = JSON.parse(text)
|
|
369
|
+
if (parsed && typeof parsed === 'object') uiState = parsed
|
|
370
|
+
} catch (e) { /* 默认状态 */ }
|
|
371
|
+
}
|
|
372
|
+
return uiState
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async function saveUiState() {
|
|
376
|
+
const fs = ctx.get('fs')
|
|
377
|
+
const sp = ctx.get('sandboxPolicy')
|
|
378
|
+
const root = sp && sp.workspaceRoot ? sp.workspaceRoot : null
|
|
379
|
+
if (!fs || !root) return
|
|
380
|
+
try {
|
|
381
|
+
const target = await fs.resolve('.xueqiu-ui-state.json', { cwd: root })
|
|
382
|
+
await fs.writeText(target, JSON.stringify(uiState))
|
|
383
|
+
} catch (e) { /* 保持内存态 */ }
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async function actUiGet() {
|
|
387
|
+
const s = await loadUiState()
|
|
388
|
+
return { pos: s.pos || null, tab: s.tab || 'market', minimized: !!s.minimized }
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async function actUiSet(args) {
|
|
392
|
+
const s = await loadUiState()
|
|
393
|
+
if (args.pos && typeof args.pos === 'object' && isFinite(Number(args.pos.x)) && isFinite(Number(args.pos.y))) {
|
|
394
|
+
s.pos = { x: Number(args.pos.x), y: Number(args.pos.y) }
|
|
395
|
+
}
|
|
396
|
+
if (args.tab !== undefined) s.tab = String(args.tab)
|
|
397
|
+
if (args.minimized !== undefined) s.minimized = !!args.minimized
|
|
398
|
+
await saveUiState()
|
|
399
|
+
return { ok: true }
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ---- RPC dispatch ----
|
|
403
|
+
const dispatch = {
|
|
404
|
+
quote: actQuote, quoteDetail: actQuoteDetail, kline: actKline, minute: actMinute,
|
|
405
|
+
hot: actHot, search: actSearch, searchPosts: actSearchPosts, news: actNews,
|
|
406
|
+
kol: actKOL, user: actUser, finance: actFinance,
|
|
407
|
+
'watchlist.get': actWatchlistGet, 'watchlist.add': actWatchlistAdd, 'watchlist.remove': actWatchlistRemove,
|
|
408
|
+
'ui.get': actUiGet, 'ui.set': actUiSet
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
ctx.effect(function () {
|
|
412
|
+
return harness.handle('xq.call', async function (req) {
|
|
413
|
+
const reqObj = (req && typeof req === 'object') ? req : {}
|
|
414
|
+
const action = reqObj.action
|
|
415
|
+
const args = reqObj.args || {}
|
|
416
|
+
if (!dispatch[action]) return { ok: false, error: '未知操作: ' + action }
|
|
417
|
+
try {
|
|
418
|
+
const data = await dispatch[action](args)
|
|
419
|
+
return { ok: true, data: sanitize(data) }
|
|
420
|
+
} catch (e) {
|
|
421
|
+
return { ok: false, error: String((e && e.message) || e) }
|
|
422
|
+
}
|
|
423
|
+
})
|
|
424
|
+
})
|
|
425
|
+
}
|
|
426
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-xueqiu",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "雪球 mini 行情面板 — DeepSeek Harness 免登录 A股/港美股实时行情、K线(蜡烛/成交量/均线)、分时图、热榜、搜索、7x24 快讯、热议用户。可拖拽悬浮面板,交易时段智能刷新。",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dsh",
|
|
7
|
+
"dsh-plugin",
|
|
8
|
+
"deepseek-harness",
|
|
9
|
+
"cordis",
|
|
10
|
+
"xueqiu",
|
|
11
|
+
"雪球",
|
|
12
|
+
"stock",
|
|
13
|
+
"a-share",
|
|
14
|
+
"finance"
|
|
15
|
+
],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"type": "module",
|
|
18
|
+
"main": "src/index.js",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": "./src/index.js",
|
|
21
|
+
"./client": "./src/client/index.js",
|
|
22
|
+
"./dynamic/*": "./dynamic/*",
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"dsh": {
|
|
26
|
+
"client": {
|
|
27
|
+
"inject": [
|
|
28
|
+
"@deepseek-ai/dsh-client-connection"
|
|
29
|
+
],
|
|
30
|
+
"platform": "web"
|
|
31
|
+
},
|
|
32
|
+
"bundle": {
|
|
33
|
+
"patch": "./cordis.patch.yml"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
38
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.0-rc.6",
|
|
39
|
+
"react": "^18.3.1"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"src/index.js",
|
|
43
|
+
"src/client/index.js",
|
|
44
|
+
"dynamic/host.js",
|
|
45
|
+
"dynamic/client.js",
|
|
46
|
+
"cordis.patch.yml",
|
|
47
|
+
"README.md",
|
|
48
|
+
"LICENSE"
|
|
49
|
+
]
|
|
50
|
+
}
|