activitypub.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import * as Bluebird from 'bluebird'
  2. import validator from 'validator'
  3. import { ResultList } from '../../shared/models'
  4. import { Activity } from '../../shared/models/activitypub'
  5. import { ACTIVITY_PUB, REMOTE_SCHEME } from '../initializers/constants'
  6. import { signJsonLDObject } from './peertube-crypto'
  7. import { pageToStartAndCount } from './core-utils'
  8. import { URL } from 'url'
  9. import { MActor, MVideoAccountLight } from '../types/models'
  10. import { ContextType } from '@shared/models/activitypub/context'
  11. function getContextData (type: ContextType) {
  12. const context: any[] = [
  13. 'https://www.w3.org/ns/activitystreams',
  14. 'https://w3id.org/security/v1',
  15. {
  16. RsaSignature2017: 'https://w3id.org/security#RsaSignature2017'
  17. }
  18. ]
  19. if (type !== 'View' && type !== 'Announce') {
  20. const additional = {
  21. pt: 'https://joinpeertube.org/ns#',
  22. sc: 'http://schema.org#'
  23. }
  24. if (type === 'CacheFile') {
  25. Object.assign(additional, {
  26. expires: 'sc:expires',
  27. CacheFile: 'pt:CacheFile'
  28. })
  29. } else {
  30. Object.assign(additional, {
  31. Hashtag: 'as:Hashtag',
  32. uuid: 'sc:identifier',
  33. category: 'sc:category',
  34. licence: 'sc:license',
  35. subtitleLanguage: 'sc:subtitleLanguage',
  36. sensitive: 'as:sensitive',
  37. language: 'sc:inLanguage',
  38. isLiveBroadcast: 'sc:isLiveBroadcast',
  39. liveSaveReplay: {
  40. '@type': 'sc:Boolean',
  41. '@id': 'pt:liveSaveReplay'
  42. },
  43. permanentLive: {
  44. '@type': 'sc:Boolean',
  45. '@id': 'pt:permanentLive'
  46. },
  47. Infohash: 'pt:Infohash',
  48. Playlist: 'pt:Playlist',
  49. PlaylistElement: 'pt:PlaylistElement',
  50. originallyPublishedAt: 'sc:datePublished',
  51. views: {
  52. '@type': 'sc:Number',
  53. '@id': 'pt:views'
  54. },
  55. state: {
  56. '@type': 'sc:Number',
  57. '@id': 'pt:state'
  58. },
  59. size: {
  60. '@type': 'sc:Number',
  61. '@id': 'pt:size'
  62. },
  63. fps: {
  64. '@type': 'sc:Number',
  65. '@id': 'pt:fps'
  66. },
  67. startTimestamp: {
  68. '@type': 'sc:Number',
  69. '@id': 'pt:startTimestamp'
  70. },
  71. stopTimestamp: {
  72. '@type': 'sc:Number',
  73. '@id': 'pt:stopTimestamp'
  74. },
  75. position: {
  76. '@type': 'sc:Number',
  77. '@id': 'pt:position'
  78. },
  79. commentsEnabled: {
  80. '@type': 'sc:Boolean',
  81. '@id': 'pt:commentsEnabled'
  82. },
  83. downloadEnabled: {
  84. '@type': 'sc:Boolean',
  85. '@id': 'pt:downloadEnabled'
  86. },
  87. waitTranscoding: {
  88. '@type': 'sc:Boolean',
  89. '@id': 'pt:waitTranscoding'
  90. },
  91. support: {
  92. '@type': 'sc:Text',
  93. '@id': 'pt:support'
  94. },
  95. likes: {
  96. '@id': 'as:likes',
  97. '@type': '@id'
  98. },
  99. dislikes: {
  100. '@id': 'as:dislikes',
  101. '@type': '@id'
  102. },
  103. playlists: {
  104. '@id': 'pt:playlists',
  105. '@type': '@id'
  106. },
  107. shares: {
  108. '@id': 'as:shares',
  109. '@type': '@id'
  110. },
  111. comments: {
  112. '@id': 'as:comments',
  113. '@type': '@id'
  114. }
  115. })
  116. }
  117. context.push(additional)
  118. }
  119. return {
  120. '@context': context
  121. }
  122. }
  123. function activityPubContextify <T> (data: T, type: ContextType = 'All') {
  124. return Object.assign({}, data, getContextData(type))
  125. }
  126. type ActivityPubCollectionPaginationHandler = (start: number, count: number) => Bluebird<ResultList<any>> | Promise<ResultList<any>>
  127. async function activityPubCollectionPagination (
  128. baseUrl: string,
  129. handler: ActivityPubCollectionPaginationHandler,
  130. page?: any,
  131. size = ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE
  132. ) {
  133. if (!page || !validator.isInt(page)) {
  134. // We just display the first page URL, we only need the total items
  135. const result = await handler(0, 1)
  136. return {
  137. id: baseUrl,
  138. type: 'OrderedCollectionPage',
  139. totalItems: result.total,
  140. first: baseUrl + '?page=1'
  141. }
  142. }
  143. const { start, count } = pageToStartAndCount(page, size)
  144. const result = await handler(start, count)
  145. let next: string | undefined
  146. let prev: string | undefined
  147. // Assert page is a number
  148. page = parseInt(page, 10)
  149. // There are more results
  150. if (result.total > page * size) {
  151. next = baseUrl + '?page=' + (page + 1)
  152. }
  153. if (page > 1) {
  154. prev = baseUrl + '?page=' + (page - 1)
  155. }
  156. return {
  157. id: baseUrl + '?page=' + page,
  158. type: 'OrderedCollectionPage',
  159. prev,
  160. next,
  161. partOf: baseUrl,
  162. orderedItems: result.data,
  163. totalItems: result.total
  164. }
  165. }
  166. function buildSignedActivity (byActor: MActor, data: Object, contextType?: ContextType) {
  167. const activity = activityPubContextify(data, contextType)
  168. return signJsonLDObject(byActor, activity) as Promise<Activity>
  169. }
  170. function getAPId (activity: string | { id: string }) {
  171. if (typeof activity === 'string') return activity
  172. return activity.id
  173. }
  174. function checkUrlsSameHost (url1: string, url2: string) {
  175. const idHost = new URL(url1).host
  176. const actorHost = new URL(url2).host
  177. return idHost && actorHost && idHost.toLowerCase() === actorHost.toLowerCase()
  178. }
  179. function buildRemoteVideoBaseUrl (video: MVideoAccountLight, path: string) {
  180. const host = video.VideoChannel.Account.Actor.Server.host
  181. return REMOTE_SCHEME.HTTP + '://' + host + path
  182. }
  183. // ---------------------------------------------------------------------------
  184. export {
  185. checkUrlsSameHost,
  186. getAPId,
  187. activityPubContextify,
  188. activityPubCollectionPagination,
  189. buildSignedActivity,
  190. buildRemoteVideoBaseUrl
  191. }