]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - client/src/app/core/server/server.service.ts
add default trending page choice, revert comments count for hot strategy
[github/Chocobozzz/PeerTube.git] / client / src / app / core / server / server.service.ts
1 import { Observable, of, Subject } from 'rxjs'
2 import { first, map, share, shareReplay, switchMap, tap } from 'rxjs/operators'
3 import { HttpClient } from '@angular/common/http'
4 import { Inject, Injectable, LOCALE_ID } from '@angular/core'
5 import { getDevLocale, isOnDevLocale, sortBy } from '@app/helpers'
6 import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
7 import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n'
8 import { SearchTargetType, ServerConfig, ServerStats, VideoConstant } from '@shared/models'
9 import { environment } from '../../../environments/environment'
10
11 @Injectable()
12 export class ServerService {
13 private static BASE_CONFIG_URL = environment.apiUrl + '/api/v1/config/'
14 private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
15 private static BASE_VIDEO_PLAYLIST_URL = environment.apiUrl + '/api/v1/video-playlists/'
16 private static BASE_LOCALE_URL = environment.apiUrl + '/client/locales/'
17 private static BASE_STATS_URL = environment.apiUrl + '/api/v1/server/stats'
18
19 private static CONFIG_LOCAL_STORAGE_KEY = 'server-config'
20
21 configReloaded = new Subject<ServerConfig>()
22
23 private localeObservable: Observable<any>
24 private videoLicensesObservable: Observable<VideoConstant<number>[]>
25 private videoCategoriesObservable: Observable<VideoConstant<number>[]>
26 private videoPrivaciesObservable: Observable<VideoConstant<number>[]>
27 private videoPlaylistPrivaciesObservable: Observable<VideoConstant<number>[]>
28 private videoLanguagesObservable: Observable<VideoConstant<string>[]>
29 private configObservable: Observable<ServerConfig>
30
31 private configReset = false
32
33 private configLoaded = false
34 private config: ServerConfig = {
35 instance: {
36 name: 'PeerTube',
37 shortDescription: 'PeerTube, a federated (ActivityPub) video streaming platform ' +
38 'using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.',
39 isNSFW: false,
40 defaultNSFWPolicy: 'do_not_list' as 'do_not_list',
41 defaultClientRoute: '',
42 defaultTrendingRoute: '',
43 pages: {
44 hot: {
45 enabled: true
46 }
47 },
48 customizations: {
49 javascript: '',
50 css: ''
51 }
52 },
53 plugin: {
54 registered: [],
55 registeredExternalAuths: [],
56 registeredIdAndPassAuths: []
57 },
58 theme: {
59 registered: [],
60 default: 'default'
61 },
62 email: {
63 enabled: false
64 },
65 contactForm: {
66 enabled: false
67 },
68 serverVersion: 'Unknown',
69 signup: {
70 allowed: false,
71 allowedForCurrentIP: false,
72 requiresEmailVerification: false
73 },
74 transcoding: {
75 enabledResolutions: [],
76 hls: {
77 enabled: false
78 },
79 webtorrent: {
80 enabled: true
81 }
82 },
83 live: {
84 enabled: false,
85 allowReplay: true,
86 maxDuration: null,
87 maxInstanceLives: -1,
88 maxUserLives: -1,
89 transcoding: {
90 enabled: false,
91 enabledResolutions: []
92 },
93 rtmp: {
94 port: 1935
95 }
96 },
97 avatar: {
98 file: {
99 size: { max: 0 },
100 extensions: []
101 }
102 },
103 video: {
104 image: {
105 size: { max: 0 },
106 extensions: []
107 },
108 file: {
109 extensions: []
110 }
111 },
112 videoCaption: {
113 file: {
114 size: { max: 0 },
115 extensions: []
116 }
117 },
118 user: {
119 videoQuota: -1,
120 videoQuotaDaily: -1
121 },
122 import: {
123 videos: {
124 http: {
125 enabled: false
126 },
127 torrent: {
128 enabled: false
129 }
130 }
131 },
132 trending: {
133 videos: {
134 intervalDays: 0
135 }
136 },
137 autoBlacklist: {
138 videos: {
139 ofUsers: {
140 enabled: false
141 }
142 }
143 },
144 tracker: {
145 enabled: true
146 },
147 followings: {
148 instance: {
149 autoFollowIndex: {
150 indexUrl: 'https://instances.joinpeertube.org'
151 }
152 }
153 },
154 broadcastMessage: {
155 enabled: false,
156 message: '',
157 level: 'info',
158 dismissable: false
159 },
160 search: {
161 remoteUri: {
162 users: true,
163 anonymous: false
164 },
165 searchIndex: {
166 enabled: false,
167 url: '',
168 disableLocalSearch: false,
169 isDefaultSearch: false
170 }
171 }
172 }
173
174 constructor (
175 private http: HttpClient,
176 @Inject(LOCALE_ID) private localeId: string
177 ) {
178 this.loadConfigLocally()
179 }
180
181 getServerVersionAndCommit () {
182 const serverVersion = this.config.serverVersion
183 const commit = this.config.serverCommit || ''
184
185 let result = serverVersion
186 if (commit) result += '...' + commit
187
188 return result
189 }
190
191 resetConfig () {
192 this.configLoaded = false
193 this.configReset = true
194
195 // Notify config update
196 this.getConfig().subscribe(() => {
197 // empty, to fire a reset config event
198 })
199 }
200
201 getConfig () {
202 if (this.configLoaded) return of(this.config)
203
204 if (!this.configObservable) {
205 this.configObservable = this.http.get<ServerConfig>(ServerService.BASE_CONFIG_URL)
206 .pipe(
207 tap(config => this.saveConfigLocally(config)),
208 tap(config => {
209 this.config = config
210 this.configLoaded = true
211 }),
212 tap(config => {
213 if (this.configReset) {
214 this.configReloaded.next(config)
215 this.configReset = false
216 }
217 }),
218 share()
219 )
220 }
221
222 return this.configObservable
223 }
224
225 getTmpConfig () {
226 return this.config
227 }
228
229 getVideoCategories () {
230 if (!this.videoCategoriesObservable) {
231 this.videoCategoriesObservable = this.loadAttributeEnum<number>(ServerService.BASE_VIDEO_URL, 'categories', true)
232 }
233
234 return this.videoCategoriesObservable.pipe(first())
235 }
236
237 getVideoLicences () {
238 if (!this.videoLicensesObservable) {
239 this.videoLicensesObservable = this.loadAttributeEnum<number>(ServerService.BASE_VIDEO_URL, 'licences')
240 }
241
242 return this.videoLicensesObservable.pipe(first())
243 }
244
245 getVideoLanguages () {
246 if (!this.videoLanguagesObservable) {
247 this.videoLanguagesObservable = this.loadAttributeEnum<string>(ServerService.BASE_VIDEO_URL, 'languages', true)
248 }
249
250 return this.videoLanguagesObservable.pipe(first())
251 }
252
253 getVideoPrivacies () {
254 if (!this.videoPrivaciesObservable) {
255 this.videoPrivaciesObservable = this.loadAttributeEnum<number>(ServerService.BASE_VIDEO_URL, 'privacies')
256 }
257
258 return this.videoPrivaciesObservable.pipe(first())
259 }
260
261 getVideoPlaylistPrivacies () {
262 if (!this.videoPlaylistPrivaciesObservable) {
263 this.videoPlaylistPrivaciesObservable = this.loadAttributeEnum<number>(ServerService.BASE_VIDEO_PLAYLIST_URL, 'privacies')
264 }
265
266 return this.videoPlaylistPrivaciesObservable.pipe(first())
267 }
268
269 getServerLocale () {
270 if (!this.localeObservable) {
271 const completeLocale = isOnDevLocale() ? getDevLocale() : getCompleteLocale(this.localeId)
272
273 // Default locale, nothing to translate
274 if (isDefaultLocale(completeLocale)) {
275 this.localeObservable = of({}).pipe(shareReplay())
276 } else {
277 this.localeObservable = this.http
278 .get(ServerService.BASE_LOCALE_URL + completeLocale + '/server.json')
279 .pipe(shareReplay())
280 }
281 }
282
283 return this.localeObservable.pipe(first())
284 }
285
286 getServerStats () {
287 return this.http.get<ServerStats>(ServerService.BASE_STATS_URL)
288 }
289
290 getDefaultSearchTarget (): Promise<SearchTargetType> {
291 return this.getConfig().pipe(
292 map(config => {
293 const searchIndexConfig = config.search.searchIndex
294
295 if (searchIndexConfig.enabled && (searchIndexConfig.isDefaultSearch || searchIndexConfig.disableLocalSearch)) {
296 return 'search-index'
297 }
298
299 return 'local'
300 })
301 ).toPromise()
302 }
303
304 private loadAttributeEnum <T extends string | number> (
305 baseUrl: string,
306 attributeName: 'categories' | 'licences' | 'languages' | 'privacies',
307 sort = false
308 ) {
309 return this.getServerLocale()
310 .pipe(
311 switchMap(translations => {
312 return this.http.get<{ [ id: string ]: string }>(baseUrl + attributeName)
313 .pipe(map(data => ({ data, translations })))
314 }),
315 map(({ data, translations }) => {
316 const hashToPopulate: VideoConstant<T>[] = Object.keys(data)
317 .map(dataKey => {
318 const label = data[ dataKey ]
319
320 const id = attributeName === 'languages'
321 ? dataKey as T
322 : parseInt(dataKey, 10) as T
323
324 return {
325 id,
326 label: peertubeTranslate(label, translations)
327 }
328 })
329
330 if (sort === true) sortBy(hashToPopulate, 'label')
331
332 return hashToPopulate
333 }),
334 shareReplay()
335 )
336 }
337
338 private saveConfigLocally (config: ServerConfig) {
339 peertubeLocalStorage.setItem(ServerService.CONFIG_LOCAL_STORAGE_KEY, JSON.stringify(config))
340 }
341
342 private loadConfigLocally () {
343 const configString = peertubeLocalStorage.getItem(ServerService.CONFIG_LOCAL_STORAGE_KEY)
344
345 if (configString) {
346 try {
347 const parsed = JSON.parse(configString)
348 Object.assign(this.config, parsed)
349 } catch (err) {
350 console.error('Cannot parse config saved in local storage.', err)
351 }
352 }
353 }
354 }