activitypub.ts 4.5 KB

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