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}