Add pl-api to workspace

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-08-28 13:43:23 +02:00
parent 966b04fdf0
commit 036fa32cd3
114 changed files with 11923 additions and 1 deletions

View file

@ -7,6 +7,6 @@
"husky": "^9.0.0",
"lint-staged": ">=10"
},
"workspaces": ["pl-fe"],
"workspaces": ["pl-api", "pl-fe"],
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View file

@ -0,0 +1,7 @@
/node_modules/**
/dist/**
/static/**
/public/**
/tmp/**
/coverage/**
/custom/**

View file

@ -0,0 +1,214 @@
{
"root": true,
"extends": [
"eslint:recommended",
"plugin:import/typescript",
"plugin:compat/recommended"
],
"env": {
"browser": true,
"node": true,
"es6": true,
"jest": true
},
"globals": {
"ATTACHMENT_HOST": false
},
"plugins": [
"import",
"promise",
"@typescript-eslint"
],
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"experimentalObjectRestSpread": true
},
"ecmaVersion": 2018
},
"settings": {
"import/extensions": [
".js",
".cjs",
".mjs",
".ts"
],
"import/ignore": [
"node_modules",
"\\.(css|scss|json)$"
],
"import/resolver": {
"typescript": true,
"node": true
},
"polyfills": [
"es:all",
"fetch",
"IntersectionObserver",
"Promise",
"ResizeObserver",
"URL",
"URLSearchParams"
],
"tailwindcss": {
"config": "tailwind.config.ts"
}
},
"rules": {
"brace-style": "error",
"comma-dangle": [
"error",
"always-multiline"
],
"comma-spacing": [
"warn",
{
"before": false,
"after": true
}
],
"comma-style": [
"warn",
"last"
],
"import/no-duplicates": "error",
"space-before-function-paren": [
"error",
"never"
],
"space-infix-ops": "error",
"space-in-parens": [
"error",
"never"
],
"keyword-spacing": "error",
"dot-notation": "error",
"eqeqeq": "error",
"indent": [
"error",
2,
{
"SwitchCase": 1,
"ignoredNodes": [
"TemplateLiteral"
]
}
],
"key-spacing": [
"error",
{
"mode": "minimum"
}
],
"no-catch-shadow": "error",
"no-cond-assign": "error",
"no-console": [
"warn",
{
"allow": [
"error",
"warn"
]
}
],
"no-extra-semi": "error",
"no-const-assign": "error",
"no-fallthrough": "error",
"no-irregular-whitespace": "error",
"no-loop-func": "error",
"no-mixed-spaces-and-tabs": "error",
"no-nested-ternary": "warn",
"no-trailing-spaces": "warn",
"no-undef": "error",
"no-unreachable": "error",
"no-unused-expressions": "error",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
"vars": "all",
"args": "none",
"ignoreRestSiblings": true
}
],
"no-useless-escape": "warn",
"no-var": "error",
"object-curly-spacing": [
"error",
"always"
],
"padded-blocks": [
"error",
{
"classes": "always"
}
],
"prefer-const": "error",
"quotes": [
"error",
"single"
],
"semi": "error",
"space-unary-ops": [
"error",
{
"words": true,
"nonwords": false
}
],
"strict": "off",
"valid-typeof": "error",
"import/extensions": [
"error",
"always",
{
"js": "never",
"mjs": "ignorePackages",
"ts": "never"
}
],
"import/newline-after-import": "error",
"import/no-extraneous-dependencies": "error",
"import/no-unresolved": "error",
"import/no-webpack-loader-syntax": "error",
"import/order": [
"error",
{
"groups": [
"builtin",
"external",
"internal",
"parent",
"sibling",
"index",
"object",
"type"
],
"newlines-between": "always",
"alphabetize": {
"order": "asc"
}
}
],
"@typescript-eslint/member-delimiter-style": "error",
"promise/catch-or-return": "error",
"sort-imports": [
"error",
{
"ignoreCase": true,
"ignoreDeclarationSort": true
}
],
"eol-last": "error"
},
"overrides": [
{
"files": ["**/*.ts"],
"rules": {
"no-undef": "off",
"space-before-function-paren": "off"
},
"parser": "@typescript-eslint/parser"
}
]
}

24
packages/pl-api/.gitignore vendored Normal file
View file

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

24
packages/pl-api/README.md Normal file
View file

