diff --git a/lib/v2/showstart/const.js b/lib/v2/showstart/const.js new file mode 100644 index 00000000000000..f7880faed03536 --- /dev/null +++ b/lib/v2/showstart/const.js @@ -0,0 +1,4 @@ +module.exports = { + HOST: 'https://www.showstart.com', + TITLE: '秀动网', +}; diff --git a/lib/v2/showstart/event.js b/lib/v2/showstart/event.js new file mode 100644 index 00000000000000..38e6aafc15f692 --- /dev/null +++ b/lib/v2/showstart/event.js @@ -0,0 +1,18 @@ +const { TITLE, HOST } = require('./const'); +const { fetchActivityList, fetchDictionary } = require('./service'); + +module.exports = async (ctx) => { + const cityCode = parseInt(ctx.params.cityCode); + const showStyle = parseInt(ctx.params.showStyle); + const { items } = await fetchActivityList({ + cityCode, + showStyle, + }); + const { cityName, showName } = await fetchDictionary(cityCode, showStyle); + const tags = [cityName, showName].filter((item) => Boolean(item)).join(' - '); + ctx.state.data = { + title: `${TITLE} - ${tags}`, + link: HOST, + item: items, + }; +}; diff --git a/lib/v2/showstart/maintainer.js b/lib/v2/showstart/maintainer.js new file mode 100644 index 00000000000000..1eea6511d8e381 --- /dev/null +++ b/lib/v2/showstart/maintainer.js @@ -0,0 +1,4 @@ +module.exports = { + '/event/:cityCode/:showStyle?': ['lchtao26'], + '/search/:keyword': ['lchtao26'], +}; diff --git a/lib/v2/showstart/radar.js b/lib/v2/showstart/radar.js new file mode 100644 index 00000000000000..55548479980b24 --- /dev/null +++ b/lib/v2/showstart/radar.js @@ -0,0 +1,28 @@ +module.exports = { + 'showstart.com': { + _name: '秀动网', + www: [ + { + title: '演出更新', + docs: 'https://docs.rsshub.app/routes/shopping#xiu-dong-wang-yan-chu-geng-xin', + source: ['/event/list'], + target: (_, url) => { + const search = new URL(url).searchParams; + const cityCode = search.get('cityCode') || 0; + const showStyle = search.get('showStyle') || 0; + return `/showstart/event/${cityCode}/${showStyle}`; + }, + }, + { + title: '演出搜索', + docs: 'https://docs.rsshub.app/routes/shopping#xiu-dong-wang-yan-chu-sou-suo', + source: ['/event/list'], + target: (_, url) => { + const search = new URL(url).searchParams; + const keyword = search.get('keyword') || ''; + return `/showstart/search/${keyword}`; + }, + }, + ], + }, +}; diff --git a/lib/v2/showstart/router.js b/lib/v2/showstart/router.js new file mode 100644 index 00000000000000..e8588a62ac6863 --- /dev/null +++ b/lib/v2/showstart/router.js @@ -0,0 +1,4 @@ +module.exports = (router) => { + router.get('/event/:cityCode/:showStyle?', require('./event')); + router.get('/search/:keyword', require('./search')); +}; diff --git a/lib/v2/showstart/search.js b/lib/v2/showstart/search.js new file mode 100644 index 00000000000000..3a9bddb58f8edc --- /dev/null +++ b/lib/v2/showstart/search.js @@ -0,0 +1,14 @@ +const { TITLE, HOST } = require('./const'); +const { fetchActivityList } = require('./service'); + +module.exports = async (ctx) => { + const keyword = ctx.params.keyword; + const { items } = await fetchActivityList({ + keyword, + }); + ctx.state.data = { + title: `${TITLE} - ${keyword}`, + link: HOST, + item: items, + }; +}; diff --git a/lib/v2/showstart/service.js b/lib/v2/showstart/service.js new file mode 100644 index 00000000000000..925918ab27b316 --- /dev/null +++ b/lib/v2/showstart/service.js @@ -0,0 +1,53 @@ +const { HOST } = require('./const'); +const { getAccessToken, post } = require('./utils'); + +async function fetchActivityList( + params = { + pageNo: '1', + pageSize: '30', + cityCode: '', + activityIds: '', + coupon: '', + keyword: '', + organizerId: '', + performerId: '', + showStyle: '', + showTime: '', + showType: '', + siteId: '', + sortType: '', + themeId: '', + timeRange: '', + tourId: '', + type: '', + tag: '', + } +) { + const accessToken = await getAccessToken(); + const resp = await post('/web/activity/list', accessToken, params); + return { + items: resp.result.result.map((item) => ({ + title: item.title, + link: `${HOST}/event/${item.id}`, + description: `地点:${item.cityName} | ${item.siteName}`, + })), + }; +} + +async function fetchDictionary(cityCode, showStyle) { + const accessToken = await getAccessToken(); + const resp = await post('/web/activity/list/params', accessToken); + const target = resp.result.find((item) => item.cityCode === cityCode); + if (!target) { + return {}; + } + return { + cityName: target.cityName, + showName: target.styles.find((item) => item.key === showStyle)?.showName, + }; +} + +module.exports = { + fetchActivityList, + fetchDictionary, +}; diff --git a/lib/v2/showstart/utils.js b/lib/v2/showstart/utils.js new file mode 100644 index 00000000000000..7a8b9a551392a1 --- /dev/null +++ b/lib/v2/showstart/utils.js @@ -0,0 +1,67 @@ +const got = require('@/utils/got'); +const md5 = require('@/utils/md5'); + +const uuid = (length = 20) => { + const e = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + Date.now(); + const r = []; + for (let i = 0; i < length; i++) { + r.push(e.charAt(Math.floor(Math.random() * e.length))); + } + return r.join(''); +}; + +const cookieMap = new Map([['token', uuid(32).toLowerCase()]]); + +const devioceInfo = { + vendorName: '', + deviceMode: '', + deviceName: '', + systemName: '', + systemVersion: '', + cpuMode: ' ', // Note the space + cpuCores: '', + cpuArch: '', + memerySize: '', + diskSize: '', + network: '', + resolution: '1920*1080', + pixelResolution: '', +}; + +const getAccessToken = async () => { + const { result } = await post('/waf/gettoken'); + cookieMap.set('accessToken', result.accessToken.access_token); + cookieMap.set('idToken', result.idToken.id_token); + return cookieMap.get('accessToken'); +}; + +const post = async (requestPath, accessToken = md5(Date.now().toString()), payload) => { + const traceId = uuid(32) + Date.now(); + + const { data: response } = await got.post(`https://www.showstart.com/api${requestPath}`, { + headers: { + cdeviceinfo: encodeURIComponent(JSON.stringify(devioceInfo)), + cdeviceno: cookieMap.get('token'), + cookie: [...cookieMap.entries()].map(([key, value]) => `${key}=${value}`).join('; '), + crpsign: md5(accessToken + /* sign/cusut (empty) + idToken (empty) + userInfo.userId (empty) + */ 'web' + cookieMap.get('token') + (payload ? JSON.stringify(payload) : '') + requestPath + '999web' + traceId), + crtraceid: traceId, + csappid: 'web', + cterminal: 'web', + cusat: accessToken, + cusid: '', + cusit: '', + cusname: '', + cusut: '', + cversion: '999', + }, + json: payload, + }); + + return response; +}; + +module.exports = { + post, + getAccessToken, + uuid, +}; diff --git a/website/docs/routes/shopping.mdx b/website/docs/routes/shopping.mdx index a3672d1b62e581..76836f9429045c 100644 --- a/website/docs/routes/shopping.mdx +++ b/website/docs/routes/shopping.mdx @@ -329,6 +329,18 @@ For instance, in `https://www.zagg.com/en_us/new-arrivals?brand=164&cat=3038%2C3 城市、分类名、子分类名,请参见[大麦网搜索页面](https://search.damai.cn/search.htm) +## 秀动网 {#xiu-dong-wang} + +### 演出更新 {#xiu-dong-wang-yan-chu-geng-xin} + + + +相关路由参数可在 URL 中找到,如:`https://www.showstart.com/event/list?pageNo=1&pageSize=20&cityCode=571&showStyle=26` + +### 演出搜索 {#xiu-dong-wang-yan-chu-sou-suo} + + + ## 多抓鱼 {#duo-zhua-yu} ### 搜索结果 {#duo-zhua-yu-sou-suo-jie-guo}