diff --git a/docs/en/social-media.md b/docs/en/social-media.md index 4e477c3d45..2f30b32d8a 100644 --- a/docs/en/social-media.md +++ b/docs/en/social-media.md @@ -336,6 +336,27 @@ This route requires Twitter token's corresponding id, therefore it's only availa +## Vimeo + +### User Profile + + +::: tip Special category name attention + +Some of the categories contain slash like `3D/CG` , must change the slash `/` to the vertical bar`|`. + +::: + + + +### Channel + + + +### Category + + + ## YouTube ::: tip Tiny Tiny RSS users please notice diff --git a/docs/social-media.md b/docs/social-media.md index 3cb2b140e1..82a13f2bd6 100644 --- a/docs/social-media.md +++ b/docs/social-media.md @@ -695,6 +695,32 @@ Tiny Tiny RSS 会给所有 iframe 元素添加 `sandbox="allow-scripts"` 属性 +## Vimeo + +### 用户页面 + + + +::: tip 请注意带有斜杠的的标签名 + +如果分类名带有斜杠符号的如 `3D/CG` 时,必须把斜杠`/`转成 `|` + +::: + + + +### 频道页面 + + + + + +### 分类页面 + + + + + ## VueVlog ### 用户 diff --git a/lib/v2/vimeo/category.js b/lib/v2/vimeo/category.js new file mode 100644 index 0000000000..ffffd52227 --- /dev/null +++ b/lib/v2/vimeo/category.js @@ -0,0 +1,64 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); +const { art } = require('@/utils/render'); +const path = require('path'); + +module.exports = async (ctx) => { + const { category, staffpicks } = ctx.params; + + const categoryparams = category; + let sortparams = '&direction=desc&sort=date'; + let feedtitle = category; + if (staffpicks && staffpicks !== 'staffpicks') { + return; + } + + if (staffpicks) { + sortparams += '&filter=conditional_featured'; + feedtitle += `: ${category} staffpicks`; + } + + const url = `https://api.vimeo.com/categories/${categoryparams}/videos?page=1&per_page=18${sortparams}`; + const tokenresponse = await got({ + method: 'get', + url: 'https://vimeo.com/_rv/viewer', + }); + const VimeoAuthorization = tokenresponse.data.jwt; + + const response = await got({ + method: 'get', + url, + headers: { + Authorization: `jwt ${VimeoAuthorization}`, + }, + }); + + const vimeojs = response.data.data; + + const feedlink = `https://vimeo.com/categories/${category}/videos/sort:latest`; + const feedlinkstaffpicks = '?staffpicked=ture'; + const feedDescription = await ctx.cache.tryGet(feedlink + (staffpicks ? feedlinkstaffpicks : ''), async () => { + const response = await got({ + url: feedlink + (staffpicks ? feedlinkstaffpicks : ''), + }); + const description = cheerio.load(response.data); + return description('meta[name="description"]').attr('content'); + }); + + ctx.state.data = { + title: `${feedtitle} | Vimeo category`, + link: feedlink + (staffpicks ? feedlinkstaffpicks : ''), + description: feedDescription, + item: vimeojs.map((item) => ({ + title: item.name, + description: art(path.join(__dirname, 'templates/description.art'), { + videoUrl: item.uri.replace(`/videos`, ''), + vdescription: item.description ? item.description : '', + }), + pubDate: parseDate(item.created_time), + link: item.link, + author: item.user.name, + })), + }; +}; diff --git a/lib/v2/vimeo/channel.js b/lib/v2/vimeo/channel.js new file mode 100644 index 0000000000..9b0a764252 --- /dev/null +++ b/lib/v2/vimeo/channel.js @@ -0,0 +1,72 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); +const { art } = require('@/utils/render'); +const path = require('path'); + +module.exports = async (ctx) => { + const channel = ctx.params.channel; + const url = `https://vimeo.com/channels/${channel}/videos`; + const page1 = await got({ + method: 'get', + url: `${url}/page:1/sort:date/format:detail`, + headers: { + 'X-Requested-With': 'XMLHttpRequest', + }, + }); + const page2 = + channel === `bestoftheyear` + ? await got({ + method: 'get', + url: `${url}/page:2/sort:date/format:detail`, + headers: { + 'X-Requested-With': 'XMLHttpRequest', + }, + }) + : ''; + const pagedata = page1.data.concat(page2.data); + const $ = cheerio.load(pagedata); + const list = $('ol li.clearfix'); + + const description = await Promise.all( + list.get().map((item) => { + item = $(item); + const link = item.find('.more').attr('href'); + return ctx.cache.tryGet(link, async () => { + const response2 = await got({ + method: 'get', + url: `https://vimeo.com${link}/description?breeze=1`, + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) ', + }, + }); + const articledata = response2.data; + const $2 = cheerio.load(articledata); + $2('span').remove(); + return $2.html(); + }); + }) + ); + ctx.state.data = { + title: `${channel} | Vimeo channel`, + link: url, + item: list + .map((index, item) => { + item = $(item); + const title = item.find('.title a').text(); + const author = item.find('.meta a').text(); + return { + title, + description: art(path.join(__dirname, 'templates/description.art'), { + videoUrl: item.find('.more').attr('href'), + vdescription: description[index] ? description[index] : '', + }), + pubDate: parseDate(item.find('time').attr('datetime')), + link: `https://vimeo.com${item.find('.more').attr('href')}`, + author, + }; + }) + .get(), + }; +}; diff --git a/lib/v2/vimeo/radar.js b/lib/v2/vimeo/radar.js new file mode 100644 index 0000000000..1edb5b7925 --- /dev/null +++ b/lib/v2/vimeo/radar.js @@ -0,0 +1,33 @@ +module.exports = { + 'vimeo.com': { + _name: 'Vimeo', + '.': [ + { + title: 'User videos', + docs: 'https://docs.rsshub.app/social-media.html#vimeo-yong-hu-ye-mian', + source: '/:username/', + target: (params, url, document) => { + const uid = document && document.querySelector('html').innerHTML.match(/app.vimeo.com\/users\/([0-9]+)/)[1]; + return uid ? `/vimeo/user/${uid}` : ''; + }, + }, + { + title: 'User Video Category', + docs: 'https://docs.rsshub.app/social-media.html#vimeo-yong-hu-ye-mian', + source: '/', + }, + { + title: 'Channel', + docs: 'https://docs.rsshub.app/social-media.html#vimeo-channel', + source: ['/channels/:channel', '/channels/:channel/videos', '/channels/:channel/videos/:sort/:format'], + target: '/vimeo/channel/:channel', + }, + { + title: 'Category', + docs: 'https://docs.rsshub.app/social-media.html#vimeo-category', + source: ['/categories/:category', '/categories/:category/:subcategory', '/categories/:category/:subcategory/videos'], + target: (params) => `/vimeo/category/:category${params.subcategory ? `/` + params.subcategory : ''}`, + }, + ], + }, +}; diff --git a/lib/v2/vimeo/router.js b/lib/v2/vimeo/router.js new file mode 100644 index 0000000000..d1ee9a681c --- /dev/null +++ b/lib/v2/vimeo/router.js @@ -0,0 +1,5 @@ +module.exports = (router) => { + router.get('/category/:category/:staffpicks?', require('./category')); + router.get('/channel/:channel', require('./channel')); + router.get('/user/:username/:cat?', require('./usr-videos')); +}; diff --git a/lib/v2/vimeo/templates/description.art b/lib/v2/vimeo/templates/description.art new file mode 100644 index 0000000000..2ea8f44f7e --- /dev/null +++ b/lib/v2/vimeo/templates/description.art @@ -0,0 +1,4 @@ + +{{ if vdescription }} +