@ -0,0 +1,24 @@
# `pl-api`
This project should be considered unstable before the 1.0.0 release. I will not provide any changelog or information on breaking changes until then.
## Projects using `pl-api`
[`pl-fe`](https://github.com/mkljczk/pl-fe) is a web client for Mastodon-compatible servers forked from Soapbox. It uses `pl-api` for API interactions.
## License
`pl-api` utilizes code from [Soapbox](https://gitlab.com/soapbox-pub/soapbox) and bases off official [Mastodon documentation](https://docs.joinmastodon.org).
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.

View file

@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>pl-api</title>
</head>
<body>
<script type="module" src="/lib/main.ts"></script>
<script type="module">
import PlApiClient from './lib/client.ts';
window.client = PlApiClient;
</script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,27 @@
import { z } from 'zod';
import { Resolve } from '../utils/types';
import { accountSchema } from './account';
import { dateSchema } from './utils';
/** @see {@link https://docs.joinmastodon.org/entities/Appeal/} */
const appealSchema = z.object({
text: z.string(),
state: z.enum(['approved', 'rejected', 'pending']),
});
/** @see {@link https://docs.joinmastodon.org/entities/AccountWarning/} */
const accountWarningSchema = z.object({
id: z.string(),
action: z.enum(['none', 'disable', 'mark_statuses_as_sensitive', 'delete_statuses', 'sensitive', 'silence', 'suspend']),
text: z.string().catch(''),
status_ids: z.array(z.string()).catch([]),
target_account: accountSchema,
appeal: appealSchema.nullable().catch(null),
created_at: dateSchema,
});
type AccountWarning = Resolve<z.infer<typeof accountWarningSchema>>;
export { accountWarningSchema, type AccountWarning };

View file

@ -0,0 +1,177 @@
import pick from 'lodash.pick';
import z from 'zod';
import { customEmojiSchema } from './custom-emoji';
import { relationshipSchema } from './relationship';
import { roleSchema } from './role';
import { coerceObject, dateSchema, filteredArray } from './utils';
const filterBadges = (tags?: string[]) =>
tags?.filter(tag => tag.startsWith('badge:')).map(tag => roleSchema.parse({ id: tag, name: tag.replace(/^badge:/, '') }));
const preprocessAccount = (account: any) => {
if (!account?.acct) return null;
return {
username: account.username || account.acct.split('@')[0],
display_name: account.display_name.trim() || account.username,
roles: account.roles?.length ? account.roles : filterBadges(account.pleroma?.tags),
avatar_static: account.avatar_static || account.avatar,
header_static: account.header_static || account.header,
source: account.source
? { ...(pick(account.pleroma?.source || {}, [
'show_role', 'no_rich_text', 'discoverable', 'actor_type', 'show_birthday',
])), ...account.source }
: undefined,
local: typeof account.pleroma?.is_local === 'boolean' ? account.pleroma.is_local : account.acct.split('@')[1] === undefined,
discoverable: account.discoverable || account.pleroma?.source?.discoverable,
verified: account.verified || account.pleroma?.tags?.includes('verified'),
...(pick(account.pleroma || {}, [
'ap_id',
'background_image',
'relationship',
'is_moderator',
'is_admin',
'hide_favorites',
'hide_followers',
'hide_follows',
'hide_followers_count',
'hide_follows_count',
'accepts_chat_messages',
'favicon',
'birthday',
'deactivated',
'settings_store',
'chat_token',
'allow_following_move',
'unread_conversation_count',
'unread_notifications_count',
'notification_settings',
'location',
])),
...(pick(account.other_settings || {}), ['birthday', 'location']),
__meta: pick(account, ['pleroma', 'source']),
...account,
};
};
const fieldSchema = z.object({
name: z.string(),
value: z.string(),
verified_at: z.string().datetime({ offset: true }).nullable().catch(null),
});
const baseAccountSchema = z.object({
id: z.string(),
username: z.string().catch(''),
acct: z.string().catch(''),
url: z.string().url(),
display_name: z.string().catch(''),
note: z.string().catch(''),
avatar: z.string().catch(''),
avatar_static: z.string().url().catch(''),
header: z.string().url().catch(''),
header_static: z.string().url().catch(''),
locked: z.boolean().catch(false),
fields: filteredArray(fieldSchema),
emojis: filteredArray(customEmojiSchema),
bot: z.boolean().catch(false),
group: z.boolean().catch(false),
discoverable: z.boolean().catch(false),
noindex: z.boolean().nullable().catch(null),
suspended: z.boolean().optional().catch(undefined),
limited: z.boolean().optional().catch(undefined),
created_at: z.string().datetime().catch(new Date().toUTCString()),
last_status_at: z.string().date().nullable().catch(null),
statuses_count: z.number().catch(0),
followers_count: z.number().catch(0),
following_count: z.number().catch(0),
roles: filteredArray(roleSchema),
fqn: z.string().nullable().catch(null),
ap_id: z.string().nullable().catch(null),
background_image: z.string().nullable().catch(null),
relationship: relationshipSchema.optional().catch(undefined),
is_moderator: z.boolean().optional().catch(undefined),
is_admin: z.boolean().optional().catch(undefined),
is_suggested: z.boolean().optional().catch(undefined),
hide_favorites: z.boolean().catch(true),
hide_followers: z.boolean().optional().catch(undefined),
hide_follows: z.boolean().optional().catch(undefined),
hide_followers_count: z.boolean().optional().catch(undefined),
hide_follows_count: z.boolean().optional().catch(undefined),
accepts_chat_messages: z.boolean().nullable().catch(null),
favicon: z.string().optional().catch(undefined),
birthday: z.string().date().optional().catch(undefined),
deactivated: z.boolean().optional().catch(undefined),
location: z.string().optional().catch(undefined),
local: z.boolean().optional().catch(false),
avatar_description: z.string().catch(''),
enable_rss: z.boolean().catch(false),
header_description: z.string().catch(''),
verified: z.boolean().optional().catch(undefined),
__meta: coerceObject({
pleroma: z.any().optional().catch(undefined),
source: z.any().optional().catch(undefined),
}),
});
const accountWithMovedAccountSchema = baseAccountSchema.extend({
moved: baseAccountSchema.optional().catch(undefined),
});
/** @see {@link https://docs.joinmastodon.org/entities/Account/} */
const accountSchema = z.preprocess(preprocessAccount, accountWithMovedAccountSchema);
type Account = z.infer<typeof accountSchema>;
const credentialAccountSchema = z.preprocess(preprocessAccount, accountWithMovedAccountSchema.extend({
source: z.object({
note: z.string().catch(''),
fields: filteredArray(fieldSchema),
privacy: z.enum(['public', 'unlisted', 'private', 'direct']),
sensitive: z.boolean().catch(false),
language: z.string().nullable().catch(null),
follow_requests_count: z.number().int().nonnegative().catch(0),
show_role: z.boolean().optional().nullable().catch(undefined),
no_rich_text: z.boolean().optional().nullable().catch(undefined),
discoverable: z.boolean().optional().catch(undefined),
actor_type: z.string().optional().catch(undefined),
show_birthday: z.boolean().optional().catch(undefined),
}).nullable().catch(null),
role: roleSchema.nullable().catch(null),
settings_store: z.record(z.any()).optional().catch(undefined),
chat_token: z.string().optional().catch(undefined),
allow_following_move: z.boolean().optional().catch(undefined),
unread_conversation_count: z.number().optional().catch(undefined),
unread_notifications_count: z.number().optional().catch(undefined),
notification_settings: z.object({
block_from_strangers: z.boolean().catch(false),
hide_notification_contents: z.boolean().catch(false),
}).optional().catch(undefined),
}));
type CredentialAccount = z.infer<typeof credentialAccountSchema>;
const mutedAccountSchema = z.preprocess(preprocessAccount, accountWithMovedAccountSchema.extend({
mute_expires_at: dateSchema.nullable().catch(null),
}));
type MutedAccount = z.infer<typeof mutedAccountSchema>;
export {
accountSchema,
credentialAccountSchema,
mutedAccountSchema,
type Account,
type CredentialAccount,
type MutedAccount,
};

View file

@ -0,0 +1,69 @@
import { z } from 'zod';
import { accountSchema } from '../account';
import { roleSchema } from '../role';
import { dateSchema, filteredArray } from '../utils';
import { adminIpSchema } from './ip';
/** @see {@link https://docs.joinmastodon.org/entities/Admin_Account/} */
const adminAccountSchema = z.preprocess((account: any) => {
if (!account.account) {
/**
* Convert Pleroma account schema
* @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminusers}
*/
return {
id: account.id,
account: null,
username: account.nickname,
domain: account.nickname.split('@')[1] || null,
created_at: account.created_at,
email: account.email,
invite_request: account.registration_reason,
role: account.roles?.is_admin
? roleSchema.parse({ name: 'Admin' })
: account.roles?.moderator
? roleSchema.parse({ name: 'Moderator ' }) :
null,
confirmed: account.is_confirmed,
approved: account.is_approved,
disabled: !account.is_active,
actor_type: account.actor_type,
display_name: account.display_name,
suggested: account.is_suggested,
};
}
return account;
}, z.object({
id: z.string(),
username: z.string(),
domain: z.string().nullable().catch(null),
created_at: dateSchema,
email: z.string().nullable().catch(null),
ip: z.string().ip().nullable().catch(null),
ips: filteredArray(adminIpSchema),
locale: z.string().nullable().catch(null),
invite_request: z.string().nullable().catch(null),
role: roleSchema.nullable().catch(null),
confirmed: z.boolean().catch(false),
approved: z.boolean().catch(false),
disabled: z.boolean().catch(false),
silenced: z.boolean().catch(false),
suspended: z.boolean().catch(false),
account: accountSchema.nullable().catch(null),
created_by_application_id: z.string().optional().catch(undefined),
invited_by_account_id: z.string().optional().catch(undefined),
actor_type: z.string().nullable().catch(null),
display_name: z.string().nullable().catch(null),
suggested: z.boolean().nullable().catch(null),
}));
type AdminAccount = z.infer<typeof adminAccountSchema>;
export {
adminAccountSchema,
type AdminAccount,
};

View file

@ -0,0 +1,17 @@
import pick from 'lodash.pick';
import { z } from 'zod';
import { Resolve } from '../../utils/types';
import { announcementSchema } from '../announcement';
/** @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminannouncements} */
const adminAnnouncementSchema = z.preprocess((announcement: any) => ({
...announcement,
...pick(announcement.pleroma, 'raw_content'),
}), announcementSchema.extend({
raw_content: z.string().catch(''),
}));
type AdminAnnouncement = Resolve<z.infer<typeof adminAnnouncementSchema>>;
export { adminAnnouncementSchema, type AdminAnnouncement };

View file

@ -0,0 +1,14 @@
import { z } from 'zod';
/** @see {@link https://docs.joinmastodon.org/entities/Admin_CanonicalEmailBlock/} */
const adminCanonicalEmailBlockSchema = z.object({
id: z.string(),
canonical_email_hash: z.string(),
});
type AdminCanonicalEmailBlock = z.infer<typeof adminCanonicalEmailBlockSchema>;
export {
adminCanonicalEmailBlockSchema,
type AdminCanonicalEmailBlock,
};

View file

@ -0,0 +1,19 @@
import { z } from 'zod';
/** @see {@link https://docs.joinmastodon.org/entities/Admin_Cohort/} */
const adminCohortSchema = z.object({
period: z.string().datetime({ offset: true }),
frequency: z.enum(['day', 'month']),
data: z.array(z.object({
date: z.string().datetime({ offset: true }),
rate: z.number(),
value: z.number().int(),
})),
});
type AdminCohort = z.infer<typeof adminCohortSchema>;
export {
adminCohortSchema,
type AdminCohort,
};

View file

@ -0,0 +1,20 @@
import { z } from 'zod';
/** @see {@link https://docs.joinmastodon.org/entities/Admin_Dimension/} */
const adminDimensionSchema = z.object({
key: z.string(),
data: z.object({
key: z.string(),
human_key: z.string(),
value: z.string(),
unit: z.string().optional().catch(undefined),
human_value: z.string().optional().catch(undefined),
}),
});
type AdminDimension = z.infer<typeof adminDimensionSchema>;
export {
adminDimensionSchema,
type AdminDimension,
};

View file

@ -0,0 +1,17 @@
import { z } from 'zod';
import { dateSchema } from '../utils';
/** @see {@link https://docs.joinmastodon.org/entities/Admin_DomainAllow/} */
const adminDomainAllowSchema = z.object({
id: z.string(),
domain: z.string(),
created_at: dateSchema,
});
type AdminDomainAllow = z.infer<typeof adminDomainAllowSchema>;
export {
adminDomainAllowSchema,
type AdminDomainAllow,
};

View file

@ -0,0 +1,24 @@
import { z } from 'zod';
import { dateSchema } from '../utils';
/** @see {@link https://docs.joinmastodon.org/entities/Admin_DomainBlock/} */
const adminDomainBlockSchema = z.object({
id: z.string(),
domain: z.string(),
digest: z.string(),
created_at: dateSchema,
severity: z.enum(['silence', 'suspend', 'noop']),
reject_media: z.boolean(),
reject_reports: z.boolean(),
private_comment: z.string().nullable().catch(null),
public_comment: z.string().nullable().catch(null),
obfuscate: z.boolean(),
});
type AdminDomainBlock = z.infer<typeof adminDomainBlockSchema>;
export {
adminDomainBlockSchema,
type AdminDomainBlock,
};

View file

@ -0,0 +1,13 @@
import z from 'zod';
const adminDomainSchema = z.object({
domain: z.string().catch(''),
id: z.coerce.string(),
public: z.boolean().catch(false),
resolves: z.boolean().catch(false),
last_checked_at: z.string().datetime().catch(''),
});
type AdminDomain = z.infer<typeof adminDomainSchema>
export { adminDomainSchema, type AdminDomain };

View file

@ -0,0 +1,22 @@
import { z } from 'zod';
import { dateSchema } from '../utils';
/** @see {@link https://docs.joinmastodon.org/entities/Admin_EmailDomainBlock/} */
const adminEmailDomainBlockSchema = z.object({
id: z.string(),
domain: z.string(),
created_at: dateSchema,
history: z.array(z.object({
day: z.coerce.string(),
accounts: z.coerce.string(),
uses: z.coerce.string(),
})),
});
type AdminEmailDomainBlock = z.infer<typeof adminEmailDomainBlockSchema>;
export {
adminEmailDomainBlockSchema,
type AdminEmailDomainBlock,
};

View file

@ -0,0 +1,20 @@
import { z } from 'zod';
import { dateSchema } from '../utils';
/** @see {@link https://docs.joinmastodon.org/entities/Admin_IpBlock/} */
const adminIpBlockSchema = z.object({
id: z.string(),
ip: z.string().ip(),
severity: z.enum(['sign_up_requires_approval', 'sign_up_block', 'no_access']),
comment: z.string().catch(''),
created_at: dateSchema,
expires_at: z.string().datetime({ offset: true }),
});
type AdminIpBlock = z.infer<typeof adminIpBlockSchema>;
export {
adminIpBlockSchema,
type AdminIpBlock,
};

View file

@ -0,0 +1,16 @@
import { z } from 'zod';
import { dateSchema } from '../utils';
/** @see {@link https://docs.joinmastodon.org/entities/Admin_Ip/} */
const adminIpSchema = z.object({
ip: z.string().ip(),
used_at: dateSchema,
});
type AdminIp = z.infer<typeof adminIpSchema>;
export {
adminIpSchema,
type AdminIp,
};

View file

@ -0,0 +1,21 @@
import { z } from 'zod';
/** @see {@link https://docs.joinmastodon.org/entities/Admin_Measure/} */
const adminMeasureSchema = z.object({
key: z.string(),
unit: z.string().nullable().catch(null),
total: z.coerce.number(),
human_value: z.string().optional().catch(undefined),
previous_total: z.coerce.string().optional().catch(undefined),
data: z.array(z.object({
date: z.string().datetime({ offset: true }),
value: z.coerce.string(),
})),
});
type AdminMeasure = z.infer<typeof adminMeasureSchema>;
export {
adminMeasureSchema,
type AdminMeasure,
};

View file

@ -0,0 +1,13 @@
import z from 'zod';
/** @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminmoderation_log} */
const adminModerationLogEntrySchema = z.object({
id: z.coerce.string(),
data: z.record(z.string(), z.any()).catch({}),
time: z.number().catch(0),
message: z.string().catch(''),
});
type AdminModerationLogEntry = z.infer<typeof adminModerationLogEntrySchema>
export { adminModerationLogEntrySchema, type AdminModerationLogEntry };

View file

@ -0,0 +1,11 @@
import z from 'zod';
const adminRelaySchema = z.preprocess((data: any) => ({ id: data.actor, ...data }), z.object({
actor: z.string().catch(''),
id: z.string(),
followed_back: z.boolean().catch(false),
}));
type AdminRelay = z.infer<typeof adminRelaySchema>
export { adminRelaySchema, type AdminRelay };

View file

@ -0,0 +1,46 @@
import pick from 'lodash.pick';
import { z } from 'zod';
import { ruleSchema } from '../rule';
import { statusWithoutAccountSchema } from '../status';
import { dateSchema, filteredArray } from '../utils';
import { adminAccountSchema } from './account';
/** @see {@link https://docs.joinmastodon.org/entities/Admin_Report/} */
const adminReportSchema = z.preprocess((report: any) => {
if (report.actor) {
/**
* Convert Pleroma report schema
* @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminreports}
*/
return {
action_taken: report.state !== 'open',
comment: report.content,
updated_at: report.created_at,
account: report.actor,
target_account: report.account,
...(pick(report, ['id', 'assigned_account', 'created_at', 'rules', 'statuses'])),
};
}
return report;
}, z.object({
id: z.string(),
action_taken: z.boolean().optional().catch(undefined),
action_taken_at: dateSchema.nullable().catch(null),
category: z.string().optional().catch(undefined),
comment: z.string().optional().catch(undefined),
forwarded: z.boolean().optional().catch(undefined),
created_at: dateSchema.optional().catch(undefined),
updated_at: dateSchema.optional().catch(undefined),
account: adminAccountSchema,
target_account: adminAccountSchema,
assigned_account: adminAccountSchema.nullable().catch(null),
action_taken_by_account: adminAccountSchema.nullable().catch(null),
statuses: filteredArray(statusWithoutAccountSchema),
rules: filteredArray(ruleSchema),
}));
type AdminReport = z.infer<typeof adminReportSchema>;
export { adminReportSchema, type AdminReport };

View file

@ -0,0 +1,13 @@
import { z } from 'zod';
/** @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminrules} */
const adminRuleSchema = z.object({
id: z.string(),
text: z.string().catch(''),
hint: z.string().catch(''),
priority: z.number().nullable().catch(null),
});
type AdminRule = z.infer<typeof adminRuleSchema>;
export { adminRuleSchema, type AdminRule };

View file

@ -0,0 +1,18 @@
import { z } from 'zod';
import { tagSchema } from '../tag';
/** @see {@link https://docs.joinmastodon.org/entities/Tag/#admin} */
const adminTagSchema = tagSchema.extend({
id: z.string(),
trendable: z.boolean(),
usable: z.boolean(),
requires_review: z.boolean(),
});
type AdminTag = z.infer<typeof adminTagSchema>;
export {
adminTagSchema,
type AdminTag,
};

View file

@ -0,0 +1,17 @@
import { z } from 'zod';
import type { Resolve } from '../utils/types';
/** @see {@link https://docs.joinmastodon.org/entities/announcement/} */
const announcementReactionSchema = z.object({
name: z.string().catch(''),
count: z.number().int().nonnegative().catch(0),
me: z.boolean().catch(false),
url: z.string().nullable().catch(null),
static_url: z.string().nullable().catch(null),
announcement_id: z.string().catch(''),
});
type AnnouncementReaction = Resolve<z.infer<typeof announcementReactionSchema>>;
export { announcementReactionSchema, type AnnouncementReaction };

View file

@ -0,0 +1,35 @@
import { z } from 'zod';
import { announcementReactionSchema } from './announcement-reaction';
import { customEmojiSchema } from './custom-emoji';
import { mentionSchema } from './mention';
import { tagSchema } from './tag';
import { dateSchema, filteredArray } from './utils';
import type { Resolve } from '../utils/types';
/** @see {@link https://docs.joinmastodon.org/entities/announcement/} */
const announcementSchema = z.object({
id: z.string(),
content: z.string().catch(''),
starts_at: z.string().datetime().nullable().catch(null),
ends_at: z.string().datetime().nullable().catch(null),
all_day: z.boolean().catch(false),
read: z.boolean().catch(false),
published_at: dateSchema,
reactions: filteredArray(announcementReactionSchema),
statuses: z.preprocess(
(statuses: any) => Array.isArray(statuses)
? Object.fromEntries(statuses.map((status: any) => [status.url, status.account?.acct]) || [])
: statuses,
z.record(z.string(), z.string()),
),
mentions: filteredArray(mentionSchema),
tags: filteredArray(tagSchema),
emojis: filteredArray(customEmojiSchema),
updated_at: dateSchema,
});
type Announcement = Resolve<z.infer<typeof announcementSchema>>;
export { announcementSchema, type Announcement };

View file

@ -0,0 +1,21 @@
import { z } from 'zod';
import type { Resolve } from '../utils/types';
/** @see {@link https://docs.joinmastodon.org/entities/Application/} */
const applicationSchema = z.object({
name: z.string().catch(''),
website: z.string().optional().catch(undefined),
client_id: z.string().optional().catch(undefined),
client_secret: z.string().optional().catch(undefined),
redirect_uri: z.string().optional().catch(undefined),
id: z.string().optional().catch(undefined),
/** @deprecated */
vapid_key: z.string().optional().catch(undefined),
});
type Application = Resolve<z.infer<typeof applicationSchema>>;
export { applicationSchema, type Application };

View file

@ -0,0 +1,19 @@
import { z } from 'zod';
import { dateSchema, mimeSchema } from './utils';
import type { Resolve } from '../utils/types';
/** @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#post-apiv1pleromabackups} */
const backupSchema = z.object({
id: z.coerce.string(),
contentType: mimeSchema,
file_size: z.number().catch(0),
inserted_at: dateSchema,
processed: z.boolean().catch(false),
url: z.string().catch(''),
});
type Backup = Resolve<z.infer<typeof backupSchema>>;
export { backupSchema, type Backup };

View file

@ -0,0 +1,14 @@
import { z } from 'zod';
import type { Resolve } from '../utils/types';
const bookmarkFolderSchema = z.object({
id: z.coerce.string(),
name: z.string().catch(''),
emoji: z.string().nullable().catch(null),
emoji_url: z.string().nullable().catch(null),
});
type BookmarkFolder = Resolve<z.infer<typeof bookmarkFolderSchema>>;
export { bookmarkFolderSchema, type BookmarkFolder };

View file

@ -0,0 +1,23 @@
import { z } from 'zod';
import { customEmojiSchema } from './custom-emoji';
import { mediaAttachmentSchema } from './media-attachment';
import { previewCardSchema } from './preview-card';
import { dateSchema, filteredArray } from './utils';
/** @see {@link https://docs.pleroma.social/backend/development/API/chats/#getting-the-messages-for-a-chat} */
const chatMessageSchema = z.object({
id: z.string(),
content: z.string().catch(''),
chat_id: z.string(),
account_id: z.string(),
created_at: dateSchema,
emojis: filteredArray(customEmojiSchema),
attachment: mediaAttachmentSchema.nullable().catch(null),
unread: z.boolean(),
card: previewCardSchema.nullable().catch(null),
});
type ChatMessage = z.infer<typeof chatMessageSchema>;
export { chatMessageSchema, type ChatMessage };

View file

@ -0,0 +1,18 @@
import { z } from 'zod';
import { accountSchema } from './account';
import { chatMessageSchema } from './chat-message';
import { dateSchema } from './utils';
/** @see {@link https://docs.pleroma.social/backend/development/API/chats/#getting-a-list-of-chats} */
const chatSchema = z.object({
id: z.string(),
account: accountSchema,
unread: z.number().int(),
last_message: chatMessageSchema.nullable().catch(null),
created_at: dateSchema,
});
type Chat = z.infer<typeof chatSchema>;
export { chatSchema, type Chat };

View file

@ -0,0 +1,15 @@
import { z } from 'zod';
import { Resolve } from '../utils/types';
import { statusSchema } from './status';
/** @see {@link https://docs.joinmastodon.org/entities/Context/} */
const contextSchema = z.object({
ancestors: z.array(statusSchema),
descendants: z.array(statusSchema),
});
type Context = Resolve<z.infer<typeof contextSchema>>;
export { contextSchema, type Context };

View file

@ -0,0 +1,17 @@
import { z } from 'zod';
import { filteredArray } from './utils';
import { accountSchema, statusSchema } from '.';
/** @see {@link https://docs.joinmastodon.org/entities/Conversation} */
const conversationSchema = z.object({
id: z.string(),
unread: z.boolean().catch(false),
accounts: filteredArray(accountSchema),
last_status: statusSchema.nullable().catch(null),
});
type Conversation = z.infer<typeof conversationSchema>;
export { conversationSchema, type Conversation };

View file

@ -0,0 +1,17 @@
import z from 'zod';
/**
* Represents a custom emoji.
* @see {@link https://docs.joinmastodon.org/entities/CustomEmoji/}
*/
const customEmojiSchema = z.object({
shortcode: z.string(),
url: z.string(),
static_url: z.string().catch(''),
visible_in_picker: z.boolean().catch(true),
category: z.string().nullable().catch(null),
});
type CustomEmoji = z.infer<typeof customEmojiSchema>;
export { customEmojiSchema, type CustomEmoji };

View file

@ -0,0 +1,13 @@
import { z } from 'zod';
/** @see {@link https://docs.joinmastodon.org/entities/DomainBlock} */
const domainBlockSchema = z.object({
domain: z.string(),
digest: z.string(),
severity: z.enum(['silence', 'suspend']),
comment: z.string().optional().catch(undefined),
});
type DomainBlock = z.infer<typeof domainBlockSchema>;
export { domainBlockSchema, type DomainBlock };

View file

@ -0,0 +1,27 @@
import { z } from 'zod';
import { accountSchema } from './account';
import { emojiSchema, filteredArray } from './utils';
const baseEmojiReactionSchema = z.object({
count: z.number().nullable().catch(null),
me: z.boolean().catch(false),
name: emojiSchema,
url: z.literal(undefined).catch(undefined),
accounts: filteredArray(accountSchema),
});
const customEmojiReactionSchema = baseEmojiReactionSchema.extend({
name: z.string(),
url: z.string().url(),
});
/**
* Pleroma emoji reaction.
* @see {@link https://docs.pleroma.social/backend/development/API/differences_in_mastoapi_responses/#statuses}
*/
const emojiReactionSchema = baseEmojiReactionSchema.or(customEmojiReactionSchema);
type EmojiReaction = z.infer<typeof emojiReactionSchema>;
export { emojiReactionSchema, type EmojiReaction };

View file

@ -0,0 +1,13 @@
import { z } from 'zod';
import { dateSchema } from './utils';
/** @see {@link https://docs.joinmastodon.org/entities/ExtendedDescription} */
const extendedDescriptionSchema = z.object({
updated_at: dateSchema,
content: z.string(),
});
type ExtendedDescription = z.infer<typeof extendedDescriptionSchema>;
export { extendedDescriptionSchema, type ExtendedDescription };

View file

@ -0,0 +1,14 @@
import z from 'zod';
import { accountSchema } from './account';
import { filteredArray } from './utils';
/** @see {@link https://docs.joinmastodon.org/entities/FamiliarFollowers/} */
const familiarFollowersSchema = z.object({
id: z.string(),
accounts: filteredArray(accountSchema),
});
type FamiliarFollowers = z.infer<typeof familiarFollowersSchema>
export { familiarFollowersSchema, type FamiliarFollowers };

View file

@ -0,0 +1,14 @@
import { z } from 'zod';
/** @see {@link https://docs.joinmastodon.org/entities/FeaturedTag/} */
const featuredTagSchema = z.object({
id: z.string(),
name: z.string(),
url: z.string().optional().catch(undefined),
statuses_count: z.number(),
last_status_at: z.number(),
});
type FeaturedTag = z.infer<typeof featuredTagSchema>;
export { featuredTagSchema, type FeaturedTag };

View file

@ -0,0 +1,16 @@
import { z } from 'zod';
import { Resolve } from '../utils/types';
import { filterSchema } from './filter';
/** @see {@link https://docs.joinmastodon.org/entities/FilterResult/} */
const filterResultSchema = z.object({
filter: filterSchema,
keyword_matches: z.array(z.string()).nullable().catch(null),
status_matches: z.array(z.string()).nullable().catch(null),
});
type FilterResult = Resolve<z.infer<typeof filterResultSchema>>;
export { filterResultSchema, type FilterResult };

View file

@ -0,0 +1,47 @@
import { z } from 'zod';
import { Resolve } from '../utils/types';
import { filteredArray } from './utils';
/** @see {@link https://docs.joinmastodon.org/entities/FilterKeyword/} */
const filterKeywordSchema = z.object({
id: z.string(),
keyword: z.string(),
whole_word: z.boolean(),
});
/** @see {@link https://docs.joinmastodon.org/entities/FilterStatus/} */
const filterStatusSchema = z.object({
id: z.string(),
status_id: z.string(),
});
/** @see {@link https://docs.joinmastodon.org/entities/Filter/} */
const filterSchema = z.preprocess((filter: any) => {
if (filter.phrase) {
return {
...filter,
title: filter.phrase,
keywords: [{
id: '1',
keyword: filter.phrase,
whole_word: filter.whole_word,
}],
filter_action: filter.irreversible ? 'hide' : 'warn',
};
}
return filter;
}, z.object({
id: z.string(),
title: z.string(),
context: z.array(z.enum(['home', 'notifications', 'public', 'thread', 'account'])),
expires_at: z.string().datetime({ offset: true }).nullable().catch(null),
filter_action: z.enum(['warn', 'hide']).catch('warn'),
keywords: filteredArray(filterKeywordSchema),
statuses: filteredArray(filterStatusSchema),
}));
type Filter = Resolve<z.infer<typeof filterSchema>>;
export { filterKeywordSchema, filterStatusSchema, filterSchema, type Filter };

View file

@ -0,0 +1,21 @@
import z from 'zod';
import { accountSchema } from './account';
enum GroupRoles {
OWNER = 'owner',
ADMIN = 'admin',
USER = 'user'
}
type GroupRole =`${GroupRoles}`;
const groupMemberSchema = z.object({
id: z.string(),
account: accountSchema,
role: z.nativeEnum(GroupRoles),
});
type GroupMember = z.infer<typeof groupMemberSchema>;
export { groupMemberSchema, type GroupMember, GroupRoles, type GroupRole };

View file

@ -0,0 +1,14 @@
import z from 'zod';
import { GroupRoles } from './group-member';
const groupRelationshipSchema = z.object({
id: z.string(),
member: z.boolean().catch(false),
role: z.nativeEnum(GroupRoles).catch(GroupRoles.USER),
requested: z.boolean().catch(false),
});
type GroupRelationship = z.infer<typeof groupRelationshipSchema>;
export { groupRelationshipSchema, type GroupRelationship };

View file

@ -0,0 +1,33 @@
import z from 'zod';
import { customEmojiSchema } from './custom-emoji';
import { groupRelationshipSchema } from './group-relationship';
import { filteredArray } from './utils';
const groupSchema = z.object({
avatar: z.string().catch(''),
avatar_static: z.string().catch(''),
created_at: z.string().datetime().catch(new Date().toUTCString()),
display_name: z.string().catch(''),
domain: z.string().catch(''),
emojis: filteredArray(customEmojiSchema),
header: z.string().catch(''),
header_static: z.string().catch(''),
id: z.coerce.string(),
locked: z.boolean().catch(false),
membership_required: z.boolean().catch(false),
members_count: z.number().catch(0),
owner: z.object({ id: z.string() }).nullable().catch(null),
note: z.string().transform(note => note === '<p></p>' ? '' : note).catch(''),
relationship: groupRelationshipSchema.nullable().catch(null), // Dummy field to be overwritten later
statuses_visibility: z.string().catch('public'),
uri: z.string().catch(''),
url: z.string().catch(''),
avatar_description: z.string().catch(''),
header_description: z.string().catch(''),
});
type Group = z.infer<typeof groupSchema>;
export { groupSchema, type Group };

View file

@ -0,0 +1,69 @@
export * from './account';
export * from './account-warning';
export * from './admin/account';
export * from './admin/announcement';
export * from './admin/canonical-email-block';
export * from './admin/cohort';
export * from './admin/dimension';
export * from './admin/domain';
export * from './admin/domain-allow';
export * from './admin/domain-block';
export * from './admin/email-domain-block';
export * from './admin/ip';
export * from './admin/ip-block';
export * from './admin/measure';
export * from './admin/moderation-log-entry';
export * from './admin/relay';
export * from './admin/report';
export * from './admin/rule';
export * from './admin/tag';
export * from './announcement';
export * from './announcement-reaction';
export * from './application';
export * from './backup';
export * from './bookmark-folder';
export * from './chat';
export * from './chat-message';
export * from './context';
export * from './conversation';
export * from './custom-emoji';
export * from './domain-block';
export * from './emoji-reaction';
export * from './extended-description';
export * from './familiar-followers';
export * from './featured-tag';
export * from './filter';
export * from './group';
export * from './group-member';
export * from './group-relationship';
export * from './instance';
export * from './interaction-policy';
export * from './interaction-request';
export * from './list';
export * from './location';
export * from './marker';
export * from './media-attachment';
export * from './mention';
export * from './notification';
export * from './notification-policy';
export * from './notification-request';
export * from './oauth-token';
export * from './poll';
export * from './preview-card';
export * from './relationship';
export * from './relationship-severance-event';
export * from './report';
export * from './role';
export * from './rule';
export * from './scheduled-status';
export * from './search';
export * from './status';
export * from './status-edit';
export * from './status-source';
export * from './streaming-event';
export * from './suggestion';
export * from './tag';
export * from './token';
export * from './translation';
export * from './trends-link';
export * from './web-push-subscription';

View file

@ -0,0 +1,325 @@
/* eslint sort-keys: "error" */
import z from 'zod';
import { accountSchema } from './account';
import { ruleSchema } from './rule';
import { coerceObject, filteredArray, mimeSchema } from './utils';
const fixVersion = (version: string) => {
// Handle Mastodon release candidates
if (new RegExp(/[0-9.]+rc[0-9]+/g).test(version)) {
version = version.split('rc').join('-rc');
}
// Rename Akkoma to Pleroma+akkoma
if (version.includes('Akkoma')) {
version = '2.7.2 (compatible; Pleroma 2.4.50+akkoma)';
}
// Set Takahē version to a Pleroma-like string
if (version.startsWith('takahe/')) {
version = `0.0.0 (compatible; Takahe ${version.slice(7)})`;
}
return version;
};
const configurationSchema = coerceObject({
accounts: z.object({
allow_custom_css: z.boolean(),
max_featured_tags: z.number().int(),
max_profile_fields: z.number().int(),
}).nullable().catch(null),
chats: coerceObject({
max_characters: z.number().catch(5000),
}),
groups: coerceObject({
max_characters_description: z.number().catch(160),
max_characters_name: z.number().catch(50),
}),
media_attachments: coerceObject({
image_matrix_limit: z.number().optional().catch(undefined),
image_size_limit: z.number().optional().catch(undefined),
supported_mime_types: mimeSchema.array().optional().catch(undefined),
video_duration_limit: z.number().optional().catch(undefined),
video_frame_rate_limit: z.number().optional().catch(undefined),
video_matrix_limit: z.number().optional().catch(undefined),
video_size_limit: z.number().optional().catch(undefined),
}),
polls: coerceObject({
max_characters_per_option: z.number().optional().catch(undefined),
max_expiration: z.number().optional().catch(undefined),
max_options: z.number().optional().catch(undefined),
min_expiration: z.number().optional().catch(undefined),
}),
reactions: coerceObject({
max_reactions: z.number().catch(0),
}),
statuses: coerceObject({
characters_reserved_per_url: z.number().optional().catch(undefined),
max_characters: z.number().optional().catch(undefined),
max_media_attachments: z.number().optional().catch(undefined),
}),
translation: coerceObject({
enabled: z.boolean().catch(false),
}),
urls: coerceObject({
streaming: z.string().url().optional().catch(undefined),
}),
});
const contactSchema = coerceObject({
contact_account: accountSchema.optional().catch(undefined),
email: z.string().email().catch(''),
});
const pleromaSchema = coerceObject({
metadata: coerceObject({
account_activation_required: z.boolean().catch(false),
birthday_min_age: z.number().catch(0),
birthday_required: z.boolean().catch(false),
description_limit: z.number().catch(1500),
features: z.string().array().catch([]),
federation: coerceObject({
enabled: z.boolean().catch(true), // Assume true unless explicitly false
mrf_policies: z.string().array().optional().catch(undefined),
mrf_simple: coerceObject({
accept: z.string().array().catch([]),
avatar_removal: z.string().array().catch([]),
banner_removal: z.string().array().catch([]),
federated_timeline_removal: z.string().array().catch([]),
followers_only: z.string().array().catch([]),
media_nsfw: z.string().array().catch([]),
media_removal: z.string().array().catch([]),
reject: z.string().array().catch([]),
reject_deletes: z.string().array().catch([]),
report_removal: z.string().array().catch([]),
}),
}),
fields_limits: coerceObject({
max_fields: z.number().nonnegative().catch(4),
name_length: z.number().nonnegative().catch(255),
value_length: z.number().nonnegative().catch(2047),
}),
markup: coerceObject({
allow_headings: z.boolean().catch(false),
allow_inline_images: z.boolean().catch(false),
}),
migration_cooldown_period: z.number().optional().catch(undefined),
multitenancy: coerceObject({
domains: z
.array(
z.object({
domain: z.coerce.string(),
id: z.string(),
public: z.boolean().catch(false),
}),
)
.optional(),
enabled: z.boolean().catch(false),
}),
post_formats: z.string().array().optional().catch(undefined),
restrict_unauthenticated: coerceObject({
activities: coerceObject({
local: z.boolean().catch(false),
remote: z.boolean().catch(false),
}),
profiles: coerceObject({
local: z.boolean().catch(false),
remote: z.boolean().catch(false),
}),
timelines: coerceObject({
bubble: z.boolean().catch(false),
federated: z.boolean().catch(false),
local: z.boolean().catch(false),
}),
}),
translation: coerceObject({
allow_remote: z.boolean().catch(true),
allow_unauthenticated: z.boolean().catch(false),
source_languages: z.string().array().optional().catch(undefined),
target_languages: z.string().array().optional().catch(undefined),
}),
}),
oauth_consumer_strategies: z.string().array().catch([]),
stats: coerceObject({
mau: z.number().optional().catch(undefined),
}),
vapid_public_key: z.string().catch(''),
});
const pleromaPollLimitsSchema = coerceObject({
max_expiration: z.number().optional().catch(undefined),
max_option_chars: z.number().optional().catch(undefined),
max_options: z.number().optional().catch(undefined),
min_expiration: z.number().optional().catch(undefined),
});
const registrations = coerceObject({
approval_required: z.boolean().catch(false),
enabled: z.boolean().catch(false),
message: z.string().optional().catch(undefined),
});
const statsSchema = coerceObject({
domain_count: z.number().optional().catch(undefined),
status_count: z.number().optional().catch(undefined),
user_count: z.number().optional().catch(undefined),
});
const thumbnailSchema = coerceObject({
url: z.string().catch(''),
});
const usageSchema = coerceObject({
users: coerceObject({
active_month: z.number().catch(0),
}),
});
const instanceV1Schema = coerceObject({
account_domain: z.string().catch(''),
approval_required: z.boolean().catch(false),
configuration: configurationSchema,
contact_account: accountSchema.optional().catch(undefined),
description: z.string().catch(''),
description_limit: z.number().catch(1500),
email: z.string().email().catch(''),
feature_quote: z.boolean().catch(false),
fedibird_capabilities: z.array(z.string()).catch([]),
languages: z.string().array().catch([]),
max_media_attachments: z.number().optional().catch(undefined),
max_toot_chars: z.number().optional().catch(undefined),
pleroma: pleromaSchema,
poll_limits: pleromaPollLimitsSchema,
registrations: z.boolean().catch(false),
rules: filteredArray(ruleSchema),
short_description: z.string().catch(''),
stats: statsSchema,
thumbnail: z.string().catch(''),
title: z.string().catch(''),
uri: z.string().catch(''),
urls: coerceObject({
streaming_api: z.string().url().optional().catch(undefined),
}),
usage: usageSchema,
version: z.string().catch('0.0.0'),
});
/** @see {@link https://docs.joinmastodon.org/entities/Instance/} */
const instanceSchema = z.preprocess((data: any) => {
// Detect GoToSocial
if (typeof data.configuration?.accounts?.allow_custom_css === 'boolean') {
data.version = `0.0.0 (compatible; GoToSocial ${data.version})`;
}
if (data.domain) return { account_domain: data.domain, ...data };
const {
approval_required,
configuration,
contact_account,
description,
description_limit,
email,
max_media_attachments,
max_toot_chars,
poll_limits,
pleroma,
registrations,
short_description,
thumbnail,
uri,
urls,
...instance
} = instanceV1Schema.parse(data);
return {
...instance,
account_domain: instance.account_domain || uri,
configuration: {
...configuration,
polls: {
...configuration.polls,
max_characters_per_option: configuration.polls.max_characters_per_option ?? poll_limits.max_option_chars ?? 25,
max_expiration: configuration.polls.max_expiration ?? poll_limits.max_expiration ?? 2629746,
max_options: configuration.polls.max_options ?? poll_limits.max_options ?? 4,
min_expiration: configuration.polls.min_expiration ?? poll_limits.min_expiration ?? 300,
},
statuses: {
...configuration.statuses,
max_characters: configuration.statuses.max_characters ?? max_toot_chars ?? 500,
max_media_attachments: configuration.statuses.max_media_attachments ?? max_media_attachments,
},
urls: {
streaming: urls.streaming_api,
},
},
contact: {
account: contact_account,
email: email,
},
description: short_description || description,
domain: uri,
pleroma: {
...pleroma,
metadata: {
...pleroma.metadata,
description_limit,
},
},
registrations: {
approval_required: approval_required,
enabled: registrations,
},
thumbnail: { url: thumbnail },
};
}, coerceObject({
account_domain: z.string().catch(''),
configuration: configurationSchema,
contact: contactSchema,
description: z.string().catch(''),
domain: z.string().catch(''),
feature_quote: z.boolean().catch(false),
fedibird_capabilities: z.array(z.string()).catch([]),
languages: z.string().array().catch([]),
pleroma: pleromaSchema,
registrations: registrations,
rules: filteredArray(ruleSchema),
stats: statsSchema,
thumbnail: thumbnailSchema,
title: z.string().catch(''),
usage: usageSchema,
version: z.string().catch('0.0.0'),
}).transform(({ configuration, ...instance }) => {
const version = fixVersion(instance.version);
const polls = {
...configuration.polls,
max_characters_per_option: configuration.polls.max_characters_per_option ?? 25,
max_expiration: configuration.polls.max_expiration ?? 2629746,
max_options: configuration.polls.max_options ?? 4,
min_expiration: configuration.polls.min_expiration ?? 300,
};
const statuses = {
...configuration.statuses,
max_characters: configuration.statuses.max_characters ?? 500,
max_media_attachments: configuration.statuses.max_media_attachments ?? 4,
};
return {
...instance,
configuration: {
...configuration,
polls,
statuses,
},
version,
};
}));
type Instance = z.infer<typeof instanceSchema>;
export { instanceSchema, type Instance };

View file

@ -0,0 +1,33 @@
import { z } from 'zod';
import { Resolve } from '../utils/types';
import { coerceObject } from './utils';
const interactionPolicyEntrySchema = z.enum(['public', 'followers', 'following', 'mutuals', 'mentioned', 'author', 'me']);
const interactionPolicyRuleSchema = coerceObject({
always: z.array(interactionPolicyEntrySchema).default(['public']),
with_approval: z.array(interactionPolicyEntrySchema).default([]),
});
/** @see {@link https://docs.gotosocial.org/en/latest/api/swagger/} */
const interactionPolicySchema = coerceObject({
can_favourite: interactionPolicyRuleSchema,
can_reblog: interactionPolicyRuleSchema,
can_reply: interactionPolicyRuleSchema,
});
type InteractionPolicy = Resolve<z.infer<typeof interactionPolicySchema>>;
const interactionPoliciesSchema = coerceObject({
public: interactionPolicySchema,
unlisted: interactionPolicySchema,
private: interactionPolicySchema,
direct: interactionPolicySchema,
});
type InteractionPolicies = Resolve<z.infer<typeof interactionPoliciesSchema>>;
export { interactionPolicySchema, interactionPoliciesSchema, type InteractionPolicy, type InteractionPolicies };

View file

@ -0,0 +1,23 @@
import { z } from 'zod';
import { Resolve } from '../utils/types';
import { accountSchema } from './account';
import { statusSchema } from './status';
/** @see {@link https://docs.gotosocial.org/en/latest/api/swagger.yaml#/definitions/interactionRequest} */
const interactionRequestSchema = z.object({
accepted_at: z.string().datetime().nullable().catch(null),
account: accountSchema,
created_at: z.string().datetime(),
id: z.string(),
rejected_at: z.string().datetime().nullable().catch(null),
reply: statusSchema.nullable().catch(null),
status: statusSchema.nullable().catch(null),
type: z.enum(['favourite', 'reply', 'reblog']),
uri: z.string().nullable().catch(null),
});
type InteractionRequest = Resolve<z.infer<typeof interactionRequestSchema>>;
export { interactionRequestSchema, type InteractionRequest };

View file

@ -0,0 +1,13 @@
import { z } from 'zod';
/** @see {@link https://docs.joinmastodon.org/entities/List/} */
const listSchema = z.object({
id: z.coerce.string(),
title: z.string(),
replies_policy: z.string().optional().catch(undefined),
exclusive: z.boolean().optional().catch(undefined),
});
type List = z.infer<typeof listSchema>;
export { listSchema, type List };

View file

@ -0,0 +1,23 @@
import { z } from 'zod';
const locationSchema = z.object({
url: z.string().url().catch(''),
description: z.string().catch(''),
country: z.string().catch(''),
locality: z.string().catch(''),
region: z.string().catch(''),
postal_code: z.string().catch(''),
street: z.string().catch(''),
origin_id: z.string().catch(''),
origin_provider: z.string().catch(''),
type: z.string().catch(''),
timezone: z.string().catch(''),
geom: z.object({
coordinates: z.tuple([z.number(), z.number()]).nullable().catch(null),
srid: z.string().catch(''),
}).nullable().catch(null),
});
type Location = z.infer<typeof locationSchema>;
export { locationSchema, type Location };

View file

@ -0,0 +1,27 @@
import { z } from 'zod';
import { dateSchema } from './utils';
const markerSchema = z.preprocess((marker: any) => ({
unread_count: marker.pleroma?.unread_count,
...marker,
}), z.object({
last_read_id: z.string(),
version: z.number().int(),
updated_at: dateSchema,
unread_count: z.number().int().optional().catch(undefined),
}));
/** @see {@link https://docs.joinmastodon.org/entities/Marker/} */
type Marker = z.infer<typeof markerSchema>;
const markersSchema = z.record(markerSchema);
type Markers = z.infer<typeof markersSchema>;
export {
markerSchema,
markersSchema,
type Marker,
type Markers,
};

View file

@ -0,0 +1,105 @@
import { isBlurhashValid } from 'blurhash';
import { z } from 'zod';
import { mimeSchema } from './utils';
const blurhashSchema = z.string().superRefine((value, ctx) => {
const r = isBlurhashValid(value);
if (!r.result) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: r.errorReason,
});
}
});
const baseAttachmentSchema = z.object({
id: z.string(),
type: z.string(),
url: z.string().url().catch(''),
preview_url: z.string().url().catch(''),
remote_url: z.string().url().nullable().catch(null),
description: z.string().catch(''),
blurhash: blurhashSchema.nullable().catch(null),
mime_type: mimeSchema.nullable().catch(null),
});
const imageMetaSchema = z.object({
width: z.number(),
height: z.number(),
size: z.string().regex(/\d+x\d+$/).nullable().catch(null),
aspect: z.number().nullable().catch(null),
});
const imageAttachmentSchema = baseAttachmentSchema.extend({
type: z.literal('image'),
meta: z.object({
original: imageMetaSchema.optional().catch(undefined),
small: imageMetaSchema.optional().catch(undefined),
focus: z.object({
x: z.number().min(-1).max(1),
y: z.number().min(-1).max(1),
}).optional().catch(undefined),
}).catch({}),
});
const videoAttachmentSchema = baseAttachmentSchema.extend({
type: z.literal('video'),
meta: z.object({
duration: z.number().optional().catch(undefined),
original: imageMetaSchema.extend({
frame_rate: z.string().regex(/\d+\/\d+$/).nullable().catch(null),
duration: z.number().nonnegative().nullable().catch(null),
}).optional().catch(undefined),
small: imageMetaSchema.optional().catch(undefined),
// WIP: add rest
}).catch({}),
});
const gifvAttachmentSchema = baseAttachmentSchema.extend({
type: z.literal('gifv'),
meta: z.object({
duration: z.number().optional().catch(undefined),
original: imageMetaSchema.optional().catch(undefined),
}).catch({}),
});
const audioAttachmentSchema = baseAttachmentSchema.extend({
type: z.literal('audio'),
meta: z.object({
duration: z.number().optional().catch(undefined),
colors: z.object({
background: z.string().optional().catch(undefined),
foreground: z.string().optional().catch(undefined),
accent: z.string().optional().catch(undefined),
duration: z.number().optional().catch(undefined),
}).optional().catch(undefined),
original: z.object({
duration: z.number().optional().catch(undefined),
bitrate: z.number().nonnegative().optional().catch(undefined),
}).optional().catch(undefined),
}).catch({}),
});
const unknownAttachmentSchema = baseAttachmentSchema.extend({
type: z.literal('unknown'),
});
/** @see {@link https://docs.joinmastodon.org/entities/MediaAttachment} */
const mediaAttachmentSchema = z.preprocess((data: any) => ({
mime_type: data.pleroma?.mime_type,
preview_url: data.url,
...data,
}), z.discriminatedUnion('type', [
imageAttachmentSchema,
videoAttachmentSchema,
gifvAttachmentSchema,
audioAttachmentSchema,
unknownAttachmentSchema,
]));
type MediaAttachment = z.infer<typeof mediaAttachmentSchema>;
export { blurhashSchema, mediaAttachmentSchema, type MediaAttachment };

