adding initial support for nodeinfo
This commit is contained in:
parent
4278710d5b
commit
3f6d68d967
7 changed files with 246 additions and 16 deletions
|
@ -16,7 +16,7 @@ import { VideoModel } from '../../models/video/video'
|
||||||
import { VideoChannelModel } from '../../models/video/video-channel'
|
import { VideoChannelModel } from '../../models/video/video-channel'
|
||||||
import { VideoCommentModel } from '../../models/video/video-comment'
|
import { VideoCommentModel } from '../../models/video/video-comment'
|
||||||
import { VideoShareModel } from '../../models/video/video-share'
|
import { VideoShareModel } from '../../models/video/video-share'
|
||||||
import { cacheRoute } from '../../middlewares/cache'
|
import { cache } from '../../middlewares/cache'
|
||||||
import { activityPubResponse } from './utils'
|
import { activityPubResponse } from './utils'
|
||||||
import { AccountVideoRateModel } from '../../models/account/account-video-rate'
|
import { AccountVideoRateModel } from '../../models/account/account-video-rate'
|
||||||
import {
|
import {
|
||||||
|
@ -25,7 +25,6 @@ import {
|
||||||
getVideoLikesActivityPubUrl,
|
getVideoLikesActivityPubUrl,
|
||||||
getVideoSharesActivityPubUrl
|
getVideoSharesActivityPubUrl
|
||||||
} from '../../lib/activitypub'
|
} from '../../lib/activitypub'
|
||||||
import { VideoCaption } from '../../../shared/models/videos/video-caption.model'
|
|
||||||
import { VideoCaptionModel } from '../../models/video/video-caption'
|
import { VideoCaptionModel } from '../../models/video/video-caption'
|
||||||
|
|
||||||
const activityPubClientRouter = express.Router()
|
const activityPubClientRouter = express.Router()
|
||||||
|
@ -44,7 +43,7 @@ activityPubClientRouter.get('/accounts?/:name/following',
|
||||||
)
|
)
|
||||||
|
|
||||||
activityPubClientRouter.get('/videos/watch/:id',
|
activityPubClientRouter.get('/videos/watch/:id',
|
||||||
executeIfActivityPub(asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS))),
|
executeIfActivityPub(asyncMiddleware(cache(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS))),
|
||||||
executeIfActivityPub(asyncMiddleware(videosGetValidator)),
|
executeIfActivityPub(asyncMiddleware(videosGetValidator)),
|
||||||
executeIfActivityPub(asyncMiddleware(videoController))
|
executeIfActivityPub(asyncMiddleware(videoController))
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { asyncMiddleware, setDefaultSort, videoCommentsFeedsValidator, videoFeed
|
||||||
import { VideoModel } from '../models/video/video'
|
import { VideoModel } from '../models/video/video'
|
||||||
import * as Feed from 'pfeed'
|
import * as Feed from 'pfeed'
|
||||||
import { AccountModel } from '../models/account/account'
|
import { AccountModel } from '../models/account/account'
|
||||||
import { cacheRoute } from '../middlewares/cache'
|
import { cache } from '../middlewares/cache'
|
||||||
import { VideoChannelModel } from '../models/video/video-channel'
|
import { VideoChannelModel } from '../models/video/video-channel'
|
||||||
import { VideoCommentModel } from '../models/video/video-comment'
|
import { VideoCommentModel } from '../models/video/video-comment'
|
||||||
import { buildNSFWFilter } from '../helpers/express-utils'
|
import { buildNSFWFilter } from '../helpers/express-utils'
|
||||||
|
@ -13,7 +13,7 @@ import { buildNSFWFilter } from '../helpers/express-utils'
|
||||||
const feedsRouter = express.Router()
|
const feedsRouter = express.Router()
|
||||||
|
|
||||||
feedsRouter.get('/feeds/video-comments.:format',
|
feedsRouter.get('/feeds/video-comments.:format',
|
||||||
asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.FEEDS)),
|
asyncMiddleware(cache(ROUTE_CACHE_LIFETIME.FEEDS)),
|
||||||
asyncMiddleware(videoCommentsFeedsValidator),
|
asyncMiddleware(videoCommentsFeedsValidator),
|
||||||
asyncMiddleware(generateVideoCommentsFeed)
|
asyncMiddleware(generateVideoCommentsFeed)
|
||||||
)
|
)
|
||||||
|
@ -21,7 +21,7 @@ feedsRouter.get('/feeds/video-comments.:format',
|
||||||
feedsRouter.get('/feeds/videos.:format',
|
feedsRouter.get('/feeds/videos.:format',
|
||||||
videosSortValidator,
|
videosSortValidator,
|
||||||
setDefaultSort,
|
setDefaultSort,
|
||||||
asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.FEEDS)),
|
asyncMiddleware(cache(ROUTE_CACHE_LIFETIME.FEEDS)),
|
||||||
asyncMiddleware(videoFeedsValidator),
|
asyncMiddleware(videoFeedsValidator),
|
||||||
asyncMiddleware(generateVideoFeed)
|
asyncMiddleware(generateVideoFeed)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
import * as cors from 'cors'
|
import * as cors from 'cors'
|
||||||
import * as express from 'express'
|
import * as express from 'express'
|
||||||
import { CONFIG, STATIC_DOWNLOAD_PATHS, STATIC_MAX_AGE, STATIC_PATHS } from '../initializers'
|
import { CONFIG, STATIC_DOWNLOAD_PATHS, STATIC_MAX_AGE, STATIC_PATHS, ROUTE_CACHE_LIFETIME } from '../initializers'
|
||||||
import { VideosPreviewCache } from '../lib/cache'
|
import { VideosPreviewCache } from '../lib/cache'
|
||||||
|
import { cache } from '../middlewares/cache'
|
||||||
import { asyncMiddleware, videosGetValidator } from '../middlewares'
|
import { asyncMiddleware, videosGetValidator } from '../middlewares'
|
||||||
import { VideoModel } from '../models/video/video'
|
import { VideoModel } from '../models/video/video'
|
||||||
import { VideosCaptionCache } from '../lib/cache/videos-caption-cache'
|
import { VideosCaptionCache } from '../lib/cache/videos-caption-cache'
|
||||||
|
import { UserModel } from '../models/account/user'
|
||||||
|
import { VideoCommentModel } from '../models/video/video-comment'
|
||||||
|
import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../models/nodeinfo'
|
||||||
|
|
||||||
|
const packageJSON = require('../../../package.json')
|
||||||
const staticRouter = express.Router()
|
const staticRouter = express.Router()
|
||||||
|
|
||||||
staticRouter.use(cors())
|
staticRouter.use(cors())
|
||||||
|
@ -65,10 +70,32 @@ staticRouter.use(
|
||||||
)
|
)
|
||||||
|
|
||||||
// robots.txt service
|
// robots.txt service
|
||||||
staticRouter.get('/robots.txt', (req: express.Request, res: express.Response) => {
|
staticRouter.get('/robots.txt',
|
||||||
res.type('text/plain')
|
asyncMiddleware(cache(ROUTE_CACHE_LIFETIME.ROBOTS)),
|
||||||
return res.send(CONFIG.INSTANCE.ROBOTS)
|
(_, res: express.Response) => {
|
||||||
})
|
res.type('text/plain')
|
||||||
|
return res.send(CONFIG.INSTANCE.ROBOTS)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// nodeinfo service
|
||||||
|
staticRouter.use('/.well-known/nodeinfo',
|
||||||
|
asyncMiddleware(cache(ROUTE_CACHE_LIFETIME.NODEINFO)),
|
||||||
|
(_, res: express.Response) => {
|
||||||
|
return res.json({
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
|
||||||
|
href: CONFIG.WEBSERVER.URL + '/nodeinfo/2.0.json'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
staticRouter.use('/nodeinfo/:version.json',
|
||||||
|
asyncMiddleware(cache(ROUTE_CACHE_LIFETIME.NODEINFO)),
|
||||||
|
asyncMiddleware(generateNodeinfo)
|
||||||
|
)
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -95,6 +122,54 @@ async function getVideoCaption (req: express.Request, res: express.Response) {
|
||||||
return res.sendFile(path, { maxAge: STATIC_MAX_AGE })
|
return res.sendFile(path, { maxAge: STATIC_MAX_AGE })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function generateNodeinfo (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
|
const { totalVideos } = await VideoModel.getStats()
|
||||||
|
const { totalLocalVideoComments } = await VideoCommentModel.getStats()
|
||||||
|
const { totalUsers } = await UserModel.getStats()
|
||||||
|
let json = {}
|
||||||
|
|
||||||
|
if (req.params.version && (req.params.version === '2.0')) {
|
||||||
|
json = {
|
||||||
|
version: '2.0',
|
||||||
|
software: {
|
||||||
|
name: 'peertube',
|
||||||
|
version: packageJSON.version
|
||||||
|
},
|
||||||
|
protocols: [
|
||||||
|
'activitypub'
|
||||||
|
],
|
||||||
|
services: {
|
||||||
|
inbound: [],
|
||||||
|
outbound: [
|
||||||
|
'atom1.0',
|
||||||
|
'rss2.0'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
openRegistrations: CONFIG.SIGNUP.ENABLED,
|
||||||
|
usage: {
|
||||||
|
users: {
|
||||||
|
total: totalUsers
|
||||||
|
},
|
||||||
|
localPosts: totalVideos,
|
||||||
|
localComments: totalLocalVideoComments
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
taxonomy: {
|
||||||
|
postsName: 'Videos'
|
||||||
|
},
|
||||||
|
nodeName: CONFIG.INSTANCE.NAME,
|
||||||
|
nodeDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION
|
||||||
|
}
|
||||||
|
} as HttpNodeinfoDiasporaSoftwareNsSchema20
|
||||||
|
res.set('Content-Type', 'application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8')
|
||||||
|
} else {
|
||||||
|
json = { error: 'Nodeinfo schema version not handled' }
|
||||||
|
res.status(404)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.end(JSON.stringify(json))
|
||||||
|
}
|
||||||
|
|
||||||
async function downloadTorrent (req: express.Request, res: express.Response, next: express.NextFunction) {
|
async function downloadTorrent (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
const { video, videoFile } = getVideoAndFile(req, res)
|
const { video, videoFile } = getVideoAndFile(req, res)
|
||||||
if (!videoFile) return res.status(404).end()
|
if (!videoFile) return res.status(404).end()
|
||||||
|
|
|
@ -104,6 +104,36 @@ function computeResolutionsToTranscode (videoFileHeight: number) {
|
||||||
return resolutionsEnabled
|
return resolutionsEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const timeTable = {
|
||||||
|
ms: 1,
|
||||||
|
second: 1000,
|
||||||
|
minute: 60000,
|
||||||
|
hour: 3600000,
|
||||||
|
day: 3600000 * 24,
|
||||||
|
week: 3600000 * 24 * 7,
|
||||||
|
month: 3600000 * 24 * 30
|
||||||
|
}
|
||||||
|
export function parseDuration (duration: number | string, defaultDuration: number): number {
|
||||||
|
if (typeof duration === 'number') return duration
|
||||||
|
|
||||||
|
if (typeof duration === 'string') {
|
||||||
|
const split = duration.match(/^([\d\.,]+)\s?(\w+)$/)
|
||||||
|
|
||||||
|
if (split.length === 3) {
|
||||||
|
const len = parseFloat(split[1])
|
||||||
|
let unit = split[2].replace(/s$/i,'').toLowerCase()
|
||||||
|
if (unit === 'm') {
|
||||||
|
unit = 'ms'
|
||||||
|
}
|
||||||
|
|
||||||
|
return (len || 1) * (timeTable[unit] || 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error('Duration could not be properly parsed, defaulting to ' + defaultDuration)
|
||||||
|
return defaultDuration
|
||||||
|
}
|
||||||
|
|
||||||
function resetSequelizeInstance (instance: Model<any>, savedFields: object) {
|
function resetSequelizeInstance (instance: Model<any>, savedFields: object) {
|
||||||
Object.keys(savedFields).forEach(key => {
|
Object.keys(savedFields).forEach(key => {
|
||||||
const value = savedFields[key]
|
const value = savedFields[key]
|
||||||
|
|
|
@ -46,9 +46,11 @@ const OAUTH_LIFETIME = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ROUTE_CACHE_LIFETIME = {
|
const ROUTE_CACHE_LIFETIME = {
|
||||||
FEEDS: 1000 * 60 * 15, // 15 minutes
|
FEEDS: '15 minutes',
|
||||||
|
ROBOTS: '2 hours',
|
||||||
|
NODEINFO: '10 minutes',
|
||||||
ACTIVITY_PUB: {
|
ACTIVITY_PUB: {
|
||||||
VIDEOS: 1000 // 1 second, cache concurrent requests after a broadcast for example
|
VIDEOS: '1 second' // 1 second, cache concurrent requests after a broadcast for example
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as express from 'express'
|
import * as express from 'express'
|
||||||
import * as AsyncLock from 'async-lock'
|
import * as AsyncLock from 'async-lock'
|
||||||
|
import { parseDuration } from '../helpers/utils'
|
||||||
import { Redis } from '../lib/redis'
|
import { Redis } from '../lib/redis'
|
||||||
import { logger } from '../helpers/logger'
|
import { logger } from '../helpers/logger'
|
||||||
|
|
||||||
|
@ -20,7 +21,7 @@ function cacheRoute (lifetime: number) {
|
||||||
|
|
||||||
res.send = (body) => {
|
res.send = (body) => {
|
||||||
if (res.statusCode >= 200 && res.statusCode < 400) {
|
if (res.statusCode >= 200 && res.statusCode < 400) {
|
||||||
const contentType = res.getHeader('content-type').toString()
|
const contentType = res.get('content-type')
|
||||||
Redis.Instance.setCachedRoute(req, body, lifetime, contentType, res.statusCode)
|
Redis.Instance.setCachedRoute(req, body, lifetime, contentType, res.statusCode)
|
||||||
.then(() => done())
|
.then(() => done())
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
@ -35,7 +36,7 @@ function cacheRoute (lifetime: number) {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cached.contentType) res.contentType(cached.contentType)
|
if (cached.contentType) res.set('content-type', cached.contentType)
|
||||||
|
|
||||||
if (cached.statusCode) {
|
if (cached.statusCode) {
|
||||||
const statusCode = parseInt(cached.statusCode, 10)
|
const statusCode = parseInt(cached.statusCode, 10)
|
||||||
|
@ -50,8 +51,14 @@ function cacheRoute (lifetime: number) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cache = (duration: number | string) => {
|
||||||
|
const _lifetime = parseDuration(duration, 3600000)
|
||||||
|
return cacheRoute(_lifetime)
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
cacheRoute
|
cacheRoute,
|
||||||
|
cache
|
||||||
}
|
}
|
||||||
|
|
117
server/models/nodeinfo/index.d.ts
vendored
Normal file
117
server/models/nodeinfo/index.d.ts
vendored
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
/**
|
||||||
|
* NodeInfo schema version 2.0.
|
||||||
|
*/
|
||||||
|
export interface HttpNodeinfoDiasporaSoftwareNsSchema20 {
|
||||||
|
/**
|
||||||
|
* The schema version, must be 2.0.
|
||||||
|
*/
|
||||||
|
version: '2.0'
|
||||||
|
/**
|
||||||
|
* Metadata about server software in use.
|
||||||
|
*/
|
||||||
|
software: {
|
||||||
|
/**
|
||||||
|
* The canonical name of this server software.
|
||||||
|
*/
|
||||||
|
name: string
|
||||||
|
/**
|
||||||
|
* The version of this server software.
|
||||||
|
*/
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* The protocols supported on this server.
|
||||||
|
*/
|
||||||
|
protocols: (
|
||||||
|
| 'activitypub'
|
||||||
|
| 'buddycloud'
|
||||||
|
| 'dfrn'
|
||||||
|
| 'diaspora'
|
||||||
|
| 'libertree'
|
||||||
|
| 'ostatus'
|
||||||
|
| 'pumpio'
|
||||||
|
| 'tent'
|
||||||
|
| 'xmpp'
|
||||||
|
| 'zot')[]
|
||||||
|
/**
|
||||||
|
* The third party sites this server can connect to via their application API.
|
||||||
|
*/
|
||||||
|
services: {
|
||||||
|
/**
|
||||||
|
* The third party sites this server can retrieve messages from for combined display with regular traffic.
|
||||||
|
*/
|
||||||
|
inbound: ('atom1.0' | 'gnusocial' | 'imap' | 'pnut' | 'pop3' | 'pumpio' | 'rss2.0' | 'twitter')[]
|
||||||
|
/**
|
||||||
|
* The third party sites this server can publish messages to on the behalf of a user.
|
||||||
|
*/
|
||||||
|
outbound: (
|
||||||
|
| 'atom1.0'
|
||||||
|
| 'blogger'
|
||||||
|
| 'buddycloud'
|
||||||
|
| 'diaspora'
|
||||||
|
| 'dreamwidth'
|
||||||
|
| 'drupal'
|
||||||
|
| 'facebook'
|
||||||
|
| 'friendica'
|
||||||
|
| 'gnusocial'
|
||||||
|
| 'google'
|
||||||
|
| 'insanejournal'
|
||||||
|
| 'libertree'
|
||||||
|
| 'linkedin'
|
||||||
|
| 'livejournal'
|
||||||
|
| 'mediagoblin'
|
||||||
|
| 'myspace'
|
||||||
|
| 'pinterest'
|
||||||
|
| 'pnut'
|
||||||
|
| 'posterous'
|
||||||
|
| 'pumpio'
|
||||||
|
| 'redmatrix'
|
||||||
|
| 'rss2.0'
|
||||||
|
| 'smtp'
|
||||||
|
| 'tent'
|
||||||
|
| 'tumblr'
|
||||||
|
| 'twitter'
|
||||||
|
| 'wordpress'
|
||||||
|
| 'xmpp')[]
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Whether this server allows open self-registration.
|
||||||
|
*/
|
||||||
|
openRegistrations: boolean
|
||||||
|
/**
|
||||||
|
* Usage statistics for this server.
|
||||||
|
*/
|
||||||
|
usage: {
|
||||||
|
/**
|
||||||
|
* statistics about the users of this server.
|
||||||
|
*/
|
||||||
|
users: {
|
||||||
|
/**
|
||||||
|
* The total amount of on this server registered users.
|
||||||
|
*/
|
||||||
|
total?: number
|
||||||
|
/**
|
||||||
|
* The amount of users that signed in at least once in the last 180 days.
|
||||||
|
*/
|
||||||
|
activeHalfyear?: number
|
||||||
|
/**
|
||||||
|
* The amount of users that signed in at least once in the last 30 days.
|
||||||
|
*/
|
||||||
|
activeMonth?: number
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The amount of posts that were made by users that are registered on this server.
|
||||||
|
*/
|
||||||
|
localPosts?: number
|
||||||
|
/**
|
||||||
|
* The amount of comments that were made by users that are registered on this server.
|
||||||
|
*/
|
||||||
|
localComments?: number
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Free form key value pairs for software specific values. Clients should not rely on any specific key present.
|
||||||
|
*/
|
||||||
|
metadata: {
|
||||||
|
[k: string]: any
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue