From 0a506a9e67e503922cf7feb0eabc39d5118f3f06 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 2 May 2024 03:44:06 +0800 Subject: [PATCH] feat(route): wellcee (#15433) --- lib/routes/weekendhk/posts.ts | 4 +- lib/routes/wellcee/namespace.ts | 7 +++ lib/routes/wellcee/rent.ts | 81 ++++++++++++++++++++++++++ lib/routes/wellcee/support-city.ts | 46 +++++++++++++++ lib/routes/wellcee/templates/house.art | 18 ++++++ lib/routes/wellcee/types.ts | 58 ++++++++++++++++++ lib/routes/wellcee/utils.ts | 45 ++++++++++++++ 7 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 lib/routes/wellcee/namespace.ts create mode 100644 lib/routes/wellcee/rent.ts create mode 100644 lib/routes/wellcee/support-city.ts create mode 100644 lib/routes/wellcee/templates/house.art create mode 100644 lib/routes/wellcee/types.ts create mode 100644 lib/routes/wellcee/utils.ts diff --git a/lib/routes/weekendhk/posts.ts b/lib/routes/weekendhk/posts.ts index 6b84ae2fdf30dd..d642a4c6c05192 100644 --- a/lib/routes/weekendhk/posts.ts +++ b/lib/routes/weekendhk/posts.ts @@ -4,13 +4,13 @@ import { parseDate } from '@/utils/parse-date'; export const route: Route = { path: '/', + example: '/weekendhk', radar: [ { source: ['weekendhk.com/'], - target: '', }, ], - name: 'Unknown', + name: '最新文章', maintainers: ['TonyRL'], handler, url: 'weekendhk.com/', diff --git a/lib/routes/wellcee/namespace.ts b/lib/routes/wellcee/namespace.ts new file mode 100644 index 00000000000000..8641c71079161b --- /dev/null +++ b/lib/routes/wellcee/namespace.ts @@ -0,0 +1,7 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'Wellcee 唯心所寓', + url: 'wellcee.com', + categories: ['other'], +}; diff --git a/lib/routes/wellcee/rent.ts b/lib/routes/wellcee/rent.ts new file mode 100644 index 00000000000000..dc67f30c91bfef --- /dev/null +++ b/lib/routes/wellcee/rent.ts @@ -0,0 +1,81 @@ +import { Route } from '@/types'; +import type { Context } from 'hono'; +import { District, House } from './types'; + +import ofetch from '@/utils/ofetch'; +import { parseDate } from '@/utils/parse-date'; +import { baseUrl, getCitys, getDistricts } from './utils'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; +import { art } from '@/utils/render'; +import path from 'path'; +import { getCurrentPath } from '@/utils/helpers'; + +const __dirname = getCurrentPath(import.meta.url); +const render = (data) => art(path.join(__dirname, 'templates', 'house.art'), data); + +export const route: Route = { + path: '/rent/:city/:district?', + example: '/wellcee/rent/北京', + parameters: { + city: '城市', + district: '地区', + }, + name: '租房信息', + maintainers: ['TonyRL'], + handler, + url: 'www.wellcee.com', + description: '支持的城市可以通过 [/wellcee/support-city](https://rsshub.app/wellcee/support-city) 获取', +}; + +async function handler(ctx: Context) { + const { city, district = '' } = ctx.req.param(); + const citys = await getCitys(); + const cityInfo = citys.find((item) => item.chCityName === city); + if (!cityInfo) { + throw new InvalidParameterError('Invalid city'); + } + + let districtInfo: District | undefined; + if (district) { + const districts = await getDistricts(cityInfo.id); + const d = districts.find((item) => item.name === district); + if (!d) { + throw new InvalidParameterError('Invalid district'); + } + districtInfo = d; + } + + const response = await ofetch(`${baseUrl}/api/house/filter`, { + method: 'POST', + body: { + districtIds: districtInfo?.id ? [districtInfo.id] : [], + subways: [], + rentTypeIds: [], + timeTypeIds: [], + price: [], + tagTypeIds: [], + cityId: cityInfo.id, + lang: 1, + pn: 1, + }, + }); + + const items = (response.data.list as House[]).map((item) => ({ + title: item.address, + link: `${baseUrl}/rent-apartment/${item.id}`, + description: render({ item }), + pubDate: parseDate(item.loginTime, 'X'), + author: item.userInfo.name, + category: [...item.tags, ...item.typeTags], + })); + + return { + title: `${city}${districtInfo?.name ?? ''}租房信息 - Wellcee`, + description: `${cityInfo.statics.online_text} ${cityInfo.statics.total_text}`, + image: cityInfo.icon, + icon: cityInfo.icon, + logo: cityInfo.icon, + link: `${baseUrl}/rent-apartment/${cityInfo.cityName}/list?cityId=${cityInfo.id}&lang=zh${district ? `#districtIds=${districtInfo?.id}` : ''}`, + item: items, + }; +} diff --git a/lib/routes/wellcee/support-city.ts b/lib/routes/wellcee/support-city.ts new file mode 100644 index 00000000000000..2b0b88d9b729f2 --- /dev/null +++ b/lib/routes/wellcee/support-city.ts @@ -0,0 +1,46 @@ +import { Route } from '@/types'; +import type { Context } from 'hono'; + +import { baseUrl, getCitys, getDistricts } from './utils'; + +export const route: Route = { + path: '/support-city', + example: '/wellcee/support-city', + name: '支持的城市', + maintainers: ['TonyRL'], + radar: [ + { + source: ['www.wellcee.com'], + }, + ], + handler, + url: 'www.wellcee.com', +}; + +async function handler(ctx: Context) { + const citys = await getCitys(); + + const list = await Promise.all( + citys.map(async (city) => ({ + ...city, + district: await getDistricts(city.id), + })) + ); + const requestHost = new URL(ctx.req.url).host; + + const items = list.flatMap((city) => + city.district.map((district) => ({ + title: `${city.chCityName} - ${district.name}`, + description: `${city.chCityName} - ${district.name}`, + link: `https://${requestHost}/wellcee/rent/${city.chCityName}/${district.name}`, + })) + ); + + return { + title: '支持的城市 - Wellcee', + description: + '上海国际化租房平台|北京合租&找室友|香港留学生租房|深圳无中介租房|广州外国人租房 |杭州高品质租房|成都房东直租;同志友好&宠物友好;Wellcee 的生活方式:社交|活动|交友|美食|宠物领养|音乐&艺术;Wellcee 的二手市集:家居|电子|奢侈品|时尚。', + link: baseUrl, + item: items, + }; +} diff --git a/lib/routes/wellcee/templates/house.art b/lib/routes/wellcee/templates/house.art new file mode 100644 index 00000000000000..2fb3b737931dc7 --- /dev/null +++ b/lib/routes/wellcee/templates/house.art @@ -0,0 +1,18 @@ +租金: {{ item.rent }}
+{{ if item.dailyRent }} +日租: {{ item.dailyRent }}
+{{ /if }} +
+ +{{ if item.video }} + +
+{{ /if }} + +{{ if item.imgs }} + {{ each item.imgs img }} +
+ {{ /each }} +{{ /if }} diff --git a/lib/routes/wellcee/types.ts b/lib/routes/wellcee/types.ts new file mode 100644 index 00000000000000..9e718a0087066c --- /dev/null +++ b/lib/routes/wellcee/types.ts @@ -0,0 +1,58 @@ +export interface City { + id: string; + icon: string; + city: string; + enCityName: string; + cityName: string; + chCityName: string; + twCityName: string; + statics: { + total_text: string; + online_text: string; + total_count: number; + online_count: number; + }; + district: District[]; +} + +export interface District { + id: string; + longitude: number; + latitude: number; + name: string; + business: { + id: string; + name: string; + }[]; +} + +export interface House { + id: string; + imgs: string[]; + ev: number; + city: string; + district: string; + tagsText: string; + tags: string[]; + typeTags: string[]; + rent: string; + video: string; + personalDesc: string; + address: string; + longitude: number; + latitude: number; + collectNum: number; + isCollect: boolean; + userInfo: { + id: string; + name: string; + gender: number; + avatar: string; + language: string[]; + loginTime: string; + tenant_status: number; + landlord_status: number; + }; + loginTime: number; + dailyRent: string; +} diff --git a/lib/routes/wellcee/utils.ts b/lib/routes/wellcee/utils.ts new file mode 100644 index 00000000000000..f10baf402c8abd --- /dev/null +++ b/lib/routes/wellcee/utils.ts @@ -0,0 +1,45 @@ +import { City, District } from './types'; + +import ofetch from '@/utils/ofetch'; +import cache from '@/utils/cache'; +import { config } from '@/config'; + +export const baseUrl = 'https://www.wellcee.com'; +export const getCitys = () => + cache.tryGet( + 'wellcee:citys', + async () => { + const response = await ofetch(`${baseUrl}/api/home/index`, { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ + lang: '1', + userId: '', + type: '1', + }).toString(), + }); + + return response.data.citys; + }, + config.cache.routeExpire, + false + ) as Promise; + +export const getDistricts = (cityId: string) => + cache.tryGet( + `wellcee:city:${cityId}`, + async () => { + const response = await ofetch(`${baseUrl}/api/house/filterType`, { + query: { + cityId, + lang: '1', + }, + }); + + return response.data.district; + }, + config.cache.routeExpire, + false + ) as Promise;