View file

@ -0,0 +1,19 @@
import { z } from 'zod';
/** @see {@link https://docs.joinmastodon.org/entities/Status/#Mention} */
const mentionSchema = z.object({
id: z.string(),
username: z.string().catch(''),
url: z.string().url().catch(''),
acct: z.string(),
}).transform((mention) => {
if (!mention.username) {
mention.username = mention.acct.split('@')[0];
}
return mention;
});
type Mention = z.infer<typeof mentionSchema>;
export { mentionSchema, type Mention };

View file

@ -0,0 +1,17 @@
import { z } from 'zod';
/** @see {@link https://docs.joinmastodon.org/entities/NotificationPolicy} */
const notificationPolicySchema = z.object({
filter_not_following: z.boolean(),
filter_not_followers: z.boolean(),
filter_new_accounts: z.boolean(),
filter_private_mentions: z.boolean(),
summary: z.object({
pending_requests_count: z.number().int(),
pending_notifications_count: z.number().int(),
}),
});
type NotificationPolicy = z.infer<typeof notificationPolicySchema>;
export { notificationPolicySchema, type NotificationPolicy };

View file

@ -0,0 +1,19 @@
import { z } from 'zod';
import { dateSchema } from './utils';
import { accountSchema, statusSchema } from '.';
/** @see {@link https://docs.joinmastodon.org/entities/NotificationRequest} */
const notificationRequestSchema = z.object({
id: z.string(),
created_at: dateSchema,
updated_at: dateSchema,
account: accountSchema,
notifications_count: z.coerce.string(),
last_status: statusSchema.optional().catch(undefined),
});
type NotificationRequest = z.infer<typeof notificationRequestSchema>;
export { notificationRequestSchema, type NotificationRequest };

