diff options
Diffstat (limited to 'server/models/activitypub/actor.ts')
-rw-r--r-- | server/models/activitypub/actor.ts | 699 |
1 files changed, 0 insertions, 699 deletions
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts deleted file mode 100644 index 1af9efac2..000000000 --- a/server/models/activitypub/actor.ts +++ /dev/null | |||
@@ -1,699 +0,0 @@ | |||
1 | import { values } from 'lodash' | ||
2 | import { extname } from 'path' | ||
3 | import { literal, Op, Transaction } from 'sequelize' | ||
4 | import { | ||
5 | AllowNull, | ||
6 | BelongsTo, | ||
7 | Column, | ||
8 | CreatedAt, | ||
9 | DataType, | ||
10 | DefaultScope, | ||
11 | ForeignKey, | ||
12 | HasMany, | ||
13 | HasOne, | ||
14 | Is, | ||
15 | Model, | ||
16 | Scopes, | ||
17 | Table, | ||
18 | UpdatedAt | ||
19 | } from 'sequelize-typescript' | ||
20 | import { ModelCache } from '@server/models/model-cache' | ||
21 | import { ActivityIconObject, ActivityPubActorType } from '../../../shared/models/activitypub' | ||
22 | import { ActorImage } from '../../../shared/models/actors/actor-image.model' | ||
23 | import { activityPubContextify } from '../../helpers/activitypub' | ||
24 | import { | ||
25 | isActorFollowersCountValid, | ||
26 | isActorFollowingCountValid, | ||
27 | isActorPreferredUsernameValid, | ||
28 | isActorPrivateKeyValid, | ||
29 | isActorPublicKeyValid | ||
30 | } from '../../helpers/custom-validators/activitypub/actor' | ||
31 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | ||
32 | import { | ||
33 | ACTIVITY_PUB, | ||
34 | ACTIVITY_PUB_ACTOR_TYPES, | ||
35 | CONSTRAINTS_FIELDS, | ||
36 | MIMETYPES, | ||
37 | SERVER_ACTOR_NAME, | ||
38 | WEBSERVER | ||
39 | } from '../../initializers/constants' | ||
40 | import { | ||
41 | MActor, | ||
42 | MActorAccountChannelId, | ||
43 | MActorAPAccount, | ||
44 | MActorAPChannel, | ||
45 | MActorFormattable, | ||
46 | MActorFull, | ||
47 | MActorHost, | ||
48 | MActorServer, | ||
49 | MActorSummaryFormattable, | ||
50 | MActorUrl, | ||
51 | MActorWithInboxes | ||
52 | } from '../../types/models' | ||
53 | import { AccountModel } from '../account/account' | ||
54 | import { ActorImageModel } from '../account/actor-image' | ||
55 | import { ServerModel } from '../server/server' | ||
56 | import { isOutdated, throwIfNotValid } from '../utils' | ||
57 | import { VideoModel } from '../video/video' | ||
58 | import { VideoChannelModel } from '../video/video-channel' | ||
59 | import { ActorFollowModel } from './actor-follow' | ||
60 | |||
61 | enum ScopeNames { | ||
62 | FULL = 'FULL' | ||
63 | } | ||
64 | |||
65 | export const unusedActorAttributesForAPI = [ | ||
66 | 'publicKey', | ||
67 | 'privateKey', | ||
68 | 'inboxUrl', | ||
69 | 'outboxUrl', | ||
70 | 'sharedInboxUrl', | ||
71 | 'followersUrl', | ||
72 | 'followingUrl' | ||
73 | ] | ||
74 | |||
75 | @DefaultScope(() => ({ | ||
76 | include: [ | ||
77 | { | ||
78 | model: ServerModel, | ||
79 | required: false | ||
80 | }, | ||
81 | { | ||
82 | model: ActorImageModel, | ||
83 | as: 'Avatar', | ||
84 | required: false | ||
85 | } | ||
86 | ] | ||
87 | })) | ||
88 | @Scopes(() => ({ | ||
89 | [ScopeNames.FULL]: { | ||
90 | include: [ | ||
91 | { | ||
92 | model: AccountModel.unscoped(), | ||
93 | required: false | ||
94 | }, | ||
95 | { | ||
96 | model: VideoChannelModel.unscoped(), | ||
97 | required: false, | ||
98 | include: [ | ||
99 | { | ||
100 | model: AccountModel, | ||
101 | required: true | ||
102 | } | ||
103 | ] | ||
104 | }, | ||
105 | { | ||
106 | model: ServerModel, | ||
107 | required: false | ||
108 | }, | ||
109 | { | ||
110 | model: ActorImageModel, | ||
111 | as: 'Avatar', | ||
112 | required: false | ||
113 | }, | ||
114 | { | ||
115 | model: ActorImageModel, | ||
116 | as: 'Banner', | ||
117 | required: false | ||
118 | } | ||
119 | ] | ||
120 | } | ||
121 | })) | ||
122 | @Table({ | ||
123 | tableName: 'actor', | ||
124 | indexes: [ | ||
125 | { | ||
126 | fields: [ 'url' ], | ||
127 | unique: true | ||
128 | }, | ||
129 | { | ||
130 | fields: [ 'preferredUsername', 'serverId' ], | ||
131 | unique: true, | ||
132 | where: { | ||
133 | serverId: { | ||
134 | [Op.ne]: null | ||
135 | } | ||
136 | } | ||
137 | }, | ||
138 | { | ||
139 | fields: [ 'preferredUsername' ], | ||
140 | unique: true, | ||
141 | where: { | ||
142 | serverId: null | ||
143 | } | ||
144 | }, | ||
145 | { | ||
146 | fields: [ 'inboxUrl', 'sharedInboxUrl' ] | ||
147 | }, | ||
148 | { | ||
149 | fields: [ 'sharedInboxUrl' ] | ||
150 | }, | ||
151 | { | ||
152 | fields: [ 'serverId' ] | ||
153 | }, | ||
154 | { | ||
155 | fields: [ 'avatarId' ] | ||
156 | }, | ||
157 | { | ||
158 | fields: [ 'followersUrl' ] | ||
159 | } | ||
160 | ] | ||
161 | }) | ||
162 | export class ActorModel extends Model { | ||
163 | |||
164 | @AllowNull(false) | ||
165 | @Column(DataType.ENUM(...values(ACTIVITY_PUB_ACTOR_TYPES))) | ||
166 | type: ActivityPubActorType | ||
167 | |||
168 | @AllowNull(false) | ||
169 | @Is('ActorPreferredUsername', value => throwIfNotValid(value, isActorPreferredUsernameValid, 'actor preferred username')) | ||
170 | @Column | ||
171 | preferredUsername: string | ||
172 | |||
173 | @AllowNull(false) | ||
174 | @Is('ActorUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) | ||
175 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | ||
176 | url: string | ||
177 | |||
178 | @AllowNull(true) | ||
179 | @Is('ActorPublicKey', value => throwIfNotValid(value, isActorPublicKeyValid, 'public key', true)) | ||
180 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY.max)) | ||
181 | publicKey: string | ||
182 | |||
183 | @AllowNull(true) | ||
184 | @Is('ActorPublicKey', value => throwIfNotValid(value, isActorPrivateKeyValid, 'private key', true)) | ||
185 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.PRIVATE_KEY.max)) | ||
186 | privateKey: string | ||
187 | |||
188 | @AllowNull(false) | ||
189 | @Is('ActorFollowersCount', value => throwIfNotValid(value, isActorFollowersCountValid, 'followers count')) | ||
190 | @Column | ||
191 | followersCount: number | ||
192 | |||
193 | @AllowNull(false) | ||
194 | @Is('ActorFollowersCount', value => throwIfNotValid(value, isActorFollowingCountValid, 'following count')) | ||
195 | @Column | ||
196 | followingCount: number | ||
197 | |||
198 | @AllowNull(false) | ||
199 | @Is('ActorInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'inbox url')) | ||
200 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | ||
201 | inboxUrl: string | ||
202 | |||
203 | @AllowNull(true) | ||
204 | @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url', true)) | ||
205 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | ||
206 | outboxUrl: string | ||
207 | |||
208 | @AllowNull(true) | ||
209 | @Is('ActorSharedInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'shared inbox url', true)) | ||
210 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | ||
211 | sharedInboxUrl: string | ||
212 | |||
213 | @AllowNull(true) | ||
214 | @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url', true)) | ||
215 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | ||
216 | followersUrl: string | ||
217 | |||
218 | @AllowNull(true) | ||
219 | @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url', true)) | ||
220 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | ||
221 | followingUrl: string | ||
222 | |||
223 | @AllowNull(true) | ||
224 | @Column | ||
225 | remoteCreatedAt: Date | ||
226 | |||
227 | @CreatedAt | ||
228 | createdAt: Date | ||
229 | |||
230 | @UpdatedAt | ||
231 | updatedAt: Date | ||
232 | |||
233 | @ForeignKey(() => ActorImageModel) | ||
234 | @Column | ||
235 | avatarId: number | ||
236 | |||
237 | @ForeignKey(() => ActorImageModel) | ||
238 | @Column | ||
239 | bannerId: number | ||
240 | |||
241 | @BelongsTo(() => ActorImageModel, { | ||
242 | foreignKey: { | ||
243 | name: 'avatarId', | ||
244 | allowNull: true | ||
245 | }, | ||
246 | as: 'Avatar', | ||
247 | onDelete: 'set null', | ||
248 | hooks: true | ||
249 | }) | ||
250 | Avatar: ActorImageModel | ||
251 | |||
252 | @BelongsTo(() => ActorImageModel, { | ||
253 | foreignKey: { | ||
254 | name: 'bannerId', | ||
255 | allowNull: true | ||
256 | }, | ||
257 | as: 'Banner', | ||
258 | onDelete: 'set null', | ||
259 | hooks: true | ||
260 | }) | ||
261 | Banner: ActorImageModel | ||
262 | |||
263 | @HasMany(() => ActorFollowModel, { | ||
264 | foreignKey: { | ||
265 | name: 'actorId', | ||
266 | allowNull: false | ||
267 | }, | ||
268 | as: 'ActorFollowings', | ||
269 | onDelete: 'cascade' | ||
270 | }) | ||
271 | ActorFollowing: ActorFollowModel[] | ||
272 | |||
273 | @HasMany(() => ActorFollowModel, { | ||
274 | foreignKey: { | ||
275 | name: 'targetActorId', | ||
276 | allowNull: false | ||
277 | }, | ||
278 | as: 'ActorFollowers', | ||
279 | onDelete: 'cascade' | ||
280 | }) | ||
281 | ActorFollowers: ActorFollowModel[] | ||
282 | |||
283 | @ForeignKey(() => ServerModel) | ||
284 | @Column | ||
285 | serverId: number | ||
286 | |||
287 | @BelongsTo(() => ServerModel, { | ||
288 | foreignKey: { | ||
289 | allowNull: true | ||
290 | }, | ||
291 | onDelete: 'cascade' | ||
292 | }) | ||
293 | Server: ServerModel | ||
294 | |||
295 | @HasOne(() => AccountModel, { | ||
296 | foreignKey: { | ||
297 | allowNull: true | ||
298 | }, | ||
299 | onDelete: 'cascade', | ||
300 | hooks: true | ||
301 | }) | ||
302 | Account: AccountModel | ||
303 | |||
304 | @HasOne(() => VideoChannelModel, { | ||
305 | foreignKey: { | ||
306 | allowNull: true | ||
307 | }, | ||
308 | onDelete: 'cascade', | ||
309 | hooks: true | ||
310 | }) | ||
311 | VideoChannel: VideoChannelModel | ||
312 | |||
313 | static load (id: number): Promise<MActor> { | ||
314 | return ActorModel.unscoped().findByPk(id) | ||
315 | } | ||
316 | |||
317 | static loadFull (id: number): Promise<MActorFull> { | ||
318 | return ActorModel.scope(ScopeNames.FULL).findByPk(id) | ||
319 | } | ||
320 | |||
321 | static loadFromAccountByVideoId (videoId: number, transaction: Transaction): Promise<MActor> { | ||
322 | const query = { | ||
323 | include: [ | ||
324 | { | ||
325 | attributes: [ 'id' ], | ||
326 | model: AccountModel.unscoped(), | ||
327 | required: true, | ||
328 | include: [ | ||
329 | { | ||
330 | attributes: [ 'id' ], | ||
331 | model: VideoChannelModel.unscoped(), | ||
332 | required: true, | ||
333 | include: [ | ||
334 | { | ||
335 | attributes: [ 'id' ], | ||
336 | model: VideoModel.unscoped(), | ||
337 | required: true, | ||
338 | where: { | ||
339 | id: videoId | ||
340 | } | ||
341 | } | ||
342 | ] | ||
343 | } | ||
344 | ] | ||
345 | } | ||
346 | ], | ||
347 | transaction | ||
348 | } | ||
349 | |||
350 | return ActorModel.unscoped().findOne(query) | ||
351 | } | ||
352 | |||
353 | static isActorUrlExist (url: string) { | ||
354 | const query = { | ||
355 | raw: true, | ||
356 | where: { | ||
357 | url | ||
358 | } | ||
359 | } | ||
360 | |||
361 | return ActorModel.unscoped().findOne(query) | ||
362 | .then(a => !!a) | ||
363 | } | ||
364 | |||
365 | static listByFollowersUrls (followersUrls: string[], transaction?: Transaction): Promise<MActorFull[]> { | ||
366 | const query = { | ||
367 | where: { | ||
368 | followersUrl: { | ||
369 | [Op.in]: followersUrls | ||
370 | } | ||
371 | }, | ||
372 | transaction | ||
373 | } | ||
374 | |||
375 | return ActorModel.scope(ScopeNames.FULL).findAll(query) | ||
376 | } | ||
377 | |||
378 | static loadLocalByName (preferredUsername: string, transaction?: Transaction): Promise<MActorFull> { | ||
379 | const fun = () => { | ||
380 | const query = { | ||
381 | where: { | ||
382 | preferredUsername, | ||
383 | serverId: null | ||
384 | }, | ||
385 | transaction | ||
386 | } | ||
387 | |||
388 | return ActorModel.scope(ScopeNames.FULL) | ||
389 | .findOne(query) | ||
390 | } | ||
391 | |||
392 | return ModelCache.Instance.doCache({ | ||
393 | cacheType: 'local-actor-name', | ||
394 | key: preferredUsername, | ||
395 | // The server actor never change, so we can easily cache it | ||
396 | whitelist: () => preferredUsername === SERVER_ACTOR_NAME, | ||
397 | fun | ||
398 | }) | ||
399 | } | ||
400 | |||
401 | static loadLocalUrlByName (preferredUsername: string, transaction?: Transaction): Promise<MActorUrl> { | ||
402 | const fun = () => { | ||
403 | const query = { | ||
404 | attributes: [ 'url' ], | ||
405 | where: { | ||
406 | preferredUsername, | ||
407 | serverId: null | ||
408 | }, | ||
409 | transaction | ||
410 | } | ||
411 | |||
412 | return ActorModel.unscoped() | ||
413 | .findOne(query) | ||
414 | } | ||
415 | |||
416 | return ModelCache.Instance.doCache({ | ||
417 | cacheType: 'local-actor-name', | ||
418 | key: preferredUsername, | ||
419 | // The server actor never change, so we can easily cache it | ||
420 | whitelist: () => preferredUsername === SERVER_ACTOR_NAME, | ||
421 | fun | ||
422 | }) | ||
423 | } | ||
424 | |||
425 | static loadByNameAndHost (preferredUsername: string, host: string): Promise<MActorFull> { | ||
426 | const query = { | ||
427 | where: { | ||
428 | preferredUsername | ||
429 | }, | ||
430 | include: [ | ||
431 | { | ||
432 | model: ServerModel, | ||
433 | required: true, | ||
434 | where: { | ||
435 | host | ||
436 | } | ||
437 | } | ||
438 | ] | ||
439 | } | ||
440 | |||
441 | return ActorModel.scope(ScopeNames.FULL).findOne(query) | ||
442 | } | ||
443 | |||
444 | static loadByUrl (url: string, transaction?: Transaction): Promise<MActorAccountChannelId> { | ||
445 | const query = { | ||
446 | where: { | ||
447 | url | ||
448 | }, | ||
449 | transaction, | ||
450 | include: [ | ||
451 | { | ||
452 | attributes: [ 'id' ], | ||
453 | model: AccountModel.unscoped(), | ||
454 | required: false | ||
455 | }, | ||
456 | { | ||
457 | attributes: [ 'id' ], | ||
458 | model: VideoChannelModel.unscoped(), | ||
459 | required: false | ||
460 | } | ||
461 | ] | ||
462 | } | ||
463 | |||
464 | return ActorModel.unscoped().findOne(query) | ||
465 | } | ||
466 | |||
467 | static loadByUrlAndPopulateAccountAndChannel (url: string, transaction?: Transaction): Promise<MActorFull> { | ||
468 | const query = { | ||
469 | where: { | ||
470 | url | ||
471 | }, | ||
472 | transaction | ||
473 | } | ||
474 | |||
475 | return ActorModel.scope(ScopeNames.FULL).findOne(query) | ||
476 | } | ||
477 | |||
478 | static rebuildFollowsCount (ofId: number, type: 'followers' | 'following', transaction?: Transaction) { | ||
479 | const sanitizedOfId = parseInt(ofId + '', 10) | ||
480 | const where = { id: sanitizedOfId } | ||
481 | |||
482 | let columnToUpdate: string | ||
483 | let columnOfCount: string | ||
484 | |||
485 | if (type === 'followers') { | ||
486 | columnToUpdate = 'followersCount' | ||
487 | columnOfCount = 'targetActorId' | ||
488 | } else { | ||
489 | columnToUpdate = 'followingCount' | ||
490 | columnOfCount = 'actorId' | ||
491 | } | ||
492 | |||
493 | return ActorModel.update({ | ||
494 | [columnToUpdate]: literal(`(SELECT COUNT(*) FROM "actorFollow" WHERE "${columnOfCount}" = ${sanitizedOfId})`) | ||
495 | }, { where, transaction }) | ||
496 | } | ||
497 | |||
498 | static loadAccountActorByVideoId (videoId: number): Promise<MActor> { | ||
499 | const query = { | ||
500 | include: [ | ||
501 | { | ||
502 | attributes: [ 'id' ], | ||
503 | model: AccountModel.unscoped(), | ||
504 | required: true, | ||
505 | include: [ | ||
506 | { | ||
507 | attributes: [ 'id', 'accountId' ], | ||
508 | model: VideoChannelModel.unscoped(), | ||
509 | required: true, | ||
510 | include: [ | ||
511 | { | ||
512 | attributes: [ 'id', 'channelId' ], | ||
513 | model: VideoModel.unscoped(), | ||
514 | where: { | ||
515 | id: videoId | ||
516 | } | ||
517 | } | ||
518 | ] | ||
519 | } | ||
520 | ] | ||
521 | } | ||
522 | ] | ||
523 | } | ||
524 | |||
525 | return ActorModel.unscoped().findOne(query) | ||
526 | } | ||
527 | |||
528 | getSharedInbox (this: MActorWithInboxes) { | ||
529 | return this.sharedInboxUrl || this.inboxUrl | ||
530 | } | ||
531 | |||
532 | toFormattedSummaryJSON (this: MActorSummaryFormattable) { | ||
533 | let avatar: ActorImage = null | ||
534 | if (this.Avatar) { | ||
535 | avatar = this.Avatar.toFormattedJSON() | ||
536 | } | ||
537 | |||
538 | return { | ||
539 | url: this.url, | ||
540 | name: this.preferredUsername, | ||
541 | host: this.getHost(), | ||
542 | avatar | ||
543 | } | ||
544 | } | ||
545 | |||
546 | toFormattedJSON (this: MActorFormattable) { | ||
547 | const base = this.toFormattedSummaryJSON() | ||
548 | |||
549 | let banner: ActorImage = null | ||
550 | if (this.Banner) { | ||
551 | banner = this.Banner.toFormattedJSON() | ||
552 | } | ||
553 | |||
554 | return Object.assign(base, { | ||
555 | id: this.id, | ||
556 | hostRedundancyAllowed: this.getRedundancyAllowed(), | ||
557 | followingCount: this.followingCount, | ||
558 | followersCount: this.followersCount, | ||
559 | banner, | ||
560 | createdAt: this.getCreatedAt() | ||
561 | }) | ||
562 | } | ||
563 | |||
564 | toActivityPubObject (this: MActorAPChannel | MActorAPAccount, name: string) { | ||
565 | let icon: ActivityIconObject | ||
566 | let image: ActivityIconObject | ||
567 | |||
568 | if (this.avatarId) { | ||
569 | const extension = extname(this.Avatar.filename) | ||
570 | |||
571 | icon = { | ||
572 | type: 'Image', | ||
573 | mediaType: MIMETYPES.IMAGE.EXT_MIMETYPE[extension], | ||
574 | height: this.Avatar.height, | ||
575 | width: this.Avatar.width, | ||
576 | url: this.getAvatarUrl() | ||
577 | } | ||
578 | } | ||
579 | |||
580 | if (this.bannerId) { | ||
581 | const banner = (this as MActorAPChannel).Banner | ||
582 | const extension = extname(banner.filename) | ||
583 | |||
584 | image = { | ||
585 | type: 'Image', | ||
586 | mediaType: MIMETYPES.IMAGE.EXT_MIMETYPE[extension], | ||
587 | height: banner.height, | ||
588 | width: banner.width, | ||
589 | url: this.getBannerUrl() | ||
590 | } | ||
591 | } | ||
592 | |||
593 | const json = { | ||
594 | type: this.type, | ||
595 | id: this.url, | ||
596 | following: this.getFollowingUrl(), | ||
597 | followers: this.getFollowersUrl(), | ||
598 | playlists: this.getPlaylistsUrl(), | ||
599 | inbox: this.inboxUrl, | ||
600 | outbox: this.outboxUrl, | ||
601 | preferredUsername: this.preferredUsername, | ||
602 | url: this.url, | ||
603 | name, | ||
604 | endpoints: { | ||
605 | sharedInbox: this.sharedInboxUrl | ||
606 | }, | ||
607 | publicKey: { | ||
608 | id: this.getPublicKeyUrl(), | ||
609 | owner: this.url, | ||
610 | publicKeyPem: this.publicKey | ||
611 | }, | ||
612 | published: this.getCreatedAt().toISOString(), | ||
613 | icon, | ||
614 | image | ||
615 | } | ||
616 | |||
617 | return activityPubContextify(json) | ||
618 | } | ||
619 | |||
620 | getFollowerSharedInboxUrls (t: Transaction) { | ||
621 | const query = { | ||
622 | attributes: [ 'sharedInboxUrl' ], | ||
623 | include: [ | ||
624 | { | ||
625 | attribute: [], | ||
626 | model: ActorFollowModel.unscoped(), | ||
627 | required: true, | ||
628 | as: 'ActorFollowing', | ||
629 | where: { | ||
630 | state: 'accepted', | ||
631 | targetActorId: this.id | ||
632 | } | ||
633 | } | ||
634 | ], | ||
635 | transaction: t | ||
636 | } | ||
637 | |||
638 | return ActorModel.findAll(query) | ||
639 | .then(accounts => accounts.map(a => a.sharedInboxUrl)) | ||
640 | } | ||
641 | |||
642 | getFollowingUrl () { | ||
643 | return this.url + '/following' | ||
644 | } | ||
645 | |||
646 | getFollowersUrl () { | ||
647 | return this.url + '/followers' | ||
648 | } | ||
649 | |||
650 | getPlaylistsUrl () { | ||
651 | return this.url + '/playlists' | ||
652 | } | ||
653 | |||
654 | getPublicKeyUrl () { | ||
655 | return this.url + '#main-key' | ||
656 | } | ||
657 | |||
658 | isOwned () { | ||
659 | return this.serverId === null | ||
660 | } | ||
661 | |||
662 | getWebfingerUrl (this: MActorServer) { | ||
663 | return 'acct:' + this.preferredUsername + '@' + this.getHost() | ||
664 | } | ||
665 | |||
666 | getIdentifier () { | ||
667 | return this.Server ? `${this.preferredUsername}@${this.Server.host}` : this.preferredUsername | ||
668 | } | ||
669 | |||
670 | getHost (this: MActorHost) { | ||
671 | return this.Server ? this.Server.host : WEBSERVER.HOST | ||
672 | } | ||
673 | |||
674 | getRedundancyAllowed () { | ||
675 | return this.Server ? this.Server.redundancyAllowed : false | ||
676 | } | ||
677 | |||
678 | getAvatarUrl () { | ||
679 | if (!this.avatarId) return undefined | ||
680 | |||
681 | return WEBSERVER.URL + this.Avatar.getStaticPath() | ||
682 | } | ||
683 | |||
684 | getBannerUrl () { | ||
685 | if (!this.bannerId) return undefined | ||
686 | |||
687 | return WEBSERVER.URL + this.Banner.getStaticPath() | ||
688 | } | ||
689 | |||
690 | isOutdated () { | ||
691 | if (this.isOwned()) return false | ||
692 | |||
693 | return isOutdated(this, ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL) | ||
694 | } | ||
695 | |||
696 | getCreatedAt (this: MActorAPChannel | MActorAPAccount | MActorFormattable) { | ||
697 | return this.remoteCreatedAt || this.createdAt | ||
698 | } | ||
699 | } | ||