145 lines
4.8 KiB
JavaScript
145 lines
4.8 KiB
JavaScript
const path = require('path');
|
|
|
|
const { CookieJar } = require('tough-cookie');
|
|
const cheerio = require('cheerio');
|
|
|
|
const got = require('@/utils/got');
|
|
const { art } = require('@/utils/render');
|
|
const { isValidHost } = require('@/utils/valid-host');
|
|
|
|
const cookieJar = new CookieJar();
|
|
|
|
const client = got.extend({
|
|
cookieJar,
|
|
});
|
|
|
|
function appendRentalAPIParams(urlString) {
|
|
const searchParams = new URLSearchParams(urlString);
|
|
|
|
searchParams.set('is_format_data', '1');
|
|
searchParams.set('is_new_list', '1');
|
|
searchParams.set('type', '1');
|
|
|
|
return searchParams.toString();
|
|
}
|
|
|
|
async function getToken() {
|
|
const html = await client('https://rent.591.com.tw').text();
|
|
|
|
const $ = cheerio.load(html);
|
|
const csrfToken = $('meta[name="csrf-token"]').attr('content');
|
|
|
|
if (!csrfToken) {
|
|
throw 'CSRF token not found';
|
|
}
|
|
|
|
return csrfToken;
|
|
}
|
|
|
|
async function getHouseList(houseListURL) {
|
|
const csrfToken = await getToken();
|
|
|
|
const data = await client({
|
|
url: houseListURL,
|
|
headers: {
|
|
'X-CSRF-TOKEN': csrfToken,
|
|
},
|
|
}).json();
|
|
|
|
const {
|
|
data: { data: houseList },
|
|
} = data;
|
|
|
|
return houseList;
|
|
}
|
|
|
|
/**
|
|
|
|
@typedef {object} House
|
|
@property {string} title - The title of the house.
|
|
@property {string} type - The type of the house.
|
|
@property {number} post_id - The post id of the house.
|
|
@property {string} kind_name - The name of the kind of the house.
|
|
@property {string} room_str - A string representation of the number of rooms in the house.
|
|
@property {string} floor_str - A string representation of the floor of the house.
|
|
@property {string} community - The community the house is located in.
|
|
@property {string} price - The price of the house.
|
|
@property {string} price_unit - The unit of the price of the house.
|
|
@property {string[]} photo_list - A list of photos of the house.
|
|
@property {string} section_name - The name of the section where the house is located.
|
|
@property {string} street_name - The name of the street where the house is located.
|
|
@property {string} location - The location of the house.
|
|
@property {RentTagItem[]} rent_tag - An array of rent tags for the house.
|
|
@property {string} area - The area of the house.
|
|
@property {string} role_name - The name of the role of the house.
|
|
@property {string} contact - The contact information for the house.
|
|
@property {string} refresh_time - The time the information about the house was last refreshed.
|
|
@property {number} yesterday_hit - The number of hits the house received yesterday.
|
|
@property {number} is_vip - A flag indicating whether the house is VIP or not.
|
|
@property {number} is_combine - A flag indicating whether the house is combined or not.
|
|
@property {number} hurry - A flag indicating whether there is a hurry for the house.
|
|
@property {number} is_socail - A flag indicating whether the house is social or not.
|
|
@property {Surrounding} surrounding - The surrounding area of the house.
|
|
@property {string} discount_price_str - A string representation of the discounted price of the house.
|
|
@property {number} cases_id - The id of the cases for the house.
|
|
@property {number} is_video - A flag indicating whether there is a video for the house.
|
|
@property {number} preferred - A flag indicating whether the house is preferred or not.
|
|
@property {number} cid - The id of the house.
|
|
*/
|
|
|
|
/**
|
|
|
|
@typedef {object} RentTagItem
|
|
@property {string} id - The id of the rent tag item.
|
|
@property {string} name - The name of the rent tag item.
|
|
*/
|
|
/**
|
|
|
|
@typedef {object} Surrounding
|
|
@property {string} type - The type of the surrounding.
|
|
@property {string} desc - The description of the surrounding.
|
|
@property {string} distance - The distance to the surrounding.
|
|
*/
|
|
|
|
const renderHouse = (house) => art(path.join(__dirname, 'templates/house.art'), { house });
|
|
|
|
module.exports = async (ctx) => {
|
|
const query = ctx.params.query ?? '';
|
|
const country = ctx.params.country ?? 'tw';
|
|
|
|
if (!isValidHost(country) && country !== 'tw') {
|
|
throw Error('Invalid country codes. Only "tw" is supported now.');
|
|
}
|
|
|
|
/** @type {House[]} */
|
|
const houses = await getHouseList(`https://rent.591.com.tw/home/search/rsList?${appendRentalAPIParams(query)}`);
|
|
|
|
const queryUrl = `https://rent.591.com.tw/?${query}`;
|
|
|
|
const items = houses.map((house) => {
|
|
const { title, post_id, price, price_unit } = house;
|
|
|
|
const itemUrl = `https://rent.591.com.tw/home/${post_id}`;
|
|
const itemTitle = `${title} - ${price} ${price_unit}`;
|
|
|
|
const description = renderHouse(house);
|
|
|
|
return {
|
|
title: itemTitle,
|
|
description,
|
|
link: itemUrl,
|
|
};
|
|
});
|
|
|
|
ctx.state.data = {
|
|
title: '591 租屋 - 自訂查詢',
|
|
link: queryUrl,
|
|
description: `591 租屋 - 自訂查詢, query: ${query}`,
|
|
item: items,
|
|
};
|
|
|
|
ctx.state.json = {
|
|
houses,
|
|
};
|
|
};
|