View file

@ -0,0 +1,100 @@
import pick from 'lodash.pick';
import { z } from 'zod';
import { accountSchema } from './account';
import { accountWarningSchema } from './account-warning';
import { chatMessageSchema } from './chat-message';
import { relationshipSeveranceEventSchema } from './relationship-severance-event';
import { reportSchema } from './report';
import { statusSchema } from './status';
import { dateSchema } from './utils';
const baseNotificationSchema = z.object({
account: accountSchema,
created_at: dateSchema,
id: z.string(),
type: z.string(),
is_muted: z.boolean().optional().catch(undefined),
is_seen: z.boolean().optional().catch(undefined),
});
const accountNotificationSchema = baseNotificationSchema.extend({
type: z.enum(['follow', 'follow_request', 'admin.sign_up', 'bite']),
});
const statusNotificationSchema = baseNotificationSchema.extend({
type: z.enum(['mention', 'status', 'reblog', 'favourite', 'poll', 'update', 'event_reminder']),
status: statusSchema,
});
const reportNotificationSchema = baseNotificationSchema.extend({
type: z.literal('admin.report'),
report: reportSchema,
});
const severedRelationshipNotificationSchema = baseNotificationSchema.extend({
type: z.literal('severed_relationships'),
relationship_severance_event: relationshipSeveranceEventSchema,
});
const moderationWarningNotificationSchema = baseNotificationSchema.extend({
type: z.literal('moderation_warning'),
moderation_warning: accountWarningSchema,
});
const moveNotificationSchema = baseNotificationSchema.extend({
type: z.literal('move'),
target: accountSchema,
});
const emojiReactionNotificationSchema = baseNotificationSchema.extend({
type: z.literal('emoji_reaction'),
emoji: z.string(),
emoji_url: z.string().nullable().catch(null),
status: statusSchema,
});
const chatMentionNotificationSchema = baseNotificationSchema.extend({
type: z.literal('chat_mention'),
chat_message: chatMessageSchema,
});
const eventParticipationRequestNotificationSchema = baseNotificationSchema.extend({
type: z.enum(['participation_accepted', 'participation_request']),
status: statusSchema,
participation_message: z.string().nullable().catch(null),
});
/** @see {@link https://docs.joinmastodon.org/entities/Notification/} */
const notificationSchema: z.ZodType<Notification> = z.preprocess((notification: any) => ({
...pick(notification.pleroma || {}, ['is_muted', 'is_seen']),
...notification,
type: notification.type === 'pleroma:report'
? 'admin.report'
: notification.type?.replace(/^pleroma:/, ''),
}), z.discriminatedUnion('type', [
accountNotificationSchema,
statusNotificationSchema,
reportNotificationSchema,
severedRelationshipNotificationSchema,
moderationWarningNotificationSchema,
moveNotificationSchema,
emojiReactionNotificationSchema,
chatMentionNotificationSchema,
eventParticipationRequestNotificationSchema,
])) as any;
type Notification = z.infer<
| typeof accountNotificationSchema
| typeof statusNotificationSchema
| typeof reportNotificationSchema
| typeof severedRelationshipNotificationSchema
| typeof moderationWarningNotificationSchema
| typeof moveNotificationSchema
| typeof emojiReactionNotificationSchema
| typeof chatMentionNotificationSchema
| typeof eventParticipationRequestNotificationSchema
>;
export { notificationSchema, type Notification };

