summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorHazelnoot <acomputerdog@gmail.com>2025-05-26 11:16:48 -0400
committerHazelnoot <acomputerdog@gmail.com>2025-05-26 11:16:48 -0400
commit5f0bb5dcd7ced04035e409f7768c6bfccb950683 (patch)
treeff3c4e41aeb92ef4c0dc0450fe34f128dc2e1985 /packages
parentsupport fetching anonymous AP objects (diff)
downloadsharkey-5f0bb5dcd7ced04035e409f7768c6bfccb950683.tar.gz
sharkey-5f0bb5dcd7ced04035e409f7768c6bfccb950683.tar.bz2
sharkey-5f0bb5dcd7ced04035e409f7768c6bfccb950683.zip
implement resolver.resolveCollectionItems
Diffstat (limited to 'packages')
-rw-r--r--packages/backend/src/core/activitypub/ApResolverService.ts64
-rw-r--r--packages/backend/src/core/activitypub/type.ts46
-rw-r--r--packages/misskey-js/src/autogen/types.ts2
3 files changed, 86 insertions, 26 deletions
diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts
index a7c5125928..74050f456b 100644
--- a/packages/backend/src/core/activitypub/ApResolverService.ts
+++ b/packages/backend/src/core/activitypub/ApResolverService.ts
@@ -19,11 +19,12 @@ import { ApLogService, calculateDurationSince, extractObjectContext } from '@/co
import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js';
import { SystemAccountService } from '@/core/SystemAccountService.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
-import { getApId, getNullableApId, IObjectWithId, isCollectionOrOrderedCollection } from './type.js';
+import { toArray } from '@/misc/prelude/array.js';
+import { AnyCollection, getApId, getNullableApId, IObjectWithId, isCollection, isCollectionOrOrderedCollection, isCollectionPage, isOrderedCollection, isOrderedCollectionPage } from './type.js';
import { ApDbResolverService } from './ApDbResolverService.js';
import { ApRendererService } from './ApRendererService.js';
import { ApRequestService } from './ApRequestService.js';
-import type { IObject, ICollection, IOrderedCollection, ApObject } from './type.js';
+import type { IObject, ApObject } from './type.js';
export class Resolver {
private history: Set<string>;
@@ -78,6 +79,65 @@ export class Resolver {
}
}
+ @bindThis
+ public async resolveCollectionItems(value: string | IObject, limit?: number, allowAnonymousItems?: boolean): Promise<IObjectWithId[]> {
+ const items: IObjectWithId[] = [];
+
+ const collection = await this.resolveCollection(value);
+ await this.resolveCollectionItemsTo(collection, limit, allowAnonymousItems, collection.id, items);
+
+ return items;
+ }
+
+ private async resolveCollectionItemsTo(current: AnyCollection | null, limit: number | undefined, allowAnonymousItems: boolean | undefined, sourceUri: string | undefined, destination: IObjectWithId[]): Promise<void> {
+ // This is pulled up to avoid code duplication below
+ const iterate = async(items: ApObject): Promise<void> => {
+ for (const item of toArray(items)) {
+ // Stop when we reach the fetch limit
+ if (this.history.size > this.recursionLimit) break;
+
+ // Stop when we reach the item limit
+ if (limit != null && limit < 1) break;
+
+ // Use secureResolve whenever possible, to avoid re-fetching items that were included inline.
+ const resolved = (sourceUri && !allowAnonymousItems)
+ ? await this.secureResolve(item, sourceUri)
+ : await this.resolve(getApId(item), allowAnonymousItems);
+ destination.push(resolved);
+
+ // Decrement the outer variable directly, because the code below checks it too
+ if (limit != null) limit--;
+ }
+ };
+
+ while (current != null) {
+ // Iterate all items in the current page
+ if (current.items) {
+ await iterate(current.items);
+ }
+ if (current.orderedItems) {
+ await iterate(current.orderedItems);
+ }
+
+ if (this.history.size >= this.recursionLimit) {
+ // Stop when we reach the fetch limit
+ current = null;
+ } else if (limit != null && limit < 1) {
+ // Stop when we reach the item limit
+ current = null;
+ } else if (isCollection(current) || isOrderedCollection(current)) {
+ // Continue to first page
+ current = current.first ? await this.resolveCollection(current.first, true) : null;
+ } else if (isCollectionPage(current) || isOrderedCollectionPage(current)) {
+ // Continue to next page
+ current = current.next ? await this.resolveCollection(current.next, true) : null;
+ } else {
+ // Stop in all other conditions
+ current = null;
+ }
+ }
+ }
+
/**
* Securely resolves an AP object or URL that has been sent from another instance.
* An input object is trusted if and only if its ID matches the authority of sentFromUri.
diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts
index 281733d484..e33dec18d7 100644
--- a/packages/backend/src/core/activitypub/type.ts
+++ b/packages/backend/src/core/activitypub/type.ts
@@ -125,48 +125,46 @@ export interface IActivity extends IObject {
};
}
-export interface ICollection extends IObject {
- type: 'Collection';
- totalItems: number;
+export interface CollectionBase extends IObject {
+ totalItems?: number;
first?: IObject | string;
last?: IObject | string;
current?: IObject | string;
+ partOf?: IObject | string;
+ next?: IObject | string;
+ prev?: IObject | string;
items?: ApObject;
+ orderedItems?: ApObject;
}
-export interface IOrderedCollection extends IObject {
+export interface ICollection extends CollectionBase {
+ type: 'Collection';
+ totalItems: number;
+ items?: ApObject;
+ orderedItems?: undefined;
+}
+
+export interface IOrderedCollection extends CollectionBase {
type: 'OrderedCollection';
totalItems: number;
- first?: IObject | string;
- last?: IObject | string;
- current?: IObject | string;
+ items?: undefined;
orderedItems?: ApObject;
}
-export interface ICollectionPage extends IObject {
+export interface ICollectionPage extends CollectionBase {
type: 'CollectionPage';
- totalItems: number;
- first?: IObject | string;
- last?: IObject | string;
- current?: IObject | string;
- partOf?: IObject | string;
- next?: IObject | string;
- prev?: IObject | string;
items?: ApObject;
+ orderedItems?: undefined;
}
-export interface IOrderedCollectionPage extends IObject {
+export interface IOrderedCollectionPage extends CollectionBase {
type: 'OrderedCollectionPage';
- totalItems: number;
- first?: IObject | string;
- last?: IObject | string;
- current?: IObject | string;
- partOf?: IObject | string;
- next?: IObject | string;
- prev?: IObject | string;
+ items?: undefined;
orderedItems?: ApObject;
}
+export type AnyCollection = ICollection | IOrderedCollection | ICollectionPage | IOrderedCollectionPage;
+
export const validPost = ['Note', 'Question', 'Article', 'Audio', 'Document', 'Image', 'Page', 'Video', 'Event'];
export const isPost = (object: IObject): object is IPost => {
@@ -269,7 +267,7 @@ export const isCollectionPage = (object: IObject): object is ICollectionPage =>
export const isOrderedCollectionPage = (object: IObject): object is IOrderedCollectionPage =>
getApType(object) === 'OrderedCollectionPage';
-export const isCollectionOrOrderedCollection = (object: IObject): object is ICollection | IOrderedCollection =>
+export const isCollectionOrOrderedCollection = (object: IObject): object is AnyCollection =>
isCollection(object) || isOrderedCollection(object) || isCollectionPage(object) || isOrderedCollectionPage(object);
export interface IApPropertyValue extends IObject {
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 55302960dc..678e980892 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -12918,6 +12918,8 @@ export type operations = {
content: {
'application/json': {
uri: string;
+ expandCollectionItems?: boolean;
+ allowAnonymous?: boolean;
};
};
};