Skip to content

Commit 90b03e8

Browse files
authored
Merge pull request #253 from twitchdev/ccl-updates
Content Classification Label updates
2 parents 5a867de + 1f9dbe2 commit 90b03e8

13 files changed

Lines changed: 548 additions & 50 deletions

File tree

internal/database/init.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"github.com/jmoiron/sqlx"
1111
)
1212

13-
const currentVersion = 4
13+
const currentVersion = 5
1414

1515
type migrateMap struct {
1616
SQL string
@@ -37,12 +37,18 @@ var migrateSQL = map[int]migrateMap{
3737
4: {
3838
SQL: `
3939
ALTER TABLE categories ADD COLUMN igdb_id text not null default 0; UPDATE categories SET igdb_id = abs(random() % 100000); ALTER TABLE clips ADD COLUMN vod_offset int default 0; UPDATE clips SET vod_offset = abs(random() % 3000); ALTER TABLE drops_entitlements ADD COLUMN last_updated text default '2023-01-01T04:17:53.325Z';
40-
CREATE TABLE chat_settings( broadcaster_id text not null primary key, slow_mode boolean not null default 0, slow_mode_wait_time int not null default 10, follower_mode boolean not null default 0, follower_mode_duration int not null default 60, subscriber_mode boolean not null default 0, emote_mode boolean not null default 0, unique_chat_mode boolean not null default 0, non_moderator_chat_delay boolean not null default 0, non_moderator_chat_delay_duration int not null default 10, shieldmode_is_active boolean not null default 0, shieldmode_moderator_id text not null default '', shieldmode_moderator_login text not null default '', shieldmode_moderator_name text not null default '', shieldmode_last_activated text not null default '' );
40+
CREATE TABLE chat_settings (broadcaster_id text not null primary key, slow_mode boolean not null default 0, slow_mode_wait_time int not null default 10, follower_mode boolean not null default 0, follower_mode_duration int not null default 60, subscriber_mode boolean not null default 0, emote_mode boolean not null default 0, unique_chat_mode boolean not null default 0, non_moderator_chat_delay boolean not null default 0, non_moderator_chat_delay_duration int not null default 10, shieldmode_is_active boolean not null default 0, shieldmode_moderator_id text not null default '', shieldmode_moderator_login text not null default '', shieldmode_moderator_name text not null default '', shieldmode_last_activated text not null default '' );
4141
INSERT INTO chat_settings (broadcaster_id) SELECT id FROM users;
4242
ALTER TABLE users ADD COLUMN chat_color text not null default '#9146FF';
4343
CREATE TABLE vips ( broadcaster_id text not null, user_id text not null, created_at text not null default '', primary key (broadcaster_id, user_id), foreign key (broadcaster_id) references users(id), foreign key (user_id) references users(id) );`,
4444
Message: `Updating database to include API changes since last version. See Twitch CLI changelog for more info.`,
4545
},
46+
5: {
47+
SQL: `
48+
ALTER TABLE users ADD COLUMN branded_content boolean not null default false;
49+
ALTER TABLE users ADD COLUMN content_labels text not null default '';`,
50+
Message: `Updating database to include Content Classification Label field.`,
51+
},
4652
}
4753

4854
func checkAndUpdate(db sqlx.DB) error {
@@ -81,7 +87,7 @@ func initDatabase(db sqlx.DB) error {
8187
createSQL := `
8288
create table events( id text not null primary key, event text not null, json text not null, from_user text not null, to_user text not null, transport text not null, timestamp text not null);
8389
create table categories( id text not null primary key, category_name text not null, igdb_id text not null );
84-
create table users( id text not null primary key, user_login text not null, display_name text not null, email text not null, user_type text, broadcaster_type text, user_description text, created_at text not null, category_id text, modified_at text, stream_language text not null default 'en', title text not null default '', delay int not null default 0, chat_color text not null default '#9146FF', foreign key (category_id) references categories(id) );
90+
create table users( id text not null primary key, user_login text not null, display_name text not null, email text not null, user_type text, broadcaster_type text, user_description text, created_at text not null, category_id text, modified_at text, stream_language text not null default 'en', title text not null default '', delay int not null default 0, chat_color text not null default '#9146FF', branded_content boolean not null default false, content_labels text not null default '', foreign key (category_id) references categories(id) );
8591
create table follows ( broadcaster_id text not null, user_id text not null, created_at text not null, primary key (broadcaster_id, user_id), foreign key (broadcaster_id) references users(id), foreign key (user_id) references users(id) );
8692
create table blocks ( broadcaster_id text not null, user_id text not null, created_at text not null, primary key (broadcaster_id, user_id), foreign key (broadcaster_id) references users(id), foreign key (user_id) references users(id) );
8793
create table bans ( broadcaster_id text not null, user_id text not null, created_at text not null, expires_at text, primary key (broadcaster_id, user_id), foreign key (broadcaster_id) references users(id), foreign key (user_id) references users(id) );

internal/database/user.go

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,28 @@ import (
1313
)
1414

1515
type User struct {
16-
ID string `db:"id" json:"id" dbs:"u1.id"`
17-
UserLogin string `db:"user_login" json:"login"`
18-
DisplayName string `db:"display_name" json:"display_name"`
19-
Email string `db:"email" json:"email,omitempty"`
20-
UserType string `db:"user_type" json:"type"`
21-
BroadcasterType string `db:"broadcaster_type" json:"broadcaster_type"`
22-
UserDescription string `db:"user_description" json:"description"`
23-
CreatedAt string `db:"created_at" json:"created_at"`
24-
ModifiedAt string `db:"modified_at" json:"-"`
25-
ProfileImageURL string `dbi:"false" json:"profile_image_url" `
26-
OfflineImageURL string `dbi:"false" json:"offline_image_url" `
27-
ViewCount int `dbi:"false" json:"view_count"`
28-
CategoryID sql.NullString `db:"category_id" json:"game_id" dbi:"force"`
29-
CategoryName sql.NullString `db:"category_name" json:"game_name" dbi:"false"`
30-
Title string `db:"title" json:"title"`
31-
Language string `db:"stream_language" json:"stream_language"`
32-
Delay int `db:"delay" json:"delay" dbi:"force"`
33-
ChatColor string `db:"chat_color" json:"-"`
16+
ID string `db:"id" json:"id" dbs:"u1.id"`
17+
UserLogin string `db:"user_login" json:"login"`
18+
DisplayName string `db:"display_name" json:"display_name"`
19+
Email string `db:"email" json:"email,omitempty"`
20+
UserType string `db:"user_type" json:"type"`
21+
BroadcasterType string `db:"broadcaster_type" json:"broadcaster_type"`
22+
UserDescription string `db:"user_description" json:"description"`
23+
CreatedAt string `db:"created_at" json:"created_at"`
24+
ModifiedAt string `db:"modified_at" json:"-"`
25+
ProfileImageURL string `dbi:"false" json:"profile_image_url" `
26+
OfflineImageURL string `dbi:"false" json:"offline_image_url" `
27+
ViewCount int `dbi:"false" json:"view_count"`
28+
CategoryID sql.NullString `db:"category_id" json:"game_id" dbi:"force"`
29+
CategoryName sql.NullString `db:"category_name" json:"game_name" dbi:"false"`
30+
Title string `db:"title" json:"title"`
31+
Language string `db:"stream_language" json:"stream_language"`
32+
Delay int `db:"delay" json:"delay" dbi:"force"`
33+
ChatColor string `db:"chat_color" json:"-"`
34+
IsBrandedContent bool `db:"branded_content" json:"is_branded_content"`
35+
36+
// UnparsedCCLs is a comma seperated array (e.g. "Gambling,ViolentGraphic,ProfanityVulgarity")
37+
UnparsedCCLs string `db:"content_labels" json:"-"`
3438
}
3539

3640
type Follow struct {

internal/events/types/stream_change/stream_change_event.go renamed to internal/events/types/channel_update_v1/channel_update.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3-
package stream_change
3+
package channel_update_v1
44

55
import (
66
"encoding/json"
@@ -71,7 +71,7 @@ func (e Event) GenerateEvent(params events.MockEventParameters) (events.MockEven
7171
StreamLanguage: "en",
7272
StreamCategoryID: params.GameID,
7373
StreamCategoryName: params.ItemName,
74-
IsMature: false,
74+
IsMature: falsePtr(),
7575
},
7676
}
7777
event, err = json.Marshal(body)
@@ -142,3 +142,8 @@ func (e Event) GetEventSubAlias(t string) string {
142142
func (e Event) SubscriptionVersion() string {
143143
return "1"
144144
}
145+
146+
func falsePtr() *bool {
147+
f := false
148+
return &f
149+
}

internal/events/types/stream_change/stream_change_event_test.go renamed to internal/events/types/channel_update_v1/channel_update_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3-
package stream_change
3+
package channel_update_v1
44

55
import (
66
"encoding/json"
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package channel_update_v2
4+
5+
import (
6+
"encoding/json"
7+
"strings"
8+
9+
"github.com/twitchdev/twitch-cli/internal/events"
10+
"github.com/twitchdev/twitch-cli/internal/models"
11+
)
12+
13+
var transportsSupported = map[string]bool{
14+
models.TransportWebhook: true,
15+
models.TransportWebSocket: true,
16+
}
17+
18+
var triggerSupported = []string{"stream-change"}
19+
20+
var triggerMapping = map[string]map[string]string{
21+
models.TransportWebhook: {
22+
"stream-change": "channel.update",
23+
},
24+
models.TransportWebSocket: {
25+
"stream-change": "channel.update",
26+
},
27+
}
28+
29+
type Event struct{}
30+
31+
func (e Event) GenerateEvent(params events.MockEventParameters) (events.MockEventResponse, error) {
32+
var event []byte
33+
var err error
34+
35+
if params.Description == "" {
36+
params.Description = "Example title from the CLI!"
37+
}
38+
if params.ItemID == "" && params.GameID == "" {
39+
params.GameID = "509658"
40+
} else if params.ItemID != "" && params.GameID == "" {
41+
params.GameID = params.ItemID
42+
}
43+
if params.ItemName == "" {
44+
params.ItemName = "Just Chatting"
45+
}
46+
47+
switch params.Transport {
48+
case models.TransportWebhook, models.TransportWebSocket:
49+
body := &models.EventsubResponse{
50+
// make the eventsub response (if supported)
51+
Subscription: models.EventsubSubscription{
52+
ID: params.ID,
53+
Status: params.SubscriptionStatus,
54+
Type: triggerMapping[params.Transport][params.Trigger],
55+
Version: e.SubscriptionVersion(),
56+
Condition: models.EventsubCondition{
57+
BroadcasterUserID: params.ToUserID,
58+
},
59+
Transport: models.EventsubTransport{
60+
Method: "webhook",
61+
Callback: "null",
62+
},
63+
Cost: 0,
64+
CreatedAt: params.Timestamp,
65+
},
66+
Event: models.ChannelUpdateEventSubEvent{
67+
BroadcasterUserID: params.ToUserID,
68+
BroadcasterUserLogin: params.ToUserName,
69+
BroadcasterUserName: params.ToUserName,
70+
StreamTitle: params.Description,
71+
StreamLanguage: "en",
72+
StreamCategoryID: params.GameID,
73+
StreamCategoryName: params.ItemName,
74+
ContentClassificationLabels: []string{
75+
"MatureGame",
76+
"ViolentGraphic",
77+
},
78+
},
79+
}
80+
event, err = json.Marshal(body)
81+
if err != nil {
82+
return events.MockEventResponse{}, err
83+
}
84+
85+
// Delete event info if Subscription.Status is not set to "enabled"
86+
if !strings.EqualFold(params.SubscriptionStatus, "enabled") {
87+
var i interface{}
88+
if err := json.Unmarshal([]byte(event), &i); err != nil {
89+
return events.MockEventResponse{}, err
90+
}
91+
if m, ok := i.(map[string]interface{}); ok {
92+
delete(m, "event") // Matches JSON key defined in body variable above
93+
}
94+
95+
event, err = json.Marshal(i)
96+
if err != nil {
97+
return events.MockEventResponse{}, err
98+
}
99+
}
100+
default:
101+
return events.MockEventResponse{}, nil
102+
}
103+
104+
return events.MockEventResponse{
105+
ID: params.ID,
106+
JSON: event,
107+
FromUser: params.FromUserID,
108+
ToUser: params.ToUserID,
109+
}, nil
110+
}
111+
112+
func (e Event) ValidTransport(t string) bool {
113+
return transportsSupported[t]
114+
}
115+
116+
func (e Event) ValidTrigger(t string) bool {
117+
for _, ts := range triggerSupported {
118+
if ts == t {
119+
return true
120+
}
121+
}
122+
return false
123+
}
124+
125+
func (e Event) GetTopic(transport string, trigger string) string {
126+
return triggerMapping[transport][trigger]
127+
}
128+
func (e Event) GetAllTopicsByTransport(transport string) []string {
129+
allTopics := []string{}
130+
for _, topic := range triggerMapping[transport] {
131+
allTopics = append(allTopics, topic)
132+
}
133+
return allTopics
134+
}
135+
func (e Event) GetEventSubAlias(t string) string {
136+
// check for aliases
137+
for trigger, topic := range triggerMapping[models.TransportWebhook] {
138+
if topic == t {
139+
return trigger
140+
}
141+
}
142+
return ""
143+
}
144+
145+
func (e Event) SubscriptionVersion() string {
146+
return "beta"
147+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package channel_update_v2
4+
5+
import (
6+
"encoding/json"
7+
"testing"
8+
9+
"github.com/twitchdev/twitch-cli/internal/events"
10+
"github.com/twitchdev/twitch-cli/internal/models"
11+
"github.com/twitchdev/twitch-cli/test_setup"
12+
)
13+
14+
var fromUser = "1234"
15+
var toUser = "4567"
16+
17+
func TestEventSub(t *testing.T) {
18+
a := test_setup.SetupTestEnv(t)
19+
20+
params := *&events.MockEventParameters{
21+
FromUserID: fromUser,
22+
ToUserID: toUser,
23+
Transport: models.TransportWebhook,
24+
Trigger: "stream-change",
25+
SubscriptionStatus: "enabled",
26+
}
27+
28+
r, err := Event{}.GenerateEvent(params)
29+
a.Nil(err)
30+
31+
var body models.ChannelUpdateEventSubResponse
32+
err = json.Unmarshal(r.JSON, &body)
33+
a.Nil(err, "Error unmarshalling JSON")
34+
35+
// write actual tests here (making sure you set appropriate values and the like) for eventsub
36+
a.Equal(toUser, body.Event.BroadcasterUserID, "Expected Stream Channel %v, got %v", toUser, body.Event.BroadcasterUserID)
37+
38+
// test for changing a title
39+
params = events.MockEventParameters{
40+
FromUserID: fromUser,
41+
ToUserID: toUser,
42+
Transport: models.TransportWebhook,
43+
Trigger: "stream_change",
44+
SubscriptionStatus: "enabled",
45+
GameID: "1234",
46+
}
47+
48+
r, err = Event{}.GenerateEvent(params)
49+
a.Nil(err)
50+
51+
err = json.Unmarshal(r.JSON, &body)
52+
a.Nil(err)
53+
54+
a.Equal(toUser, body.Event.BroadcasterUserID, "Expected Stream Channel %v, got %v", toUser, body.Event.BroadcasterUserID)
55+
a.Equal("Example title from the CLI!", body.Event.StreamTitle, "Expected new stream title, got %v", body.Event.StreamTitle)
56+
a.Equal("1234", body.Event.StreamCategoryID)
57+
}
58+
59+
func TestFakeTransport(t *testing.T) {
60+
a := test_setup.SetupTestEnv(t)
61+
62+
params := events.MockEventParameters{
63+
FromUserID: fromUser,
64+
ToUserID: toUser,
65+
Transport: "fake_transport",
66+
Trigger: "stream-change",
67+
SubscriptionStatus: "enabled",
68+
}
69+
70+
r, err := Event{}.GenerateEvent(params)
71+
a.Nil(err)
72+
a.Empty(r)
73+
}
74+
func TestValidTrigger(t *testing.T) {
75+
a := test_setup.SetupTestEnv(t)
76+
77+
r := Event{}.ValidTrigger("stream-change")
78+
a.Equal(true, r)
79+
80+
r = Event{}.ValidTrigger("not_trigger_keyword")
81+
a.Equal(false, r)
82+
}
83+
84+
func TestValidTransport(t *testing.T) {
85+
a := test_setup.SetupTestEnv(t)
86+
87+
r := Event{}.ValidTransport(models.TransportWebhook)
88+
a.Equal(true, r)
89+
90+
r = Event{}.ValidTransport("noteventsub")
91+
a.Equal(false, r)
92+
}
93+
func TestGetTopic(t *testing.T) {
94+
a := test_setup.SetupTestEnv(t)
95+
96+
r := Event{}.GetTopic(models.TransportWebhook, "stream-change")
97+
a.NotNil(r)
98+
}

internal/events/types/types.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
"github.com/twitchdev/twitch-cli/internal/events/types/ban"
1515
"github.com/twitchdev/twitch-cli/internal/events/types/channel_points_redemption"
1616
"github.com/twitchdev/twitch-cli/internal/events/types/channel_points_reward"
17+
"github.com/twitchdev/twitch-cli/internal/events/types/channel_update_v1"
18+
"github.com/twitchdev/twitch-cli/internal/events/types/channel_update_v2"
1719
"github.com/twitchdev/twitch-cli/internal/events/types/charity"
1820
"github.com/twitchdev/twitch-cli/internal/events/types/cheer"
1921
"github.com/twitchdev/twitch-cli/internal/events/types/drop"
@@ -29,7 +31,6 @@ import (
2931
"github.com/twitchdev/twitch-cli/internal/events/types/raid"
3032
"github.com/twitchdev/twitch-cli/internal/events/types/shield_mode"
3133
"github.com/twitchdev/twitch-cli/internal/events/types/shoutout"
32-
"github.com/twitchdev/twitch-cli/internal/events/types/stream_change"
3334
"github.com/twitchdev/twitch-cli/internal/events/types/streamdown"
3435
"github.com/twitchdev/twitch-cli/internal/events/types/streamup"
3536
"github.com/twitchdev/twitch-cli/internal/events/types/subscribe"
@@ -60,7 +61,8 @@ func AllEvents() []events.MockEvent {
6061
raid.Event{},
6162
shield_mode.Event{},
6263
shoutout.Event{},
63-
stream_change.Event{},
64+
channel_update_v1.Event{},
65+
channel_update_v2.Event{},
6466
streamup.Event{},
6567
streamdown.Event{},
6668
subscribe.Event{},

0 commit comments

Comments
 (0)