View file

@ -0,0 +1,15 @@
import { z } from 'zod';
/** @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#get-apioauth_tokens} */
const oauthTokenSchema = z.preprocess((token: any) => ({
...token,
valid_until: token?.valid_until?.padEnd(27, 'Z'),
}), z.object({
app_name: z.string(),
id: z.number(),
valid_until: z.string().datetime({ offset: true }),
}));
type OauthToken = z.infer<typeof oauthTokenSchema>;
export { oauthTokenSchema, type OauthToken };

View file

@ -0,0 +1,32 @@
import { z } from 'zod';
import { customEmojiSchema } from './custom-emoji';
import { filteredArray } from './utils';
const pollOptionSchema = z.object({
title: z.string().catch(''),
votes_count: z.number().catch(0),
title_map: z.record(z.string()).nullable().catch(null),
});
/** @see {@link https://docs.joinmastodon.org/entities/Poll/} */
const pollSchema = z.object({
emojis: filteredArray(customEmojiSchema),
expired: z.boolean().catch(false),
expires_at: z.string().datetime().nullable().catch(null),
id: z.string(),
multiple: z.boolean().catch(false),
options: z.array(pollOptionSchema).min(2),
voters_count: z.number().catch(0),
votes_count: z.number().catch(0),
own_votes: z.array(z.number()).nonempty().nullable().catch(null),
voted: z.boolean().catch(false),
non_anonymous: z.boolean().catch(false),
});
type Poll = z.infer<typeof pollSchema>;
type PollOption = Poll['options'][number];
export { pollSchema, type Poll, type PollOption };

View file

@ -0,0 +1,24 @@
import { z } from 'zod';
/** @see {@link https://docs.joinmastodon.org/entities/PreviewCard/} */
const previewCardSchema = z.object({
author_name: z.string().catch(''),
author_url: z.string().url().catch(''),
blurhash: z.string().nullable().catch(null),
description: z.string().catch(''),
embed_url: z.string().url().catch(''),
height: z.number().catch(0),
html: z.string().catch(''),
image: z.string().nullable().catch(null),
image_description: z.string().catch(''),
provider_name: z.string().catch(''),
provider_url: z.string().url().catch(''),
title: z.string().catch(''),
type: z.enum(['link', 'photo', 'video', 'rich']).catch('link'),
url: z.string().url(),
width: z.number().catch(0),
});
type PreviewCard = z.infer<typeof previewCardSchema>;
export { previewCardSchema, type PreviewCard };

View file

@ -0,0 +1,18 @@
import { z } from 'zod';
import { Resolve } from '../utils/types';
import { dateSchema } from './utils';
/** @see {@link https://docs.joinmastodon.org/entities/RelationshipSeveranceEvent/} */
const relationshipSeveranceEventSchema = z.object({
id: z.string(),
type: z.enum(['domain_block', 'user_domain_block', 'account_suspension']),
purged: z.string(),
relationships_count: z.number().optional().catch(undefined),
created_at: dateSchema,
});
type RelationshipSeveranceEvent = Resolve<z.infer<typeof relationshipSeveranceEventSchema>>;
export { relationshipSeveranceEventSchema, type RelationshipSeveranceEvent };

View file

@ -0,0 +1,22 @@
import z from 'zod';
/** @see {@link https://docs.joinmastodon.org/entities/Relationship/} */
const relationshipSchema = z.object({
blocked_by: z.boolean().catch(false),
blocking: z.boolean().catch(false),
domain_blocking: z.boolean().catch(false),
endorsed: z.boolean().catch(false),
followed_by: z.boolean().catch(false),
following: z.boolean().catch(false),
id: z.string(),
muting: z.boolean().catch(false),
muting_notifications: z.boolean().catch(false),
note: z.string().catch(''),
notifying: z.boolean().catch(false),
requested: z.boolean().catch(false),
showing_reblogs: z.boolean().catch(false),
});
type Relationship = z.infer<typeof relationshipSchema>;
export { relationshipSchema, type Relationship };

View file

@ -0,0 +1,22 @@
import { z } from 'zod';
import { accountSchema } from './account';
import { dateSchema } from './utils';
/** @see {@link https://docs.joinmastodon.org/entities/Report/} */
const reportSchema = z.object({
id: z.string(),
action_taken: z.boolean().optional().catch(undefined),
action_taken_at: dateSchema.nullable().catch(null),
category: z.string().optional().catch(undefined),
comment: z.string().optional().catch(undefined),
forwarded: z.boolean().optional().catch(undefined),
created_at: dateSchema.optional().catch(undefined),
status_ids: z.array(z.string()).nullable().catch(null),
rule_ids: z.array(z.string()).nullable().catch(null),
target_account: accountSchema,
});
type Report = z.infer<typeof reportSchema>;
export { reportSchema, type Report };

View file

