Allow ignoring moderation reports by subject ()

*  Allow ignoring moderation reports by subject

* 🔧 Remove unwanted package.json change

*  Remove subject format specifier

*  Port over ignore subject implementation to bsky package

* Revert " Port over ignore subject implementation to bsky package"

This reverts commit 3c782c1805548d1ebdbbd08b68b6e92ac97fdee5.

* 🚧 Port over ignore subject implementation to bsky package

*  Generate lexicons for bsky
This commit is contained in:
Foysal Ahamed 2023-05-29 06:39:03 +02:00 committed by GitHub
parent 237bb97e20
commit 3414fcedbf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 460 additions and 148 deletions
lexicons/com/atproto/admin
packages
api/src/client
lexicons.ts
types/com/atproto/admin
bsky/src
pds
src
api/com/atproto/admin
lexicon
lexicons.ts
types/com/atproto/admin
services/moderation
tests/views/admin

@ -9,6 +9,7 @@
"type": "params",
"properties": {
"subject": {"type": "string"},
"ignoreSubjects": {"type": "array", "items": {"type": "string"}},
"resolved": {"type": "boolean"},
"actionType": {
"type": "string",

@ -856,6 +856,12 @@ export const schemaDict = {
subject: {
type: 'string',
},
ignoreSubjects: {
type: 'array',
items: {
type: 'string',
},
},
resolved: {
type: 'boolean',
},

@ -10,6 +10,7 @@ import * as ComAtprotoAdminDefs from './defs'
export interface QueryParams {
subject?: string
ignoreSubjects?: string[]
resolved?: boolean
actionType?:
| 'com.atproto.admin.defs#takedown'

@ -7,7 +7,14 @@ export default function (server: Server, ctx: AppContext) {
auth: adminVerifier(ctx.cfg.adminPassword),
handler: async ({ params }) => {
const { db, services } = ctx
const { subject, resolved, actionType, limit = 50, cursor } = params
const {
subject,
resolved,
actionType,
limit = 50,
cursor,
ignoreSubjects,
} = params
const moderationService = services.moderation(db)
const results = await moderationService.getReports({
subject,
@ -15,6 +22,7 @@ export default function (server: Server, ctx: AppContext) {
actionType,
limit,
cursor,
ignoreSubjects,
})
return {
encoding: 'application/json',

@ -74,18 +74,18 @@ import * as AppBskyActorGetSuggestions from './types/app/bsky/actor/getSuggestio
import * as AppBskyActorPutPreferences from './types/app/bsky/actor/putPreferences'
import * as AppBskyActorSearchActors from './types/app/bsky/actor/searchActors'
import * as AppBskyActorSearchActorsTypeahead from './types/app/bsky/actor/searchActorsTypeahead'
import * as AppBskyFeedDescribeFeedGenerator from './types/app/bsky/feed/describeFeedGenerator'
import * as AppBskyFeedGetActorFeeds from './types/app/bsky/feed/getActorFeeds'
import * as AppBskyFeedGetAuthorFeed from './types/app/bsky/feed/getAuthorFeed'
import * as AppBskyFeedGetFeed from './types/app/bsky/feed/getFeed'
import * as AppBskyFeedGetFeedGenerator from './types/app/bsky/feed/getFeedGenerator'
import * as AppBskyFeedGetFeedGenerators from './types/app/bsky/feed/getFeedGenerators'
import * as AppBskyFeedGetFeedSkeleton from './types/app/bsky/feed/getFeedSkeleton'
import * as AppBskyFeedGetLikes from './types/app/bsky/feed/getLikes'
import * as AppBskyFeedGetPostThread from './types/app/bsky/feed/getPostThread'
import * as AppBskyFeedGetPosts from './types/app/bsky/feed/getPosts'
import * as AppBskyFeedGetRepostedBy from './types/app/bsky/feed/getRepostedBy'
import * as AppBskyFeedGetSavedFeeds from './types/app/bsky/feed/getSavedFeeds'
import * as AppBskyFeedGetTimeline from './types/app/bsky/feed/getTimeline'
import * as AppBskyFeedSaveFeed from './types/app/bsky/feed/saveFeed'
import * as AppBskyFeedUnsaveFeed from './types/app/bsky/feed/unsaveFeed'
import * as AppBskyGraphGetBlocks from './types/app/bsky/graph/getBlocks'
import * as AppBskyGraphGetFollowers from './types/app/bsky/graph/getFollowers'
import * as AppBskyGraphGetFollows from './types/app/bsky/graph/getFollows'
@ -101,6 +101,7 @@ import * as AppBskyNotificationGetUnreadCount from './types/app/bsky/notificatio
import * as AppBskyNotificationListNotifications from './types/app/bsky/notification/listNotifications'
import * as AppBskyNotificationUpdateSeen from './types/app/bsky/notification/updateSeen'
import * as AppBskyUnspeccedGetPopular from './types/app/bsky/unspecced/getPopular'
import * as AppBskyUnspeccedGetPopularFeedGenerators from './types/app/bsky/unspecced/getPopularFeedGenerators'
export const COM_ATPROTO_ADMIN = {
DefsTakedown: 'com.atproto.admin.defs#takedown',
@ -800,6 +801,16 @@ export class FeedNS {
this._server = server
}
describeFeedGenerator<AV extends AuthVerifier>(
cfg: ConfigOf<
AV,
AppBskyFeedDescribeFeedGenerator.Handler<ExtractAuth<AV>>
>,
) {
const nsid = 'app.bsky.feed.describeFeedGenerator' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
getActorFeeds<AV extends AuthVerifier>(
cfg: ConfigOf<AV, AppBskyFeedGetActorFeeds.Handler<ExtractAuth<AV>>>,
) {
@ -821,6 +832,20 @@ export class FeedNS {
return this._server.xrpc.method(nsid, cfg)
}
getFeedGenerator<AV extends AuthVerifier>(
cfg: ConfigOf<AV, AppBskyFeedGetFeedGenerator.Handler<ExtractAuth<AV>>>,
) {
const nsid = 'app.bsky.feed.getFeedGenerator' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
getFeedGenerators<AV extends AuthVerifier>(
cfg: ConfigOf<AV, AppBskyFeedGetFeedGenerators.Handler<ExtractAuth<AV>>>,
) {
const nsid = 'app.bsky.feed.getFeedGenerators' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
getFeedSkeleton<AV extends AuthVerifier>(
cfg: ConfigOf<AV, AppBskyFeedGetFeedSkeleton.Handler<ExtractAuth<AV>>>,
) {
@ -856,33 +881,12 @@ export class FeedNS {
return this._server.xrpc.method(nsid, cfg)
}
getSavedFeeds<AV extends AuthVerifier>(
cfg: ConfigOf<AV, AppBskyFeedGetSavedFeeds.Handler<ExtractAuth<AV>>>,
) {
const nsid = 'app.bsky.feed.getSavedFeeds' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
getTimeline<AV extends AuthVerifier>(
cfg: ConfigOf<AV, AppBskyFeedGetTimeline.Handler<ExtractAuth<AV>>>,
) {
const nsid = 'app.bsky.feed.getTimeline' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
saveFeed<AV extends AuthVerifier>(
cfg: ConfigOf<AV, AppBskyFeedSaveFeed.Handler<ExtractAuth<AV>>>,
) {
const nsid = 'app.bsky.feed.saveFeed' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
unsaveFeed<AV extends AuthVerifier>(
cfg: ConfigOf<AV, AppBskyFeedUnsaveFeed.Handler<ExtractAuth<AV>>>,
) {
const nsid = 'app.bsky.feed.unsaveFeed' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
}
export class GraphNS {
@ -1026,6 +1030,16 @@ export class UnspeccedNS {
const nsid = 'app.bsky.unspecced.getPopular' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
getPopularFeedGenerators<AV extends AuthVerifier>(
cfg: ConfigOf<
AV,
AppBskyUnspeccedGetPopularFeedGenerators.Handler<ExtractAuth<AV>>
>,
) {
const nsid = 'app.bsky.unspecced.getPopularFeedGenerators' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
}
type ConfigOf<Auth, Handler> =

@ -856,6 +856,12 @@ export const schemaDict = {
subject: {
type: 'string',
},
ignoreSubjects: {
type: 'array',
items: {
type: 'string',
},
},
resolved: {
type: 'boolean',
},
@ -1228,12 +1234,12 @@ export const schemaDict = {
description: 'Provides the DID of a repo.',
parameters: {
type: 'params',
required: ['handle'],
properties: {
handle: {
type: 'string',
format: 'handle',
description:
"The handle to resolve. If not supplied, will resolve the host's own handle.",
description: 'The handle to resolve.',
},
},
},
@ -1619,6 +1625,7 @@ export const schemaDict = {
},
rkey: {
type: 'string',
maxLength: 15,
},
value: {
type: 'unknown',
@ -1684,6 +1691,7 @@ export const schemaDict = {
rkey: {
type: 'string',
description: 'The key of the record.',
maxLength: 15,
},
validate: {
type: 'boolean',
@ -2005,6 +2013,7 @@ export const schemaDict = {
rkey: {
type: 'string',
description: 'The key of the record.',
maxLength: 15,
},
validate: {
type: 'boolean',
@ -3569,6 +3578,7 @@ export const schemaDict = {
refs: [
'lex:app.bsky.actor.defs#adultContentPref',
'lex:app.bsky.actor.defs#contentLabelPref',
'lex:app.bsky.actor.defs#savedFeedsPref',
],
},
},
@ -3595,6 +3605,26 @@ export const schemaDict = {
},
},
},
savedFeedsPref: {
type: 'object',
required: ['pinned', 'saved'],
properties: {
pinned: {
type: 'array',
items: {
type: 'string',
format: 'at-uri',
},
},
saved: {
type: 'array',
items: {
type: 'string',
format: 'at-uri',
},
},
},
},
},
},
AppBskyActorGetPreferences: {
@ -4329,12 +4359,16 @@ export const schemaDict = {
},
generatorView: {
type: 'object',
required: ['uri', 'creator', 'indexedAt'],
required: ['uri', 'cid', 'creator', 'displayName', 'indexedAt'],
properties: {
uri: {
type: 'string',
format: 'at-uri',
},
cid: {
type: 'string',
format: 'cid',
},
did: {
type: 'string',
format: 'did',
@ -4361,6 +4395,10 @@ export const schemaDict = {
avatar: {
type: 'string',
},
likeCount: {
type: 'integer',
minimum: 0,
},
viewer: {
type: 'ref',
ref: 'lex:app.bsky.feed.defs#generatorViewerState',
@ -4374,9 +4412,6 @@ export const schemaDict = {
generatorViewerState: {
type: 'object',
properties: {
saved: {
type: 'boolean',
},
like: {
type: 'string',
format: 'at-uri',
@ -4409,6 +4444,62 @@ export const schemaDict = {
},
},
},
AppBskyFeedDescribeFeedGenerator: {
lexicon: 1,
id: 'app.bsky.feed.describeFeedGenerator',
defs: {
main: {
type: 'query',
description:
'Returns information about a given feed generator including TOS & offered feed URIs',
output: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['did', 'feeds'],
properties: {
did: {
type: 'string',
format: 'did',
},
feeds: {
type: 'array',
items: {
type: 'ref',
ref: 'lex:app.bsky.feed.describeFeedGenerator#feed',
},
},
links: {
type: 'ref',
ref: 'lex:app.bsky.feed.describeFeedGenerator#links',
},
},
},
},
},
feed: {
type: 'object',
required: ['uri'],
properties: {
uri: {
type: 'string',
format: 'at-uri',
},
},
},
links: {
type: 'object',
properties: {
privacyPolicy: {
type: 'string',
},
termsOfService: {
type: 'string',
},
},
},
},
},
AppBskyFeedGenerator: {
lexicon: 1,
id: 'app.bsky.feed.generator',
@ -4419,7 +4510,7 @@ export const schemaDict = {
key: 'any',
record: {
type: 'object',
required: ['did', 'createdAt'],
required: ['did', 'displayName', 'createdAt'],
properties: {
did: {
type: 'string',
@ -4427,8 +4518,8 @@ export const schemaDict = {
},
displayName: {
type: 'string',
maxGraphemes: 64,
maxLength: 640,
maxGraphemes: 24,
maxLength: 240,
},
description: {
type: 'string',
@ -4614,6 +4705,85 @@ export const schemaDict = {
},
},
},
AppBskyFeedGetFeedGenerator: {
lexicon: 1,
id: 'app.bsky.feed.getFeedGenerator',
defs: {
main: {
type: 'query',
description:
'Get information about a specific feed offered by a feed generator, such as its online status',
parameters: {
type: 'params',
required: ['feed'],
properties: {
feed: {
type: 'string',
format: 'at-uri',
},
},
},
output: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['view', 'isOnline', 'isValid'],
properties: {
view: {
type: 'ref',
ref: 'lex:app.bsky.feed.defs#generatorView',
},
isOnline: {
type: 'boolean',
},
isValid: {
type: 'boolean',
},
},
},
},
},
},
},
AppBskyFeedGetFeedGenerators: {
lexicon: 1,
id: 'app.bsky.feed.getFeedGenerators',
defs: {
main: {
type: 'query',
description: 'Get information about a list of feed generators',
parameters: {
type: 'params',
required: ['feeds'],
properties: {
feeds: {
type: 'array',
items: {
type: 'string',
format: 'at-uri',
},
},
},
},
output: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['feeds'],
properties: {
feeds: {
type: 'array',
items: {
type: 'ref',
ref: 'lex:app.bsky.feed.defs#generatorView',
},
},
},
},
},
},
},
},
AppBskyFeedGetFeedSkeleton: {
lexicon: 1,
id: 'app.bsky.feed.getFeedSkeleton',
@ -4896,49 +5066,6 @@ export const schemaDict = {
},
},
},
AppBskyFeedGetSavedFeeds: {
lexicon: 1,
id: 'app.bsky.feed.getSavedFeeds',
defs: {
main: {
type: 'query',
description: "Retrieve a list of the authenticated user's saved feeds",
parameters: {
type: 'params',
properties: {
limit: {
type: 'integer',
minimum: 1,
maximum: 100,
default: 50,
},
cursor: {
type: 'string',
},
},
},
output: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['feeds'],
properties: {
cursor: {
type: 'string',
},
feeds: {
type: 'array',
items: {
type: 'ref',
ref: 'lex:app.bsky.feed.defs#generatorView',
},
},
},
},
},
},
},
},
AppBskyFeedGetTimeline: {
lexicon: 1,
id: 'app.bsky.feed.getTimeline',
@ -5134,52 +5261,6 @@ export const schemaDict = {
},
},
},
AppBskyFeedSaveFeed: {
lexicon: 1,
id: 'app.bsky.feed.saveFeed',
defs: {
main: {
type: 'procedure',
description: 'Save a 3rd party feed for use across clients',
input: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['feed'],
properties: {
feed: {
type: 'string',
format: 'at-uri',
},
},
},
},
},
},
},
AppBskyFeedUnsaveFeed: {
lexicon: 1,
id: 'app.bsky.feed.unsaveFeed',
defs: {
main: {
type: 'procedure',
description: 'Unsave a 3rd party feed',
input: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['feed'],
properties: {
feed: {
type: 'string',
format: 'at-uri',
},
},
},
},
},
},
},
AppBskyGraphBlock: {
lexicon: 1,
id: 'app.bsky.graph.block',
@ -6110,6 +6191,32 @@ export const schemaDict = {
},
},
},
AppBskyUnspeccedGetPopularFeedGenerators: {
lexicon: 1,
id: 'app.bsky.unspecced.getPopularFeedGenerators',
defs: {
main: {
type: 'query',
description: 'An unspecced view of globally popular feed generators',
output: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['feeds'],
properties: {
feeds: {
type: 'array',
items: {
type: 'ref',
ref: 'lex:app.bsky.feed.defs#generatorView',
},
},
},
},
},
},
},
},
}
export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[]
export const lexicons: Lexicons = new Lexicons(schemas)
@ -6197,22 +6304,22 @@ export const ids = {
AppBskyEmbedRecord: 'app.bsky.embed.record',
AppBskyEmbedRecordWithMedia: 'app.bsky.embed.recordWithMedia',
AppBskyFeedDefs: 'app.bsky.feed.defs',
AppBskyFeedDescribeFeedGenerator: 'app.bsky.feed.describeFeedGenerator',
AppBskyFeedGenerator: 'app.bsky.feed.generator',
AppBskyFeedGetActorFeeds: 'app.bsky.feed.getActorFeeds',
AppBskyFeedGetAuthorFeed: 'app.bsky.feed.getAuthorFeed',
AppBskyFeedGetFeed: 'app.bsky.feed.getFeed',
AppBskyFeedGetFeedGenerator: 'app.bsky.feed.getFeedGenerator',
AppBskyFeedGetFeedGenerators: 'app.bsky.feed.getFeedGenerators',
AppBskyFeedGetFeedSkeleton: 'app.bsky.feed.getFeedSkeleton',
AppBskyFeedGetLikes: 'app.bsky.feed.getLikes',
AppBskyFeedGetPostThread: 'app.bsky.feed.getPostThread',
AppBskyFeedGetPosts: 'app.bsky.feed.getPosts',
AppBskyFeedGetRepostedBy: 'app.bsky.feed.getRepostedBy',
AppBskyFeedGetSavedFeeds: 'app.bsky.feed.getSavedFeeds',
AppBskyFeedGetTimeline: 'app.bsky.feed.getTimeline',
AppBskyFeedLike: 'app.bsky.feed.like',
AppBskyFeedPost: 'app.bsky.feed.post',
AppBskyFeedRepost: 'app.bsky.feed.repost',
AppBskyFeedSaveFeed: 'app.bsky.feed.saveFeed',
AppBskyFeedUnsaveFeed: 'app.bsky.feed.unsaveFeed',
AppBskyGraphBlock: 'app.bsky.graph.block',
AppBskyGraphDefs: 'app.bsky.graph.defs',
AppBskyGraphFollow: 'app.bsky.graph.follow',
@ -6235,4 +6342,6 @@ export const ids = {
AppBskyNotificationUpdateSeen: 'app.bsky.notification.updateSeen',
AppBskyRichtextFacet: 'app.bsky.richtext.facet',
AppBskyUnspeccedGetPopular: 'app.bsky.unspecced.getPopular',
AppBskyUnspeccedGetPopularFeedGenerators:
'app.bsky.unspecced.getPopularFeedGenerators',
}

@ -107,6 +107,7 @@ export function validateViewerState(v: unknown): ValidationResult {
export type Preferences = (
| AdultContentPref
| ContentLabelPref
| SavedFeedsPref
| { $type: string; [k: string]: unknown }
)[]
@ -144,3 +145,21 @@ export function isContentLabelPref(v: unknown): v is ContentLabelPref {
export function validateContentLabelPref(v: unknown): ValidationResult {
return lexicons.validate('app.bsky.actor.defs#contentLabelPref', v)
}
export interface SavedFeedsPref {
pinned: string[]
saved: string[]
[k: string]: unknown
}
export function isSavedFeedsPref(v: unknown): v is SavedFeedsPref {
return (
isObj(v) &&
hasProp(v, '$type') &&
v.$type === 'app.bsky.actor.defs#savedFeedsPref'
)
}
export function validateSavedFeedsPref(v: unknown): ValidationResult {
return lexicons.validate('app.bsky.actor.defs#savedFeedsPref', v)
}

@ -188,12 +188,14 @@ export function validateBlockedPost(v: unknown): ValidationResult {
export interface GeneratorView {
uri: string
cid: string
did?: string
creator: AppBskyActorDefs.ProfileView
displayName?: string
displayName: string
description?: string
descriptionFacets?: AppBskyRichtextFacet.Main[]
avatar?: string
likeCount?: number
viewer?: GeneratorViewerState
indexedAt: string
[k: string]: unknown
@ -212,7 +214,6 @@ export function validateGeneratorView(v: unknown): ValidationResult {
}
export interface GeneratorViewerState {
saved?: boolean
like?: string
[k: string]: unknown
}

@ -0,0 +1,76 @@
/**
* GENERATED CODE - DO NOT MODIFY
*/
import express from 'express'
import { ValidationResult, BlobRef } from '@atproto/lexicon'
import { lexicons } from '../../../../lexicons'
import { isObj, hasProp } from '../../../../util'
import { CID } from 'multiformats/cid'
import { HandlerAuth } from '@atproto/xrpc-server'
export interface QueryParams {}
export type InputSchema = undefined
export interface OutputSchema {
did: string
feeds: Feed[]
links?: Links
[k: string]: unknown
}
export type HandlerInput = undefined
export interface HandlerSuccess {
encoding: 'application/json'
body: OutputSchema
}
export interface HandlerError {
status: number
message?: string
}
export type HandlerOutput = HandlerError | HandlerSuccess
export type Handler<HA extends HandlerAuth = never> = (ctx: {
auth: HA
params: QueryParams
input: HandlerInput
req: express.Request
res: express.Response
}) => Promise<HandlerOutput> | HandlerOutput
export interface Feed {
uri: string
[k: string]: unknown
}
export function isFeed(v: unknown): v is Feed {
return (
isObj(v) &&
hasProp(v, '$type') &&
v.$type === 'app.bsky.feed.describeFeedGenerator#feed'
)
}
export function validateFeed(v: unknown): ValidationResult {
return lexicons.validate('app.bsky.feed.describeFeedGenerator#feed', v)
}
export interface Links {
privacyPolicy?: string
termsOfService?: string
[k: string]: unknown
}
export function isLinks(v: unknown): v is Links {
return (
isObj(v) &&
hasProp(v, '$type') &&
v.$type === 'app.bsky.feed.describeFeedGenerator#links'
)
}
export function validateLinks(v: unknown): ValidationResult {
return lexicons.validate('app.bsky.feed.describeFeedGenerator#links', v)
}

@ -9,7 +9,7 @@ import * as AppBskyRichtextFacet from '../richtext/facet'
export interface Record {
did: string
displayName?: string
displayName: string
description?: string
descriptionFacets?: AppBskyRichtextFacet.Main[]
avatar?: BlobRef

@ -7,17 +7,26 @@ import { lexicons } from '../../../../lexicons'
import { isObj, hasProp } from '../../../../util'
import { CID } from 'multiformats/cid'
import { HandlerAuth } from '@atproto/xrpc-server'
import * as AppBskyFeedDefs from './defs'
export interface QueryParams {}
export interface InputSchema {
export interface QueryParams {
feed: string
}
export type InputSchema = undefined
export interface OutputSchema {
view: AppBskyFeedDefs.GeneratorView
isOnline: boolean
isValid: boolean
[k: string]: unknown
}
export interface HandlerInput {
export type HandlerInput = undefined
export interface HandlerSuccess {
encoding: 'application/json'
body: InputSchema
body: OutputSchema
}
export interface HandlerError {
@ -25,7 +34,7 @@ export interface HandlerError {
message?: string
}
export type HandlerOutput = HandlerError | void
export type HandlerOutput = HandlerError | HandlerSuccess
export type Handler<HA extends HandlerAuth = never> = (ctx: {
auth: HA
params: QueryParams

@ -10,14 +10,12 @@ import { HandlerAuth } from '@atproto/xrpc-server'
import * as AppBskyFeedDefs from './defs'
export interface QueryParams {
limit: number
cursor?: string
feeds: string[]
}
export type InputSchema = undefined
export interface OutputSchema {
cursor?: string
feeds: AppBskyFeedDefs.GeneratorView[]
[k: string]: unknown
}

@ -7,17 +7,22 @@ import { lexicons } from '../../../../lexicons'
import { isObj, hasProp } from '../../../../util'
import { CID } from 'multiformats/cid'
import { HandlerAuth } from '@atproto/xrpc-server'
import * as AppBskyFeedDefs from '../feed/defs'
export interface QueryParams {}
export interface InputSchema {
feed: string
export type InputSchema = undefined
export interface OutputSchema {
feeds: AppBskyFeedDefs.GeneratorView[]
[k: string]: unknown
}
export interface HandlerInput {
export type HandlerInput = undefined
export interface HandlerSuccess {
encoding: 'application/json'
body: InputSchema
body: OutputSchema
}
export interface HandlerError {
@ -25,7 +30,7 @@ export interface HandlerError {
message?: string
}
export type HandlerOutput = HandlerError | void
export type HandlerOutput = HandlerError | HandlerSuccess
export type Handler<HA extends HandlerAuth = never> = (ctx: {
auth: HA
params: QueryParams

@ -11,6 +11,7 @@ import * as ComAtprotoAdminDefs from './defs'
export interface QueryParams {
subject?: string
ignoreSubjects?: string[]
resolved?: boolean
actionType?:
| 'com.atproto.admin.defs#takedown'

@ -9,8 +9,8 @@ import { CID } from 'multiformats/cid'
import { HandlerAuth } from '@atproto/xrpc-server'
export interface QueryParams {
/** The handle to resolve. If not supplied, will resolve the host's own handle. */
handle?: string
/** The handle to resolve. */
handle: string
}
export type InputSchema = undefined

@ -81,8 +81,10 @@ export class ModerationService {
actionType?: string
limit: number
cursor?: string
ignoreSubjects?: string[]
}): Promise<ModerationReportRow[]> {
const { subject, resolved, actionType, limit, cursor } = opts
const { subject, resolved, actionType, limit, cursor, ignoreSubjects } =
opts
const { ref } = this.db.db.dynamic
let builder = this.db.db.selectFrom('moderation_report')
if (subject) {
@ -92,6 +94,14 @@ export class ModerationService {
.orWhere('subjectUri', '=', subject)
})
}
if (ignoreSubjects?.length) {
builder = builder.where((qb) => {
return qb
.where('subjectDid', 'not in', ignoreSubjects)
.where('subjectUri', 'not in', ignoreSubjects)
})
}
if (resolved !== undefined) {
const resolutionsQuery = this.db.db
.selectFrom('moderation_report_resolution')

@ -6,7 +6,14 @@ export default function (server: Server, ctx: AppContext) {
auth: ctx.moderatorVerifier,
handler: async ({ params }) => {
const { db, services } = ctx
const { subject, resolved, actionType, limit = 50, cursor } = params
const {
subject,
resolved,
actionType,
limit = 50,
cursor,
ignoreSubjects = [],
} = params
const moderationService = services.moderation(db)
const results = await moderationService.getReports({
subject,
@ -14,6 +21,7 @@ export default function (server: Server, ctx: AppContext) {
actionType,
limit,
cursor,
ignoreSubjects,
})
return {
encoding: 'application/json',

@ -856,6 +856,12 @@ export const schemaDict = {
subject: {
type: 'string',
},
ignoreSubjects: {
type: 'array',
items: {
type: 'string',
},
},
resolved: {
type: 'boolean',
},

@ -11,6 +11,7 @@ import * as ComAtprotoAdminDefs from './defs'
export interface QueryParams {
subject?: string
ignoreSubjects?: string[]
resolved?: boolean
actionType?:
| 'com.atproto.admin.defs#takedown'

@ -99,8 +99,10 @@ export class ModerationService {
actionType?: string
limit: number
cursor?: string
ignoreSubjects?: string[]
}): Promise<ModerationReportRow[]> {
const { subject, resolved, actionType, limit, cursor } = opts
const { subject, resolved, actionType, limit, cursor, ignoreSubjects } =
opts
const { ref } = this.db.db.dynamic
let builder = this.db.db.selectFrom('moderation_report')
if (subject) {
@ -110,6 +112,15 @@ export class ModerationService {
.orWhere('subjectUri', '=', subject)
})
}
if (ignoreSubjects?.length) {
builder = builder.where((qb) => {
return qb
.where('subjectDid', 'not in', ignoreSubjects)
.where('subjectUri', 'not in', ignoreSubjects)
})
}
if (resolved !== undefined) {
const resolutionsQuery = this.db.db
.selectFrom('moderation_report_resolution')
@ -141,6 +152,7 @@ export class ModerationService {
.selectAll()
builder = builder.whereExists(resolutionActionsQuery)
}
if (cursor) {
const cursorNumeric = parseInt(cursor, 10)
if (isNaN(cursorNumeric)) {

@ -120,6 +120,33 @@ describe('pds admin get moderation reports view', () => {
}
})
it('ignores subjects when specified.', async () => {
// Get all reports and then make another request with a filter to ignore some subject dids
// and assert that the reports for those subject dids are ignored in the result set
const getDids = (reportsResponse) =>
reportsResponse.data.reports
.map((report) => report.subject.did)
// Not all reports contain a did so we're discarding the undefined values in the mapped array
.filter(Boolean)
const allReports = await agent.api.com.atproto.admin.getModerationReports(
{},
{ headers: { authorization: adminAuth() } },
)
const ignoreSubjects = getDids(allReports).slice(0, 2)
const filteredReports =
await agent.api.com.atproto.admin.getModerationReports(
{ ignoreSubjects },
{ headers: { authorization: adminAuth() } },
)
getDids(filteredReports).forEach((resultDid) =>
expect(ignoreSubjects).not.toContain(resultDid),
)
})
it('gets all moderation reports.', async () => {
const result = await agent.api.com.atproto.admin.getModerationReports(
{},