diff --git a/docs/en/social-media.md b/docs/en/social-media.md index 0dd47ed38..fccc7da8e 100644 --- a/docs/en/social-media.md +++ b/docs/en/social-media.md @@ -300,6 +300,12 @@ Due to Telegram restrictions, some channels involving pornography, copyright, an +## TikTok + +### User + + + ## Twitter ::: warning diff --git a/lib/v2/tiktok/router.js b/lib/v2/tiktok/router.js new file mode 100644 index 000000000..5aabfd076 --- /dev/null +++ b/lib/v2/tiktok/router.js @@ -0,0 +1,3 @@ +module.exports = (router) => { + router.get('/user/:user', require('./user')); +}; diff --git a/lib/v2/tiktok/templates/maintainer.js b/lib/v2/tiktok/templates/maintainer.js new file mode 100644 index 000000000..59dcb464e --- /dev/null +++ b/lib/v2/tiktok/templates/maintainer.js @@ -0,0 +1,3 @@ +module.exports = { + '/user/:user': ['TonyRL'], +}; diff --git a/lib/v2/tiktok/templates/radar.js b/lib/v2/tiktok/templates/radar.js new file mode 100644 index 000000000..8bfb99d0c --- /dev/null +++ b/lib/v2/tiktok/templates/radar.js @@ -0,0 +1,13 @@ +module.exports = { + 'tiktok.com': { + _name: 'TikTok', + '.': [ + { + title: 'User', + docs: 'https://docs.rsshub.app/en/social-media.html#tiktok', + source: ['/:user'], + target: '/tiktok/user/:user', + }, + ], + }, +}; diff --git a/lib/v2/tiktok/templates/user.art b/lib/v2/tiktok/templates/user.art new file mode 100644 index 000000000..a8e2982d6 --- /dev/null +++ b/lib/v2/tiktok/templates/user.art @@ -0,0 +1,3 @@ + diff --git a/lib/v2/tiktok/user.js b/lib/v2/tiktok/user.js new file mode 100644 index 000000000..15dd52e23 --- /dev/null +++ b/lib/v2/tiktok/user.js @@ -0,0 +1,66 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const config = require('@/config').value; +const { parseDate } = require('@/utils/parse-date'); +const { art } = require('@/utils/render'); +const path = require('path'); + +const baseUrl = 'https://www.tiktok.com'; + +module.exports = async (ctx) => { + const { user } = ctx.params; + + const html = await ctx.cache.tryGet( + `tiktok:user:${user}`, + async () => { + const browser = await require('@/utils/puppeteer')(); + const page = await browser.newPage(); + await page.goto(`${baseUrl}/${user}`, { + waitUntil: 'networkidle2', + }); + const html = await page.evaluate(() => document.documentElement.innerHTML); + browser.close(); + return html; + }, + config.cache.routeExpire, + false + ); + + const $ = cheerio.load(html); + + let items = $('[class*=-DivItemContainerV2]') + .toArray() + .map((item) => ({ link: $(item).find('[class*=-DivWrapper] a').attr('href') })); + + items = await Promise.all( + items.map((item) => + ctx.cache.tryGet(item.link, async () => { + const videoId = item.link.substring(item.link.lastIndexOf('/') + 1); + const api = ( + await got(`${baseUrl}/node/share/video/${user}/${videoId}`, { + headers: { + referer: `${baseUrl}/${user}`, + }, + }) + ).data; + + item.title = api.itemInfo.itemStruct.desc; + item.description = art(path.join(__dirname, 'templates/user.art'), { + poster: api.itemInfo.itemStruct.video.cover, + source: api.itemInfo.itemStruct.video.playAddr, + }); + item.author = api.itemInfo.itemStruct.author.nickname; + item.pubDate = parseDate(api.itemInfo.itemStruct.createTime * 1000); + + return item; + }) + ) + ); + + ctx.state.data = { + title: $('title').text(), + description: $('[data-e2e=user-bio]').text(), + link: `${baseUrl}/${user}`, + item: items, + }; +};