@ -0,0 +1,18 @@
import { z } from 'zod';
const hexSchema = z.string().regex(/^#[a-f0-9]{6}$/i);
const roleSchema = z.object({
id: z.string().catch(''),
name: z.string().catch(''),
color: hexSchema.catch(''),
permissions: z.string().catch(''),
highlighted: z.boolean().catch(true),
});
type Role = z.infer<typeof roleSchema>;
export {
roleSchema,
type Role,
};

View file

@ -0,0 +1,17 @@
import { z } from 'zod';
const baseRuleSchema = z.object({
id: z.string(),
text: z.string().catch(''),
hint: z.string().catch(''),
});
/** @see {@link https://docs.joinmastodon.org/entities/Rule/} */
const ruleSchema = z.preprocess((data: any) => ({
...data,
hint: data.hint || data.subtext,
}), baseRuleSchema);
type Rule = z.infer<typeof ruleSchema>;
export { ruleSchema, type Rule };

View file

@ -0,0 +1,38 @@
import { z } from 'zod';
import { mediaAttachmentSchema } from './media-attachment';
import { filteredArray } from './utils';
import type { Resolve } from '../utils/types';
/** @see {@link https://docs.joinmastodon.org/entities/ScheduledStatus/} */
const scheduledStatusSchema = z.object({
id: z.string(),
scheduled_at: z.string().datetime({ offset: true }),
params: z.object({
text: z.string().nullable().catch(null),
poll: z.object({
options: z.array(z.string()),
expires_in: z.coerce.string(),
multiple: z.boolean().optional().catch(undefined),
hide_totals: z.boolean().optional().catch(undefined),
}).nullable().catch(null),
media_ids: z.array(z.string()).nullable().catch(null),
sensitive: z.coerce.boolean().nullable().catch(null),
spoiler_text: z.string().nullable().catch(null),
visibility: z.string().catch('public'),
in_reply_to_id: z.string().nullable().catch(null),
language: z.string().nullable().catch(null),
application_id: z.number().int().nullable().catch(null),
scheduled_at: z.string().datetime({ offset: true }).nullable().catch(null),
idempotency: z.string().nullable().catch(null),
with_rate_limit: z.boolean().catch(false),
expires_in: z.number().nullable().catch(null),
}),
media_attachments: filteredArray(mediaAttachmentSchema),
});
type ScheduledStatus = Resolve<z.infer<typeof scheduledStatusSchema>>;
export { scheduledStatusSchema, type ScheduledStatus };

View file

@ -0,0 +1,17 @@
import { z } from 'zod';
import { filteredArray } from './utils';
import { accountSchema, groupSchema, statusSchema, tagSchema } from '.';
/** @see {@link https://docs.joinmastodon.org/entities/Search} */
const searchSchema = z.object({
accounts: filteredArray(accountSchema),
statuses: filteredArray(statusSchema),
hashtags: filteredArray(tagSchema),
groups: filteredArray(groupSchema),
});
type Search = z.infer<typeof searchSchema>;
export { searchSchema, type Search };

View file

@ -0,0 +1,28 @@
import { z } from 'zod';
import { Resolve } from '../utils/types';
import { accountSchema } from './account';
import { customEmojiSchema } from './custom-emoji';
import { mediaAttachmentSchema } from './media-attachment';
import { dateSchema, filteredArray } from './utils';
/** @see {@link https://docs.joinmastodon.org/entities/StatusEdit/} */
const statusEditSchema = z.object({
content: z.string().catch(''),
spoiler_text: z.string().catch(''),
sensitive: z.coerce.boolean(),
created_at: dateSchema,
account: accountSchema,
poll: z.object({
options: z.array(z.object({
title: z.string(),
})),
}).nullable().catch(null),
media_attachments: filteredArray(mediaAttachmentSchema),
emojis: filteredArray(customEmojiSchema),
});
type StatusEdit = Resolve<z.infer<typeof statusEditSchema>>;
export { statusEditSchema, type StatusEdit };

View file

@ -0,0 +1,22 @@
import { z } from 'zod';
import { Resolve } from '../utils/types';
import { locationSchema } from './location';
/** @see {@link https://docs.joinmastodon.org/entities/StatusSource/} */
const statusSourceSchema = z.object({
id: z.string(),
text: z.string().catch(''),
spoiler_text: z.string().catch(''),
content_type: z.string().catch('text/plain'),
location: locationSchema.nullable().catch(null),
text_map: z.record(z.string()).nullable().catch(null),
spoiler_text_map: z.record(z.string()).nullable().catch(null),
});
type StatusSource = Resolve<z.infer<typeof statusSourceSchema>>;
export { statusSourceSchema, type StatusSource };

View file

@ -0,0 +1,151 @@
import pick from 'lodash.pick';
import { z } from 'zod';
import { accountSchema } from './account';
import { customEmojiSchema } from './custom-emoji';
import { emojiReactionSchema } from './emoji-reaction';
import { filterResultSchema } from './filter-result';
import { groupSchema } from './group';
import { interactionPolicySchema } from './interaction-policy';
import { mediaAttachmentSchema } from './media-attachment';
import { mentionSchema } from './mention';
import { pollSchema } from './poll';
import { previewCardSchema } from './preview-card';
import { tagSchema } from './tag';
import { translationSchema } from './translation';
import { dateSchema, filteredArray } from './utils';
import type { Resolve } from '../utils/types';
const statusEventSchema = z.object({
name: z.string().catch(''),
start_time: z.string().datetime().nullable().catch(null),
end_time: z.string().datetime().nullable().catch(null),
join_mode: z.enum(['free', 'restricted', 'invite']).nullable().catch(null),
participants_count: z.number().catch(0),
location: z.object({
name: z.string().catch(''),
url: z.string().url().catch(''),
latitude: z.number().catch(0),
longitude: z.number().catch(0),
street: z.string().catch(''),
postal_code: z.string().catch(''),
locality: z.string().catch(''),
region: z.string().catch(''),
country: z.string().catch(''),
}).nullable().catch(null),
join_state: z.enum(['pending', 'reject', 'accept']).nullable().catch(null),
});
/** @see {@link https://docs.joinmastodon.org/entities/Status/} */
const baseStatusSchema = z.object({
id: z.string(),
uri: z.string().url().catch(''),
created_at: dateSchema,
account: accountSchema,
content: z.string().catch(''),
visibility: z.string().catch('public'),
sensitive: z.coerce.boolean(),
spoiler_text: z.string().catch(''),
media_attachments: filteredArray(mediaAttachmentSchema),
application: z.object({
name: z.string(),
website: z.string().url().nullable().catch(null),
}).nullable().catch(null),
mentions: filteredArray(mentionSchema),
tags: filteredArray(tagSchema),
emojis: filteredArray(customEmojiSchema),
reblogs_count: z.number().catch(0),
favourites_count: z.number().catch(0),
replies_count: z.number().catch(0),
url: z.string().url().catch(''),
in_reply_to_id: z.string().nullable().catch(null),
in_reply_to_account_id: z.string().nullable().catch(null),
poll: pollSchema.nullable().catch(null),
card: previewCardSchema.nullable().catch(null),
language: z.string().nullable().catch(null),
text: z.string().nullable().catch(null),
edited_at: z.string().datetime().nullable().catch(null),
favourited: z.coerce.boolean(),
reblogged: z.coerce.boolean(),
muted: z.coerce.boolean(),
bookmarked: z.coerce.boolean(),
pinned: z.coerce.boolean(),
filtered: filteredArray(filterResultSchema),
approval_status: z.enum(['pending', 'approval', 'rejected']).nullable().catch(null),
group: groupSchema.nullable().catch(null),
scheduled_at: z.null().catch(null),
local: z.boolean().optional().catch(undefined),
conversation_id: z.string().optional().catch(undefined),
direct_conversation_id: z.string().optional().catch(undefined),
in_reply_to_account_acct: z.string().optional().catch(undefined),
expires_at: z.string().datetime({ offset: true }).optional().catch(undefined),
thread_muted: z.boolean().optional().catch(undefined),
emoji_reactions: filteredArray(emojiReactionSchema),
parent_visible: z.boolean().optional().catch(undefined),
pinned_at: z.string().datetime({ offset: true }).nullable().catch(null),
quote_visible: z.boolean().optional().catch(undefined),
quote_url: z.string().optional().catch(undefined),
quotes_count: z.number().catch(0),
bookmark_folder: z.string().nullable().catch(null),
event: statusEventSchema.nullable().catch(null),
translation: translationSchema.nullable().or(z.literal(false)).catch(null),
content_map: z.record(z.string()).nullable().catch(null),
text_map: z.record(z.string()).nullable().catch(null),
spoiler_text_map: z.record(z.string()).nullable().catch(null),
dislikes_count: z.number().catch(0),
disliked: z.coerce.boolean().catch(false),
interaction_policy: interactionPolicySchema,
});
const preprocess = (status: any) => {
if (!status) return null;
status = {
...(pick(status.pleroma || {}, [
'quote',
'local',
'conversation_id',
'direct_conversation_id',
'in_reply_to_account_acct',
'expires_at',
'thread_muted',
'emoji_reactions',
'parent_visible',
'pinned_at',
'quotes_count',
'bookmark_folder',
'event',
'translation',
])),
...(pick(status.friendica || {}, [
'dislikes_count',
'disliked',
])),
...status,
};
return status;
};
const statusSchema = z.preprocess(preprocess, baseStatusSchema.extend({
reblog: z.lazy(() => baseStatusSchema).nullable().catch(null),
quote: z.lazy(() => baseStatusSchema).nullable().catch(null),
}));
const statusWithoutAccountSchema = z.preprocess(preprocess, baseStatusSchema.omit({ account: true }).extend({
account: accountSchema.nullable().catch(null),
reblog: z.lazy(() => baseStatusSchema).nullable().catch(null),
quote: z.lazy(() => baseStatusSchema).nullable().catch(null),
}));
type Status = Resolve<z.infer<typeof statusSchema>>;
export { statusSchema, statusWithoutAccountSchema, type Status };

View file

@ -0,0 +1,117 @@
import { z } from 'zod';
import { announcementSchema } from './announcement';
import { announcementReactionSchema } from './announcement-reaction';
import { chatSchema } from './chat';
import { conversationSchema } from './conversation';
import { notificationSchema } from './notification';
import { statusSchema } from './status';
const followRelationshipUpdateSchema = z.object({
state: z.enum(['follow_pending', 'follow_accept', 'follow_reject']),
follower: z.object({
id: z.string(),
follower_count: z.number().nullable().catch(null),
following_count: z.number().nullable().catch(null),
}),
following: z.object({
id: z.string(),
follower_count: z.number().nullable().catch(null),
following_count: z.number().nullable().catch(null),
}),
});
type FollowRelationshipUpdate = z.infer<typeof followRelationshipUpdateSchema>;
const baseStreamingEventSchema = z.object({
stream: z.array(z.string()).catch([]),
});
const statusStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.enum(['update', 'status.update']),
payload: z.preprocess((payload: any) => JSON.parse(payload), statusSchema),
});
const stringStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.enum(['delete', 'announcement.delete']),
payload: z.string(),
});
const notificationStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.literal('notification'),
payload: z.preprocess((payload: any) => JSON.parse(payload), notificationSchema),
});
const emptyStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.literal('filters_changed'),
});
const conversationStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.literal('conversation'),
payload: z.preprocess((payload: any) => JSON.parse(payload), conversationSchema),
});
const announcementStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.literal('announcement'),
payload: z.preprocess((payload: any) => JSON.parse(payload), announcementSchema),
});
const announcementReactionStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.literal('announcement.reaction'),
payload: z.preprocess((payload: any) => JSON.parse(payload), announcementReactionSchema),
});
const chatUpdateStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.literal('chat_update'),
payload: z.preprocess((payload: any) => JSON.parse(payload), chatSchema),
});
const followRelationshipsUpdateStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.literal('follow_relationships_update'),
payload: z.preprocess((payload: any) => JSON.parse(payload), followRelationshipUpdateSchema),
});
const respondStreamingEventSchema = baseStreamingEventSchema.extend({
event: z.literal('respond'),
payload: z.preprocess((payload: any) => JSON.parse(payload), z.object({
type: z.string(),
result: z.enum(['success', 'ignored', 'error']),
})),
});
/** @see {@link https://docs.joinmastodon.org/methods/streaming/#events} */
const streamingEventSchema: z.ZodType<StreamingEvent> = z.preprocess((event: any) => ({
...event,
event: event.event?.replace(/^pleroma:/, ''),
}), z.discriminatedUnion('event', [
statusStreamingEventSchema,
stringStreamingEventSchema,
notificationStreamingEventSchema,
emptyStreamingEventSchema,
conversationStreamingEventSchema,
announcementStreamingEventSchema,
announcementReactionStreamingEventSchema,
chatUpdateStreamingEventSchema,
followRelationshipsUpdateStreamingEventSchema,
respondStreamingEventSchema,
])) as any;
type StreamingEvent = z.infer<
| typeof statusStreamingEventSchema
| typeof stringStreamingEventSchema
| typeof notificationStreamingEventSchema
| typeof emptyStreamingEventSchema
| typeof conversationStreamingEventSchema
| typeof announcementStreamingEventSchema
| typeof announcementReactionStreamingEventSchema
| typeof chatUpdateStreamingEventSchema
| typeof followRelationshipsUpdateStreamingEventSchema
| typeof respondStreamingEventSchema
>;
export {
followRelationshipUpdateSchema,
streamingEventSchema,
type FollowRelationshipUpdate,
type StreamingEvent,
};

View file

@ -0,0 +1,25 @@
import { z } from 'zod';
import { accountSchema } from './account';
/** @see {@link https://docs.joinmastodon.org/entities/Suggestion} */
const suggestionSchema = z.preprocess((suggestion: any) => {
/**
* Support `/api/v1/suggestions`
* @see {@link https://docs.joinmastodon.org/methods/suggestions/#v1}
*/
if (suggestion?.acct) return {
source: 'staff',
sources: 'featured',
account: suggestion,
};
return suggestion;
}, z.object({
source: z.string(),
sources: z.array(z.string()),
account: accountSchema,
}));
type Suggestion = z.infer<typeof suggestionSchema>;
export { suggestionSchema, type Suggestion };

View file

@ -0,0 +1,19 @@
import { z } from 'zod';
const historySchema = z.object({
day: z.coerce.number(),
accounts: z.coerce.number(),
uses: z.coerce.number(),
});
/** @see {@link https://docs.joinmastodon.org/entities/tag} */
const tagSchema = z.object({
name: z.string().min(1),
url: z.string().url().catch(''),
history: z.array(historySchema).nullable().catch(null),
following: z.boolean().optional().catch(undefined),
});
type Tag = z.infer<typeof tagSchema>;
export { historySchema, tagSchema, type Tag };

View file

@ -0,0 +1,18 @@
import { z } from 'zod';
/** @see {@link https://docs.joinmastodon.org/entities/Token/} */
const tokenSchema = z.object({
access_token: z.string(),
token_type: z.string(),
scope: z.string(),
created_at: z.number().optional().catch(undefined),
id: z.number().optional().catch(undefined),
refresh_token: z.string().optional().catch(undefined),
expires_in: z.number().optional().catch(undefined),
me: z.string().optional().catch(undefined),
});
type Token = z.infer<typeof tokenSchema>;
export { tokenSchema, type Token };

View file

@ -0,0 +1,43 @@
import { z } from 'zod';
import { Resolve } from '../utils/types';
import { filteredArray } from './utils';
const translationPollSchema = z.object({
id: z.string(),
options: z.array(z.object({
title: z.string(),
})),
});
const translationMediaAttachment = z.object({
id: z.string(),
description: z.string().catch(''),
});
/** @see {@link https://docs.joinmastodon.org/entities/Translation/} */
const translationSchema = z.preprocess((translation: any) => {
/**
* handle Akkoma
* @see {@link https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/lib/pleroma/web/mastodon_api/controllers/status_controller.ex#L504}
*/
if (translation?.text) return {
content: translation.text,
detected_source_language: translation.detected_language,
provider: '',
};
return translation;
}, z.object({
content: z.string().catch(''),
spoiler_text: z.string().catch(''),
poll: translationPollSchema.optional().catch(undefined),
media_attachments: filteredArray(translationMediaAttachment),
detected_source_language: z.string(),
provider: z.string(),
}));
type Translation = Resolve<z.infer<typeof translationSchema>>;
export { translationSchema, type Translation };

View file

