+async function fetchActorTotalItems (url: string) {
+ const options = {
+ uri: url,
+ method: 'GET',
+ json: true,
+ activityPub: true
+ }
+
+ try {
+ const { body } = await doRequest(options)
+ return body.totalItems ? body.totalItems : 0
+ } catch (err) {
+ logger.warn('Cannot fetch remote actor count %s.', url, { err })
+ return 0
+ }
+}
+
+async function fetchAvatarIfExists (actorJSON: ActivityPubActor) {
+ if (
+ actorJSON.icon && actorJSON.icon.type === 'Image' && MIMETYPES.IMAGE.MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined &&
+ isActivityPubUrlValid(actorJSON.icon.url)
+ ) {
+ const extension = MIMETYPES.IMAGE.MIMETYPE_EXT[actorJSON.icon.mediaType]
+
+ const avatarName = uuidv4() + extension
+ await downloadImage(actorJSON.icon.url, CONFIG.STORAGE.AVATARS_DIR, avatarName, AVATARS_SIZE)
+
+ return avatarName
+ }
+
+ return undefined
+}
+
+async function addFetchOutboxJob (actor: ActorModel) {
+ // Don't fetch ourselves
+ const serverActor = await getServerActor()
+ if (serverActor.id === actor.id) {
+ logger.error('Cannot fetch our own outbox!')
+ return undefined
+ }
+
+ const payload = {
+ uri: actor.outboxUrl,
+ type: 'activity' as 'activity'
+ }
+
+ return JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })
+}
+
+async function refreshActorIfNeeded (
+ actorArg: ActorModel,
+ fetchedType: ActorFetchByUrlType
+): Promise<{ actor: ActorModel, refreshed: boolean }> {
+ if (!actorArg.isOutdated()) return { actor: actorArg, refreshed: false }
+
+ // We need more attributes
+ const actor = fetchedType === 'all' ? actorArg : await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorArg.url)
+
+ try {
+ let actorUrl: string
+ try {
+ actorUrl = await getUrlFromWebfinger(actor.preferredUsername + '@' + actor.getHost())
+ } catch (err) {
+ logger.warn('Cannot get actor URL from webfinger, keeping the old one.', err)
+ actorUrl = actor.url
+ }
+
+ const { result, statusCode } = await fetchRemoteActor(actorUrl)
+
+ if (statusCode === 404) {
+ logger.info('Deleting actor %s because there is a 404 in refresh actor.', actor.url)
+ actor.Account ? actor.Account.destroy() : actor.VideoChannel.destroy()
+ return { actor: undefined, refreshed: false }
+ }
+
+ if (result === undefined) {
+ logger.warn('Cannot fetch remote actor in refresh actor.')
+ return { actor, refreshed: false }
+ }
+
+ return sequelizeTypescript.transaction(async t => {
+ updateInstanceWithAnother(actor, result.actor)
+
+ if (result.avatarName !== undefined) {
+ await updateActorAvatarInstance(actor, result.avatarName, t)
+ }
+
+ // Force update
+ actor.setDataValue('updatedAt', new Date())
+ await actor.save({ transaction: t })
+
+ if (actor.Account) {
+ actor.Account.set('name', result.name)
+ actor.Account.set('description', result.summary)
+
+ await actor.Account.save({ transaction: t })
+ } else if (actor.VideoChannel) {
+ actor.VideoChannel.set('name', result.name)
+ actor.VideoChannel.set('description', result.summary)
+ actor.VideoChannel.set('support', result.support)
+
+ await actor.VideoChannel.save({ transaction: t })
+ }
+
+ return { refreshed: true, actor }
+ })
+ } catch (err) {
+ logger.warn('Cannot refresh actor.', { err })
+ return { actor, refreshed: false }
+ }
+}
+
+export {
+ getOrCreateActorAndServerAndModel,
+ buildActorInstance,
+ setAsyncActorKeys,
+ fetchActorTotalItems,
+ fetchAvatarIfExists,
+ updateActorInstance,
+ refreshActorIfNeeded,
+ updateActorAvatarInstance,
+ addFetchOutboxJob
+}
+
+// ---------------------------------------------------------------------------
+