mirror of
https://github.com/DIYgod/RSSHub.git
synced 2026-09-01 14:57:14 +08:00
feat(route): Add Shopify App store 搜索、App reviews (#14819)
* feat(route): 添加 Shopify App store 搜索、App reviews * feat: remove unused var `index` * Update lib/routes/shopify/apps/[handle].reviews.ts Co-authored-by: Tony <TonyRL@users.noreply.github.com> * chore: auto build * style: auto format * fix: cr --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import { Data, DataItem, Route } from '@/types';
|
||||
import { Context } from 'hono';
|
||||
import { baseURL } from './const';
|
||||
import got from '@/utils/got';
|
||||
import { load } from 'cheerio';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/apps/:handle/reviews/:page?',
|
||||
example: '/shopify/apps/flow/reviews',
|
||||
parameters: { handle: '例如一个 App 的链接 https://apps.shopify.com/flow,其中 flow 就是指的是 handle' },
|
||||
name: 'App reviews',
|
||||
maintainers: ['PrintNow'],
|
||||
handler,
|
||||
radar: [
|
||||
{
|
||||
source: ['apps.shopify.com/:handle'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
async function handler(ctx: Context): Promise<Data> {
|
||||
const { handle = '', page = '1' } = ctx.req.param();
|
||||
const response = await got.get(`${baseURL}/${handle}/reviews`, {
|
||||
searchParams: {
|
||||
sort_by: 'newest',
|
||||
page,
|
||||
},
|
||||
headers: {
|
||||
accept: 'text/html, application/xhtml+xml',
|
||||
'accept-language': 'en-US;q=0.9',
|
||||
referer: baseURL,
|
||||
dnt: '1',
|
||||
},
|
||||
});
|
||||
|
||||
const htmlContent = response.data;
|
||||
const $ = load(htmlContent);
|
||||
|
||||
const items = $('div[data-merchant-review]')
|
||||
.toArray()
|
||||
.map((item) => {
|
||||
const $item = $(item);
|
||||
|
||||
const reviewID = $item.attr('data-review-content-id');
|
||||
|
||||
const $review1 = $item.find('div:nth-child(1)');
|
||||
const $review2 = $item.find('div:nth-child(2)');
|
||||
|
||||
const description = $item.find('div[data-truncate-review] div[data-truncate-content-copy] p').html() || '';
|
||||
const author = $review2.find('div.tw-text-fg-primary').text().trim();
|
||||
|
||||
const result: DataItem = {
|
||||
guid: reviewID,
|
||||
title: description,
|
||||
author,
|
||||
pubDate: new Date($review1.find('div[aria-label] + div').text().trim()),
|
||||
|
||||
// 评论内容
|
||||
description,
|
||||
|
||||
_extra: {
|
||||
ratting_value: Number($review1.find('div[role="img"]').attr('aria-label')?.substring(0, 1)),
|
||||
location: $review2.find('div.tw-text-fg-primary + div').text().trim(),
|
||||
author,
|
||||
},
|
||||
};
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
return {
|
||||
title: `Reviews handle:${handle} page:${page} – Shopify App Store`,
|
||||
link: `${baseURL}/${handle}/reviews`,
|
||||
allowEmpty: true,
|
||||
language: 'en-US',
|
||||
item: items,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const baseURL = 'https://apps.shopify.com';
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Data, DataItem, Route } from '@/types';
|
||||
import type { Context } from 'hono';
|
||||
import got from '@/utils/got';
|
||||
import { load } from 'cheerio';
|
||||
import { baseURL } from './const';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/apps/search/:q',
|
||||
example: '/shopify/apps/search/flow',
|
||||
parameters: { q: '需要搜索的 App' },
|
||||
name: 'App store search',
|
||||
maintainers: ['PrintNow'],
|
||||
handler,
|
||||
radar: [
|
||||
{
|
||||
source: ['apps.shopify.com/search'],
|
||||
target: (_params, url) => {
|
||||
const { searchParams } = new URL(url).searchParams;
|
||||
return searchParams.has('q') ? `/shopify/apps/search/${searchParams.get('q')}` : null;
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
async function handler(ctx: Context): Promise<Data> {
|
||||
const { q = '' } = ctx.req.param();
|
||||
const response = await got.get(`${baseURL}/search`, {
|
||||
searchParams: {
|
||||
q,
|
||||
},
|
||||
headers: {
|
||||
accept: 'text/html, application/xhtml+xml',
|
||||
'accept-language': 'en-US;q=0.9',
|
||||
'turbo-frame': 'search_page',
|
||||
referer: baseURL,
|
||||
dnt: '1',
|
||||
},
|
||||
});
|
||||
|
||||
const htmlContent = await response.data;
|
||||
const $ = load(htmlContent);
|
||||
|
||||
const items = $('.search-results-component div[data-controller="app-card"]')
|
||||
.toArray()
|
||||
.map((item) => {
|
||||
const handle = $(item).attr('data-app-card-handle-value');
|
||||
|
||||
// const appInfo = $(item).find('div.tw-transition-colors.tw-text-fg-primary + div.tw-self-stretch');
|
||||
const appInfo = $(item).find('div.tw-self-stretch').clone();
|
||||
|
||||
const rattingMatch = appInfo
|
||||
.find('span')
|
||||
.text()
|
||||
.match(/\d\.\d/);
|
||||
const rattingCountMatch = appInfo.find('span + span.tw-sr-only').text().match(/\d+/);
|
||||
|
||||
const result: DataItem = {
|
||||
title: $(item).attr('data-app-card-name-value') ?? '',
|
||||
link: `${baseURL}/${handle}`,
|
||||
description: $(item).find(`div.tw-text-fg-tertiary`).first().text().trim(),
|
||||
image: $(item).attr('data-app-card-icon-url-value'),
|
||||
_extra: {
|
||||
handle,
|
||||
description: $(item).find(`div.tw-text-fg-tertiary`).first().text().trim(),
|
||||
built_for_shopify: $(item).find(`span.built-for-shopify-badge`).length > 0,
|
||||
ratting: rattingMatch ? Number.parseFloat(rattingMatch[0]) : 0,
|
||||
ratting_count: rattingCountMatch ? Number(rattingCountMatch[0]) : 0,
|
||||
},
|
||||
};
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
return {
|
||||
title: `Search results for "${q}" – Shopify App Store`,
|
||||
link: `https://apps.shopify.com/search?q=${q}`,
|
||||
// description: `Search results for "${q}" – Shopify App Store`,
|
||||
allowEmpty: true,
|
||||
language: 'en-US',
|
||||
item: items,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { Namespace } from '@/types';
|
||||
|
||||
export const namespace: Namespace = {
|
||||
name: 'Shopify',
|
||||
url: 'shopify.com',
|
||||
};
|
||||
Reference in New Issue
Block a user