@ -0,0 +1,29 @@
import { z } from 'zod';
import { blurhashSchema } from './media-attachment';
import { historySchema } from './tag';
/** @see {@link https://docs.joinmastodon.org/entities/PreviewCard/#trends-link} */
const trendsLinkSchema = z.preprocess((link: any) => ({ ...link, id: link.url }), z.object({
id: z.string().catch(''),
url: z.string().url().catch(''),
title: z.string().catch(''),
description: z.string().catch(''),
type: z.enum(['link', 'photo', 'video', 'rich']).catch('link'),
author_name: z.string().catch(''),
author_url: z.string().catch(''),
provider_name: z.string().catch(''),
provider_url: z.string().catch(''),
html: z.string().catch(''),
width: z.number().nullable().catch(null),
height: z.number().nullable().catch(null),
image: z.string().nullable().catch(null),
image_description: z.string().nullable().catch(null),
embed_url: z.string().catch(''),
blurhash: blurhashSchema.nullable().catch(null),
history: z.array(historySchema).nullable().catch(null),
}));
type TrendsLink = z.infer<typeof trendsLinkSchema>;
export { trendsLinkSchema, type TrendsLink };

View file

@ -0,0 +1,26 @@
import z from 'zod';
/** Validate to Mastodon's date format, or use the current date. */
const dateSchema = z.string().datetime({ offset: true }).catch(new Date().toUTCString());
/** Validates individual items in an array, dropping any that aren't valid. */
const filteredArray = <T extends z.ZodTypeAny>(schema: T) =>
z.any().array().catch([])
.transform((arr) => (
arr.map((item) => {
const parsed = schema.safeParse(item);
return parsed.success ? parsed.data : undefined;
}).filter((item): item is z.infer<T> => Boolean(item))
));
/** Validates the string as an emoji. */
const emojiSchema = z.string().refine((v) => /\p{Extended_Pictographic}|[\u{1F1E6}-\u{1F1FF}]{2}/u.test(v));
/** MIME schema, eg `image/png`. */
const mimeSchema = z.string().regex(/^\w+\/[-+.\w]+$/);
/** zod schema to force the value into an object, if it isn't already. */
const coerceObject = <T extends z.ZodRawShape>(shape: T) =>
z.object({}).passthrough().catch({}).pipe(z.object(shape));
export { filteredArray, emojiSchema, dateSchema, mimeSchema, coerceObject };

View file

@ -0,0 +1,13 @@
import { z } from 'zod';
/** @see {@link https://docs.joinmastodon.org/entities/WebPushSubscription/} */
const webPushSubscriptionSchema = z.object({
id: z.coerce.string(),
endpoint: z.string(),
alerts: z.record(z.boolean()),
server_key: z.string(),
});
type WebPushSubscription = z.infer<typeof webPushSubscriptionSchema>;
export { webPushSubscriptionSchema, type WebPushSubscription };

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,6 @@
export { PlApiClient } from './client';
export { type Response as PlApiResponse } from './request';
export * from './entities';
export * from './features';
export * from './params';
export * from './responses';

View file

@ -0,0 +1,67 @@
import type { LanguageParam, OnlyEventsParam, OnlyMediaParam, PaginationParams, WithMutedParam, WithRelationshipsParam } from './common';
type GetAccountParams = WithMutedParam;
interface GetAccountStatusesParams extends PaginationParams, WithMutedParam, OnlyEventsParam, OnlyMediaParam, LanguageParam {
/** Boolean. Filter out statuses in reply to a different account. */
exclude_replies?: boolean;
/** Boolean. Filter for pinned statuses only. Defaults to false, which includes all statuses. Pinned statuses do not receive special priority in the order of the returned results. */
pinned?: boolean;
/** String. Filter for statuses using a specific hashtag. */
tagged?: string;
}
type GetAccountFollowersParams = PaginationParams & WithRelationshipsParam;
type GetAccountFollowingParams = PaginationParams & WithRelationshipsParam;
interface FollowAccountParams {
/** Boolean. Receive this accounts reblogs in home timeline? Defaults to true. */
reblogs?: boolean;
/** Boolean. Receive notifications when this account posts a status? Defaults to false. */
notify?: boolean;
/**
* Array of String (ISO 639-1 language two-letter code). Filter received statuses for these languages. If not provided, you will receive this accounts posts in all languages.
* Requires `features.followAccountLangugaes`.
*/
languages?: string[];
}
interface GetRelationshipsParams {
/** Boolean. Whether relationships should be returned for suspended users, defaults to false. */
with_suspended?: boolean;
}
interface SearchAccountParams {
/** Integer. Maximum number of results. Defaults to 40 accounts. Max 80 accounts. */
limit?: number;
/** Integer. Skip the first n results. */
offset?: number;
/** Boolean. Attempt WebFinger lookup. Defaults to false. Use this when `q` is an exact address. */
resolve?: boolean;
/** Boolean. Limit the search to users you are following. Defaults to false. */
following?: boolean;
}
interface ReportAccountParams {
status_ids?: string[];
comment?: string;
forward?: boolean;
category?: 'spam' | 'legal' | 'violation' | 'other';
rule_ids?: string[];
}
type GetAccountEndorsementsParams = WithRelationshipsParam;
type GetAccountFavouritesParams = PaginationParams;
export type {
GetAccountParams,
GetAccountStatusesParams,
GetAccountFollowersParams,
GetAccountFollowingParams,
FollowAccountParams,
GetRelationshipsParams,
SearchAccountParams,
ReportAccountParams,
GetAccountEndorsementsParams,
GetAccountFavouritesParams,
};

View file

@ -0,0 +1,200 @@
import type { PaginationParams } from './common';
interface AdminGetAccountsParams extends PaginationParams {
/** String. Filter for `local` or `remote` accounts. */
origin?: 'local' | 'remote';
/** String. Filter for `active`, `pending`, `disabled`, `silenced`, or suspended accounts. */
status?: 'active' | 'pending' | 'disabled' | 'silenced' | 'suspended';
/** String. Filter for accounts with `staff` permissions (users that can manage reports). */
permissions?: 'staff';
/** Array of String. Filter for users with these roles. */
role_ids?: string[];
/** String. Lookup users invited by the account with this ID. */
invited_by?: string;
/** String. Search for the given username. */
username?: string;
/** String. Search for the given display name */
display_name?: string;
/** String. Filter by the given domain */
by_domain?: string;
/** String. Lookup a user with this email */
email?: string;
/** String. Lookup users with this IP address */
ip?: string;
}
type AdminAccountAction = 'none' | 'sensitive' | 'disable' | 'silence' | 'suspend';
interface AdminPerformAccountActionParams {
/** String. The ID of an associated report that caused this action to be taken. */
report_id?: string;
/** String. The ID of a preset warning. */
warning_preset_id?: string;
/** String. Additional clarification for why this action was taken. */
text?: string;
/** Boolean. Should an email be sent to the user with the above information? */
send_email_notification?: boolean;
}
type AdminGetDomainBlocksParams = PaginationParams;
interface AdminCreateDomainBlockParams {
/** String. Whether to apply a `silence`, `suspend`, or `noop` to the domain. Defaults to `silence` */
severity?: 'silence' | 'suspend' | 'noop';
/** Boolean. Whether media attachments should be rejected. Defaults to false */
reject_media?: boolean;
/** Boolean. Whether reports from this domain should be rejected. Defaults to false */
reject_reports?: boolean;
/** String. A private note about this domain block, visible only to admins. */
private_comment?: string;
/** String. A public note about this domain block, optionally shown on the about page. */
public_comment?: string;
/** Boolean. Whether to partially censor the domain when shown in public. Defaults to false */
obfuscate?: boolean;
}
type AdminUpdateDomainBlockParams = AdminCreateDomainBlockParams;
interface AdminGetReportsParams extends PaginationParams {
/** Boolean. Filter for resolved reports? */
resolved?: boolean;
/** String. Filter for reports filed by this account. */
account_id?: string;
/** String. Filter for reports targeting this account. */
target_account_id?: string;
}
interface AdminUpdateReportParams {
/** String. Change the classification of the report to `spam`, `violation`, or `other`. */
category?: 'spam' | 'violation' | 'other';
/** Array of Integer. For `violation` category reports, specify the ID of the exact rules broken. Rules and their IDs are available via [GET /api/v1/instance/rules](https://docs.joinmastodon.org/methods/instance/#rules) and [GET /api/v1/instance](https://docs.joinmastodon.org/methods/instance/#get). */
rule_ids?: string[];
}
interface AdminGetStatusesParams {
limit?: number;
local_only?: boolean;
with_reblogs?: boolean;
with_private?: boolean;
}
interface AdminUpdateStatusParams {
sensitive?: boolean;
visibility?: 'public' | 'private' | 'unlisted';
}
type AdminGetCanonicalEmailBlocks = PaginationParams;
type AdminDimensionKey = 'languages' | 'sources' | 'servers' | 'space_usage' | 'software_versions' | 'tag_servers' | 'tag_languages' | 'instance_accounts' | 'instance_languages';
interface AdminGetDimensionsParams {
/** String (ISO 8601 Datetime). The start date for the time period. If a time is provided, it will be ignored. */
start_at?: string;
/** String (ISO 8601 Datetime). The end date for the time period. If a time is provided, it will be ignored. */
end_at?: string;
/** Integer. The maximum number of results to return for sources, servers, languages, tag or instance dimensions. */
limit?: number;
tag_servers?: {
/** String. When tag_servers is one of the requested keys, you must provide a trending tag ID to obtain information about which servers are posting the tag. */
id?: string;
};
tag_languages?: {
/** String. When tag_languages is one of the requested keys, you must provide a trending tag ID to obtain information about which languages are posting the tag. */
id?: string;
};
instance_accounts?: {
/** String. When instance_accounts is one of the requested keys, you must provide a domain to obtain information about popular accounts from that server. */
domain?: string;
};
instance_languages?: {
/** String. When instance_accounts is one of the requested keys, you must provide a domain to obtain information about popular languages from that server. */
domain?: string;
};
}
type AdminGetDomainAllowsParams = PaginationParams;
type AdminGetEmailDomainBlocksParams = PaginationParams;
type AdminGetIpBlocksParams = PaginationParams;
interface AdminCreateIpBlockParams {
/** String. The IP address and prefix to block. Defaults to 0.0.0.0/32 */
ip?: string;
/** String. The policy to apply to this IP range: sign_up_requires_approval, sign_up_block, or no_access */
severity: string;
/** String. The reason for this IP block. */
comment?: string;
/** Integer. The number of seconds in which this IP block will expire. */
expires_in?: number;
}
type AdminUpdateIpBlockParams = Partial<AdminCreateIpBlockParams>;
type AdminMeasureKey = 'active_users' | 'new_users' | 'interactions' | 'opened_reports' | 'resolved_reports' | 'tag_accounts' | 'tag_uses' | 'tag_servers' | 'instance_accounts' | 'instance_media_attachments' | 'instance_reports' | 'instance_statuses' | 'instance_follows' | 'instance_followers';
interface AdminGetMeasuresParams {
tag_accounts?: {
/** String. When `tag_accounts` is one of the requested keys, you must provide a tag ID to obtain the measure of how many accounts used that hashtag in at least one status within the given time period. */
id?: string;
};
tag_uses?: {
/** String. When `tag_uses` is one of the requested keys, you must provide a tag ID to obtain the measure of how many statuses used that hashtag within the given time period. */
id?: string;
};
tag_servers?: {
/** String. When `tag_servers` is one of the requested keys, you must provide a tag ID to obtain the measure of how many servers used that hashtag in at least one status within the given time period. */
id?: string;
};
instance_accounts?: {
/** String. When `instance_accounts` is one of the requested keys, you must provide a remote domain to obtain the measure of how many accounts have been discovered from that server within the given time period. */
domain?: string;
};
instance_media_attachments?: {
/** String. When `instance_media_attachments` is one of the requested keys, you must provide a remote domain to obtain the measure of how much space is used by media attachments from that server within the given time period. */
domain?: string;
};
instance_reports?: {
/** String. When `instance_reports` is one of the requested keys, you must provide a remote domain to obtain the measure of how many reports have been filed against accounts from that server within the given time period. */
domain?: string;
};
instance_statuses?: {
/** String. When `instance_statuses` is one of the requested keys, you must provide a remote domain to obtain the measure of how many statuses originate from that server within the given time period. */
domain?: string;
};
instance_follows?: {
/** String. When `instance_follows` is one of the requested keys, you must provide a remote domain to obtain the measure of how many follows were performed on accounts from that server by local accounts within the given time period. */
domain?: string;
};
instance_followers?: {
/** String. When `instance_followers` is one of the requested keys, you must provide a remote domain to obtain the measure of how many follows were performed by accounts from that server on local accounts within the given time period. */
domain?: string;
};
}
interface AdminGetGroupsParams {
}
export type {
AdminGetAccountsParams,
AdminAccountAction,
AdminPerformAccountActionParams,
AdminGetDomainBlocksParams,
AdminCreateDomainBlockParams,
AdminUpdateDomainBlockParams,
AdminGetReportsParams,
AdminUpdateReportParams,
AdminGetStatusesParams,
AdminUpdateStatusParams,
AdminGetCanonicalEmailBlocks,
AdminDimensionKey,
AdminGetDimensionsParams,
AdminGetDomainAllowsParams,
AdminGetEmailDomainBlocksParams,
AdminGetIpBlocksParams,
AdminCreateIpBlockParams,
AdminUpdateIpBlockParams,
AdminMeasureKey,
AdminGetMeasuresParams,
AdminGetGroupsParams,
};

View file

@ -0,0 +1,14 @@
interface CreateApplicationParams {
/** String. A name for your application */
client_name: string;
/** String. Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter. */
redirect_uris: string;
/** String. Space separated list of scopes. If none is provided, defaults to `read`. See [OAuth Scopes](https://docs.joinmastodon.org/api/oauth-scopes/) for a list of possible scopes. */
scopes?: string;
/** String. A URL to the homepage of your app */
website?: string;
}
export type {
CreateApplicationParams,
};

