From 4b407d70b4d4a7635bdbdcee617765853e671a50 Mon Sep 17 00:00:00 2001 From: OpenWaygate <27287694+OpenWaygate@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:36:26 +0800 Subject: [PATCH] :sparkles: Add liveChatBan resource package --- pkg/liveChatBan/BUILD.bazel | 22 +++ pkg/liveChatBan/liveChatBan.go | 115 +++++++++++ pkg/liveChatBan/liveChatBan_test.go | 287 ++++++++++++++++++++++++++++ 3 files changed, 424 insertions(+) create mode 100644 pkg/liveChatBan/BUILD.bazel create mode 100644 pkg/liveChatBan/liveChatBan.go create mode 100644 pkg/liveChatBan/liveChatBan_test.go diff --git a/pkg/liveChatBan/BUILD.bazel b/pkg/liveChatBan/BUILD.bazel new file mode 100644 index 0000000..256d5ee --- /dev/null +++ b/pkg/liveChatBan/BUILD.bazel @@ -0,0 +1,22 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "liveChatBan", + srcs = ["liveChatBan.go"], + importpath = "github.com/eat-pray-ai/yutu/pkg/liveChatBan", + visibility = ["//visibility:public"], + deps = [ + "//pkg/common", + "@org_golang_google_api//youtube/v3:youtube", + ], +) + +go_test( + name = "liveChatBan_test", + srcs = ["liveChatBan_test.go"], + embed = [":liveChatBan"], + deps = [ + "//pkg/common", + "@org_golang_google_api//youtube/v3:youtube", + ], +) diff --git a/pkg/liveChatBan/liveChatBan.go b/pkg/liveChatBan/liveChatBan.go new file mode 100644 index 0000000..e6b4c01 --- /dev/null +++ b/pkg/liveChatBan/liveChatBan.go @@ -0,0 +1,115 @@ +// Copyright 2026 eat-pray-ai & OpenWaygate +// SPDX-License-Identifier: Apache-2.0 + +package liveChatBan + +import ( + "errors" + "fmt" + "io" + + "github.com/eat-pray-ai/yutu/pkg/common" + "google.golang.org/api/youtube/v3" +) + +var ( + errInsertLiveChatBan = errors.New("failed to insert live chat ban") + errDeleteLiveChatBan = errors.New("failed to delete live chat ban") +) + +type LiveChatBan struct { + common.Fields + LiveChatId string `yaml:"live_chat_id" json:"live_chat_id,omitempty"` + BannedUserChannelId string `yaml:"banned_user_channel_id" json:"banned_user_channel_id,omitempty"` + BanDurationSeconds uint64 `yaml:"ban_duration_seconds" json:"ban_duration_seconds,omitempty"` + BanType string `yaml:"ban_type" json:"ban_type,omitempty"` +} + +type ILiveChatBan interface { + Insert(io.Writer) error + Delete(io.Writer) error +} + +type Option func(*LiveChatBan) + +func NewLiveChatBan(opts ...Option) ILiveChatBan { + b := &LiveChatBan{Fields: common.Fields{}} + for _, opt := range opts { + opt(b) + } + return b +} + +func (b *LiveChatBan) Insert(writer io.Writer) error { + if err := b.EnsureService(); err != nil { + return err + } + ban := &youtube.LiveChatBan{ + Snippet: &youtube.LiveChatBanSnippet{ + LiveChatId: b.LiveChatId, + BannedUserDetails: &youtube.ChannelProfileDetails{ + ChannelId: b.BannedUserChannelId, + }, + Type: b.BanType, + BanDurationSeconds: b.BanDurationSeconds, + }, + } + + call := b.Service.LiveChatBans.Insert(b.Parts, ban) + res, err := call.Do() + if err != nil { + return errors.Join(errInsertLiveChatBan, err) + } + + common.PrintResult( + b.Output, res, writer, "Live chat ban inserted: %s\n", res.Id, + ) + return nil +} + +func (b *LiveChatBan) Delete(writer io.Writer) error { + if err := b.EnsureService(); err != nil { + return err + } + for _, id := range b.Ids { + call := b.Service.LiveChatBans.Delete(id) + err := call.Do() + if err != nil { + return errors.Join(errDeleteLiveChatBan, err) + } + + _, _ = fmt.Fprintf(writer, "Live chat ban %s deleted\n", id) + } + return nil +} + +func WithLiveChatId(liveChatId string) Option { + return func(b *LiveChatBan) { + b.LiveChatId = liveChatId + } +} + +func WithBannedUserChannelId(channelId string) Option { + return func(b *LiveChatBan) { + b.BannedUserChannelId = channelId + } +} + +func WithBanDurationSeconds(duration uint64) Option { + return func(b *LiveChatBan) { + b.BanDurationSeconds = duration + } +} + +func WithBanType(banType string) Option { + return func(b *LiveChatBan) { + b.BanType = banType + } +} + +var ( + WithParts = common.WithParts[*LiveChatBan] + WithOutput = common.WithOutput[*LiveChatBan] + WithService = common.WithService[*LiveChatBan] + WithIds = common.WithIds[*LiveChatBan] +) diff --git a/pkg/liveChatBan/liveChatBan_test.go b/pkg/liveChatBan/liveChatBan_test.go new file mode 100644 index 0000000..9d48a16 --- /dev/null +++ b/pkg/liveChatBan/liveChatBan_test.go @@ -0,0 +1,287 @@ +// Copyright 2026 eat-pray-ai & OpenWaygate +// SPDX-License-Identifier: Apache-2.0 + +package liveChatBan + +import ( + "bytes" + "encoding/json" + "net/http" + "reflect" + "testing" + + "github.com/eat-pray-ai/yutu/pkg/common" + "google.golang.org/api/youtube/v3" +) + +func TestNewLiveChatBan(t *testing.T) { + type args struct { + opts []Option + } + + svc := &youtube.Service{} + + tests := []struct { + name string + args args + want ILiveChatBan + }{ + { + name: "with all options", + args: args{ + opts: []Option{ + WithIds([]string{"ban1", "ban2"}), + WithLiveChatId("liveChat123"), + WithBannedUserChannelId("channel456"), + WithBanDurationSeconds(300), + WithBanType("temporary"), + WithParts([]string{"snippet"}), + WithOutput("json"), + WithService(svc), + }, + }, + want: &LiveChatBan{ + Fields: common.Fields{ + Service: svc, + Parts: []string{"snippet"}, + Output: "json", + Ids: []string{"ban1", "ban2"}, + }, + LiveChatId: "liveChat123", + BannedUserChannelId: "channel456", + BanDurationSeconds: 300, + BanType: "temporary", + }, + }, + { + name: "with no options", + args: args{ + opts: []Option{}, + }, + want: &LiveChatBan{Fields: common.Fields{}}, + }, + { + name: "with empty string values", + args: args{ + opts: []Option{ + WithLiveChatId(""), + WithBannedUserChannelId(""), + WithBanType(""), + }, + }, + want: &LiveChatBan{ + Fields: common.Fields{}, + LiveChatId: "", + BannedUserChannelId: "", + BanType: "", + }, + }, + { + name: "with permanent ban", + args: args{ + opts: []Option{ + WithLiveChatId("chat789"), + WithBannedUserChannelId("user123"), + WithBanType("permanent"), + }, + }, + want: &LiveChatBan{ + Fields: common.Fields{}, + LiveChatId: "chat789", + BannedUserChannelId: "user123", + BanType: "permanent", + }, + }, + { + name: "with partial options", + args: args{ + opts: []Option{ + WithLiveChatId("chat456"), + WithBanType("temporary"), + WithBanDurationSeconds(600), + }, + }, + want: &LiveChatBan{ + Fields: common.Fields{}, + LiveChatId: "chat456", + BanType: "temporary", + BanDurationSeconds: 600, + }, + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + if got := NewLiveChatBan(tt.args.opts...); !reflect.DeepEqual( + got, tt.want, + ) { + t.Errorf("%s\nNewLiveChatBan() = %v\nwant %v", tt.name, got, tt.want) + } + }, + ) + } +} + +func TestLiveChatBan_Insert(t *testing.T) { + tests := []struct { + name string + opts []Option + verify func(*http.Request) + wantErr bool + }{ + { + name: "insert live chat ban", + opts: []Option{ + WithLiveChatId("live-chat-id"), + WithBannedUserChannelId("banned-channel-id"), + WithBanType("permanent"), + WithParts([]string{"snippet"}), + }, + verify: func(r *http.Request) { + if r.Method != "POST" { + t.Errorf("expected POST, got %s", r.Method) + } + + defer func() { _ = r.Body.Close() }() + var body struct { + Snippet struct { + LiveChatId string `json:"liveChatId"` + BannedUserDetails struct { + ChannelId string `json:"channelId"` + } `json:"bannedUserDetails"` + Type string `json:"type"` + } `json:"snippet"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + t.Fatalf("failed to decode request body: %v", err) + } + + if body.Snippet.LiveChatId != "live-chat-id" { + t.Errorf( + "expected snippet.liveChatId=live-chat-id, got %s", + body.Snippet.LiveChatId, + ) + } + if body.Snippet.BannedUserDetails.ChannelId != "banned-channel-id" { + t.Errorf( + "expected snippet.bannedUserDetails.channelId=banned-channel-id, got %s", + body.Snippet.BannedUserDetails.ChannelId, + ) + } + if body.Snippet.Type != "permanent" { + t.Errorf("expected snippet.type=permanent, got %s", body.Snippet.Type) + } + }, + wantErr: false, + }, + { + name: "insert temporary ban with duration", + opts: []Option{ + WithLiveChatId("live-chat-id"), + WithBannedUserChannelId("banned-channel-id"), + WithBanType("temporary"), + WithBanDurationSeconds(300), + WithParts([]string{"snippet"}), + }, + verify: func(r *http.Request) { + defer func() { _ = r.Body.Close() }() + var body struct { + Snippet struct { + BanDurationSeconds string `json:"banDurationSeconds"` + Type string `json:"type"` + } `json:"snippet"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + t.Fatalf("failed to decode request body: %v", err) + } + + if body.Snippet.Type != "temporary" { + t.Errorf("expected snippet.type=temporary, got %s", body.Snippet.Type) + } + if body.Snippet.BanDurationSeconds != "300" { + t.Errorf( + "expected snippet.banDurationSeconds=300, got %s", + body.Snippet.BanDurationSeconds, + ) + } + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + svc := common.NewTestService( + t, http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if tt.verify != nil { + tt.verify(r) + } + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"id": "new-ban-id"}`)) + }, + ), + ) + + opts := append([]Option{WithService(svc)}, tt.opts...) + b := NewLiveChatBan(opts...) + var buf bytes.Buffer + if err := b.Insert(&buf); (err != nil) != tt.wantErr { + t.Errorf( + "LiveChatBan.Insert() error = %v, wantErr %v", err, tt.wantErr, + ) + } + }, + ) + } +} + +func TestLiveChatBan_Delete(t *testing.T) { + tests := []struct { + name string + opts []Option + verify func(*http.Request) + wantErr bool + }{ + { + name: "delete live chat ban", + opts: []Option{ + WithIds([]string{"ban-id"}), + }, + verify: func(r *http.Request) { + if r.Method != "DELETE" { + t.Errorf("expected DELETE, got %s", r.Method) + } + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + svc := common.NewTestService( + t, http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if tt.verify != nil { + tt.verify(r) + } + w.WriteHeader(http.StatusNoContent) + }, + ), + ) + + opts := append([]Option{WithService(svc)}, tt.opts...) + b := NewLiveChatBan(opts...) + var buf bytes.Buffer + if err := b.Delete(&buf); (err != nil) != tt.wantErr { + t.Errorf( + "LiveChatBan.Delete() error = %v, wantErr %v", err, tt.wantErr, + ) + } + }, + ) + } +}