aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers/feeds/shared/common-feed-utils.ts
blob: 8f35a8b350f2708b0e7835e1b575ba59e501f095 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import express from 'express'
import { maxBy } from 'lodash'
import { Feed } from '@peertube/feed'
import { CustomTag, CustomXMLNS, Person } from '@peertube/feed/lib/typings'
import { mdToOneLinePlainText } from '@server/helpers/markdown'
import { CONFIG } from '@server/initializers/config'
import { WEBSERVER } from '@server/initializers/constants'
import { UserModel } from '@server/models/user/user'
import { MAccountDefault, MChannelBannerAccountDefault, MUser, MVideoFullLight } from '@server/types/models'
import { pick } from '@shared/core-utils'
import { ActorImageType } from '@shared/models'

export function initFeed (parameters: {
  name: string
  description: string
  imageUrl: string
  isPodcast: boolean
  link?: string
  locked?: { isLocked: boolean, email: string }
  author?: {
    name: string
    link: string
    imageUrl: string
  }
  person?: Person[]
  resourceType?: 'videos' | 'video-comments'
  queryString?: string
  medium?: string
  stunServers?: string[]
  trackers?: string[]
  customXMLNS?: CustomXMLNS[]
  customTags?: CustomTag[]
}) {
  const webserverUrl = WEBSERVER.URL
  const { name, description, link, imageUrl, isPodcast, resourceType, queryString, medium } = parameters

  return new Feed({
    title: name,
    description: mdToOneLinePlainText(description),
    // updated: TODO: somehowGetLatestUpdate, // optional, default = today
    id: link || webserverUrl,
    link: link || webserverUrl,
    image: imageUrl,
    favicon: webserverUrl + '/client/assets/images/favicon.png',
    copyright: `All rights reserved, unless otherwise specified in the terms specified at ${webserverUrl}/about` +
    ` and potential licenses granted by each content's rightholder.`,
    generator: `Toraifōsu`, // ^.~
    medium: medium || 'video',
    feedLinks: {
      json: `${webserverUrl}/feeds/${resourceType}.json${queryString}`,
      atom: `${webserverUrl}/feeds/${resourceType}.atom${queryString}`,
      rss: isPodcast
        ? `${webserverUrl}/feeds/podcast/videos.xml${queryString}`
        : `${webserverUrl}/feeds/${resourceType}.xml${queryString}`
    },

    ...pick(parameters, [ 'stunServers', 'trackers', 'customXMLNS', 'customTags', 'author', 'person', 'locked' ])
  })
}

export function sendFeed (feed: Feed, req: express.Request, res: express.Response) {
  const format = req.params.format

  if (format === 'atom' || format === 'atom1') {
    return res.send(feed.atom1()).end()
  }

  if (format === 'json' || format === 'json1') {
    return res.send(feed.json1()).end()
  }

  if (format === 'rss' || format === 'rss2') {
    return res.send(feed.rss2()).end()
  }

  // We're in the ambiguous '.xml' case and we look at the format query parameter
  if (req.query.format === 'atom' || req.query.format === 'atom1') {
    return res.send(feed.atom1()).end()
  }

  return res.send(feed.rss2()).end()
}

export async function buildFeedMetadata (options: {
  videoChannel?: MChannelBannerAccountDefault
  account?: MAccountDefault
  video?: MVideoFullLight
}) {
  const { video, videoChannel, account } = options

  let imageUrl = WEBSERVER.URL + '/client/assets/images/icons/icon-96x96.png'
  let accountImageUrl: string
  let name: string
  let userName: string
  let description: string
  let email: string
  let link: string
  let accountLink: string
  let user: MUser

  if (videoChannel) {
    name = videoChannel.getDisplayName()
    description = videoChannel.description
    link = videoChannel.getClientUrl()
    accountLink = videoChannel.Account.getClientUrl()

    if (videoChannel.Actor.hasImage(ActorImageType.AVATAR)) {
      const videoChannelAvatar = maxBy(videoChannel.Actor.Avatars, 'width')
      imageUrl = WEBSERVER.URL + videoChannelAvatar.getStaticPath()
    }

    if (videoChannel.Account.Actor.hasImage(ActorImageType.AVATAR)) {
      const accountAvatar = maxBy(videoChannel.Account.Actor.Avatars, 'width')
      accountImageUrl = WEBSERVER.URL + accountAvatar.getStaticPath()
    }

    user = await UserModel.loadById(videoChannel.Account.userId)
    userName = videoChannel.Account.getDisplayName()
  } else if (account) {
    name = account.getDisplayName()
    description = account.description
    link = account.getClientUrl()
    accountLink = link

    if (account.Actor.hasImage(ActorImageType.AVATAR)) {
      const accountAvatar = maxBy(account.Actor.Avatars, 'width')
      imageUrl = WEBSERVER.URL + accountAvatar?.getStaticPath()
      accountImageUrl = imageUrl
    }

    user = await UserModel.loadById(account.userId)
  } else if (video) {
    name = video.name
    description = video.description
    link = video.url
  } else {
    name = CONFIG.INSTANCE.NAME
    description = CONFIG.INSTANCE.DESCRIPTION
    link = WEBSERVER.URL
  }

  // If the user is local, has a verified email address, and allows it to be publicly displayed
  // Return it so the owner can prove ownership of their feed
  if (user && !user.pluginAuth && user.emailVerified && user.emailPublic) {
    email = user.email
  }

  return { name, userName, description, imageUrl, accountImageUrl, email, link, accountLink }
}