View file

@ -0,0 +1,18 @@
import { PaginationParams, WithMutedParam } from './common';
type GetChatsParams = PaginationParams & WithMutedParam;
type GetChatMessagesParams = PaginationParams;
type CreateChatMessageParams = {
content?: string;
media_id: string;
} | {
content: string;
media_id?: string;
};
export type {
GetChatsParams,
GetChatMessagesParams,
CreateChatMessageParams,
};

View file

@ -0,0 +1,59 @@
interface PaginationParams {
/** String. All results returned will be lesser than this ID. In effect, sets an upper bound on results. */
max_id?: string;
/** String. All results returned will be greater than this ID. In effect, sets a lower bound on results. */
since_id?: string;
/** Integer. Maximum number of results to return. */
limit?: number;
/** String. Returns results immediately newer than this ID. In effect, sets a cursor at this ID and paginates forward. */
min_id?: string;
}
interface WithMutedParam {
/**
* Boolean. Also show statuses from muted users. Default to false.
*
* Requires `features.timelinesWithMuted`.
*/
with_muted?: boolean;
}
interface WithRelationshipsParam {
/**
* Embed relationships into accounts.
* Supported by Pleroma.
*/
with_relationships?: boolean;
}
interface OnlyMediaParam {
/** Boolean. Show only statuses with media attached? Defaults to false. */
only_media?: boolean;
}
interface OnlyEventsParam {
/**
* Boolean. Filter out statuses without events.
*
* Requires `features.events`.
*/
only_events?: boolean;
}
interface LanguageParam {
/**
* Fetch a translation in given language
*
* Requires `features.fetchStatusesWithTranslation`.
*/
language?: string;
}
export type {
PaginationParams,
WithMutedParam,
WithRelationshipsParam,
OnlyMediaParam,
OnlyEventsParam,
LanguageParam,
};

View file

@ -0,0 +1,33 @@
import { PaginationParams } from './common';
interface CreateEventParams {
/** name of the event */
name: string;
/** optional, description of the event */
status?: string;
/** optional, event banner attachment ID */
banner_id?: string;
/** start time of the event */
start_time?: string;
/** optional, end time of the event */
end_time?: string;
/** optional, event join mode, either free or restricted, defaults to free */
join_mode?: 'free' | 'restricted';
/** optional, location ID from the location provider used by server */
location_id?: string;
/** string, contain the MIME type of the status. */
content_type?: string;
}
type EditEventParams = Partial<Omit<CreateEventParams, 'join_mode'>>;
type GetJoinedEventsParams = PaginationParams;
type GetEventParticipationsParams = PaginationParams;
type GetEventParticipationRequestsParams = PaginationParams;
export type {
CreateEventParams,
EditEventParams,
GetJoinedEventsParams,
GetEventParticipationsParams,
GetEventParticipationRequestsParams,
};

View file

@ -0,0 +1,48 @@
import type { PaginationParams, WithRelationshipsParam } from './common';
interface MuteAccountParams {
/** Boolean. Mute notifications in addition to statuses? Defaults to true. */
notifications?: boolean;
/** Number. How long the mute should last, in seconds. Defaults to 0 (indefinite). */
duration?: number;
}
type GetMutesParams = Omit<PaginationParams, 'min_id'> & WithRelationshipsParam;
type GetBlocksParams = PaginationParams & WithRelationshipsParam;
type GetDomainBlocksParams = PaginationParams;
type FilterContext = 'home' | 'notifications' | 'public' | 'thread' | 'account';
interface CreateFilterParams {
title: string;
context: Array<FilterContext>;
filter_action?: 'warn' | 'hide';
expires_in?: number;
keywords_attributes: Array<{
keyword: string;
whole_word?: boolean;
}>;
}
interface UpdateFilterParams {
title?: string;
context?: Array<FilterContext>;
filter_action?: 'warn' | 'hide';
expires_in?: number;
keywords_attributes?: Array<{
keyword: string;
whole_word?: boolean;
id?: string;
_destroy?: boolean;
}>;
}
export type {
MuteAccountParams,
GetMutesParams,
GetBlocksParams,
GetDomainBlocksParams,
FilterContext,
CreateFilterParams,
UpdateFilterParams,
};

View file

@ -0,0 +1,27 @@
import type { PaginationParams } from './common';
interface CreateGroupParams {
display_name: string;
note?: string;
avatar?: File;
header?: File;
}
interface UpdateGroupParams {
display_name?: string;
note?: string;
avatar?: File | '';
header?: File | '';
}
type GetGroupMembershipsParams = Omit<PaginationParams, 'min_id'>;
type GetGroupMembershipRequestsParams = Omit<PaginationParams, 'min_id'>;
type GetGroupBlocksParams = Omit<PaginationParams, 'min_id'>;
export type {
CreateGroupParams,
UpdateGroupParams,
GetGroupMembershipsParams,
GetGroupMembershipRequestsParams,
GetGroupBlocksParams,
};

View file

@ -0,0 +1,21 @@
export * from './accounts';
export * from './admin';
export * from './apps';
export * from './chats';
export * from './events';
export * from './filtering';
export * from './groups';
export * from './instance';
export * from './interaction-requests';
export * from './lists';
export * from './media';
export * from './my-account';
export * from './notifications';
export * from './oauth';
export * from './push-notifications';
export * from './scheduled-statuses';
export * from './search';
export * from './settings';
export * from './statuses';
export * from './timelines';
export * from './trends';

View file

@ -0,0 +1,14 @@
interface ProfileDirectoryParams {
/** Number. Skip the first n results. */
offset?: number;
/** Number. How many accounts to load. Defaults to 40 accounts. Max 80 accounts. */
limit?: number;
/** String. Use active to sort by most recently posted statuses (default) or new to sort by most recently created profiles. */
order?: string;
/** Boolean. If true, returns only local accounts. */
local?: boolean;
}
export type {
ProfileDirectoryParams,
};

View file

@ -0,0 +1,16 @@
import { PaginationParams } from './common';
interface GetInteractionRequestsParams extends PaginationParams {
/** If set, then only interactions targeting the given status_id will be included in the results. */
status_id?: string;
/** If true or not set, pending favourites will be included in the results. At least one of favourites, replies, and reblogs must be true. */
favourites?: boolean;
/** If true or not set, pending replies will be included in the results. At least one of favourites, replies, and reblogs must be true. */
replies?: boolean;
/** If true or not set, pending reblogs will be included in the results. At least one of favourites, replies, and reblogs must be true. */
reblogs?: boolean;
}
export type {
GetInteractionRequestsParams,
};

View file

@ -0,0 +1,19 @@
import type { PaginationParams } from './common';
interface CreateListParams {
/** String. The title of the list to be created. */
title: string;
/** String. One of followed, list, or none. Defaults to list. */
replies_policy?: 'followed' | 'list' | 'none';
/** Boolean. Whether members of this list need to get removed from the “Home” feed */
exclusive?: boolean;
}
type UpdateListParams = CreateListParams;
type GetListAccountsParams = PaginationParams;
export type {
CreateListParams,
UpdateListParams,
GetListAccountsParams,
};

View file

@ -0,0 +1,30 @@
interface UploadMediaParams {
/** Object. The file to be attached, encoded using multipart form data. The file must have a MIME type. */
file: File;
/** Object. The custom thumbnail of the media to be attached, encoded using multipart form data. */
thumbnail?: File;
/** String. A plain-text description of the media, for accessibility purposes. */
description?: string;
/**
* String. Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0. See Focal points for cropping media thumbnails for more information.
* @see {@link https://docs.joinmastodon.org/api/guidelines/#focal-points}
*/
focus?: string;
}
interface UpdateMediaParams {
/** Object. The custom thumbnail of the media to be attached, encoded using multipart form data. */
thumbnail?: File;
/** String. A plain-text description of the media, for accessibility purposes. */
description?: string;
/**
* String. Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0. See Focal points for cropping media thumbnails for more information.
* @see {@link https://docs.joinmastodon.org/api/guidelines/#focal-points}
*/
focus?: string;
}
export type {
UploadMediaParams,
UpdateMediaParams,
};

View file

@ -0,0 +1,31 @@
import type { PaginationParams } from './common';
interface GetBookmarksParams extends PaginationParams {
/**
* Bookmark folder ID
* Requires `features.bookmarkFolders`.
*/
folder_id?: string;
}
type GetFavouritesParams = PaginationParams;
type GetFollowRequestsParams = Omit<PaginationParams, 'min_id'>;
type GetEndorsementsParams = Omit<PaginationParams, 'min_id'>;
type GetFollowedTagsParams = PaginationParams;
interface CreateBookmarkFolderParams {
name: string;
emoji?: string;
}
type UpdateBookmarkFolderParams = Partial<CreateBookmarkFolderParams>;
export type {
GetBookmarksParams,
GetFavouritesParams,
GetFollowRequestsParams,
GetEndorsementsParams,
GetFollowedTagsParams,
CreateBookmarkFolderParams,
UpdateBookmarkFolderParams,
};

View file

@ -0,0 +1,34 @@
import type { PaginationParams } from './common';
interface GetNotificationParams extends PaginationParams {
/** Array of String. Types to include in the result. */
types?: string[];
/** Array of String. Types to exclude from the results. */
exclude_types?: string[];
/** String. Return only notifications received from the specified account. */
account_id?: string;
/**
* will exclude the notifications for activities with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`).
* Requires `features.notificationsExcludeVisibilities`.
*/
exclude_visibilities?: string[];
}
interface UpdateNotificationPolicyRequest {
/** Boolean. Whether to filter notifications from accounts the user is not following. */
filter_not_following?: boolean;
/** Boolean. Whether to filter notifications from accounts that are not following the user. */
filter_not_followers?: boolean;
/** Boolean. Whether to filter notifications from accounts created in the past 30 days. */
filter_new_accounts?: boolean;
/** Boolean. Whether to filter notifications from private mentions. Replies to private mentions initiated by the user, as well as accounts the user follows, are never filtered. */
filter_private_mentions?: boolean;
}
type GetNotificationRequestsParams = PaginationParams;
export type {
GetNotificationParams,
UpdateNotificationPolicyRequest,
GetNotificationRequestsParams,
};

View file

@ -0,0 +1,54 @@
interface OauthAuthorizeParams {
/** String. Should be set equal to `code`. */
response_type: string;
/** String. The client ID, obtained during app registration. */
client_id: string;
/** String. Set a URI to redirect the user to. If this parameter is set to `urn:ietf:wg:oauth:2.0:oob` then the authorization code will be shown instead. Must match one of the `redirect_uris` declared during app registration. */
redirect_uri: string;
/** String. List of requested OAuth scopes, separated by spaces (or by pluses, if using query parameters). Must be a subset of `scopes` declared during app registration. If not provided, defaults to `read`. */
scope?: string;
/** Boolean. Forces the user to re-login, which is necessary for authorizing with multiple accounts from the same instance. */
force_login?: boolean;
/** String. The ISO 639-1 two-letter language code to use while rendering the authorization form. */
lang?: string;
}
interface GetTokenParams {
/** String. Set equal to `authorization_code` if `code` is provided in order to gain user-level access. Otherwise, set equal to `client_credentials` to obtain app-level access only. */
grant_type: string;
/** String. A user authorization code, obtained via [GET /oauth/authorize](https://docs.joinmastodon.org/methods/oauth/#authorize). */
code?: string;
/** String. The client ID, obtained during app registration. */
client_id: string;
/** String. The client secret, obtained during app registration. */
client_secret: string;
/** String. Set a URI to redirect the user to. If this parameter is set to urn:ietf:wg:oauth:2.0:oob then the token will be shown instead. Must match one of the `redirect_uris` declared during app registration. */
redirect_uri: string;
/** String. List of requested OAuth scopes, separated by spaces (or by pluses, if using query parameters). If `code` was provided, then this must be equal to the `scope` requested from the user. Otherwise, it must be a subset of `scopes` declared during app registration. If not provided, defaults to `read`. */
scope?: string;
}
interface RevokeTokenParams {
/** String. The client ID, obtained during app registration. */
client_id: string;
/** String. The client secret, obtained during app registration. */
client_secret: string;
/** String. The previously obtained token, to be invalidated. */
token: string;
}
interface MfaChallengeParams {
client_id: string;
client_secret: string;
/** access token to check second step of mfa */
mfa_token: string;
challenge_type: 'totp' | 'recovery';
code: string;
}
export type {
OauthAuthorizeParams,
GetTokenParams,
RevokeTokenParams,
MfaChallengeParams,
};

View file

@ -0,0 +1,30 @@
interface CreatePushNotificationsSubscriptionParams {
subscription: {
/** String. The endpoint URL that is called when a notification event occurs. */
endpoint: string;
keys?: {
/** String. User agent public key. Base64 encoded string of a public key from a ECDH keypair using the prime256v1 curve. */
p256dh: string;
/** String. Auth secret. Base64 encoded string of 16 bytes of random data. */
auth: string;
};
};
data?: {
alerts?: Record<string, boolean>;
/** String. Specify whether to receive push notifications from `all`, `followed`, `follower`, or `none` users. */
policy?: string;
};
}
interface UpdatePushNotificationsSubscriptionParams {
data?: {
alerts?: Record<string, boolean>;
};
/** String. Specify whether to receive push notifications from `all`, `followed`, `follower`, or `none` users. */
policy?: string;
}
export type {
CreatePushNotificationsSubscriptionParams,
UpdatePushNotificationsSubscriptionParams,
};

View file

@ -0,0 +1,7 @@
import type { PaginationParams } from './common';
type GetScheduledStatusesParams = PaginationParams;
export type {
GetScheduledStatusesParams,
};

Some files were not shown because too many files have changed in this diff Show more