ligmo/api/main.tsp
Branden J Brown 1d659d8d95 api: webp instead of avif
There are no options for encoding animations to AVIF in Go. We'll start
with WebP instead.

Yeah this is largely unrelated to the PR but it's fine.
2025-05-27 17:42:05 -04:00

388 lines
9.7 KiB
Plaintext

import "@typespec/http";
import "@typespec/openapi";
import "@typespec/versioning";
import "./models.tsp";
using Http;
using OpenAPI;
using Versioning;
using Ligmotes.Models;
@service(#{ title: "Ligmotes API" })
@server(
"https://api.ligmotes.com/{version}",
"Ligmotes API Server",
{
version: Versions,
}
)
@versioned(Versions)
namespace Ligmotes;
enum Versions {
v1,
}
/**
* The Ligmotes Read API for general use.
*/
@useAuth(NoAuth)
namespace Read {
/** Requested emotes. */
model EmoteResp
is Success<{
/** Information about emotes that were found. */
found: Emote[];
/** IDs that were not found. */
error?: id[];
}>;
/** Requested user. */
model UserResp is Success<User>;
/** Requested emote set. */
model SetResp is Success<EmoteSet>;
/** A user requested by ID or connected platform ID is not found. */
model UserNotFound is Error<404>;
/** An emote set requested by ID is not found. */
model SetNotFound is Error<404>;
/** Get emote data by IDs. */
@get
@operationId("GetEmotes")
@route("/emotes")
op emote(
@query(#{ name: "id", explode: true })
@maxItems(100)
emoteID: id[],
): EmoteResp;
/**
* Get user data by their Ligmotes ID.
* The emote sets returned in this response may have truncated contents.
*/
@get
@operationId("GetUser")
@route("/users/{userID}")
op user(@path userID: id): UserResp | UserNotFound;
/** Get the contents of an emote set. */
@get
@operationId("GetSet")
@route("/sets/{setID}")
op set(@path setID: id, @query mediaType?: mediaType = mediaType.webp): SetResp | SetNotFound;
/**
* Operations for connected platforms.
* These operations specify the platform of the connection and a user ID
* on that platform.
*/
@route("/platforms/{platform}/connections/{connectedID}")
namespace Platforms {
/**
* Parameters common to all platform-related operations (aside from auth).
*/
model platformParams {
@path platform: platform;
@path connectedID: string;
}
/** Get user data by their ID on a connected platform. */
@get
@operationId("GetPlatformUser")
@route("/user")
op user(...platformParams): UserResp | UserNotFound;
/** Get a user's active emote set for a connection by their ID there. */
@get
@operationId("GetPlatformSet")
@route("/set")
op set(...platformParams, @query mediaType?: mediaType = mediaType.webp): SetResp | SetNotFound;
/**
* Report used emotes.
* This always responds with No Content, regardless of whether the update
* is accepted, parameters are invalid, &c.
*/
@post
@operationId("ReportPlatformUse")
@route("/emotes")
op report(
...platformParams,
@body
body: {
/**
* Emote IDs used.
* IDs should be deduplicated.
*/
@maxItems(250)
emotes: id[];
},
): NoContentResponse;
}
}
/**
* The Ligmotes Emote Management API.
*/
@useAuth(BearerAuth)
namespace EmoteManagement {
/**
* Image types accepted in uploads.
*/
model Image is File<"image/png" | "image/jpeg" | "image/gif" | "image/webp" | "image/avif">;
/** Created emote. */
model CreatedEmote is Success<Emote, 202>;
/** Updated emote. */
model UpdatedEmote is Success<Emote>;
/** Request body contained an emote image which was too large. */
@error
model ErrExcessiveUpload is Error<413>;
/**
* Upload an emote.
*/
@post
@operationId("CreateEmote")
@route("/emotes")
op new(
@header contentType: "multipart/form-data",
@multipartBody fields: {
meta: HttpPart<Create<Emote> & {
@header contentType: "application/json";
}>;
image: HttpPart<Image>;
},
): CreatedEmote | ErrBadParameters | ErrAuth | ErrExcessiveUpload;
/**
* Modify an existing emote.
*/
@patch(#{ implicitOptionality: false })
@operationId("UpdateEmote")
@route("/emotes/{emoteID}")
op update(@path emoteID: id, @body emote: Emote): UpdatedEmote | ErrBadParameters | ErrAuth;
/**
* Delete an existing emote.
*/
@delete
@operationId("DeleteEmote")
@route("/emotes/{emoteID}")
op delete(@path emoteID: id): NoContentResponse | ErrAuth;
}
/**
* The Ligmotes User Management API.
*/
@useAuth(BearerAuth)
namespace UserManagement {
// NOTE(zephyr): creating users goes through the auth api, not this
/**
* Update a user's name or picture.
* Omitting the picture field in the request body causes the user's picture
* to be removed.
*/
@patch(#{ implicitOptionality: false })
@operationId("UpdateUser")
@route("/users/{userID}")
op update(@path userID: id, @body user: User): Success<User> | ErrAuth | ErrBadParameters | ErrNotFound;
/**
* Soft-delete a user.
*
* Typically called by a user for themselves; moderators would use the
* /moderate/user API to ban them instead.
* This preserves the user's uploaded emotes as well as any moderation
* reviews they may have performed.
*/
@delete
@operationId("DeleteUser")
@route("/users/{userID}")
op delete(@path userID: id): Success<User> | ErrAuth | ErrNotFound;
}
/**
* The Ligmotes Chat Management API.
* Relates to managing emote sets and connections.
*/
@useAuth(BearerAuth)
namespace ChatManagement {
/**
* ID and name of an emote within a set.
* The same ID can appear multiple times in one set, but names are unique.
*/
model SetEmote {
id: id;
name: string;
/** RenameEmote may specify a new name for an existing emote in the set. */
rename?: string;
}
/**
* Create a new emote set.
* Note that creating a user should automatically create their first emote
* set as well, without needing a call to this operation.
*/
@post
@operationId("CreateEmoteSet")
@route("/sets")
op create(@body emoteSet: EmoteSet): Success<EmoteSet> | ErrAuth | ErrBadParameters;
/** Update the name of an existing emote set. */
@patch(#{ implicitOptionality: false })
@operationId("UpdateEmoteSet")
@route("/sets/{setID}")
op update(@path setID: id, @body emoteSet: EmoteSet): Success<EmoteSet> | ErrAuth | ErrBadParameters | ErrNotFound;
// NOTE(zephyr): We do not allow deleting emote sets.
// That would enable many techniques to circumvent limits.
/** Add an emote to an emote set. */
// TODO(zephyr): is returning the entire updated emote set too expensive?
@post
@operationId("AddEmote")
@route("/sets/{setID}/emotes")
op addEmote(@path setID: id, @body emote: SetEmote):
| Success<EmoteSet>
| ErrAuth
| ErrBadParameters
| ErrNotFound
| ErrDuplicate;
/** Rename an emote that is already in an emote set. */
@patch(#{ implicitOptionality: false })
@operationId("RenameEmote")
@route("/sets/{setID}/emotes")
op renameEmote(@path setID: id, @body emote: SetEmote):
| Success<EmoteSet>
| ErrAuth
| ErrBadParameters
| ErrNotFound
| ErrDuplicate;
/**
* Remove an emote from an emote set.
* This performs a soft-delete of the association between the two.
*/
@delete
@operationId("RemoveEmote")
@route("/sets/{setID}/emotes")
op removeEmote(@path setID: id, @body emote: SetEmote): Success<EmoteSet> | ErrAuth | ErrNotFound;
/** Give a user editor permission for an emote set. */
@post
@operationId("AddEditor")
@route("/sets/{setID}/editors")
op addEditor(
@path setID: id,
@body editor: {
userID: id;
},
): Success<EmoteSet> | ErrAuth | ErrNotFound | ErrDuplicate;
/** Remove a user's permission to edit an emote set. */
@delete
@operationId("RemoveEditor")
@route("/sets/{setID}/editors")
op removeEditor(
@path setID: id,
@body editor: {
userID: id;
},
): Success<EmoteSet> | ErrAuth | ErrNotFound;
/** Get emote sets that the user is allowed to edit. */
// TODO(zephyr): we should report this as a field on emote set objects as well
@get
@operationId("ListEditableSets")
@route("/users/{userID}/sets")
op listEditableSets(@path userID: id): Success<EmoteSet[]> | ErrAuth | ErrBadParameters;
/** Set an emote set as active for a connection. */
@patch(#{ implicitOptionality: false })
@operationId("UpdateActiveSet")
@route("/platforms/{platform}/connections/{connectedID}/set")
op updateActiveSet(
@path platform: platform,
@path connectedID: string,
@body update: {
setID: id;
},
): Success<EmoteSet> | ErrAuth | ErrBadParameters;
}
/**
* Moderation API.
*/
@useAuth(BearerAuth)
@route("/moderate")
namespace Moderation {
/** Request body for updating emote approval status. */
model EmoteStatus {
approved: boolean;
}
/** Options for deleting an emote. */
model EmoteDeleteOptions {
deleteMedia?: boolean;
}
/**
* List a selection of emotes awaiting moderator review.
* NOTE(zephyr): Unlike other APIs, this returns the emotes directly.
* We want all the information for reviewing, and a bit extra latency for
* the join is less of an issue than it is in chat.
*/
@get
@operationId("ListUnreviewedEmotes")
@route("/emotes")
op listUnreviewed(@query limit?: integer = 100): Success<Emote[]> | ErrAuth;
/**
* Update an emote's approval status.
*/
@post
@operationId("UpdateEmoteStatus")
@route("/emotes/{emoteID}")
op updateEmoteStatus(@path emoteID: id, @body status: EmoteStatus):
| Success<Emote>
| ErrAuth
| ErrBadParameters
| ErrNotFound;
/**
* Fully remove an emote, e.g. for abusive or illegal content.
*
* This is a hard delete.
* Typically, a call to it should have an accompanying call to ban the user.
*/
@delete
@operationId("HardDeleteEmote")
@route("/emotes/{emoteID}")
op hardDeleteEmote(@path emoteID: id, @body options?: EmoteDeleteOptions):
| NoContentResponse
| ErrAuth
| ErrBadParameters
| ErrNotFound;
/**
* Ban a user for a given reason.
* This revokes all the user's current sessions, removes their active emote
* sets, and enforces the restrictions described on UserBan.
*/
@post
@operationId("Ban")
@route("/users/{userID}")
op ban(@path userID: id, @body details: UserBan): Success<User> | ErrAuth | ErrBadParameters | ErrNotFound;
}