diff --git a/lib/v2/otobanana/cast.js b/lib/v2/otobanana/cast.js new file mode 100644 index 000000000..bb45141a3 --- /dev/null +++ b/lib/v2/otobanana/cast.js @@ -0,0 +1,29 @@ +const got = require('@/utils/got'); +const { apiBase, baseUrl, getUserInfo, renderCast } = require('./utils'); + +module.exports = async (ctx) => { + const { id } = ctx.params; + + const userInfo = await getUserInfo(id, ctx.cache.tryGet); + const { data: castData } = await got(`${apiBase}/users/${id}/casts/`); + + const casts = castData.results.map((item) => renderCast(item)); + + ctx.state.data = { + title: `${userInfo.name} (@${userInfo.username}) - 音声投稿 | OTOBANANA`, + description: userInfo.bio.replace(/\n/g, ' '), + link: `${baseUrl}/user/${id}`, + image: userInfo.avatar_url, + icon: userInfo.avatar_url, + logo: userInfo.avatar_url, + language: 'ja', + author: userInfo.name, + itunes_author: userInfo.name, + item: casts, + }; + + ctx.state.json = { + userInfo, + castData, + }; +}; diff --git a/lib/v2/otobanana/livestream.js b/lib/v2/otobanana/livestream.js new file mode 100644 index 000000000..e8f029e48 --- /dev/null +++ b/lib/v2/otobanana/livestream.js @@ -0,0 +1,29 @@ +const got = require('@/utils/got'); +const { apiBase, baseUrl, getUserInfo, renderLive } = require('./utils'); + +module.exports = async (ctx) => { + const { id } = ctx.params; + + const userInfo = await getUserInfo(id, ctx.cache.tryGet); + const { data: liveData } = await got(`${apiBase}/users/${id}/livestreams/`); + + const casts = liveData.results.map((item) => renderLive(item)); + + ctx.state.data = { + title: `${userInfo.name} (@${userInfo.username}) - ライブ配信 | OTOBANANA`, + description: userInfo.bio.replace(/\n/g, ' '), + link: `${baseUrl}/user/${id}`, + image: userInfo.avatar_url, + icon: userInfo.avatar_url, + logo: userInfo.avatar_url, + language: 'ja', + author: userInfo.name, + itunes_author: userInfo.name, + item: casts, + }; + + ctx.state.json = { + userInfo, + liveData, + }; +}; diff --git a/lib/v2/otobanana/maintainer.js b/lib/v2/otobanana/maintainer.js new file mode 100644 index 000000000..1db1a6d2f --- /dev/null +++ b/lib/v2/otobanana/maintainer.js @@ -0,0 +1,5 @@ +module.exports = { + '/user/:id': ['TonyRL'], + '/user/:id/cast': ['TonyRL'], + '/user/:id/livestream': ['TonyRL'], +}; diff --git a/lib/v2/otobanana/radar.js b/lib/v2/otobanana/radar.js new file mode 100644 index 000000000..3927bb424 --- /dev/null +++ b/lib/v2/otobanana/radar.js @@ -0,0 +1,25 @@ +module.exports = { + 'otobanana.com': { + _name: 'OTOBANANA', + '.': [ + { + title: 'Timeline タイムライン', + docs: 'https://docs.rsshub.app/multimedia#otobanana', + source: ['/user/:id'], + target: '/otobanana/user/:id', + }, + { + title: 'Cast 音声投稿', + docs: 'https://docs.rsshub.app/multimedia#otobanana', + source: ['/user/:id/cast', '/user/:id'], + target: '/otobanana/user/:id/cast', + }, + { + title: 'Livestream ライブ配信', + docs: 'https://docs.rsshub.app/multimedia#otobanana', + source: ['/user/:id/livestream', '/user/:id'], + target: '/otobanana/user/:id/livestream', + }, + ], + }, +}; diff --git a/lib/v2/otobanana/router.js b/lib/v2/otobanana/router.js new file mode 100644 index 000000000..1d3ec6d66 --- /dev/null +++ b/lib/v2/otobanana/router.js @@ -0,0 +1,5 @@ +module.exports = (router) => { + router.get('/user/:id', require('./timeline')); + router.get('/user/:id/cast', require('./cast')); + router.get('/user/:id/livestream', require('./livestream')); +}; diff --git a/lib/v2/otobanana/templates/description.art b/lib/v2/otobanana/templates/description.art new file mode 100644 index 000000000..72a6921cf --- /dev/null +++ b/lib/v2/otobanana/templates/description.art @@ -0,0 +1,11 @@ +{{ if cast }} + +
+ +
+💬 {{ cast.comment_count }} ❤️ {{ cast.like_count }} 🍌 {{ cast.gift_banana }} {{ cast.play_count }} 再生 +
+{{@ cast.text.replace(/\n/g, '
') }} +{{ /if }} diff --git a/lib/v2/otobanana/timeline.js b/lib/v2/otobanana/timeline.js new file mode 100644 index 000000000..93984b608 --- /dev/null +++ b/lib/v2/otobanana/timeline.js @@ -0,0 +1,29 @@ +const got = require('@/utils/got'); +const { apiBase, baseUrl, getUserInfo, renderPost } = require('./utils'); + +module.exports = async (ctx) => { + const { id } = ctx.params; + + const userInfo = await getUserInfo(id, ctx.cache.tryGet); + const { data: postData } = await got(`${apiBase}/users/${id}/posts/`); + + const posts = postData.results.map((item) => renderPost(item)); + + ctx.state.data = { + title: `${userInfo.name} (@${userInfo.username}) - タイムライン | OTOBANANA`, + description: userInfo.bio.replace(/\n/g, ' '), + link: `${baseUrl}/user/${id}`, + image: userInfo.avatar_url, + icon: userInfo.avatar_url, + logo: userInfo.avatar_url, + language: 'ja', + author: userInfo.name, + itunes_author: userInfo.name, + item: posts, + }; + + ctx.state.json = { + userInfo, + postData, + }; +}; diff --git a/lib/v2/otobanana/utils.js b/lib/v2/otobanana/utils.js new file mode 100644 index 000000000..61b4d853c --- /dev/null +++ b/lib/v2/otobanana/utils.js @@ -0,0 +1,67 @@ +const got = require('@/utils/got'); +const { parseDate } = require('@/utils/parse-date'); +const { art } = require('@/utils/render'); +const { join } = require('path'); + +const domain = 'otobanana.com'; +const apiBase = `https://api.${domain}`; +const baseUrl = `https://${domain}`; + +const getUserInfo = (id, tryGet) => + tryGet(`otobanana:user:${id}`, async () => { + const { data } = await got(`${apiBase}/users/${id}/`); + return data; + }); + +const renderCast = (cast) => ({ + title: cast.title, + description: art(join(__dirname, 'templates/description.art'), { cast }), + pubDate: parseDate(cast.created_at), + link: `https://otobanana.com/cast/${cast.id}`, + author: `${cast.user.name} (@${cast.user.username})`, + itunes_item_image: cast.thumbnail_url, + itunes_duration: cast.duration_time, + enclosure_url: cast.audio_url, + enclosure_type: 'audio/x-m4a', + upvotes: cast.like_count, + comments: cast.comment_count, +}); + +const renderLive = (live) => ({ + title: live.title, + description: live.is_open ? '配信中のライブ' : '終了しました', + pubDate: parseDate(live.created_at), + link: live.room_url, + guid: `${live.room_url}#${live.id}`, + author: `${live.user.name} (@${live.user.username})`, + upvotes: live.like_count, + comments: live.comment_count, +}); + +const renderPost = ({ id, type_label: type, cast, /** livestream */ message /** , event */ }) => { + switch (type) { + case 'cast': + return renderCast(cast); + case 'message': + return { + title: message.text.split('\n')[0], + description: message.text.replace(/\n/g, '
'), + pubDate: parseDate(message.created_at), + link: `https://otobanana.com/${type}/${id}`, + author: `${message.user.name} (@${message.user.username})`, + upvotes: message.like_count, + comments: message.comment_count, + }; + default: + throw Error(`Unknown post type: ${type}`); + } +}; + +module.exports = { + apiBase, + baseUrl, + getUserInfo, + renderCast, + renderLive, + renderPost, +}; diff --git a/website/docs/routes/multimedia.mdx b/website/docs/routes/multimedia.mdx index f6f5b4f34..cd2e33066 100644 --- a/website/docs/routes/multimedia.mdx +++ b/website/docs/routes/multimedia.mdx @@ -957,6 +957,20 @@ JavDB 有多个备用域名,本路由默认使用永久域名 `https://javdb.c `day` 类型的关键词必须填写 **日期** ,按照示例写成形如 `20200730` 的格式 +## OTOBANANA {#otobanana} + +### Timeline タイムライン {#otobanana-timeline-%E3%82%BF%E3%82%A4%E3%83%A0%E3%83%A9%E3%82%A4%E3%83%B3} + + + +### Cast 音声投稿 {#otobanana-cast-yin-sheng-tou-gao} + + + +### Livestream ライブ配信 {#otobanana-livestream-%E3%83%A9%E3%82%A4%E3%83%96-pei-xin} + + + ## PornHub {#pornhub} ### Category {#pornhub-category}