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:
Chuwen
2024-04-06 00:35:38 +08:00
committed by GitHub
parent c454200876
commit 61d8079e6f
4 changed files with 167 additions and 0 deletions
@@ -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,
};
}
+1
View File
@@ -0,0 +1 @@
export const baseURL = 'https://apps.shopify.com';
+82
View File
@@ -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,
};
}
+6
View File
@@ -0,0 +1,6 @@
import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'Shopify',
url: 'shopify.com',
};