···22export { default as Header } from "./header";
33export { default as InformationWidget } from "./information-widget";
44export { default as ModActions } from "./mod-actions";
55+export { default as ModeratorPanel } from "./moderator-panel";
56export { default as Problems, ProblemsWrapperRef } from "./problems";
···374374 subject: ComAtprotoModerationCreateReport.InputSchema["subject"],
375375 reasonType: string,
376376 reason?: string,
377377- // no clue about this
378378- moderationSvcDid: string = "did:web:stream.place",
379377 ) => {
380378 if (!pdsAgent || !userDID) {
381379 throw new Error("No PDS agent or user DID found");
···77 PlaceStreamChatMessage,
88 PlaceStreamDefs,
99 PlaceStreamLivestream,
1010+ PlaceStreamModerationPermission,
1011 PlaceStreamSegment,
1112} from "streamplace";
1213import { SystemMessages } from "../lib/system-messages";
···120121 pendingHides: newPendingHides,
121122 };
122123 state = reduceChat(state, [], [], [hiddenMessageUri]);
124124+ } else if (
125125+ PlaceStreamModerationPermission.isRecord(message) ||
126126+ (message &&
127127+ typeof message === "object" &&
128128+ "$type" in message &&
129129+ (message as { $type?: string }).$type ===
130130+ "place.stream.moderation.permission")
131131+ ) {
132132+ // Handle moderation permission record updates
133133+ // This can be a new permission or a deletion marker
134134+ const permRecord = message as
135135+ | PlaceStreamModerationPermission.Record
136136+ | { deleted?: boolean; rkey?: string; streamer?: string };
137137+138138+ if ((permRecord as any).deleted) {
139139+ // Handle deletion: clear permissions to trigger refetch
140140+ // The useCanModerate hook will refetch and repopulate
141141+ state = {
142142+ ...state,
143143+ moderationPermissions: [],
144144+ };
145145+ } else {
146146+ // Handle new/updated permission: add or update in the list
147147+ // Use createdAt as a unique identifier since multiple records can exist for the same moderator
148148+ // (e.g., one record with "ban" permission, another with "hide" permission)
149149+ // Note: rkey would be ideal but isn't always present in the WebSocket message
150150+ const newPerm =
151151+ permRecord as PlaceStreamModerationPermission.Record & {
152152+ rkey?: string;
153153+ };
154154+ const existingIndex = state.moderationPermissions.findIndex((p) => {
155155+ const pWithRkey = p as PlaceStreamModerationPermission.Record & {
156156+ rkey?: string;
157157+ };
158158+ // Prefer matching by rkey if available, fall back to createdAt
159159+ if (newPerm.rkey && pWithRkey.rkey) {
160160+ return pWithRkey.rkey === newPerm.rkey;
161161+ }
162162+ return (
163163+ p.moderator === newPerm.moderator &&
164164+ p.createdAt === newPerm.createdAt
165165+ );
166166+ });
167167+168168+ let newPermissions: PlaceStreamModerationPermission.Record[];
169169+ if (existingIndex >= 0) {
170170+ // Update existing record with same moderator AND createdAt
171171+ newPermissions = [...state.moderationPermissions];
172172+ newPermissions[existingIndex] = newPerm;
173173+ } else {
174174+ // Add new record (could be a new record for an existing moderator with different permissions)
175175+ newPermissions = [...state.moderationPermissions, newPerm];
176176+ }
177177+178178+ state = {
179179+ ...state,
180180+ moderationPermissions: newPermissions,
181181+ };
182182+ }
123183 }
124184 }
125185 }
+138-18
js/components/src/streamplace-store/block.tsx
···22import { useState } from "react";
33import { usePDSAgent } from "./xrpc";
4455+/**
66+ * Hook to create a block record (ban user from chat).
77+ *
88+ * When the caller is the stream owner (agent.did === streamerDID), creates the
99+ * block record directly via ATProto writes to their repo.
1010+ *
1111+ * When the caller is a delegated moderator, uses the place.stream.moderation.createBlock
1212+ * XRPC endpoint which validates permissions and creates the record using the
1313+ * streamer's OAuth session.
1414+ */
515export function useCreateBlockRecord() {
616 let agent = usePDSAgent();
717 const [isLoading, setIsLoading] = useState(false);
81899- const createBlock = async (subjectDID: string) => {
1919+ const createBlock = async (subjectDID: string, streamerDID?: string) => {
1020 if (!agent) {
1121 throw new Error("No PDS agent found");
1222 }
···17271828 setIsLoading(true);
1929 try {
2020- const record: AppBskyGraphBlock.Record = {
2121- $type: "app.bsky.graph.block",
3030+ // If no streamerDID provided or caller is the streamer, use direct ATProto write
3131+ if (!streamerDID || agent.did === streamerDID) {
3232+ const record: AppBskyGraphBlock.Record = {
3333+ $type: "app.bsky.graph.block",
3434+ subject: subjectDID,
3535+ createdAt: new Date().toISOString(),
3636+ };
3737+ const result = await agent.com.atproto.repo.createRecord({
3838+ repo: agent.did,
3939+ collection: "app.bsky.graph.block",
4040+ record,
4141+ });
4242+ return result;
4343+ }
4444+4545+ // Otherwise, use delegated moderation endpoint
4646+ const result = await agent.place.stream.moderation.createBlock({
4747+ streamer: streamerDID,
2248 subject: subjectDID,
2323- createdAt: new Date().toISOString(),
2424- };
2525- const result = await agent.com.atproto.repo.createRecord({
2626- repo: agent.did,
2727- collection: "app.bsky.graph.block",
2828- record,
2949 });
3050 return result;
3151 } finally {
···3656 return { createBlock, isLoading };
3757}
38585959+/**
6060+ * Hook to create a gate record (hide a chat message).
6161+ *
6262+ * When the caller is the stream owner (agent.did === streamerDID), creates the
6363+ * gate record directly via ATProto writes to their repo.
6464+ *
6565+ * When the caller is a delegated moderator, uses the place.stream.moderation.createGate
6666+ * XRPC endpoint which validates permissions and creates the record using the
6767+ * streamer's OAuth session.
6868+ */
3969export function useCreateHideChatRecord() {
4070 let agent = usePDSAgent();
4171 const [isLoading, setIsLoading] = useState(false);
42724343- const createHideChat = async (chatMessageUri: string) => {
7373+ const createHideChat = async (
7474+ chatMessageUri: string,
7575+ streamerDID?: string,
7676+ ) => {
4477 if (!agent) {
4578 throw new Error("No PDS agent found");
4679 }
···51845285 setIsLoading(true);
5386 try {
5454- const record = {
5555- $type: "place.stream.chat.gate",
5656- hiddenMessage: chatMessageUri,
5757- };
8787+ // If no streamerDID provided or caller is the streamer, use direct ATProto write
8888+ if (!streamerDID || agent.did === streamerDID) {
8989+ const record = {
9090+ $type: "place.stream.chat.gate",
9191+ hiddenMessage: chatMessageUri,
9292+ };
9393+9494+ const result = await agent.com.atproto.repo.createRecord({
9595+ repo: agent.did,
9696+ collection: "place.stream.chat.gate",
9797+ record,
9898+ });
9999+ return result;
100100+ }
581015959- const result = await agent.com.atproto.repo.createRecord({
6060- repo: agent.did,
6161- collection: "place.stream.chat.gate",
6262- record,
102102+ // Otherwise, use delegated moderation endpoint
103103+ const result = await agent.place.stream.moderation.createGate({
104104+ streamer: streamerDID,
105105+ messageUri: chatMessageUri,
63106 });
64107 return result;
65108 } finally {
···6911270113 return { createHideChat, isLoading };
71114}
115115+116116+/**
117117+ * Hook to update a livestream record (update stream title).
118118+ *
119119+ * When the caller is the stream owner (agent.did === streamerDID), updates the
120120+ * livestream record directly via ATProto writes to their repo.
121121+ *
122122+ * When the caller is a delegated moderator, uses the place.stream.moderation.updateLivestream
123123+ * XRPC endpoint which validates permissions and updates the record using the
124124+ * streamer's OAuth session.
125125+ */
126126+export function useUpdateLivestreamRecord() {
127127+ let agent = usePDSAgent();
128128+ const [isLoading, setIsLoading] = useState(false);
129129+130130+ const updateLivestream = async (
131131+ livestreamUri: string,
132132+ title: string,
133133+ streamerDID?: string,
134134+ ) => {
135135+ if (!agent) {
136136+ throw new Error("No PDS agent found");
137137+ }
138138+139139+ if (!agent.did) {
140140+ throw new Error("No user DID found, assuming not logged in");
141141+ }
142142+143143+ setIsLoading(true);
144144+ try {
145145+ // If no streamerDID provided or caller is the streamer, use direct ATProto write
146146+ if (!streamerDID || agent.did === streamerDID) {
147147+ // Extract rkey from URI
148148+ const rkey = livestreamUri.split("/").pop();
149149+ if (!rkey) {
150150+ throw new Error("Invalid livestream URI");
151151+ }
152152+153153+ // Get existing record to copy fields
154154+ const getResult = await agent.com.atproto.repo.getRecord({
155155+ repo: agent.did,
156156+ collection: "place.stream.livestream",
157157+ rkey,
158158+ });
159159+160160+ const oldRecord = getResult.data.value as any;
161161+162162+ // Create new record (don't edit - old records are "chapter markers")
163163+ // Spread entire record to preserve all fields (agent, canonicalUrl, notificationSettings, etc.)
164164+ const record = {
165165+ ...oldRecord,
166166+ title: title, // Override title
167167+ createdAt: new Date().toISOString(), // Override timestamp for new chapter marker
168168+ };
169169+170170+ const result = await agent.com.atproto.repo.createRecord({
171171+ repo: agent.did,
172172+ collection: "place.stream.livestream",
173173+ record,
174174+ });
175175+ return result;
176176+ }
177177+178178+ // Otherwise, use delegated moderation endpoint
179179+ const result = await agent.place.stream.moderation.updateLivestream({
180180+ streamer: streamerDID,
181181+ livestreamUri: livestreamUri,
182182+ title: title,
183183+ });
184184+ return result;
185185+ } finally {
186186+ setIsLoading(false);
187187+ }
188188+ };
189189+190190+ return { updateLivestream, isLoading };
191191+}
+3
js/components/src/streamplace-store/index.tsx
···11+export * from "./block";
22+export * from "./moderation";
33+export * from "./moderator-management";
14export * from "./stream";
25export * from "./streamplace-store";
36export * from "./user";
···4975497549764976 return nil
49774977}
49784978+func (t *ModerationPermission) MarshalCBOR(w io.Writer) error {
49794979+ if t == nil {
49804980+ _, err := w.Write(cbg.CborNull)
49814981+ return err
49824982+ }
49834983+49844984+ cw := cbg.NewCborWriter(w)
49854985+ fieldCount := 5
49864986+49874987+ if t.ExpirationTime == nil {
49884988+ fieldCount--
49894989+ }
49904990+49914991+ if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
49924992+ return err
49934993+ }
49944994+49954995+ // t.LexiconTypeID (string) (string)
49964996+ if len("$type") > 1000000 {
49974997+ return xerrors.Errorf("Value in field \"$type\" was too long")
49984998+ }
49994999+50005000+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil {
50015001+ return err
50025002+ }
50035003+ if _, err := cw.WriteString(string("$type")); err != nil {
50045004+ return err
50055005+ }
50065006+50075007+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("place.stream.moderation.permission"))); err != nil {
50085008+ return err
50095009+ }
50105010+ if _, err := cw.WriteString(string("place.stream.moderation.permission")); err != nil {
50115011+ return err
50125012+ }
50135013+50145014+ // t.CreatedAt (string) (string)
50155015+ if len("createdAt") > 1000000 {
50165016+ return xerrors.Errorf("Value in field \"createdAt\" was too long")
50175017+ }
50185018+50195019+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("createdAt"))); err != nil {
50205020+ return err
50215021+ }
50225022+ if _, err := cw.WriteString(string("createdAt")); err != nil {
50235023+ return err
50245024+ }
50255025+50265026+ if len(t.CreatedAt) > 1000000 {
50275027+ return xerrors.Errorf("Value in field t.CreatedAt was too long")
50285028+ }
50295029+50305030+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.CreatedAt))); err != nil {
50315031+ return err
50325032+ }
50335033+ if _, err := cw.WriteString(string(t.CreatedAt)); err != nil {
50345034+ return err
50355035+ }
50365036+50375037+ // t.Moderator (string) (string)
50385038+ if len("moderator") > 1000000 {
50395039+ return xerrors.Errorf("Value in field \"moderator\" was too long")
50405040+ }
50415041+50425042+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("moderator"))); err != nil {
50435043+ return err
50445044+ }
50455045+ if _, err := cw.WriteString(string("moderator")); err != nil {
50465046+ return err
50475047+ }
50485048+50495049+ if len(t.Moderator) > 1000000 {
50505050+ return xerrors.Errorf("Value in field t.Moderator was too long")
50515051+ }
50525052+50535053+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Moderator))); err != nil {
50545054+ return err
50555055+ }
50565056+ if _, err := cw.WriteString(string(t.Moderator)); err != nil {
50575057+ return err
50585058+ }
50595059+50605060+ // t.Permissions ([]string) (slice)
50615061+ if len("permissions") > 1000000 {
50625062+ return xerrors.Errorf("Value in field \"permissions\" was too long")
50635063+ }
50645064+50655065+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("permissions"))); err != nil {
50665066+ return err
50675067+ }
50685068+ if _, err := cw.WriteString(string("permissions")); err != nil {
50695069+ return err
50705070+ }
50715071+50725072+ if len(t.Permissions) > 8192 {
50735073+ return xerrors.Errorf("Slice value in field t.Permissions was too long")
50745074+ }
50755075+50765076+ if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Permissions))); err != nil {
50775077+ return err
50785078+ }
50795079+ for _, v := range t.Permissions {
50805080+ if len(v) > 1000000 {
50815081+ return xerrors.Errorf("Value in field v was too long")
50825082+ }
50835083+50845084+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil {
50855085+ return err
50865086+ }
50875087+ if _, err := cw.WriteString(string(v)); err != nil {
50885088+ return err
50895089+ }
50905090+50915091+ }
50925092+50935093+ // t.ExpirationTime (string) (string)
50945094+ if t.ExpirationTime != nil {
50955095+50965096+ if len("expirationTime") > 1000000 {
50975097+ return xerrors.Errorf("Value in field \"expirationTime\" was too long")
50985098+ }
50995099+51005100+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("expirationTime"))); err != nil {
51015101+ return err
51025102+ }
51035103+ if _, err := cw.WriteString(string("expirationTime")); err != nil {
51045104+ return err
51055105+ }
51065106+51075107+ if t.ExpirationTime == nil {
51085108+ if _, err := cw.Write(cbg.CborNull); err != nil {
51095109+ return err
51105110+ }
51115111+ } else {
51125112+ if len(*t.ExpirationTime) > 1000000 {
51135113+ return xerrors.Errorf("Value in field t.ExpirationTime was too long")
51145114+ }
51155115+51165116+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.ExpirationTime))); err != nil {
51175117+ return err
51185118+ }
51195119+ if _, err := cw.WriteString(string(*t.ExpirationTime)); err != nil {
51205120+ return err
51215121+ }
51225122+ }
51235123+ }
51245124+ return nil
51255125+}
51265126+51275127+func (t *ModerationPermission) UnmarshalCBOR(r io.Reader) (err error) {
51285128+ *t = ModerationPermission{}
51295129+51305130+ cr := cbg.NewCborReader(r)
51315131+51325132+ maj, extra, err := cr.ReadHeader()
51335133+ if err != nil {
51345134+ return err
51355135+ }
51365136+ defer func() {
51375137+ if err == io.EOF {
51385138+ err = io.ErrUnexpectedEOF
51395139+ }
51405140+ }()
51415141+51425142+ if maj != cbg.MajMap {
51435143+ return fmt.Errorf("cbor input should be of type map")
51445144+ }
51455145+51465146+ if extra > cbg.MaxLength {
51475147+ return fmt.Errorf("ModerationPermission: map struct too large (%d)", extra)
51485148+ }
51495149+51505150+ n := extra
51515151+51525152+ nameBuf := make([]byte, 14)
51535153+ for i := uint64(0); i < n; i++ {
51545154+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
51555155+ if err != nil {
51565156+ return err
51575157+ }
51585158+51595159+ if !ok {
51605160+ // Field doesn't exist on this type, so ignore it
51615161+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
51625162+ return err
51635163+ }
51645164+ continue
51655165+ }
51665166+51675167+ switch string(nameBuf[:nameLen]) {
51685168+ // t.LexiconTypeID (string) (string)
51695169+ case "$type":
51705170+51715171+ {
51725172+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
51735173+ if err != nil {
51745174+ return err
51755175+ }
51765176+51775177+ t.LexiconTypeID = string(sval)
51785178+ }
51795179+ // t.CreatedAt (string) (string)
51805180+ case "createdAt":
51815181+51825182+ {
51835183+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
51845184+ if err != nil {
51855185+ return err
51865186+ }
51875187+51885188+ t.CreatedAt = string(sval)
51895189+ }
51905190+ // t.Moderator (string) (string)
51915191+ case "moderator":
51925192+51935193+ {
51945194+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
51955195+ if err != nil {
51965196+ return err
51975197+ }
51985198+51995199+ t.Moderator = string(sval)
52005200+ }
52015201+ // t.Permissions ([]string) (slice)
52025202+ case "permissions":
52035203+52045204+ maj, extra, err = cr.ReadHeader()
52055205+ if err != nil {
52065206+ return err
52075207+ }
52085208+52095209+ if extra > 8192 {
52105210+ return fmt.Errorf("t.Permissions: array too large (%d)", extra)
52115211+ }
52125212+52135213+ if maj != cbg.MajArray {
52145214+ return fmt.Errorf("expected cbor array")
52155215+ }
52165216+52175217+ if extra > 0 {
52185218+ t.Permissions = make([]string, extra)
52195219+ }
52205220+52215221+ for i := 0; i < int(extra); i++ {
52225222+ {
52235223+ var maj byte
52245224+ var extra uint64
52255225+ var err error
52265226+ _ = maj
52275227+ _ = extra
52285228+ _ = err
52295229+52305230+ {
52315231+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
52325232+ if err != nil {
52335233+ return err
52345234+ }
52355235+52365236+ t.Permissions[i] = string(sval)
52375237+ }
52385238+52395239+ }
52405240+ }
52415241+ // t.ExpirationTime (string) (string)
52425242+ case "expirationTime":
52435243+52445244+ {
52455245+ b, err := cr.ReadByte()
52465246+ if err != nil {
52475247+ return err
52485248+ }
52495249+ if b != cbg.CborNull[0] {
52505250+ if err := cr.UnreadByte(); err != nil {
52515251+ return err
52525252+ }
52535253+52545254+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
52555255+ if err != nil {
52565256+ return err
52575257+ }
52585258+52595259+ t.ExpirationTime = (*string)(&sval)
52605260+ }
52615261+ }
52625262+52635263+ default:
52645264+ // Field doesn't exist on this type, so ignore it
52655265+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
52665266+ return err
52675267+ }
52685268+ }
52695269+ }
52705270+52715271+ return nil
52725272+}
49785273func (t *LiveRecommendations) MarshalCBOR(w io.Writer) error {
49795274 if t == nil {
49805275 _, err := w.Write(cbg.CborNull)
+39
pkg/streamplace/moderationcreateBlock.go
···11+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
22+33+// Lexicon schema: place.stream.moderation.createBlock
44+55+package streamplace
66+77+import (
88+ "context"
99+1010+ lexutil "github.com/bluesky-social/indigo/lex/util"
1111+)
1212+1313+// ModerationCreateBlock_Input is the input argument to a place.stream.moderation.createBlock call.
1414+type ModerationCreateBlock_Input struct {
1515+ // reason: Optional reason for the block.
1616+ Reason *string `json:"reason,omitempty" cborgen:"reason,omitempty"`
1717+ // streamer: The DID of the streamer whose chat this block applies to.
1818+ Streamer string `json:"streamer" cborgen:"streamer"`
1919+ // subject: The DID of the user being blocked from chat.
2020+ Subject string `json:"subject" cborgen:"subject"`
2121+}
2222+2323+// ModerationCreateBlock_Output is the output of a place.stream.moderation.createBlock call.
2424+type ModerationCreateBlock_Output struct {
2525+ // cid: The CID of the created block record.
2626+ Cid string `json:"cid" cborgen:"cid"`
2727+ // uri: The AT-URI of the created block record.
2828+ Uri string `json:"uri" cborgen:"uri"`
2929+}
3030+3131+// ModerationCreateBlock calls the XRPC method "place.stream.moderation.createBlock".
3232+func ModerationCreateBlock(ctx context.Context, c lexutil.LexClient, input *ModerationCreateBlock_Input) (*ModerationCreateBlock_Output, error) {
3333+ var out ModerationCreateBlock_Output
3434+ if err := c.LexDo(ctx, lexutil.Procedure, "application/json", "place.stream.moderation.createBlock", nil, input, &out); err != nil {
3535+ return nil, err
3636+ }
3737+3838+ return &out, nil
3939+}
+37
pkg/streamplace/moderationcreateGate.go
···11+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
22+33+// Lexicon schema: place.stream.moderation.createGate
44+55+package streamplace
66+77+import (
88+ "context"
99+1010+ lexutil "github.com/bluesky-social/indigo/lex/util"
1111+)
1212+1313+// ModerationCreateGate_Input is the input argument to a place.stream.moderation.createGate call.
1414+type ModerationCreateGate_Input struct {
1515+ // messageUri: The AT-URI of the chat message to hide.
1616+ MessageUri string `json:"messageUri" cborgen:"messageUri"`
1717+ // streamer: The DID of the streamer.
1818+ Streamer string `json:"streamer" cborgen:"streamer"`
1919+}
2020+2121+// ModerationCreateGate_Output is the output of a place.stream.moderation.createGate call.
2222+type ModerationCreateGate_Output struct {
2323+ // cid: The CID of the created gate record.
2424+ Cid string `json:"cid" cborgen:"cid"`
2525+ // uri: The AT-URI of the created gate record.
2626+ Uri string `json:"uri" cborgen:"uri"`
2727+}
2828+2929+// ModerationCreateGate calls the XRPC method "place.stream.moderation.createGate".
3030+func ModerationCreateGate(ctx context.Context, c lexutil.LexClient, input *ModerationCreateGate_Input) (*ModerationCreateGate_Output, error) {
3131+ var out ModerationCreateGate_Output
3232+ if err := c.LexDo(ctx, lexutil.Procedure, "application/json", "place.stream.moderation.createGate", nil, input, &out); err != nil {
3333+ return nil, err
3434+ }
3535+3636+ return &out, nil
3737+}
+22
pkg/streamplace/moderationdefs.go
···11+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
22+33+// Lexicon schema: place.stream.moderation.defs
44+55+package streamplace
66+77+import (
88+ appbsky "github.com/bluesky-social/indigo/api/bsky"
99+ lexutil "github.com/bluesky-social/indigo/lex/util"
1010+)
1111+1212+// ModerationDefs_PermissionView is a "permissionView" in the place.stream.moderation.defs schema.
1313+type ModerationDefs_PermissionView struct {
1414+ // author: The streamer who granted these permissions
1515+ Author *appbsky.ActorDefs_ProfileViewBasic `json:"author" cborgen:"author"`
1616+ // cid: Content identifier of the permission record
1717+ Cid string `json:"cid" cborgen:"cid"`
1818+ // record: The permission record itself
1919+ Record *lexutil.LexiconTypeDecoder `json:"record" cborgen:"record"`
2020+ // uri: AT-URI of the permission record
2121+ Uri string `json:"uri" cborgen:"uri"`
2222+}
+33
pkg/streamplace/moderationdeleteBlock.go
···11+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
22+33+// Lexicon schema: place.stream.moderation.deleteBlock
44+55+package streamplace
66+77+import (
88+ "context"
99+1010+ lexutil "github.com/bluesky-social/indigo/lex/util"
1111+)
1212+1313+// ModerationDeleteBlock_Input is the input argument to a place.stream.moderation.deleteBlock call.
1414+type ModerationDeleteBlock_Input struct {
1515+ // blockUri: The AT-URI of the block record to delete.
1616+ BlockUri string `json:"blockUri" cborgen:"blockUri"`
1717+ // streamer: The DID of the streamer.
1818+ Streamer string `json:"streamer" cborgen:"streamer"`
1919+}
2020+2121+// ModerationDeleteBlock_Output is the output of a place.stream.moderation.deleteBlock call.
2222+type ModerationDeleteBlock_Output struct {
2323+}
2424+2525+// ModerationDeleteBlock calls the XRPC method "place.stream.moderation.deleteBlock".
2626+func ModerationDeleteBlock(ctx context.Context, c lexutil.LexClient, input *ModerationDeleteBlock_Input) (*ModerationDeleteBlock_Output, error) {
2727+ var out ModerationDeleteBlock_Output
2828+ if err := c.LexDo(ctx, lexutil.Procedure, "application/json", "place.stream.moderation.deleteBlock", nil, input, &out); err != nil {
2929+ return nil, err
3030+ }
3131+3232+ return &out, nil
3333+}
+33
pkg/streamplace/moderationdeleteGate.go
···11+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
22+33+// Lexicon schema: place.stream.moderation.deleteGate
44+55+package streamplace
66+77+import (
88+ "context"
99+1010+ lexutil "github.com/bluesky-social/indigo/lex/util"
1111+)
1212+1313+// ModerationDeleteGate_Input is the input argument to a place.stream.moderation.deleteGate call.
1414+type ModerationDeleteGate_Input struct {
1515+ // gateUri: The AT-URI of the gate record to delete.
1616+ GateUri string `json:"gateUri" cborgen:"gateUri"`
1717+ // streamer: The DID of the streamer.
1818+ Streamer string `json:"streamer" cborgen:"streamer"`
1919+}
2020+2121+// ModerationDeleteGate_Output is the output of a place.stream.moderation.deleteGate call.
2222+type ModerationDeleteGate_Output struct {
2323+}
2424+2525+// ModerationDeleteGate calls the XRPC method "place.stream.moderation.deleteGate".
2626+func ModerationDeleteGate(ctx context.Context, c lexutil.LexClient, input *ModerationDeleteGate_Input) (*ModerationDeleteGate_Output, error) {
2727+ var out ModerationDeleteGate_Output
2828+ if err := c.LexDo(ctx, lexutil.Procedure, "application/json", "place.stream.moderation.deleteGate", nil, input, &out); err != nil {
2929+ return nil, err
3030+ }
3131+3232+ return &out, nil
3333+}
+25
pkg/streamplace/moderationpermission.go
···11+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
22+33+// Lexicon schema: place.stream.moderation.permission
44+55+package streamplace
66+77+import (
88+ lexutil "github.com/bluesky-social/indigo/lex/util"
99+)
1010+1111+func init() {
1212+ lexutil.RegisterType("place.stream.moderation.permission", &ModerationPermission{})
1313+}
1414+1515+type ModerationPermission struct {
1616+ LexiconTypeID string `json:"$type" cborgen:"$type,const=place.stream.moderation.permission"`
1717+ // createdAt: Client-declared timestamp when this moderator was added.
1818+ CreatedAt string `json:"createdAt" cborgen:"createdAt"`
1919+ // expirationTime: Optional expiration time for this delegation. If set, the delegation is invalid after this time.
2020+ ExpirationTime *string `json:"expirationTime,omitempty" cborgen:"expirationTime,omitempty"`
2121+ // moderator: The DID of the user granted moderator permissions.
2222+ Moderator string `json:"moderator" cborgen:"moderator"`
2323+ // permissions: Array of permissions granted to this moderator. 'ban' covers blocks/bans (with optional expiration), 'hide' covers message gates, 'livestream.manage' allows updating livestream metadata.
2424+ Permissions []string `json:"permissions" cborgen:"permissions"`
2525+}
+39
pkg/streamplace/moderationupdateLivestream.go
···11+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
22+33+// Lexicon schema: place.stream.moderation.updateLivestream
44+55+package streamplace
66+77+import (
88+ "context"
99+1010+ lexutil "github.com/bluesky-social/indigo/lex/util"
1111+)
1212+1313+// ModerationUpdateLivestream_Input is the input argument to a place.stream.moderation.updateLivestream call.
1414+type ModerationUpdateLivestream_Input struct {
1515+ // livestreamUri: The AT-URI of the livestream record to update.
1616+ LivestreamUri string `json:"livestreamUri" cborgen:"livestreamUri"`
1717+ // streamer: The DID of the streamer.
1818+ Streamer string `json:"streamer" cborgen:"streamer"`
1919+ // title: New title for the livestream.
2020+ Title *string `json:"title,omitempty" cborgen:"title,omitempty"`
2121+}
2222+2323+// ModerationUpdateLivestream_Output is the output of a place.stream.moderation.updateLivestream call.
2424+type ModerationUpdateLivestream_Output struct {
2525+ // cid: The CID of the updated livestream record.
2626+ Cid string `json:"cid" cborgen:"cid"`
2727+ // uri: The AT-URI of the updated livestream record.
2828+ Uri string `json:"uri" cborgen:"uri"`
2929+}
3030+3131+// ModerationUpdateLivestream calls the XRPC method "place.stream.moderation.updateLivestream".
3232+func ModerationUpdateLivestream(ctx context.Context, c lexutil.LexClient, input *ModerationUpdateLivestream_Input) (*ModerationUpdateLivestream_Output, error) {
3333+ var out ModerationUpdateLivestream_Output
3434+ if err := c.LexDo(ctx, lexutil.Procedure, "application/json", "place.stream.moderation.updateLivestream", nil, input, &out); err != nil {
3535+ return nil, err
3636+ }
3737+3838+ return &out, nil
3939+}