{{@ vdescription }}

+{{ /if }} diff --git a/lib/v2/vimeo/usr-videos.js b/lib/v2/vimeo/usr-videos.js new file mode 100644 index 0000000000..c97511f60a --- /dev/null +++ b/lib/v2/vimeo/usr-videos.js @@ -0,0 +1,76 @@ +const got = require('@/utils/got'); +const { parseDate } = require('@/utils/parse-date'); +const { art } = require('@/utils/render'); +const path = require('path'); + +module.exports = async (ctx) => { + const tokenresponse = await got({ + method: 'get', + url: 'https://vimeo.com/_rv/viewer', + }); + const VimeoAuthorization = tokenresponse.data.jwt; + + const { username, cat } = ctx.params; + + const profileresponse = await got({ + method: 'get', + url: `https://api.vimeo.com/users/${username}?fields=name,gender,bio,uri,link,categories&fetch_user_profile=1`, + headers: { + Authorization: `jwt ${VimeoAuthorization}`, + }, + }); + + const profilesjs = profileresponse.data; + const catjs = profileresponse.data.categories; + + let catword; + if (cat && catjs.length > 0) { + for (let s = 0; s < catjs.length; s++) { + if (decodeURIComponent(cat).replace('|', '/') === catjs[s].name) { + catword = catjs[s].word; + } + } + } + + let urlfilter = `&filter=category&filter_category=${catword}`; + if (!cat) { + urlfilter = ''; + } + if (!catword && cat && cat !== 'picks') { + return ''; + } + const picked = cat && cat === 'picks'; + + const Vimeocat = `${profilesjs.uri}/videos?fields=name,uri,description,created_time&include_videos=1&page=1&per_page=10${urlfilter}`; + const Vimeoallpicks = `/users/${username}/profile_sections?fields=videos.data.clip.name,videos.data.clip.uri,videos.data.clip.description,videos.data.clip.created_time&include_videos=1&badge=1&page=1&per_page=10`; + + const contentresponse = await got({ + method: 'get', + url: `https://api.vimeo.com${picked ? Vimeoallpicks : Vimeocat}`, + headers: { + Authorization: `jwt ${VimeoAuthorization}`, + }, + }); + const vimeojs = picked ? contentresponse.data.data[0].videos.data : contentresponse.data.data; + + ctx.state.data = { + title: `${profilesjs.name} ${catword ? cat.replace('|', '/') : ''} ${picked ? 'picks' : ''} | Vimeo `, + link: profilesjs.link, + description: profilesjs.bio, + + item: vimeojs.map((item) => { + const vdescription = picked ? item.clip.description : item.description; + + return { + title: picked ? item.clip.name : item.name, + description: art(path.join(__dirname, 'templates/description.art'), { + videoUrl: picked ? item.clip.uri.replace('/videos', '') : item.uri.replace('/videos', ''), + vdescription: vdescription ? vdescription.replace(/\n/g, '
') : '', + }), + pubDate: parseDate(picked || cat ? '' : item.created_time), + link: `https://vimeo.com${picked ? item.clip.uri.replace('videos/', '') : item.uri.replace('videos/', '')}`, + author: profilesjs.name, + }; + }), + }; +};