{
  "package": "sblurp.v1",
  "docs": "https://sblurp.chat/docs/api",
  "services": [
    {
      "name": "ServerService",
      "path": "/sblurp.v1.ServerService/",
      "rpcs": [
        {
          "name": "GetServerInfo",
          "request": "GetServerInfoRequest",
          "response": "GetServerInfoResponse"
        },
        {
          "name": "Join",
          "request": "JoinRequest",
          "response": "JoinResponse"
        },
        {
          "name": "GetMe",
          "request": "GetMeRequest",
          "response": "GetMeResponse"
        },
        {
          "name": "UpdateProfile",
          "request": "UpdateProfileRequest",
          "response": "UpdateProfileResponse"
        },
        {
          "name": "GetState",
          "request": "GetStateRequest",
          "response": "GetStateResponse"
        },
        {
          "name": "Subscribe",
          "request": "SubscribeRequest",
          "response": "Event",
          "server_stream": true,
          "doc": "Server stream. Browsers support server streams over Connect; there is\ndeliberately no bidirectional stream. Clients send with unary calls."
        },
        {
          "name": "SetMemberRole",
          "request": "SetMemberRoleRequest",
          "response": "SetMemberRoleResponse"
        },
        {
          "name": "LeaveCommunity",
          "request": "LeaveCommunityRequest",
          "response": "LeaveCommunityResponse"
        },
        {
          "name": "KickMember",
          "request": "KickMemberRequest",
          "response": "KickMemberResponse"
        },
        {
          "name": "BanMember",
          "request": "BanMemberRequest",
          "response": "BanMemberResponse"
        },
        {
          "name": "ListBans",
          "request": "ListBansRequest",
          "response": "ListBansResponse"
        },
        {
          "name": "UnbanMember",
          "request": "UnbanMemberRequest",
          "response": "UnbanMemberResponse"
        },
        {
          "name": "CreateInvite",
          "request": "CreateInviteRequest",
          "response": "CreateInviteResponse"
        },
        {
          "name": "ListInvites",
          "request": "ListInvitesRequest",
          "response": "ListInvitesResponse"
        },
        {
          "name": "RevokeInvite",
          "request": "RevokeInviteRequest",
          "response": "RevokeInviteResponse"
        },
        {
          "name": "UpdateServerSettings",
          "request": "UpdateServerSettingsRequest",
          "response": "UpdateServerSettingsResponse"
        },
        {
          "name": "CreateRole",
          "request": "CreateRoleRequest",
          "response": "CreateRoleResponse"
        },
        {
          "name": "UpdateRole",
          "request": "UpdateRoleRequest",
          "response": "UpdateRoleResponse"
        },
        {
          "name": "DeleteRole",
          "request": "DeleteRoleRequest",
          "response": "DeleteRoleResponse"
        },
        {
          "name": "SetMemberRoles",
          "request": "SetMemberRolesRequest",
          "response": "SetMemberRolesResponse"
        },
        {
          "name": "DeleteEmoji",
          "request": "DeleteEmojiRequest",
          "response": "DeleteEmojiResponse"
        },
        {
          "name": "BlockMember",
          "request": "BlockMemberRequest",
          "response": "BlockMemberResponse"
        },
        {
          "name": "UnblockMember",
          "request": "UnblockMemberRequest",
          "response": "UnblockMemberResponse"
        },
        {
          "name": "MuteMember",
          "request": "MuteMemberRequest",
          "response": "MuteMemberResponse"
        },
        {
          "name": "UnmuteMember",
          "request": "UnmuteMemberRequest",
          "response": "UnmuteMemberResponse"
        },
        {
          "name": "ReportMessage",
          "request": "ReportMessageRequest",
          "response": "ReportMessageResponse"
        },
        {
          "name": "ListReports",
          "request": "ListReportsRequest",
          "response": "ListReportsResponse"
        },
        {
          "name": "ResolveReport",
          "request": "ResolveReportRequest",
          "response": "ResolveReportResponse"
        }
      ]
    },
    {
      "name": "ChatService",
      "path": "/sblurp.v1.ChatService/",
      "rpcs": [
        {
          "name": "CreateChannel",
          "request": "CreateChannelRequest",
          "response": "CreateChannelResponse"
        },
        {
          "name": "UpdateChannel",
          "request": "UpdateChannelRequest",
          "response": "UpdateChannelResponse"
        },
        {
          "name": "OpenDM",
          "request": "OpenDMRequest",
          "response": "OpenDMResponse"
        },
        {
          "name": "ListMessages",
          "request": "ListMessagesRequest",
          "response": "ListMessagesResponse"
        },
        {
          "name": "SendMessage",
          "request": "SendMessageRequest",
          "response": "SendMessageResponse"
        },
        {
          "name": "EditMessage",
          "request": "EditMessageRequest",
          "response": "EditMessageResponse"
        },
        {
          "name": "DeleteMessage",
          "request": "DeleteMessageRequest",
          "response": "DeleteMessageResponse"
        },
        {
          "name": "React",
          "request": "ReactRequest",
          "response": "ReactResponse"
        },
        {
          "name": "MarkRead",
          "request": "MarkReadRequest",
          "response": "MarkReadResponse"
        },
        {
          "name": "SetTyping",
          "request": "SetTypingRequest",
          "response": "SetTypingResponse"
        },
        {
          "name": "Search",
          "request": "SearchRequest",
          "response": "SearchResponse"
        },
        {
          "name": "GetLinkPreviews",
          "request": "GetLinkPreviewsRequest",
          "response": "GetLinkPreviewsResponse"
        },
        {
          "name": "AddChannelMembers",
          "request": "AddChannelMembersRequest",
          "response": "AddChannelMembersResponse"
        },
        {
          "name": "RemoveChannelMember",
          "request": "RemoveChannelMemberRequest",
          "response": "RemoveChannelMemberResponse"
        },
        {
          "name": "SetChannelAccess",
          "request": "SetChannelAccessRequest",
          "response": "SetChannelAccessResponse"
        },
        {
          "name": "BrowseChannels",
          "request": "BrowseChannelsRequest",
          "response": "BrowseChannelsResponse"
        },
        {
          "name": "JoinChannel",
          "request": "JoinChannelRequest",
          "response": "JoinChannelResponse"
        },
        {
          "name": "LeaveChannel",
          "request": "LeaveChannelRequest",
          "response": "LeaveChannelResponse"
        }
      ]
    },
    {
      "name": "BotService",
      "path": "/sblurp.v1.BotService/",
      "rpcs": [
        {
          "name": "CreateBot",
          "request": "CreateBotRequest",
          "response": "CreateBotResponse"
        },
        {
          "name": "ListBots",
          "request": "ListBotsRequest",
          "response": "ListBotsResponse"
        },
        {
          "name": "RotateBotToken",
          "request": "RotateBotTokenRequest",
          "response": "RotateBotTokenResponse"
        },
        {
          "name": "DeleteBot",
          "request": "DeleteBotRequest",
          "response": "DeleteBotResponse"
        },
        {
          "name": "ListPlugins",
          "request": "ListPluginsRequest",
          "response": "ListPluginsResponse"
        },
        {
          "name": "InstallPlugin",
          "request": "InstallPluginRequest",
          "response": "InstallPluginResponse"
        },
        {
          "name": "SetPluginEnabled",
          "request": "SetPluginEnabledRequest",
          "response": "SetPluginEnabledResponse"
        },
        {
          "name": "DeletePlugin",
          "request": "DeletePluginRequest",
          "response": "DeletePluginResponse"
        }
      ]
    }
  ],
  "messages": [
    {
      "name": "Member",
      "fields": [
        {
          "name": "id",
          "number": 1,
          "type": "string",
          "doc": "UUIDv7, local to this server"
        },
        {
          "name": "kind",
          "number": 2,
          "type": "MemberKind"
        },
        {
          "name": "role",
          "number": 3,
          "type": "MemberRole"
        },
        {
          "name": "username",
          "number": 4,
          "type": "string",
          "doc": "unique on this server"
        },
        {
          "name": "display_name",
          "number": 5,
          "type": "string"
        },
        {
          "name": "avatar_url",
          "number": 6,
          "type": "string",
          "doc": "may be empty"
        },
        {
          "name": "bio",
          "number": 7,
          "type": "string"
        },
        {
          "name": "presence",
          "number": 8,
          "type": "Presence"
        },
        {
          "name": "joined_at_ms",
          "number": 9,
          "type": "int64"
        },
        {
          "name": "status_text",
          "number": 10,
          "type": "string",
          "doc": "short custom status, may be empty"
        },
        {
          "name": "status_emoji",
          "number": 11,
          "type": "string"
        },
        {
          "name": "role_ids",
          "number": 12,
          "type": "string",
          "label": "repeated",
          "doc": "custom roles (visibility only), see Role"
        }
      ]
    },
    {
      "name": "Role",
      "doc": "A custom role, made by admins. Roles only decide which text channels a\nmember can see (Channel.role_ids). MemberRole still decides permissions;\ncall that one \"Permission\" in the UI and these \"Roles\".",
      "fields": [
        {
          "name": "id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "name",
          "number": 2,
          "type": "string",
          "doc": "unique, case-insensitive"
        },
        {
          "name": "color",
          "number": 3,
          "type": "string",
          "doc": "\"#rrggbb\" or empty"
        },
        {
          "name": "position",
          "number": 4,
          "type": "int32",
          "doc": "sort order, low first"
        }
      ]
    },
    {
      "name": "Channel",
      "fields": [
        {
          "name": "id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "kind",
          "number": 2,
          "type": "ChannelKind"
        },
        {
          "name": "name",
          "number": 3,
          "type": "string",
          "doc": "empty for DMs"
        },
        {
          "name": "topic",
          "number": 4,
          "type": "string"
        },
        {
          "name": "category",
          "number": 5,
          "type": "string",
          "doc": "sidebar section, e.g. \"General\"; empty = none"
        },
        {
          "name": "position",
          "number": 6,
          "type": "int32",
          "doc": "sort order inside the category"
        },
        {
          "name": "member_ids",
          "number": 7,
          "type": "string",
          "label": "repeated",
          "doc": "set for DM and PRIVATE channels"
        },
        {
          "name": "created_at_ms",
          "number": 8,
          "type": "int64"
        },
        {
          "name": "last_message_seq",
          "number": 9,
          "type": "int64",
          "doc": "seq of the newest message, 0 if none"
        },
        {
          "name": "role_ids",
          "number": 10,
          "type": "string",
          "label": "repeated",
          "doc": "TEXT only. Empty: every member can see the channel. Otherwise only\nmembers with one of these roles (and admins and the owner) can."
        },
        {
          "name": "is_default",
          "number": 11,
          "type": "bool",
          "doc": "TEXT only: OPT_IN mode joins new members to it"
        },
        {
          "name": "member_count",
          "number": 12,
          "type": "int32",
          "doc": "set by BrowseChannels only; 0 elsewhere"
        },
        {
          "name": "header_image",
          "number": 13,
          "type": "Attachment",
          "doc": "optional image behind the channel header"
        }
      ]
    },
    {
      "name": "Attachment",
      "fields": [
        {
          "name": "id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "name",
          "number": 2,
          "type": "string"
        },
        {
          "name": "content_type",
          "number": 3,
          "type": "string"
        },
        {
          "name": "size",
          "number": 4,
          "type": "int64"
        },
        {
          "name": "url",
          "number": 5,
          "type": "string",
          "doc": "relative to the server base URL"
        },
        {
          "name": "width",
          "number": 6,
          "type": "int32",
          "doc": "images only"
        },
        {
          "name": "height",
          "number": 7,
          "type": "int32"
        }
      ]
    },
    {
      "name": "Reaction",
      "fields": [
        {
          "name": "emoji",
          "number": 1,
          "type": "string",
          "doc": "unicode emoji or \":shortcode:\""
        },
        {
          "name": "member_ids",
          "number": 2,
          "type": "string",
          "label": "repeated"
        }
      ]
    },
    {
      "name": "Message",
      "fields": [
        {
          "name": "id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "channel_id",
          "number": 2,
          "type": "string"
        },
        {
          "name": "author_id",
          "number": 3,
          "type": "string"
        },
        {
          "name": "text",
          "number": 4,
          "type": "string",
          "doc": "markdown subset: bold, italic, code, links, mentions \u003c@member_id\u003e"
        },
        {
          "name": "created_at_ms",
          "number": 5,
          "type": "int64"
        },
        {
          "name": "edited_at_ms",
          "number": 6,
          "type": "int64",
          "doc": "0 if never edited"
        },
        {
          "name": "deleted",
          "number": 7,
          "type": "bool"
        },
        {
          "name": "thread_root_id",
          "number": 8,
          "type": "string",
          "doc": "set when this message is a thread reply"
        },
        {
          "name": "reply_count",
          "number": 9,
          "type": "int32",
          "doc": "on a thread root"
        },
        {
          "name": "last_reply_at_ms",
          "number": 10,
          "type": "int64"
        },
        {
          "name": "reactions",
          "number": 11,
          "type": "Reaction",
          "label": "repeated"
        },
        {
          "name": "attachments",
          "number": 12,
          "type": "Attachment",
          "label": "repeated"
        },
        {
          "name": "client_id",
          "number": 13,
          "type": "string",
          "doc": "echo of SendMessageRequest.client_id, for optimistic UI"
        },
        {
          "name": "seq",
          "number": 14,
          "type": "int64",
          "doc": "event seq that last changed this message (edits, reactions, replies bump it)"
        },
        {
          "name": "created_seq",
          "number": 15,
          "type": "int64",
          "doc": "seq of message_created; never changes. Compare this with last_read_seq."
        }
      ]
    },
    {
      "name": "ReadState",
      "fields": [
        {
          "name": "channel_id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "last_read_seq",
          "number": 2,
          "type": "int64"
        },
        {
          "name": "unread_count",
          "number": 3,
          "type": "int32"
        },
        {
          "name": "mention_count",
          "number": 4,
          "type": "int32"
        }
      ]
    },
    {
      "name": "GetServerInfoRequest",
      "fields": []
    },
    {
      "name": "GetServerInfoResponse",
      "fields": [
        {
          "name": "name",
          "number": 1,
          "type": "string"
        },
        {
          "name": "description",
          "number": 2,
          "type": "string",
          "doc": "Markdown, shown in the sidebar"
        },
        {
          "name": "icon_url",
          "number": 3,
          "type": "string"
        },
        {
          "name": "protocol_version",
          "number": 4,
          "type": "int32",
          "doc": "currently 1"
        },
        {
          "name": "trusted_issuers",
          "number": 5,
          "type": "string",
          "label": "repeated",
          "doc": "auth issuers this server accepts, e.g. \"https://sblurp.chat\""
        },
        {
          "name": "base_url",
          "number": 6,
          "type": "string",
          "doc": "canonical URL; also the JWT audience"
        },
        {
          "name": "member_count",
          "number": 7,
          "type": "int32"
        },
        {
          "name": "accent_color",
          "number": 8,
          "type": "string",
          "doc": "optional \"#rrggbb\" for the rail icon"
        },
        {
          "name": "invite_only",
          "number": 9,
          "type": "bool",
          "doc": "Join needs a valid invite_code"
        },
        {
          "name": "join_questions",
          "number": 10,
          "type": "string",
          "label": "repeated",
          "doc": "Questions a plugin asks people who join; show each with a text field\nand send the answers in JoinRequest.answers, in the same order."
        },
        {
          "name": "banner",
          "number": 11,
          "type": "Attachment",
          "doc": "optional image above the name in the sidebar"
        },
        {
          "name": "retention_days",
          "number": 12,
          "type": "int32",
          "doc": "Message retention. 0 = keep forever. Otherwise the server deletes\nmessages (with their thread replies, reactions and files) older than this\nmany days, and ListMessages stops returning them. Clients hide loaded\nmessages older than now minus this many days; no per-message events."
        },
        {
          "name": "needs_setup",
          "number": 13,
          "type": "bool",
          "doc": "A brand-new community whose owner has not finished or skipped the setup\nwizard yet. Clients show the owner the wizard; others ignore it."
        }
      ]
    },
    {
      "name": "JoinRequest",
      "fields": [
        {
          "name": "display_name",
          "number": 1,
          "type": "string",
          "doc": "Profile fields the user agreed to share with this community."
        },
        {
          "name": "avatar_url",
          "number": 2,
          "type": "string"
        },
        {
          "name": "bio",
          "number": 3,
          "type": "string"
        },
        {
          "name": "invite_code",
          "number": 4,
          "type": "string",
          "doc": "checked only when the server is invite_only; case-insensitive"
        },
        {
          "name": "answers",
          "number": 5,
          "type": "string",
          "label": "repeated",
          "doc": "to GetServerInfoResponse.join_questions, in order"
        }
      ]
    },
    {
      "name": "JoinResponse",
      "fields": [
        {
          "name": "member",
          "number": 1,
          "type": "Member"
        },
        {
          "name": "created",
          "number": 2,
          "type": "bool",
          "doc": "false if already a member"
        }
      ]
    },
    {
      "name": "GetMeRequest",
      "fields": []
    },
    {
      "name": "GetMeResponse",
      "fields": [
        {
          "name": "member",
          "number": 1,
          "type": "Member"
        },
        {
          "name": "server_version",
          "number": 2,
          "type": "string",
          "doc": "The next three are for admins and the owner only; empty for others.\n\"1.2.3\", \"1.3.0-beta.1\", \"nightly-20261016-abc1234\" or \"dev\""
        },
        {
          "name": "server_track",
          "number": 3,
          "type": "string",
          "doc": "\"stable\", \"beta\" or \"nightly\""
        },
        {
          "name": "update_available",
          "number": 4,
          "type": "string",
          "doc": "a newer version on server_track, or \"\""
        }
      ]
    },
    {
      "name": "UpdateProfileRequest",
      "fields": [
        {
          "name": "display_name",
          "number": 1,
          "type": "string",
          "label": "optional"
        },
        {
          "name": "avatar_url",
          "number": 2,
          "type": "string",
          "label": "optional"
        },
        {
          "name": "bio",
          "number": 3,
          "type": "string",
          "label": "optional"
        },
        {
          "name": "status_text",
          "number": 4,
          "type": "string",
          "label": "optional"
        },
        {
          "name": "status_emoji",
          "number": 5,
          "type": "string",
          "label": "optional"
        }
      ]
    },
    {
      "name": "UpdateProfileResponse",
      "fields": [
        {
          "name": "member",
          "number": 1,
          "type": "Member"
        }
      ]
    },
    {
      "name": "GetStateRequest",
      "doc": "Everything a client needs to render the sidebar after connecting.",
      "fields": []
    },
    {
      "name": "GetStateResponse",
      "fields": [
        {
          "name": "me",
          "number": 1,
          "type": "Member"
        },
        {
          "name": "channels",
          "number": 2,
          "type": "Channel",
          "label": "repeated",
          "doc": "channels I am in (my sidebar), incl. DMs"
        },
        {
          "name": "members",
          "number": 3,
          "type": "Member",
          "label": "repeated"
        },
        {
          "name": "read_states",
          "number": 4,
          "type": "ReadState",
          "label": "repeated"
        },
        {
          "name": "seq",
          "number": 5,
          "type": "int64",
          "doc": "server seq at snapshot time; Subscribe from here"
        },
        {
          "name": "roles",
          "number": 6,
          "type": "Role",
          "label": "repeated",
          "doc": "sorted by position"
        },
        {
          "name": "channel_mode",
          "number": 7,
          "type": "ChannelMode"
        },
        {
          "name": "blocked_member_ids",
          "number": 8,
          "type": "string",
          "label": "repeated",
          "doc": "members I blocked, see BlockMember"
        },
        {
          "name": "muted_member_ids",
          "number": 9,
          "type": "string",
          "label": "repeated",
          "doc": "members I muted, see MuteMember"
        }
      ]
    },
    {
      "name": "SubscribeRequest",
      "fields": [
        {
          "name": "after_seq",
          "number": 1,
          "type": "int64",
          "doc": "Last seq the client has applied. The server first replays every event\nafter it (that the caller may see), then streams live events.\nIf the gap is too large the server sends Event.resync and the client\nmust call GetState again."
        }
      ]
    },
    {
      "name": "TypingEvent",
      "fields": [
        {
          "name": "channel_id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "member_id",
          "number": 2,
          "type": "string"
        },
        {
          "name": "thread_root_id",
          "number": 3,
          "type": "string"
        }
      ]
    },
    {
      "name": "PresenceEvent",
      "fields": [
        {
          "name": "member_id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "presence",
          "number": 2,
          "type": "Presence"
        }
      ]
    },
    {
      "name": "PresenceBatch",
      "doc": "Many presence changes at once, the latest per member. The server sends\nthis instead of single presence events when several are waiting (a\nreconnect burst after a restart).",
      "fields": [
        {
          "name": "changes",
          "number": 1,
          "type": "PresenceEvent",
          "label": "repeated"
        }
      ]
    },
    {
      "name": "Event",
      "fields": [
        {
          "name": "seq",
          "number": 1,
          "type": "int64",
          "doc": "0 for ephemeral events (typing, presence, heartbeat)"
        },
        {
          "name": "at_ms",
          "number": 2,
          "type": "int64"
        },
        {
          "name": "message_created",
          "number": 10,
          "type": "Message",
          "oneof": "kind"
        },
        {
          "name": "message_updated",
          "number": 11,
          "type": "Message",
          "oneof": "kind",
          "doc": "edits, reactions, reply counts"
        },
        {
          "name": "message_deleted",
          "number": 12,
          "type": "Message",
          "oneof": "kind"
        },
        {
          "name": "channel_created",
          "number": 13,
          "type": "Channel",
          "oneof": "kind"
        },
        {
          "name": "channel_updated",
          "number": 14,
          "type": "Channel",
          "oneof": "kind"
        },
        {
          "name": "channel_deleted",
          "number": 15,
          "type": "string",
          "oneof": "kind",
          "doc": "channel id"
        },
        {
          "name": "member_joined",
          "number": 16,
          "type": "Member",
          "oneof": "kind"
        },
        {
          "name": "member_updated",
          "number": 17,
          "type": "Member",
          "oneof": "kind"
        },
        {
          "name": "member_left",
          "number": 18,
          "type": "string",
          "oneof": "kind",
          "doc": "member id"
        },
        {
          "name": "read_state",
          "number": 19,
          "type": "ReadState",
          "oneof": "kind",
          "doc": "my own read state, from another device"
        },
        {
          "name": "typing",
          "number": 20,
          "type": "TypingEvent",
          "oneof": "kind",
          "doc": "ephemeral"
        },
        {
          "name": "presence",
          "number": 21,
          "type": "PresenceEvent",
          "oneof": "kind",
          "doc": "ephemeral"
        },
        {
          "name": "heartbeat",
          "number": 22,
          "type": "bool",
          "oneof": "kind",
          "doc": "ephemeral, every 25s keeps proxies open"
        },
        {
          "name": "resync",
          "number": 23,
          "type": "bool",
          "oneof": "kind",
          "doc": "client must call GetState"
        },
        {
          "name": "role_updated",
          "number": 24,
          "type": "Role",
          "oneof": "kind",
          "doc": "created or changed"
        },
        {
          "name": "role_deleted",
          "number": 25,
          "type": "string",
          "oneof": "kind",
          "doc": "role id; drop it from members and channels"
        },
        {
          "name": "channel_mode_changed",
          "number": 26,
          "type": "ChannelMode",
          "oneof": "kind"
        },
        {
          "name": "server_updated",
          "number": 27,
          "type": "bool",
          "oneof": "kind",
          "doc": "description, banner or icon changed; call GetServerInfo"
        },
        {
          "name": "emoji_changed",
          "number": 28,
          "type": "bool",
          "oneof": "kind",
          "doc": "custom emoji added or deleted; refetch GET /emoji"
        },
        {
          "name": "presence_batch",
          "number": 29,
          "type": "PresenceBatch",
          "oneof": "kind",
          "doc": "ephemeral; apply each change like presence"
        },
        {
          "name": "plugins_changed",
          "number": 30,
          "type": "bool",
          "oneof": "kind",
          "doc": "a plugin was installed, enabled, disabled or deleted; admins call ListPlugins"
        },
        {
          "name": "member_blocked",
          "number": 31,
          "type": "string",
          "oneof": "kind",
          "doc": "member id; I blocked them (from any of my devices)"
        },
        {
          "name": "member_unblocked",
          "number": 32,
          "type": "string",
          "oneof": "kind",
          "doc": "member id; I unblocked them"
        },
        {
          "name": "member_muted",
          "number": 33,
          "type": "string",
          "oneof": "kind",
          "doc": "member id; I muted them (from any of my devices)"
        },
        {
          "name": "member_unmuted",
          "number": 34,
          "type": "string",
          "oneof": "kind",
          "doc": "member id; I unmuted them"
        }
      ]
    },
    {
      "name": "CreateChannelRequest",
      "doc": "Admin or owner only. Names are normalized: \"Book Club\" becomes book-club.",
      "fields": [
        {
          "name": "name",
          "number": 1,
          "type": "string"
        },
        {
          "name": "kind",
          "number": 2,
          "type": "ChannelKind",
          "doc": "TEXT or PRIVATE"
        },
        {
          "name": "topic",
          "number": 3,
          "type": "string"
        },
        {
          "name": "category",
          "number": 4,
          "type": "string"
        }
      ]
    },
    {
      "name": "CreateChannelResponse",
      "fields": [
        {
          "name": "channel",
          "number": 1,
          "type": "Channel"
        }
      ]
    },
    {
      "name": "UpdateChannelRequest",
      "doc": "Admin or owner only. DMs cannot be updated.",
      "fields": [
        {
          "name": "channel_id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "name",
          "number": 2,
          "type": "string",
          "label": "optional"
        },
        {
          "name": "topic",
          "number": 3,
          "type": "string",
          "label": "optional"
        },
        {
          "name": "category",
          "number": 4,
          "type": "string",
          "label": "optional"
        },
        {
          "name": "position",
          "number": 5,
          "type": "int32",
          "label": "optional"
        },
        {
          "name": "header_image_id",
          "number": 6,
          "type": "string",
          "label": "optional",
          "doc": "An uploaded image's id (POST /upload) for the header; \"\" removes it."
        }
      ]
    },
    {
      "name": "UpdateChannelResponse",
      "fields": [
        {
          "name": "channel",
          "number": 1,
          "type": "Channel"
        }
      ]
    },
    {
      "name": "SetChannelAccessRequest",
      "doc": "Who can see a TEXT channel, and whether OPT_IN joins new members to it.\nAdmins only. Replaces role_ids.",
      "fields": [
        {
          "name": "channel_id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "role_ids",
          "number": 2,
          "type": "string",
          "label": "repeated"
        },
        {
          "name": "is_default",
          "number": 3,
          "type": "bool"
        }
      ]
    },
    {
      "name": "SetChannelAccessResponse",
      "fields": [
        {
          "name": "channel",
          "number": 1,
          "type": "Channel"
        }
      ]
    },
    {
      "name": "BrowseChannelsRequest",
      "doc": "Every TEXT channel I can see, joined or not (OPT_IN's \"Browse channels\").",
      "fields": []
    },
    {
      "name": "BrowseChannelsResponse",
      "fields": [
        {
          "name": "channels",
          "number": 1,
          "type": "Channel",
          "label": "repeated"
        }
      ]
    },
    {
      "name": "JoinChannelRequest",
      "doc": "OPT_IN mode only (FAILED_PRECONDITION in ROLES mode). TEXT channels only.",
      "fields": [
        {
          "name": "channel_id",
          "number": 1,
          "type": "string"
        }
      ]
    },
    {
      "name": "JoinChannelResponse",
      "fields": [
        {
          "name": "channel",
          "number": 1,
          "type": "Channel"
        }
      ]
    },
    {
      "name": "LeaveChannelRequest",
      "fields": [
        {
          "name": "channel_id",
          "number": 1,
          "type": "string"
        }
      ]
    },
    {
      "name": "LeaveChannelResponse",
      "fields": []
    },
    {
      "name": "OpenDMRequest",
      "doc": "Any member, with 1 to 8 others. Returns the existing DM when one has\nexactly these members, and puts the caller back in it if they left it.\nPERMISSION_DENIED when the caller and one of the others blocked each other\n(either way).",
      "fields": [
        {
          "name": "member_ids",
          "number": 1,
          "type": "string",
          "label": "repeated",
          "doc": "the others; the caller is added"
        }
      ]
    },
    {
      "name": "OpenDMResponse",
      "fields": [
        {
          "name": "channel",
          "number": 1,
          "type": "Channel"
        }
      ]
    },
    {
      "name": "ListMessagesRequest",
      "fields": [
        {
          "name": "channel_id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "thread_root_id",
          "number": 2,
          "type": "string",
          "doc": "set to list a thread's replies"
        },
        {
          "name": "before_id",
          "number": 3,
          "type": "string",
          "doc": "page backwards from this message; empty = newest"
        },
        {
          "name": "limit",
          "number": 4,
          "type": "int32",
          "doc": "default 50, max 200"
        },
        {
          "name": "around_id",
          "number": 5,
          "type": "string",
          "doc": "Set at most one of before_id, after_id and around_id.\nthis message, about limit/2 older ones and the rest newer (jump to a search hit)"
        },
        {
          "name": "after_id",
          "number": 6,
          "type": "string",
          "doc": "page forwards from this message (newer ones)"
        }
      ]
    },
    {
      "name": "ListMessagesResponse",
      "fields": [
        {
          "name": "messages",
          "number": 1,
          "type": "Message",
          "label": "repeated",
          "doc": "oldest first"
        },
        {
          "name": "has_more",
          "number": 2,
          "type": "bool",
          "doc": "older messages exist before the first one"
        },
        {
          "name": "has_more_after",
          "number": 3,
          "type": "bool",
          "doc": "newer messages exist after the last one (after_id and around_id only)"
        }
      ]
    },
    {
      "name": "SendMessageRequest",
      "doc": "Any member who can see the channel. Text up to 8,000 characters, at most\n10 attachments. In a DM, PERMISSION_DENIED when the sender and another\nmember blocked each other (either way). A DM message puts members who left\nthat DM back in it (they get channel_updated). People may send 5 messages per second (burst 10); bots\nfollow the server's -bot-rate and -bot-burst.",
      "fields": [
        {
          "name": "channel_id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "text",
          "number": 2,
          "type": "string"
        },
        {
          "name": "thread_root_id",
          "number": 3,
          "type": "string"
        },
        {
          "name": "client_id",
          "number": 4,
          "type": "string",
          "doc": "idempotency key from the client"
        },
        {
          "name": "attachment_ids",
          "number": 5,
          "type": "string",
          "label": "repeated"
        }
      ]
    },
    {
      "name": "SendMessageResponse",
      "fields": [
        {
          "name": "message",
          "number": 1,
          "type": "Message"
        }
      ]
    },
    {
      "name": "EditMessageRequest",
      "doc": "Your own messages only.",
      "fields": [
        {
          "name": "message_id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "text",
          "number": 2,
          "type": "string"
        }
      ]
    },
    {
      "name": "EditMessageResponse",
      "fields": [
        {
          "name": "message",
          "number": 1,
          "type": "Message"
        }
      ]
    },
    {
      "name": "DeleteMessageRequest",
      "doc": "Your own messages, or anyone's for a moderator or higher.",
      "fields": [
        {
          "name": "message_id",
          "number": 1,
          "type": "string"
        }
      ]
    },
    {
      "name": "DeleteMessageResponse",
      "fields": []
    },
    {
      "name": "ReactRequest",
      "doc": "At most 50 different emoji per message.",
      "fields": [
        {
          "name": "message_id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "emoji",
          "number": 2,
          "type": "string"
        },
        {
          "name": "remove",
          "number": 3,
          "type": "bool"
        }
      ]
    },
    {
      "name": "ReactResponse",
      "fields": [
        {
          "name": "message",
          "number": 1,
          "type": "Message"
        }
      ]
    },
    {
      "name": "MarkReadRequest",
      "fields": [
        {
          "name": "channel_id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "seq",
          "number": 2,
          "type": "int64"
        }
      ]
    },
    {
      "name": "MarkReadResponse",
      "fields": [
        {
          "name": "read_state",
          "number": 1,
          "type": "ReadState"
        }
      ]
    },
    {
      "name": "SetTypingRequest",
      "fields": [
        {
          "name": "channel_id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "thread_root_id",
          "number": 2,
          "type": "string"
        }
      ]
    },
    {
      "name": "SetTypingResponse",
      "fields": []
    },
    {
      "name": "SearchRequest",
      "fields": [
        {
          "name": "query",
          "number": 1,
          "type": "string"
        },
        {
          "name": "channel_id",
          "number": 2,
          "type": "string",
          "doc": "optional filter"
        },
        {
          "name": "limit",
          "number": 3,
          "type": "int32",
          "doc": "default 20, max 100"
        }
      ]
    },
    {
      "name": "SearchResponse",
      "fields": [
        {
          "name": "messages",
          "number": 1,
          "type": "Message",
          "label": "repeated"
        }
      ]
    },
    {
      "name": "GetLinkPreviewsRequest",
      "doc": "Previews of up to 5 http(s) links in a message I can see. The server\nfetches each page once, caches it for a day and serves the image itself,\nso viewers never contact the linked site. Links that have no preview are\nleft out.",
      "fields": [
        {
          "name": "message_id",
          "number": 1,
          "type": "string"
        }
      ]
    },
    {
      "name": "GetLinkPreviewsResponse",
      "fields": [
        {
          "name": "previews",
          "number": 1,
          "type": "LinkPreview",
          "label": "repeated"
        }
      ]
    },
    {
      "name": "LinkPreview",
      "fields": [
        {
          "name": "url",
          "number": 1,
          "type": "string",
          "doc": "the link as written in the message"
        },
        {
          "name": "title",
          "number": 2,
          "type": "string"
        },
        {
          "name": "description",
          "number": 3,
          "type": "string"
        },
        {
          "name": "site_name",
          "number": 4,
          "type": "string"
        },
        {
          "name": "image_url",
          "number": 5,
          "type": "string",
          "doc": "relative to the server base URL; \"\" if none"
        }
      ]
    },
    {
      "name": "AddChannelMembersRequest",
      "doc": "Any member of a private channel may add others to it. The new members get\nchannel_updated (treat it as an upsert).",
      "fields": [
        {
          "name": "channel_id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "member_ids",
          "number": 2,
          "type": "string",
          "label": "repeated",
          "doc": "max 50"
        }
      ]
    },
    {
      "name": "AddChannelMembersResponse",
      "fields": [
        {
          "name": "channel",
          "number": 1,
          "type": "Channel"
        }
      ]
    },
    {
      "name": "RemoveChannelMemberRequest",
      "doc": "Removes a member from a private channel. Your own id leaves the channel,\nand in a DM (your own id only) hides it until someone writes there again;\nanyone else needs moderator or higher and a higher rank than theirs. The\nremoved member gets channel_deleted (the channel is gone for them); the\nrest get channel_updated.",
      "fields": [
        {
          "name": "channel_id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "member_id",
          "number": 2,
          "type": "string"
        }
      ]
    },
    {
      "name": "RemoveChannelMemberResponse",
      "fields": [
        {
          "name": "channel",
          "number": 1,
          "type": "Channel"
        }
      ]
    },
    {
      "name": "SetMemberRoleRequest",
      "doc": "Admin or owner only. The new role must be MEMBER, MODERATOR or ADMIN and\nlower than the caller's, and the target must rank lower than the caller.",
      "fields": [
        {
          "name": "member_id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "role",
          "number": 2,
          "type": "MemberRole"
        }
      ]
    },
    {
      "name": "SetMemberRoleResponse",
      "fields": [
        {
          "name": "member",
          "number": 1,
          "type": "Member"
        }
      ]
    },
    {
      "name": "LeaveCommunityRequest",
      "doc": "Leave this community. The owner cannot leave. You may Join again later.\nOthers get member_left.",
      "fields": []
    },
    {
      "name": "LeaveCommunityResponse",
      "fields": []
    },
    {
      "name": "KickMemberRequest",
      "doc": "Moderator or higher, and a higher rank than the target. The target leaves\nthe community and its private channels but may Join again. Not for bots\n(use BotService.DeleteBot).",
      "fields": [
        {
          "name": "member_id",
          "number": 1,
          "type": "string"
        }
      ]
    },
    {
      "name": "KickMemberResponse",
      "fields": []
    },
    {
      "name": "BanMemberRequest",
      "doc": "Like KickMember, and the person's login (iss, sub) cannot Join again.",
      "fields": [
        {
          "name": "member_id",
          "number": 1,
          "type": "string"
        }
      ]
    },
    {
      "name": "BanMemberResponse",
      "fields": []
    },
    {
      "name": "Ban",
      "doc": "A banned login. The member row stays, so member.id keeps working.",
      "fields": [
        {
          "name": "member",
          "number": 1,
          "type": "Member",
          "doc": "the profile they had when banned"
        },
        {
          "name": "banned_by",
          "number": 2,
          "type": "string",
          "doc": "member id"
        },
        {
          "name": "banned_at_ms",
          "number": 3,
          "type": "int64"
        }
      ]
    },
    {
      "name": "ListBansRequest",
      "doc": "Moderator or higher. Newest first.",
      "fields": []
    },
    {
      "name": "ListBansResponse",
      "fields": [
        {
          "name": "bans",
          "number": 1,
          "type": "Ban",
          "label": "repeated"
        }
      ]
    },
    {
      "name": "UnbanMemberRequest",
      "doc": "Moderator or higher. The login may Join again (with an invite if the\ncommunity is invite-only) and comes back with the same member id.",
      "fields": [
        {
          "name": "member_id",
          "number": 1,
          "type": "string"
        }
      ]
    },
    {
      "name": "UnbanMemberResponse",
      "fields": []
    },
    {
      "name": "BlockMemberRequest",
      "doc": "Any member, for themselves only. Blocking hides nothing on the server\nexcept DMs: the two cannot open a DM with each other or send in a shared\nDM. Clients collapse a blocked member's messages. The blocker's devices get\nEvent.member_blocked or member_unblocked. Blocking twice is fine.",
      "fields": [
        {
          "name": "member_id",
          "number": 1,
          "type": "string"
        }
      ]
    },
    {
      "name": "BlockMemberResponse",
      "fields": []
    },
    {
      "name": "UnblockMemberRequest",
      "fields": [
        {
          "name": "member_id",
          "number": 1,
          "type": "string"
        }
      ]
    },
    {
      "name": "UnblockMemberResponse",
      "fields": []
    },
    {
      "name": "MuteMemberRequest",
      "doc": "Any member, for themselves only. Muting is quieter than blocking and does\nnot stop DMs: a muted member's messages do not count toward my unread or\nmention counts, and clients collapse them, skip their notifications and\nhide their typing. Blocking does the same. Only the muter's devices see the\nmute list (GetState.muted_member_ids, Event.member_muted or\nmember_unmuted). Muting twice is fine.",
      "fields": [
        {
          "name": "member_id",
          "number": 1,
          "type": "string"
        }
      ]
    },
    {
      "name": "MuteMemberResponse",
      "fields": []
    },
    {
      "name": "UnmuteMemberRequest",
      "fields": [
        {
          "name": "member_id",
          "number": 1,
          "type": "string"
        }
      ]
    },
    {
      "name": "UnmuteMemberResponse",
      "fields": []
    },
    {
      "name": "ReportMessageRequest",
      "doc": "Any member, about a message they can see that someone else wrote, with an\noptional note (up to 1,000 characters). The server keeps a copy of the\ntext, so the report outlives an edit or a delete. A report of a DM\nmessage shares that one message with moderators and nothing else from the\nDM. Reporting the same message again replaces the note. People may send\n5 reports, then 1 every 10 minutes (RESOURCE_EXHAUSTED). Nobody is told\nwho reported, and the reporter cannot list reports.",
      "fields": [
        {
          "name": "message_id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "note",
          "number": 2,
          "type": "string"
        }
      ]
    },
    {
      "name": "ReportMessageResponse",
      "fields": []
    },
    {
      "name": "Report",
      "fields": [
        {
          "name": "id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "message",
          "number": 2,
          "type": "Message",
          "doc": "copy at report time: id, channel_id, author_id, text, created_at_ms, thread_root_id"
        },
        {
          "name": "dm",
          "number": 3,
          "type": "bool",
          "doc": "the message is in a DM; moderators cannot open it"
        },
        {
          "name": "author",
          "number": 4,
          "type": "Member",
          "doc": "who wrote the message (may have left)"
        },
        {
          "name": "reporter",
          "number": 5,
          "type": "Member"
        },
        {
          "name": "note",
          "number": 6,
          "type": "string"
        },
        {
          "name": "reported_at_ms",
          "number": 7,
          "type": "int64"
        }
      ]
    },
    {
      "name": "ListReportsRequest",
      "doc": "Moderator or higher. Open reports, newest first, about members the caller\noutranks (the owner sees reports about everyone, the owner included).",
      "fields": []
    },
    {
      "name": "ListReportsResponse",
      "fields": [
        {
          "name": "reports",
          "number": 1,
          "type": "Report",
          "label": "repeated"
        }
      ]
    },
    {
      "name": "ResolveReportRequest",
      "doc": "Moderator or higher, for a report ListReports shows them. Resolving\ndeletes every open report of that message, copies included.",
      "fields": [
        {
          "name": "report_id",
          "number": 1,
          "type": "string"
        }
      ]
    },
    {
      "name": "ResolveReportResponse",
      "fields": []
    },
    {
      "name": "Invite",
      "fields": [
        {
          "name": "code",
          "number": 1,
          "type": "string",
          "doc": "10 characters, A-Z and 2-7"
        },
        {
          "name": "created_by",
          "number": 2,
          "type": "string",
          "doc": "member id"
        },
        {
          "name": "created_at_ms",
          "number": 3,
          "type": "int64"
        },
        {
          "name": "expires_at_ms",
          "number": 4,
          "type": "int64",
          "doc": "0 = never"
        },
        {
          "name": "max_uses",
          "number": 5,
          "type": "int32",
          "doc": "0 = no limit"
        },
        {
          "name": "uses",
          "number": 6,
          "type": "int32",
          "doc": "joins it allowed so far"
        }
      ]
    },
    {
      "name": "CreateInviteRequest",
      "fields": [
        {
          "name": "expires_in_seconds",
          "number": 1,
          "type": "int64",
          "doc": "0 = never; max one year"
        },
        {
          "name": "max_uses",
          "number": 2,
          "type": "int32",
          "doc": "0 = no limit"
        }
      ]
    },
    {
      "name": "CreateInviteResponse",
      "fields": [
        {
          "name": "invite",
          "number": 1,
          "type": "Invite"
        }
      ]
    },
    {
      "name": "ListInvitesRequest",
      "doc": "Every invite that was not revoked, newest first, including expired and\nused-up ones (compare expires_at_ms and uses to show their state).",
      "fields": []
    },
    {
      "name": "ListInvitesResponse",
      "fields": [
        {
          "name": "invites",
          "number": 1,
          "type": "Invite",
          "label": "repeated"
        }
      ]
    },
    {
      "name": "RevokeInviteRequest",
      "fields": [
        {
          "name": "code",
          "number": 1,
          "type": "string"
        }
      ]
    },
    {
      "name": "RevokeInviteResponse",
      "fields": []
    },
    {
      "name": "UpdateServerSettingsRequest",
      "fields": [
        {
          "name": "invite_only",
          "number": 1,
          "type": "bool",
          "label": "optional"
        },
        {
          "name": "channel_mode",
          "number": 2,
          "type": "ChannelMode",
          "doc": "UNSPECIFIED = leave as is"
        },
        {
          "name": "description",
          "number": 3,
          "type": "string",
          "label": "optional",
          "doc": "Markdown; \"\" falls back to the server's -description"
        },
        {
          "name": "banner_id",
          "number": 4,
          "type": "string",
          "label": "optional",
          "doc": "an uploaded image's id (POST /upload); \"\" removes it"
        },
        {
          "name": "icon_id",
          "number": 5,
          "type": "string",
          "label": "optional",
          "doc": "rail icon, an uploaded image's id; \"\" falls back to -icon-url"
        },
        {
          "name": "retention_days",
          "number": 6,
          "type": "int32",
          "label": "optional",
          "doc": "0 = keep forever, else 1 to 3650"
        },
        {
          "name": "setup_done",
          "number": 7,
          "type": "bool",
          "doc": "true clears GetServerInfoResponse.needs_setup"
        }
      ]
    },
    {
      "name": "UpdateServerSettingsResponse",
      "fields": [
        {
          "name": "invite_only",
          "number": 1,
          "type": "bool"
        },
        {
          "name": "channel_mode",
          "number": 2,
          "type": "ChannelMode"
        },
        {
          "name": "description",
          "number": 3,
          "type": "string"
        },
        {
          "name": "banner",
          "number": 4,
          "type": "Attachment"
        },
        {
          "name": "icon_url",
          "number": 5,
          "type": "string"
        },
        {
          "name": "retention_days",
          "number": 6,
          "type": "int32"
        }
      ]
    },
    {
      "name": "CreateRoleRequest",
      "fields": [
        {
          "name": "name",
          "number": 1,
          "type": "string"
        },
        {
          "name": "color",
          "number": 2,
          "type": "string",
          "doc": "\"#rrggbb\" or empty"
        }
      ]
    },
    {
      "name": "CreateRoleResponse",
      "fields": [
        {
          "name": "role",
          "number": 1,
          "type": "Role"
        }
      ]
    },
    {
      "name": "UpdateRoleRequest",
      "fields": [
        {
          "name": "role_id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "name",
          "number": 2,
          "type": "string",
          "label": "optional"
        },
        {
          "name": "color",
          "number": 3,
          "type": "string",
          "label": "optional"
        },
        {
          "name": "position",
          "number": 4,
          "type": "int32",
          "label": "optional"
        }
      ]
    },
    {
      "name": "UpdateRoleResponse",
      "fields": [
        {
          "name": "role",
          "number": 1,
          "type": "Role"
        }
      ]
    },
    {
      "name": "DeleteRoleRequest",
      "doc": "Fails with FAILED_PRECONDITION while the role still limits a channel, so\ndeleting a role never opens a channel to everyone.",
      "fields": [
        {
          "name": "role_id",
          "number": 1,
          "type": "string"
        }
      ]
    },
    {
      "name": "DeleteRoleResponse",
      "fields": []
    },
    {
      "name": "SetMemberRolesRequest",
      "doc": "Replaces the member's roles. Works for bots too.",
      "fields": [
        {
          "name": "member_id",
          "number": 1,
          "type": "string"
        },
        {
          "name": "role_ids",
          "number": 2,
          "type": "string",
          "label": "repeated"
        }
      ]
    },
    {
      "name": "SetMemberRolesResponse",
      "fields": [
        {
          "name": "member",
          "number": 1,
          "type": "Member"
        }
      ]
    },
    {
      "name": "CustomEmoji",
      "fields": [
        {
          "name": "name",
          "number": 1,
          "type": "string",
          "doc": "1 to 62 of a-z, 0-9, - and _; unique on this server"
        },
        {
          "name": "url",
          "number": 2,
          "type": "string",
          "doc": "relative to the server base URL; never changes, cache forever"
        }
      ]
    },
    {
      "name": "ListEmojiResponse",
      "fields": [
        {
          "name": "emoji",
          "number": 1,
          "type": "CustomEmoji",
          "label": "repeated",
          "doc": "sorted by name"
        }
      ]
    },
    {
      "name": "EmojiUploadResult",
      "fields": [
        {
          "name": "file",
          "number": 1,
          "type": "string",
          "doc": "the file name, or its path inside a zip"
        },
        {
          "name": "name",
          "number": 2,
          "type": "string",
          "doc": "emoji name made from the file name"
        },
        {
          "name": "status",
          "number": 3,
          "type": "EmojiUploadStatus"
        },
        {
          "name": "error",
          "number": 4,
          "type": "string",
          "doc": "why it was rejected"
        }
      ]
    },
    {
      "name": "UploadEmojiResponse",
      "fields": [
        {
          "name": "results",
          "number": 1,
          "type": "EmojiUploadResult",
          "label": "repeated"
        }
      ]
    },
    {
      "name": "DeleteEmojiRequest",
      "doc": "Moderator or higher.",
      "fields": [
        {
          "name": "name",
          "number": 1,
          "type": "string"
        }
      ]
    },
    {
      "name": "DeleteEmojiResponse",
      "fields": []
    },
    {
      "name": "CreateBotRequest",
      "doc": "Admin or owner only. username is 2 to 32 of a-z, 0-9, _, . and -.",
      "fields": [
        {
          "name": "username",
          "number": 1,
          "type": "string"
        },
        {
          "name": "display_name",
          "number": 2,
          "type": "string"
        },
        {
          "name": "avatar_url",
          "number": 3,
          "type": "string"
        },
        {
          "name": "bio",
          "number": 4,
          "type": "string"
        }
      ]
    },
    {
      "name": "CreateBotResponse",
      "fields": [
        {
          "name": "bot",
          "number": 1,
          "type": "Member"
        },
        {
          "name": "token",
          "number": 2,
          "type": "string",
          "doc": "shown once; the server stores only a hash"
        }
      ]
    },
    {
      "name": "ListBotsRequest",
      "doc": "Any member.",
      "fields": []
    },
    {
      "name": "ListBotsResponse",
      "fields": [
        {
          "name": "bots",
          "number": 1,
          "type": "Member",
          "label": "repeated"
        }
      ]
    },
    {
      "name": "RotateBotTokenRequest",
      "doc": "Admin or owner only. The old token stops working at once.",
      "fields": [
        {
          "name": "bot_id",
          "number": 1,
          "type": "string"
        }
      ]
    },
    {
      "name": "RotateBotTokenResponse",
      "fields": [
        {
          "name": "token",
          "number": 1,
          "type": "string"
        }
      ]
    },
    {
      "name": "DeleteBotRequest",
      "doc": "Admin or owner only. The bot's messages stay; its username is freed.",
      "fields": [
        {
          "name": "bot_id",
          "number": 1,
          "type": "string"
        }
      ]
    },
    {
      "name": "DeleteBotResponse",
      "fields": []
    },
    {
      "name": "Plugin",
      "doc": "A JavaScript plugin in the plugins folder of the data directory. name is\nalso its bot's username.",
      "fields": [
        {
          "name": "name",
          "number": 1,
          "type": "string"
        },
        {
          "name": "enabled",
          "number": 2,
          "type": "bool"
        },
        {
          "name": "running",
          "number": 3,
          "type": "bool",
          "doc": "loaded and taking hooks"
        },
        {
          "name": "error",
          "number": 4,
          "type": "string",
          "doc": "why it did not load, or its last hook failure"
        },
        {
          "name": "bot_id",
          "number": 5,
          "type": "string",
          "doc": "\"\" until it has loaded once"
        }
      ]
    },
    {
      "name": "ListPluginsRequest",
      "doc": "Admin or owner only, like everything below.",
      "fields": []
    },
    {
      "name": "ListPluginsResponse",
      "fields": [
        {
          "name": "plugins",
          "number": 1,
          "type": "Plugin",
          "label": "repeated"
        }
      ]
    },
    {
      "name": "InstallPluginRequest",
      "doc": "Installs or replaces \u003cname\u003e.js and starts it. The source must compile and\nrun its top level within the time limit, or nothing changes. 256 KB max.",
      "fields": [
        {
          "name": "name",
          "number": 1,
          "type": "string",
          "doc": "a valid username; a trailing \".js\" is dropped"
        },
        {
          "name": "source",
          "number": 2,
          "type": "string"
        }
      ]
    },
    {
      "name": "InstallPluginResponse",
      "fields": [
        {
          "name": "plugin",
          "number": 1,
          "type": "Plugin"
        }
      ]
    },
    {
      "name": "SetPluginEnabledRequest",
      "doc": "Disabling stops the plugin and keeps its file as \u003cname\u003e.js.disabled.",
      "fields": [
        {
          "name": "name",
          "number": 1,
          "type": "string"
        },
        {
          "name": "enabled",
          "number": 2,
          "type": "bool"
        }
      ]
    },
    {
      "name": "SetPluginEnabledResponse",
      "fields": [
        {
          "name": "plugin",
          "number": 1,
          "type": "Plugin"
        }
      ]
    },
    {
      "name": "DeletePluginRequest",
      "doc": "Deletes the file and the plugin's bot. The bot's messages stay.",
      "fields": [
        {
          "name": "name",
          "number": 1,
          "type": "string"
        }
      ]
    },
    {
      "name": "DeletePluginResponse",
      "fields": []
    }
  ],
  "enums": [
    {
      "name": "MemberKind",
      "fields": [
        {
          "name": "MEMBER_KIND_UNSPECIFIED",
          "number": 0
        },
        {
          "name": "MEMBER_KIND_HUMAN",
          "number": 1
        },
        {
          "name": "MEMBER_KIND_BOT",
          "number": 2
        }
      ]
    },
    {
      "name": "MemberRole",
      "fields": [
        {
          "name": "MEMBER_ROLE_UNSPECIFIED",
          "number": 0
        },
        {
          "name": "MEMBER_ROLE_MEMBER",
          "number": 1
        },
        {
          "name": "MEMBER_ROLE_MODERATOR",
          "number": 2
        },
        {
          "name": "MEMBER_ROLE_ADMIN",
          "number": 3
        },
        {
          "name": "MEMBER_ROLE_OWNER",
          "number": 4
        }
      ]
    },
    {
      "name": "Presence",
      "fields": [
        {
          "name": "PRESENCE_UNSPECIFIED",
          "number": 0
        },
        {
          "name": "PRESENCE_ONLINE",
          "number": 1
        },
        {
          "name": "PRESENCE_AWAY",
          "number": 2
        },
        {
          "name": "PRESENCE_OFFLINE",
          "number": 3
        }
      ]
    },
    {
      "name": "ChannelMode",
      "doc": "How members get into text channels. Private channels and DMs keep their\nexplicit member lists in both modes.",
      "fields": [
        {
          "name": "CHANNEL_MODE_UNSPECIFIED",
          "number": 0
        },
        {
          "name": "CHANNEL_MODE_ROLES",
          "number": 1,
          "doc": "Discord-like: you are in every text channel you can see"
        },
        {
          "name": "CHANNEL_MODE_OPT_IN",
          "number": 2,
          "doc": "Slack-like: browse the channels you can see, join and leave them"
        }
      ]
    },
    {
      "name": "ChannelKind",
      "fields": [
        {
          "name": "CHANNEL_KIND_UNSPECIFIED",
          "number": 0
        },
        {
          "name": "CHANNEL_KIND_TEXT",
          "number": 1,
          "doc": "every member can see it, unless role_ids limits it"
        },
        {
          "name": "CHANNEL_KIND_PRIVATE",
          "number": 2,
          "doc": "invite only"
        },
        {
          "name": "CHANNEL_KIND_DM",
          "number": 3,
          "doc": "direct message, 2+ members"
        }
      ]
    },
    {
      "name": "EmojiUploadStatus",
      "fields": [
        {
          "name": "EMOJI_UPLOAD_STATUS_UNSPECIFIED",
          "number": 0
        },
        {
          "name": "EMOJI_UPLOAD_STATUS_ADDED",
          "number": 1
        },
        {
          "name": "EMOJI_UPLOAD_STATUS_EXISTS",
          "number": 2,
          "doc": "skipped: the name is taken"
        },
        {
          "name": "EMOJI_UPLOAD_STATUS_REJECTED",
          "number": 3,
          "doc": "see error"
        }
      ]
    }
  ]
}
