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

@ -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,
};

View File

@ -0,0 +1,20 @@
import type { PaginationParams, WithRelationshipsParam } from './common';
interface SearchParams extends Exclude<PaginationParams, 'since_id'>, WithRelationshipsParam {
/** String. Specify whether to search for only `accounts`, `hashtags`, `statuses` */
type?: 'accounts' | 'hashtags' | 'statuses' | 'groups';
/** Boolean. Only relevant if `type` includes `accounts`. If `true` and (a) the search query is for a remote account (e.g., `someaccount@someother.server`) and (b) the local server does not know about the account, WebFinger is used to try and resolve the account at `someother.server`. This provides the best recall at higher latency. If `false` only accounts the server knows about are returned. */
resolve?: boolean;
/** Boolean. Only include accounts that the user is following? Defaults to false. */
following?: boolean;
/** String. If provided, will only return statuses authored by this account. */
account_id?: string;
/** Boolean. Filter out unreviewed tags? Defaults to false. Use true when trying to find trending tags. */
exclude_unreviewed?: boolean;
/** Integer. Skip the first n results. */
offset?: number;
}
export type {
SearchParams,
};

View File

@ -0,0 +1,146 @@
interface CreateAccountParams {
/** String. The desired username for the account */
username: string;
/** String. The email address to be used for login */
email: string;
/** String. The password to be used for login */
password: string;
/** Whether the user agrees to the local rules, terms, and policies. These should be presented to the user in order to allow them to consent before setting this parameter to TRUE. */
agreement: boolean;
/** String. The language of the confirmation email that will be sent. */
locale: string;
/** String. If registrations require manual approval, this text will be reviewed by moderators. */
reason?: string;
fullname?: string;
bio?: string;
/** optional, contains provider-specific captcha solution */
captcha_solution?: string;
/** optional, contains provider-specific captcha token */
captcha_token?: string;
/** optional, contains provider-specific captcha data */
captcha_answer_data?: string;
/** invite token required when the registrations aren't public. */
token?: string;
birthday?: string;
/** optional, domain id, if multitenancy is enabled. */
domain?: string;
accepts_email_list?: boolean;
}
interface UpdateCredentialsParams {
/** String. The display name to use for the profile. */
display_name?: string;
/** String. The account bio. */
note?: string;
/** Avatar image encoded using `multipart/form-data` */
avatar?: File | '';
/** Header image encoded using `multipart/form-data` */
header?: File | '';
/** Boolean. Whether manual approval of follow requests is required. */
locked?: boolean;
/** Boolean. Whether the account has a bot flag. */
bot?: boolean;
/** Boolean. Whether the account should be shown in the profile directory. */
discoverable?: boolean;
/** Boolean. Whether to hide followers and followed accounts. */
hide_collections?: boolean;
/** Boolean. Whether public posts should be searchable to anyone. */
indexable?: boolean;
/** Hash. The profile fields to be set. Inside this hash, the key is an integer cast to a string (although the exact integer does not matter), and the value is another hash including name and value. By default, max 4 fields. */
fields_attributes?: Array<{
/** String. The name of the profile field. By default, max 255 characters. */
name: string;
/** String. The value of the profile field. By default, max 255 characters. */
value: string;
}>;
source?: {
/** String. Default post privacy for authored statuses. Can be public, unlisted, or private. */
privacy?: string;
/** Boolean. Whether to mark authored statuses as sensitive by default. */
sensitive?: string;
/** String. Default language to use for authored statuses (ISO 6391) */
language?: string;
};
/** if true, html tags are stripped from all statuses requested from the API */
no_rich_text?: boolean;
/** if true, user's followers will be hidden*/
hide_followers?: boolean;
/** if true, user's follows will be hidden */
hide_follows?: boolean;
/** if true, user's follower count will be hidden */
hide_followers_count?: boolean;
/** if true, user's follow count will be hidden */
hide_follows_count?: boolean;
/** if true, user's favorites timeline will be hidden */
hide_favorites?: boolean;
/** if true, user's role (e.g admin, moderator) will be exposed to anyone in the API */
show_role?: boolean;
/** the scope returned under privacy key in Source subentity */
default_scope?: string;
/** Opaque user settings to be saved on the backend. */
settings_store?: Record<string, any>;
/** if true, skip filtering out broken threads */
skip_thread_containment?: boolean;
/** if true, allows automatically follow moved following accounts */
allow_following_move?: boolean;
/** array of ActivityPub IDs, needed for following move */
also_known_as?: string[];
/** sets the background image of the user. Can be set to "" (an empty string) to reset. */
background_image?: string;
/** the type of this account. */
actor_type?: string;
/** if false, this account will reject all chat messages. */
accepts_chat_messages?: boolean;
/** user's preferred language for receiving emails (digest, confirmation, etc.) */
language?: string;
/**
* Description of avatar image, for alt-text.
* Requires `features.accountAvatarDescription`.
*/
avatar_description?: boolean;
/**
* Description of header image, for alt-text.
* Requires `features.accountAvatarDescription`.
*/
header_description?: boolean;
/**
* Enable RSS feed for this account's Public posts at `/[username]/feed.rss`
* Requires `features.accountEnableRss`.
*/
enable_rss?: boolean;
}
interface UpdateNotificationSettingsParams {
/**
* blocks notifications from accounts you do not follow
*/
block_from_strangers?: boolean;
/**
* When set to true, it removes the contents of a message from the push notification.
*/
hide_notification_contents?: boolean;
}
type UpdateInteractionPoliciesParams = Record<
'public' | 'unlisted' | 'private' | 'direct',
Record<
'can_favourite' | 'can_reblog' | 'can_reply',
Record<
'always' | 'with_approval',
Array<'public' | 'followers' | 'following' | 'mutuals' | 'mentioned' | 'author' | 'me' | string>
>
>
>;
export type {
CreateAccountParams,
UpdateCredentialsParams,
UpdateNotificationSettingsParams,
UpdateInteractionPoliciesParams,
};

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