Added chatbot functionality
This commit is contained in:
parent
79031a1978
commit
2906a372d5
9
NOTES.md
9
NOTES.md
|
@ -1,9 +1,18 @@
|
|||
# Doing
|
||||
|
||||
|
||||
|
||||
Next steps:
|
||||
- add option to delete bot rules
|
||||
- delete page needs to handle new architecture
|
||||
- app.producers.Active(*name) instead of app.producers.ApiP.Active(*name)
|
||||
- app.producers.Stop(*name)
|
||||
- activatePage: verify defer page.activeMu.Unlock does not conflict with display function
|
||||
|
||||
For Dashboard page,
|
||||
- Api or chat producer could error, need to be able to start/restart both handlers on error
|
||||
- Show user error if api or chat stop/error
|
||||
|
||||
On API errors
|
||||
- include backoff multiple, if exceeded then stop API
|
||||
|
||||
|
|
259
v1/app.go
259
v1/app.go
|
@ -40,10 +40,13 @@ type Page struct {
|
|||
name string
|
||||
}
|
||||
|
||||
func (p *Page) staticLiveStreamUrl() string {
|
||||
return fmt.Sprintf("https://rumble.com%s/live", p.name)
|
||||
}
|
||||
|
||||
// App struct
|
||||
type App struct {
|
||||
//apiS *ApiService
|
||||
cancelCtrl context.CancelFunc
|
||||
cancelProc context.CancelFunc
|
||||
clients map[string]*rumblelivestreamlib.Client
|
||||
clientsMu sync.Mutex
|
||||
displaying string
|
||||
|
@ -70,8 +73,6 @@ func NewApp() *App {
|
|||
log.Fatal("error initializing log: ", err)
|
||||
}
|
||||
|
||||
//app.apiS = NewApiService(app.logError, app.logInfo)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
|
@ -94,30 +95,28 @@ func (a *App) log() error {
|
|||
// so we can call the runtime methods
|
||||
func (a *App) startup(wails context.Context) {
|
||||
a.wails = wails
|
||||
|
||||
//a.apiS.Startup(a.apiS.ch)
|
||||
|
||||
// ctx, cancel := context.WithCancel(context.Background())
|
||||
// a.cancelCtrl = cancel
|
||||
|
||||
// go a.handle(ctx)
|
||||
}
|
||||
|
||||
func (a *App) handle(ctx context.Context) {
|
||||
func (a *App) process(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case apiE := <-a.producers.ApiP.Ch:
|
||||
err := a.handleApi(apiE)
|
||||
err := a.processApi(apiE)
|
||||
if err != nil {
|
||||
a.logError.Println("error handling API event:", err)
|
||||
}
|
||||
case chatE := <-a.producers.ChatP.Ch:
|
||||
err := a.processChat(chatE)
|
||||
if err != nil {
|
||||
a.logError.Println("error handling chat event:", err)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) handleApi(event events.Api) error {
|
||||
func (a *App) processApi(event events.Api) error {
|
||||
if event.Name == "" {
|
||||
return fmt.Errorf("event name is empty")
|
||||
}
|
||||
|
@ -134,7 +133,7 @@ func (a *App) handleApi(event events.Api) error {
|
|||
}
|
||||
|
||||
page.apiSt.activeMu.Lock()
|
||||
page.apiSt.active = event.Stop
|
||||
page.apiSt.active = !event.Stop
|
||||
page.apiSt.activeMu.Unlock()
|
||||
|
||||
if event.Stop {
|
||||
|
@ -158,21 +157,17 @@ func (a *App) handleApi(event events.Api) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *App) shutdown(ctx context.Context) {
|
||||
// if a.apiS != nil && a.apiS.Api != nil {
|
||||
// err := a.apiS.Shutdown()
|
||||
// if err != nil {
|
||||
// a.logError.Println("error shutting down api:", err)
|
||||
// }
|
||||
func (a *App) processChat(event events.Chat) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// close(a.apiS.ch)
|
||||
// }
|
||||
func (a *App) shutdown(ctx context.Context) {
|
||||
err := a.producers.Shutdown()
|
||||
if err != nil {
|
||||
a.logError.Println("error closing event producers:", err)
|
||||
}
|
||||
|
||||
a.cancelCtrl()
|
||||
a.cancelProc()
|
||||
|
||||
if a.services != nil {
|
||||
err := a.services.Close()
|
||||
|
@ -223,8 +218,8 @@ func (a *App) Start() (bool, error) {
|
|||
// runtime.EventsEmit(a.ctx, "StartupMessage", "Checking for updates complete.")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
a.cancelCtrl = cancel
|
||||
go a.handle(ctx)
|
||||
a.cancelProc = cancel
|
||||
go a.process(ctx)
|
||||
|
||||
signin := true
|
||||
if count > 0 {
|
||||
|
@ -238,6 +233,7 @@ func (a *App) initProducers() error {
|
|||
producers, err := events.NewProducers(
|
||||
events.WithLoggers(a.logError, a.logInfo),
|
||||
events.WithApiProducer(),
|
||||
events.WithChatProducer(),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error initializing producers: %v", err)
|
||||
|
@ -264,6 +260,7 @@ func (a *App) initServices() error {
|
|||
models.WithAccountService(),
|
||||
models.WithChannelService(),
|
||||
models.WithAccountChannelService(),
|
||||
models.WithChatbotService(),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error initializing services: %v", err)
|
||||
|
@ -310,7 +307,9 @@ func (a *App) verifyAccounts() (int, error) {
|
|||
} else {
|
||||
account.Cookies = nil
|
||||
err = a.services.AccountS.Update(&account)
|
||||
fmt.Errorf("error updating account: %v", err)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("error updating account: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -319,7 +318,7 @@ func (a *App) verifyAccounts() (int, error) {
|
|||
}
|
||||
|
||||
func (a *App) AddPage(apiKey string) error {
|
||||
client := rumblelivestreamlib.Client{StreamKey: apiKey}
|
||||
client := rumblelivestreamlib.Client{ApiKey: apiKey}
|
||||
resp, err := client.Request()
|
||||
if err != nil {
|
||||
a.logError.Println("error executing api request:", err)
|
||||
|
@ -347,6 +346,13 @@ func (a *App) AddPage(apiKey string) error {
|
|||
}
|
||||
}
|
||||
|
||||
list, err := a.accountList()
|
||||
if err != nil {
|
||||
a.logError.Println("error getting account list:", err)
|
||||
return fmt.Errorf("Error logging in. Try again.")
|
||||
}
|
||||
runtime.EventsEmit(a.wails, "PageSideBarAccounts", list)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -833,7 +839,6 @@ func (a *App) DeleteAccount(id int64) error {
|
|||
a.logError.Println("error getting account list:", err)
|
||||
return fmt.Errorf("Error deleting account. Try again.")
|
||||
}
|
||||
|
||||
runtime.EventsEmit(a.wails, "PageSideBarAccounts", list)
|
||||
|
||||
return nil
|
||||
|
@ -876,7 +881,6 @@ func (a *App) DeleteChannel(id int64) error {
|
|||
a.logError.Println("error getting account list:", err)
|
||||
return fmt.Errorf("Error deleting channel. Try again.")
|
||||
}
|
||||
|
||||
runtime.EventsEmit(a.wails, "PageSideBarAccounts", list)
|
||||
|
||||
return nil
|
||||
|
@ -958,7 +962,6 @@ func (a *App) PageStatus(name string) {
|
|||
active := false
|
||||
isLive := false
|
||||
|
||||
// resp := a.api.Response(name)
|
||||
a.pagesMu.Lock()
|
||||
defer a.pagesMu.Unlock()
|
||||
page, exists := a.pages[name]
|
||||
|
@ -1002,7 +1005,7 @@ func (a *App) UpdateAccountApi(id int64, apiKey string) error {
|
|||
}
|
||||
}
|
||||
|
||||
client := rumblelivestreamlib.Client{StreamKey: apiKey}
|
||||
client := rumblelivestreamlib.Client{ApiKey: apiKey}
|
||||
resp, err := client.Request()
|
||||
if err != nil {
|
||||
a.logError.Println("error executing api request:", err)
|
||||
|
@ -1047,7 +1050,7 @@ func (a *App) UpdateChannelApi(id int64, apiKey string) error {
|
|||
}
|
||||
}
|
||||
|
||||
client := rumblelivestreamlib.Client{StreamKey: apiKey}
|
||||
client := rumblelivestreamlib.Client{ApiKey: apiKey}
|
||||
resp, err := client.Request()
|
||||
if err != nil {
|
||||
a.logError.Println("error executing api request:", err)
|
||||
|
@ -1067,3 +1070,193 @@ func (a *App) UpdateChannelApi(id int64, apiKey string) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) DeleteChatbot(chatbot *models.Chatbot) error {
|
||||
if chatbot == nil || chatbot.ID == nil {
|
||||
return fmt.Errorf("Invalid chatbot. Try again.")
|
||||
}
|
||||
|
||||
cb, err := a.services.ChatbotS.ByID(*chatbot.ID)
|
||||
if err != nil {
|
||||
a.logError.Println("error getting chatbot by ID:", err)
|
||||
return fmt.Errorf("Error deleting chatbot. Try again.")
|
||||
}
|
||||
if cb == nil {
|
||||
return fmt.Errorf("Chatbot does not exist.")
|
||||
}
|
||||
|
||||
err = a.services.ChatbotS.Delete(chatbot)
|
||||
if err != nil {
|
||||
a.logError.Println("error deleting chatbot:", err)
|
||||
return fmt.Errorf("Error deleting chatbot. Try again.")
|
||||
}
|
||||
|
||||
list, err := a.chatbotList()
|
||||
if err != nil {
|
||||
a.logError.Println("error getting chatbot list:", err)
|
||||
return fmt.Errorf("Error deleting chatbot. Try again.")
|
||||
}
|
||||
runtime.EventsEmit(a.wails, "ChatbotList", list)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) NewChatbot(chatbot *models.Chatbot) error {
|
||||
if chatbot == nil || chatbot.Name == nil {
|
||||
return fmt.Errorf("Invalid chatbot. Try again.")
|
||||
}
|
||||
|
||||
cb, err := a.services.ChatbotS.ByName(*chatbot.Name)
|
||||
if err != nil {
|
||||
a.logError.Println("error getting chatbot by name:", err)
|
||||
return fmt.Errorf("Error creating chatbot. Try again.")
|
||||
}
|
||||
if cb != nil {
|
||||
return fmt.Errorf("Chatbot name already exists.")
|
||||
}
|
||||
|
||||
_, err = a.services.ChatbotS.Create(chatbot)
|
||||
if err != nil {
|
||||
a.logError.Println("error creating chatbot:", err)
|
||||
return fmt.Errorf("Error creating chatbot. Try again.")
|
||||
}
|
||||
|
||||
list, err := a.chatbotList()
|
||||
if err != nil {
|
||||
a.logError.Println("error getting chatbot list:", err)
|
||||
return fmt.Errorf("Error creating chatbot. Try again.")
|
||||
}
|
||||
runtime.EventsEmit(a.wails, "ChatbotList", list)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) UpdateChatbot(chatbot *models.Chatbot) error {
|
||||
if chatbot == nil || chatbot.ID == nil || chatbot.Name == nil {
|
||||
return fmt.Errorf("Invalid chatbot. Try again.")
|
||||
}
|
||||
|
||||
cb, err := a.services.ChatbotS.ByID(*chatbot.ID)
|
||||
if err != nil {
|
||||
a.logError.Println("error getting chatbot by ID:", err)
|
||||
return fmt.Errorf("Error updating chatbot. Try again.")
|
||||
}
|
||||
if cb == nil {
|
||||
return fmt.Errorf("Chatbot does not exist.")
|
||||
}
|
||||
|
||||
cbByName, err := a.services.ChatbotS.ByName(*chatbot.Name)
|
||||
if err != nil {
|
||||
a.logError.Println("error getting chatbot by Name:", err)
|
||||
return fmt.Errorf("Error updating chatbot. Try again.")
|
||||
}
|
||||
if cbByName != nil && *cbByName.ID != *cb.ID {
|
||||
return fmt.Errorf("Chatbot name already exists.")
|
||||
}
|
||||
|
||||
err = a.services.ChatbotS.Update(chatbot)
|
||||
if err != nil {
|
||||
a.logError.Println("error updating chatbot:", err)
|
||||
return fmt.Errorf("Error updating chatbot. Try again.")
|
||||
}
|
||||
|
||||
list, err := a.chatbotList()
|
||||
if err != nil {
|
||||
a.logError.Println("error getting chatbot list:", err)
|
||||
return fmt.Errorf("Error updating chatbot. Try again.")
|
||||
}
|
||||
runtime.EventsEmit(a.wails, "ChatbotList", list)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) ChatbotList() ([]models.Chatbot, error) {
|
||||
list, err := a.chatbotList()
|
||||
if err != nil {
|
||||
a.logError.Println("error getting chatbot list:", err)
|
||||
return nil, fmt.Errorf("Error retrieving chatbots. Try restarting.")
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (a *App) chatbotList() ([]models.Chatbot, error) {
|
||||
list, err := a.services.ChatbotS.All()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error querying all chatbots: %v", err)
|
||||
}
|
||||
|
||||
return list, err
|
||||
}
|
||||
|
||||
type ChatbotRule struct {
|
||||
Message *ChatbotRuleMessage `json:"message"`
|
||||
SendAs *ChatbotRuleSender `json:"send_as"`
|
||||
Trigger *ChatbotRuleTrigger `json:"trigger"`
|
||||
}
|
||||
|
||||
type ChatbotRuleMessage struct {
|
||||
FromFile *ChatbotRuleMessageFile `json:"from_file"`
|
||||
FromText string `json:"from_text"`
|
||||
}
|
||||
|
||||
type ChatbotRuleMessageFile struct {
|
||||
Filepath string `json:"filepath"`
|
||||
RandomRead bool `json:"random_read"`
|
||||
}
|
||||
|
||||
type ChatbotRuleSender struct {
|
||||
Username string `json:"username"`
|
||||
ChannelID *int `json:"channel_id"`
|
||||
}
|
||||
|
||||
type ChatbotRuleTrigger struct {
|
||||
OnCommand *ChatbotRuleTriggerCommand `json:"on_command"`
|
||||
OnEvent *ChatbotRuleTriggerEvent `json:"on_event"`
|
||||
OnTimer *time.Duration `json:"on_timer"`
|
||||
}
|
||||
|
||||
type ChatbotRuleTriggerCommand struct {
|
||||
Command string `json:"command"`
|
||||
Restrict *ChatbotRuleTriggerCommandRestriction `json:"restrict"`
|
||||
Timeout time.Duration `json:"timeout"`
|
||||
}
|
||||
|
||||
type ChatbotRuleTriggerCommandRestriction struct {
|
||||
Bypass *ChatbotRuleTriggerCommandRestrictionBypass `json:"bypass"`
|
||||
ToAdmin bool `json:"to_admin"`
|
||||
ToFollower bool `json:"to_follower"`
|
||||
ToMod bool `json:"to_mod"`
|
||||
ToStreamer bool `json:"to_streamer"`
|
||||
ToSubscriber bool `json:"to_subscriber"`
|
||||
ToRant int `json:"to_rant"`
|
||||
}
|
||||
|
||||
type ChatbotRuleTriggerCommandRestrictionBypass struct {
|
||||
IfAdmin bool `json:"if_admin"`
|
||||
IfMod bool `json:"if_mod"`
|
||||
IfStreamer bool `json:"if_streamer"`
|
||||
}
|
||||
|
||||
type ChatbotRuleTriggerEvent struct {
|
||||
OnFollow bool `json:"on_follow"`
|
||||
OnSubscribe bool `json:"on_subscribe"`
|
||||
OnRaid bool `json:"on_raid"`
|
||||
OnRant int `json:"on_rant"`
|
||||
}
|
||||
|
||||
func (a *App) OpenFileDialog() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
a.logError.Println("error getting home directory:", err)
|
||||
return "", fmt.Errorf("Error opening file explorer. Try again.")
|
||||
}
|
||||
|
||||
filepath, err := runtime.OpenFileDialog(a.wails, runtime.OpenDialogOptions{DefaultDirectory: home})
|
||||
if err != nil {
|
||||
a.logError.Println("error opening file dialog:", err)
|
||||
return "", fmt.Errorf("Error opening file explorer. Try again.")
|
||||
}
|
||||
|
||||
return filepath, err
|
||||
}
|
||||
|
|
BIN
v1/frontend/src/assets/icons/Font-Awesome/robot.png
Normal file
BIN
v1/frontend/src/assets/icons/Font-Awesome/robot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
BIN
v1/frontend/src/assets/icons/twbs/chevron-left.png
Normal file
BIN
v1/frontend/src/assets/icons/twbs/chevron-left.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
|
@ -1,4 +1,5 @@
|
|||
import chess_rook from './icons/Font-Awesome/chess-rook.png';
|
||||
import chevron_left from './icons/twbs/chevron-left.png';
|
||||
import chevron_right from './icons/twbs/chevron-right.png';
|
||||
import circle_green_background from './icons/twbs/circle-green-background.png';
|
||||
import circle_red_background from './icons/twbs/circle-red-background.png';
|
||||
|
@ -10,6 +11,7 @@ import heart from './icons/twbs/heart-fill.png';
|
|||
import pause from './icons/twbs/pause-circle-green.png';
|
||||
import play from './icons/twbs/play-circle-green.png';
|
||||
import plus_circle from './icons/twbs/plus-circle-fill.png';
|
||||
import robot from './icons/Font-Awesome/robot.png';
|
||||
import star from './icons/twbs/star-fill.png';
|
||||
import thumbs_down from './icons/twbs/hand-thumbs-down-fill.png';
|
||||
import thumbs_up from './icons/twbs/hand-thumbs-up-fill.png';
|
||||
|
@ -17,6 +19,7 @@ import x_lg from './icons/twbs/x-lg.png';
|
|||
import logo from './logo/logo.png';
|
||||
|
||||
export const ChessRook = chess_rook;
|
||||
export const ChevronLeft = chevron_left;
|
||||
export const ChevronRight = chevron_right;
|
||||
export const CircleGreenBackground = circle_green_background;
|
||||
export const CircleRedBackground = circle_red_background;
|
||||
|
@ -29,6 +32,7 @@ export const Logo = logo;
|
|||
export const Pause = pause;
|
||||
export const Play = play;
|
||||
export const PlusCircle = plus_circle;
|
||||
export const Robot = robot;
|
||||
export const Star = star;
|
||||
export const ThumbsDown = thumbs_down;
|
||||
export const ThumbsUp = thumbs_up;
|
||||
|
|
360
v1/frontend/src/components/ChatBot.css
Normal file
360
v1/frontend/src/components/ChatBot.css
Normal file
|
@ -0,0 +1,360 @@
|
|||
.chatbot {
|
||||
background-color: #344453;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-width: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbot-header {
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #061726;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-height: 55px;
|
||||
justify-content: space-between;
|
||||
padding: 10px 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbot-header-button {
|
||||
align-items: center;
|
||||
background-color: #344453;
|
||||
border: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-left: 10px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.chatbot-header-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chatbot-header-button-icon {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.chatbot-header-left {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.chatbot-header-icon {
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.chatbot-header-icon-back {
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
.chatbot-header-icon-back:hover {
|
||||
/* background-color: #415568; */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chatbot-header-right {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.chatbot-header-title {
|
||||
color: #eee;
|
||||
font-family: sans-serif;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
max-width: 250px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chatbot-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 0px 10px;
|
||||
}
|
||||
|
||||
.chatbot-list-button {
|
||||
align-items: center;
|
||||
background-color: #344453;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
padding: 15px 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbot-list-button:hover {
|
||||
background-color: #415568;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chatbot-list-item {
|
||||
}
|
||||
|
||||
.chatbot-list-item-name {
|
||||
color: #eee;
|
||||
display: inline-block;
|
||||
font-family: sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
/* width: 100%; */
|
||||
}
|
||||
|
||||
.chatbot-modal-form {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbot-modal-input {
|
||||
background-color: #061726;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
font-family: monospace;
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
padding: 10px;
|
||||
resize: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbot-modal-label {
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbot-modal-label-warning {
|
||||
color: #f23160;
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbot-modal-description {
|
||||
color: #eee;
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbot-modal-description-warning {
|
||||
color: #f23160;
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbot-modal-pages {
|
||||
background-color: white;
|
||||
/* border: 1px solid #D6E0EA; */
|
||||
border-radius: 5px;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.chatbot-modal-page {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.chatbot-modal-page-selected {
|
||||
background-color: #85c742;
|
||||
}
|
||||
|
||||
.chatbot-modal-page-button {
|
||||
background-color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
color: #061726;
|
||||
font-family: sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
padding: 10px 10px;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbot-modal-page-button:hover {
|
||||
background-color: #85c742;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chatbot-modal-setting {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding-top: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbot-modal-setting-description {
|
||||
color: #eee;
|
||||
font-family: sans-serif;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.chatbot-modal-textarea {
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
font-family: monospace;
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
padding: 10px;
|
||||
resize: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbot-modal-toggle-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.chatbot-modal-toggle-switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.chatbot-modal-toggle-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #495a6a;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.chatbot-modal-toggle-slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.choose-file {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.choose-file-button-box {
|
||||
min-width: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.choose-file-button {
|
||||
background-color: #85c742;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
color: #061726;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
text-decoration: none;
|
||||
/* width: 200px; */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.choose-file-path {
|
||||
color: #eee;
|
||||
font-family: monospace;
|
||||
font-size: 16px;
|
||||
overflow: scroll;
|
||||
margin-left: 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
input:checked + .chatbot-modal-toggle-slider {
|
||||
background-color: #85c742;
|
||||
}
|
||||
|
||||
input:checked + .chatbot-modal-toggle-slider:before {
|
||||
-webkit-transform: translateX(26px);
|
||||
-ms-transform: translateX(26px);
|
||||
transform: translateX(26px);
|
||||
}
|
||||
/* Rounded sliders */
|
||||
.chatbot-modal-toggle-slider.round {
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.chatbot-modal-toggle-slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.timer-input {
|
||||
border: none;
|
||||
border-radius: 34px;
|
||||
box-sizing: border-box;
|
||||
font-family: monospace;
|
||||
font-size: 24px;
|
||||
outline: none;
|
||||
padding: 5px 10px 5px 10px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.timer-input-zero::placeholder {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.timer-input-value::placeholder {
|
||||
color: black;
|
||||
opacity: 1;
|
||||
text-align: center;
|
||||
}
|
962
v1/frontend/src/components/ChatBot.jsx
Normal file
962
v1/frontend/src/components/ChatBot.jsx
Normal file
|
@ -0,0 +1,962 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { Modal, SmallModal } from './Modal';
|
||||
import {
|
||||
AccountList,
|
||||
ChatbotList,
|
||||
DeleteChatbot,
|
||||
NewChatbot,
|
||||
OpenFileDialog,
|
||||
UpdateChatbot,
|
||||
} from '../../wailsjs/go/main/App';
|
||||
import { EventsOn } from '../../wailsjs/runtime/runtime';
|
||||
import { ChevronLeft, ChevronRight, Gear, PlusCircle, Robot } from '../assets';
|
||||
import './ChatBot.css';
|
||||
|
||||
function ChatBot(props) {
|
||||
const [chatbots, setChatbots] = useState([]);
|
||||
const [deleteChatbot, setDeleteChatbot] = useState(false);
|
||||
const [editChatbot, setEditChatbot] = useState(null);
|
||||
const [error, setError] = useState('');
|
||||
const [openChatbot, setOpenChatbot] = useState(null);
|
||||
const [openNewChatbot, setOpenNewChatbot] = useState(false);
|
||||
const [openNewRule, setOpenNewRule] = useState(false);
|
||||
const [chatbotSettings, setChatbotSettings] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
EventsOn('ChatbotList', (event) => {
|
||||
setChatbots(event);
|
||||
if (openChatbot !== null) {
|
||||
for (const chatbot of event) {
|
||||
if (chatbot.id === openChatbot.id) {
|
||||
setOpenChatbot(chatbot);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
ChatbotList()
|
||||
.then((response) => {
|
||||
setChatbots(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
setError(error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const open = (chatbot) => {
|
||||
setOpenChatbot(chatbot);
|
||||
};
|
||||
|
||||
const closeEdit = () => {
|
||||
setEditChatbot(null);
|
||||
};
|
||||
|
||||
const openEdit = (chatbot) => {
|
||||
setEditChatbot(chatbot);
|
||||
};
|
||||
|
||||
const openNew = () => {
|
||||
setOpenNewChatbot(true);
|
||||
};
|
||||
|
||||
const sortChatbots = () => {
|
||||
let sorted = [...chatbots].sort((a, b) =>
|
||||
a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1
|
||||
);
|
||||
|
||||
return sorted;
|
||||
};
|
||||
|
||||
const confirmDelete = () => {
|
||||
DeleteChatbot(openChatbot)
|
||||
.then(() => {
|
||||
setDeleteChatbot(false);
|
||||
setEditChatbot(null);
|
||||
setOpenChatbot(null);
|
||||
})
|
||||
.catch((err) => {
|
||||
setDeleteChatbot(false);
|
||||
setError(err);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{error !== '' && (
|
||||
<SmallModal
|
||||
onClose={() => setError('')}
|
||||
show={error !== ''}
|
||||
style={{ minWidth: '300px', maxWidth: '200px', maxHeight: '200px' }}
|
||||
title={'Error'}
|
||||
message={error}
|
||||
submitButton={'OK'}
|
||||
onSubmit={() => setError('')}
|
||||
/>
|
||||
)}
|
||||
{openNewChatbot && (
|
||||
<ModalChatbot
|
||||
cancelButton={'Cancel'}
|
||||
onClose={() => setOpenNewChatbot(false)}
|
||||
show={setOpenNewChatbot}
|
||||
submit={NewChatbot}
|
||||
submitButton={'Create'}
|
||||
submittingButton={'Creating...'}
|
||||
title={'New Chatbot'}
|
||||
/>
|
||||
)}
|
||||
{editChatbot !== null && (
|
||||
<ModalChatbot
|
||||
chatbot={editChatbot}
|
||||
onClose={closeEdit}
|
||||
deleteButton={'Delete'}
|
||||
onDelete={() => setDeleteChatbot(true)}
|
||||
show={editChatbot !== null}
|
||||
submit={UpdateChatbot}
|
||||
submitButton={'Update'}
|
||||
submittingButton={'Updating...'}
|
||||
title={'Edit Chatbot'}
|
||||
/>
|
||||
)}
|
||||
{deleteChatbot && (
|
||||
<SmallModal
|
||||
cancelButton={'Cancel'}
|
||||
onCancel={() => setDeleteChatbot(false)}
|
||||
onClose={() => setDeleteChatbot(false)}
|
||||
show={deleteChatbot}
|
||||
style={{ minWidth: '300px', maxWidth: '200px', maxHeight: '200px' }}
|
||||
title={'Delete Chatbot'}
|
||||
message={
|
||||
'Are you sure you want to delete the chatbot? All rules associated with this chatbot will be deleted as well.'
|
||||
}
|
||||
submitButton={'OK'}
|
||||
onSubmit={confirmDelete}
|
||||
/>
|
||||
)}
|
||||
{openNewRule && (
|
||||
<ModalNewRule onClose={() => setOpenNewRule(false)} show={openNewRule} />
|
||||
)}
|
||||
<div className='chatbot'>
|
||||
{openChatbot === null ? (
|
||||
<>
|
||||
<div className='chatbot-header'>
|
||||
<div className='chatbot-header-left'>
|
||||
<img className='chatbot-header-icon' src={Robot} />
|
||||
<span className='chatbot-header-title'>Bots</span>
|
||||
</div>
|
||||
<div className='chatbot-header-right'>
|
||||
<button className='chatbot-header-button' onClick={openNew}>
|
||||
<img className='chatbot-header-button-icon' src={PlusCircle} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='chatbot-list'>
|
||||
{sortChatbots().map((chatbot, index) => (
|
||||
<ChatbotListItem chatbot={chatbot} key={index} onClick={open} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className='chatbot-header'>
|
||||
<div className='chatbot-header-left'>
|
||||
<img
|
||||
className='chatbot-header-icon-back'
|
||||
onClick={() => setOpenChatbot(null)}
|
||||
src={ChevronLeft}
|
||||
/>
|
||||
</div>
|
||||
<span className='chatbot-header-title'>{openChatbot.name}</span>
|
||||
<div className='chatbot-header-right'>
|
||||
<button
|
||||
className='chatbot-header-button'
|
||||
onClick={() => openEdit(openChatbot)}
|
||||
>
|
||||
<img className='chatbot-header-button-icon' src={Gear} />
|
||||
</button>
|
||||
<button
|
||||
className='chatbot-header-button'
|
||||
onClick={() => setOpenNewRule(true)}
|
||||
>
|
||||
<img className='chatbot-header-button-icon' src={PlusCircle} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChatBot;
|
||||
|
||||
function ChatbotListItem(props) {
|
||||
return (
|
||||
<div className='chatbot-list-item'>
|
||||
<button className='chatbot-list-button' onClick={() => props.onClick(props.chatbot)}>
|
||||
<span className='chatbot-list-item-name'>{props.chatbot.name}</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ModalChatbot(props) {
|
||||
const [error, setError] = useState('');
|
||||
const [id, setId] = useState(props.chatbot === undefined ? null : props.chatbot.id);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [name, setName] = useState(props.chatbot === undefined ? '' : props.chatbot.name);
|
||||
const updateName = (event) => {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
setName(event.target.value);
|
||||
};
|
||||
const [nameValid, setNameValid] = useState(true);
|
||||
const [url, setUrl] = useState(props.chatbot === undefined ? '' : props.chatbot.url);
|
||||
const updateUrl = (event) => {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
setUrl(event.target.value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
props
|
||||
.submit({ id: id, name: name, url: url })
|
||||
.then(() => {
|
||||
reset();
|
||||
props.onClose();
|
||||
})
|
||||
.catch((err) => {
|
||||
setLoading(false);
|
||||
setError(err);
|
||||
});
|
||||
}
|
||||
}, [loading]);
|
||||
|
||||
const close = () => {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
reset();
|
||||
props.onClose();
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
setLoading(false);
|
||||
setName('');
|
||||
setNameValid(true);
|
||||
};
|
||||
|
||||
const submit = () => {
|
||||
if (name == '') {
|
||||
setNameValid(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{error !== '' && (
|
||||
<SmallModal
|
||||
onClose={() => setError('')}
|
||||
show={error !== ''}
|
||||
style={{ minWidth: '300px', maxWidth: '200px', maxHeight: '200px' }}
|
||||
title={'Error'}
|
||||
message={error}
|
||||
submitButton={'OK'}
|
||||
onSubmit={() => setError('')}
|
||||
/>
|
||||
)}
|
||||
<Modal
|
||||
cancelButton={props.cancelButton}
|
||||
onCancel={close}
|
||||
onClose={close}
|
||||
deleteActive={true}
|
||||
deleteButton={props.deleteButton}
|
||||
onDelete={props.onDelete}
|
||||
show={props.show}
|
||||
style={{ minWidth: '400px', maxWidth: '400px', maxHeight: '400px' }}
|
||||
submitButton={loading ? props.submittingButton : props.submitButton}
|
||||
onSubmit={submit}
|
||||
title={props.title}
|
||||
>
|
||||
<div className='chatbot-modal-form'>
|
||||
{nameValid ? (
|
||||
<label className='chatbot-modal-label'>Chatbot Name</label>
|
||||
) : (
|
||||
<label className='chatbot-modal-label-warning'>
|
||||
Chatbot Name - Please enter a valid name
|
||||
</label>
|
||||
)}
|
||||
<input
|
||||
className='chatbot-modal-input'
|
||||
onChange={updateName}
|
||||
placeholder={'Name'}
|
||||
type={'text'}
|
||||
value={name}
|
||||
></input>
|
||||
<label className='chatbot-modal-label'>Live Stream URL</label>
|
||||
<input
|
||||
className='chatbot-modal-input'
|
||||
onChange={updateUrl}
|
||||
placeholder={'https://rumble.com'}
|
||||
type={'text'}
|
||||
value={url}
|
||||
></input>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ModalNewRule(props) {
|
||||
const [back, setBack] = useState([]);
|
||||
const [rule, setRule] = useState({});
|
||||
const [stage, setStage] = useState('trigger');
|
||||
const updateStage = (next, reverse) => {
|
||||
setBack([...back, { stage: stage, reverse: reverse }]);
|
||||
setStage(next);
|
||||
};
|
||||
|
||||
const goBack = () => {
|
||||
if (back.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const last = back.at(-1);
|
||||
setStage(last.stage);
|
||||
if (last.reverse !== undefined && last.reverse !== null) {
|
||||
setRule(last.reverse(rule));
|
||||
}
|
||||
setBack(back.slice(0, back.length - 1));
|
||||
};
|
||||
|
||||
const submit = () => {};
|
||||
|
||||
return (
|
||||
<>
|
||||
{stage === 'trigger' && (
|
||||
<ModalNewRuleTrigger
|
||||
onClose={props.onClose}
|
||||
rule={rule}
|
||||
setRule={setRule}
|
||||
setStage={updateStage}
|
||||
show={props.show}
|
||||
/>
|
||||
)}
|
||||
{stage === 'trigger-timer' && (
|
||||
<ModalNewRuleTriggerTimer
|
||||
onBack={goBack}
|
||||
onClose={props.onClose}
|
||||
rule={rule}
|
||||
setRule={setRule}
|
||||
setStage={updateStage}
|
||||
show={props.show}
|
||||
/>
|
||||
)}
|
||||
{stage === 'message' && (
|
||||
<ModalNewRuleMessage
|
||||
onBack={goBack}
|
||||
onClose={props.onClose}
|
||||
rule={rule}
|
||||
setRule={setRule}
|
||||
setStage={updateStage}
|
||||
show={props.show}
|
||||
/>
|
||||
)}
|
||||
{stage === 'sender' && (
|
||||
<ModalNewRuleSender
|
||||
onBack={goBack}
|
||||
onClose={props.onClose}
|
||||
rule={rule}
|
||||
setRule={setRule}
|
||||
setStage={updateStage}
|
||||
show={props.show}
|
||||
/>
|
||||
)}
|
||||
{stage === 'review' && (
|
||||
<ModalNewRuleReview
|
||||
onBack={goBack}
|
||||
onClose={props.onClose}
|
||||
rule={rule}
|
||||
show={props.show}
|
||||
onSubmit={submit}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ModalNewRuleTrigger(props) {
|
||||
const next = (stage) => {
|
||||
const rule = props.rule;
|
||||
rule.trigger = {};
|
||||
props.setRule(rule);
|
||||
props.setStage(stage, reverse);
|
||||
};
|
||||
|
||||
const reverse = (rule) => {
|
||||
rule.trigger = null;
|
||||
return rule;
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onClose={props.onClose}
|
||||
show={props.show}
|
||||
style={{ height: '480px', minHeight: '480px', width: '360px', minWidth: '360px' }}
|
||||
>
|
||||
<div className='modal-add-account-channel'>
|
||||
<span className='modal-add-account-channel-title'>Choose Rule Trigger</span>
|
||||
<div className='modal-add-account-channel-body'>
|
||||
<button
|
||||
className='modal-add-account-channel-button'
|
||||
onClick={() => next('trigger-command')}
|
||||
>
|
||||
<div className='modal-add-account-channel-button-left'>
|
||||
<span>Command</span>
|
||||
</div>
|
||||
<img
|
||||
className='modal-add-account-channel-button-right-icon'
|
||||
src={ChevronRight}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className='modal-add-account-channel-button'
|
||||
onClick={() => next('trigger-stream_event')}
|
||||
>
|
||||
<div className='modal-add-account-channel-button-left'>
|
||||
<span>Stream Event</span>
|
||||
</div>
|
||||
<img
|
||||
className='modal-add-account-channel-button-right-icon'
|
||||
src={ChevronRight}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className='modal-add-account-channel-button'
|
||||
onClick={() => next('trigger-timer')}
|
||||
>
|
||||
<div className='modal-add-account-channel-button-left'>
|
||||
<span>Timer</span>
|
||||
</div>
|
||||
<img
|
||||
className='modal-add-account-channel-button-right-icon'
|
||||
src={ChevronRight}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function ModalNewRuleTriggerTimer(props) {
|
||||
const [validTimer, setValidTimer] = useState(true);
|
||||
const [timer, setTimer] = useState(
|
||||
props.rule.trigger.on_timer !== undefined && props.rule.trigger.on_timer !== null
|
||||
? props.rule.trigger.on_timer
|
||||
: ''
|
||||
);
|
||||
|
||||
const back = () => {
|
||||
const rule = props.rule;
|
||||
rule.trigger.on_timer = '';
|
||||
props.setRule(rule);
|
||||
props.onBack();
|
||||
};
|
||||
|
||||
const next = () => {
|
||||
if (timer === '') {
|
||||
setValidTimer(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const rule = props.rule;
|
||||
rule.trigger.on_timer = timer;
|
||||
props.setRule(rule);
|
||||
props.setStage('message', null);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
cancelButton={'Back'}
|
||||
onCancel={back}
|
||||
onClose={props.onClose}
|
||||
show={props.show}
|
||||
submitButton={'Next'}
|
||||
onSubmit={next}
|
||||
style={{ height: '300px', minHeight: '300px', width: '360px', minWidth: '360px' }}
|
||||
>
|
||||
<div className='modal-add-account-channel'>
|
||||
<div className='modal-add-account-channel-header'>
|
||||
<span className='modal-add-account-channel-title'>Set Timer</span>
|
||||
<span className='modal-add-account-channel-subtitle'>
|
||||
Chat rule will trigger at the set interval.
|
||||
</span>
|
||||
</div>
|
||||
<div className='modal-add-account-channel-body'>
|
||||
{validTimer ? (
|
||||
<span className='chatbot-modal-description'>Enter timer</span>
|
||||
) : (
|
||||
<span className='chatbot-modal-description-warning'>
|
||||
Enter a valid timer interval.
|
||||
</span>
|
||||
)}
|
||||
<Timer timer={timer} setTimer={setTimer} />
|
||||
</div>
|
||||
<div style={{ height: '56px' }}></div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function ModalNewRuleMessage(props) {
|
||||
const [error, setError] = useState('');
|
||||
const [message, setMessage] = useState(
|
||||
props.rule.message !== undefined && props.rule.message !== null ? props.rule.message : {}
|
||||
);
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [validFile, setValidFile] = useState(true);
|
||||
const [validText, setValidText] = useState(true);
|
||||
|
||||
const back = () => {
|
||||
const rule = props.rule;
|
||||
rule.message = null;
|
||||
props.setRule(rule);
|
||||
props.onBack();
|
||||
};
|
||||
|
||||
const next = () => {
|
||||
if (fromFile()) {
|
||||
if (message.from_file.filepath === undefined || message.from_file.filepath === '') {
|
||||
setValidFile(false);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (message.from_text === undefined || message.from_text === '') {
|
||||
setValidText(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const rule = props.rule;
|
||||
rule.message = message;
|
||||
props.setRule(rule);
|
||||
props.setStage('sender', null);
|
||||
};
|
||||
|
||||
const chooseFile = () => {
|
||||
OpenFileDialog()
|
||||
.then((filepath) => {
|
||||
if (filepath !== '') {
|
||||
message.from_file.filepath = filepath;
|
||||
setMessage(message);
|
||||
setRefresh(!refresh);
|
||||
}
|
||||
})
|
||||
.catch((error) => setError(error));
|
||||
};
|
||||
|
||||
const fromFile = () => {
|
||||
return message.from_file !== undefined && message.from_file !== null;
|
||||
};
|
||||
|
||||
const toggleFromFile = () => {
|
||||
if (fromFile()) {
|
||||
message.from_file = null;
|
||||
} else {
|
||||
message.from_file = {};
|
||||
}
|
||||
|
||||
setMessage(message);
|
||||
setRefresh(!refresh);
|
||||
};
|
||||
|
||||
const randomRead = () => {
|
||||
if (!fromFile()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (message.from_file.random_read === undefined || message.from_file.random_read === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return message.from_file.random_read;
|
||||
};
|
||||
|
||||
const toggleRandomRead = () => {
|
||||
if (!fromFile()) {
|
||||
return;
|
||||
}
|
||||
|
||||
message.from_file.random_read = !randomRead();
|
||||
setMessage(message);
|
||||
setRefresh(!refresh);
|
||||
};
|
||||
|
||||
const updateMessageText = (event) => {
|
||||
message.from_text = event.target.value;
|
||||
setMessage(message);
|
||||
};
|
||||
|
||||
const updateMessageFilepath = (filepath) => {
|
||||
if (!fromFile()) {
|
||||
message.from_file = {};
|
||||
}
|
||||
message.from_file = filepath;
|
||||
setMessage(message);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{error !== '' && (
|
||||
<SmallModal
|
||||
onClose={() => setError('')}
|
||||
show={error !== ''}
|
||||
style={{ minWidth: '300px', maxWidth: '200px', maxHeight: '200px' }}
|
||||
title={'Error'}
|
||||
message={error}
|
||||
submitButton={'OK'}
|
||||
onSubmit={() => setError('')}
|
||||
/>
|
||||
)}
|
||||
<Modal
|
||||
cancelButton={'Back'}
|
||||
onCancel={back}
|
||||
onClose={props.onClose}
|
||||
show={props.show}
|
||||
submitButton={'Next'}
|
||||
onSubmit={next}
|
||||
style={{ height: '480px', minHeight: '480px', width: '360px', minWidth: '360px' }}
|
||||
>
|
||||
<div className='modal-add-account-channel'>
|
||||
<span className='modal-add-account-channel-title'>Add Message</span>
|
||||
<div className='modal-add-account-channel-body'>
|
||||
{fromFile() ? (
|
||||
validFile ? (
|
||||
<label className='modal-add-channel-label'>Message</label>
|
||||
) : (
|
||||
<label className='modal-add-channel-label-warning'>
|
||||
Select a file
|
||||
</label>
|
||||
)
|
||||
) : validText ? (
|
||||
<label className='modal-add-channel-label'>Message</label>
|
||||
) : (
|
||||
<label className='modal-add-channel-label-warning'>Add a message</label>
|
||||
)}
|
||||
{fromFile() ? (
|
||||
<div className='choose-file'>
|
||||
<div className='choose-file-button-box'>
|
||||
<button className='choose-file-button' onClick={chooseFile}>
|
||||
Choose file
|
||||
</button>
|
||||
</div>
|
||||
<span className='choose-file-path'>
|
||||
{message.from_file.filepath}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<textarea
|
||||
className='chatbot-modal-textarea'
|
||||
onChange={updateMessageText}
|
||||
rows='4'
|
||||
value={message.from_text}
|
||||
/>
|
||||
)}
|
||||
<div className='chatbot-modal-setting'>
|
||||
<label className='chatbot-modal-setting-description'>
|
||||
Read from file
|
||||
</label>
|
||||
<label className='chatbot-modal-toggle-switch'>
|
||||
<input
|
||||
checked={fromFile()}
|
||||
onChange={toggleFromFile}
|
||||
type='checkbox'
|
||||
/>
|
||||
<span className='chatbot-modal-toggle-slider round'></span>
|
||||
</label>
|
||||
</div>
|
||||
{fromFile() && (
|
||||
<div className='chatbot-modal-setting'>
|
||||
<label className='chatbot-modal-setting-description'>
|
||||
Choose lines in random order
|
||||
</label>
|
||||
<label className='chatbot-modal-toggle-switch'>
|
||||
<input
|
||||
checked={randomRead()}
|
||||
onChange={toggleRandomRead}
|
||||
type='checkbox'
|
||||
/>
|
||||
<span className='chatbot-modal-toggle-slider round'></span>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ height: '29px' }}></div>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ModalNewRuleSender(props) {
|
||||
const [accounts, setAccounts] = useState({});
|
||||
const [error, setError] = useState('');
|
||||
const [sender, setSender] = useState(
|
||||
props.rule.send_as !== undefined && props.rule.send_as !== null ? props.rule.send_as : {}
|
||||
);
|
||||
const [validSender, setValidSender] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
AccountList()
|
||||
.then((response) => {
|
||||
setAccounts(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
setError(error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const back = () => {
|
||||
const rule = props.rule;
|
||||
rule.send_as = null;
|
||||
props.setRule(rule);
|
||||
props.onBack();
|
||||
};
|
||||
|
||||
const next = () => {
|
||||
if (sender.username === undefined || sender.username === '') {
|
||||
setValidSender(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const rule = props.rule;
|
||||
rule.send_as = sender;
|
||||
props.setRule(rule);
|
||||
props.setStage('review', null);
|
||||
};
|
||||
|
||||
const selectSender = (sender) => {
|
||||
setSender(sender);
|
||||
};
|
||||
|
||||
const sortChannels = (channels) => {
|
||||
let sorted = [...channels].sort((a, b) =>
|
||||
a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1
|
||||
);
|
||||
|
||||
return sorted;
|
||||
};
|
||||
|
||||
const sortAccounts = () => {
|
||||
let keys = Object.keys(accounts);
|
||||
|
||||
let sorted = [...keys].sort((a, b) =>
|
||||
accounts[a].account.username.toLowerCase() > accounts[b].account.username.toLowerCase()
|
||||
? 1
|
||||
: -1
|
||||
);
|
||||
|
||||
return sorted;
|
||||
};
|
||||
|
||||
const sortPages = () => {
|
||||
let pages = [];
|
||||
|
||||
const keys = sortAccounts();
|
||||
keys.forEach((key, i) => {
|
||||
const account = accounts[key];
|
||||
pages.push({
|
||||
display: account.account.username,
|
||||
channel_id: null,
|
||||
username: account.account.username,
|
||||
});
|
||||
const channels = sortChannels(account.channels);
|
||||
channels.forEach((channel, j) => {
|
||||
pages.push({
|
||||
display: channel.name,
|
||||
channel_id: channel.cid,
|
||||
username: account.account.username,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return pages;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{error !== '' && (
|
||||
<SmallModal
|
||||
onClose={() => setError('')}
|
||||
show={error !== ''}
|
||||
style={{ minWidth: '300px', maxWidth: '200px', maxHeight: '200px' }}
|
||||
title={'Error'}
|
||||
message={error}
|
||||
submitButton={'OK'}
|
||||
onSubmit={() => setError('')}
|
||||
/>
|
||||
)}
|
||||
<Modal
|
||||
cancelButton={'Back'}
|
||||
onCancel={back}
|
||||
onClose={props.onClose}
|
||||
show={props.show}
|
||||
submitButton={'Next'}
|
||||
onSubmit={next}
|
||||
style={{ height: '480px', minHeight: '480px', width: '360px', minWidth: '360px' }}
|
||||
>
|
||||
<div className='modal-add-account-channel'>
|
||||
<div className='modal-add-account-channel-header'>
|
||||
<span className='modal-add-account-channel-title'>Choose sender</span>
|
||||
</div>
|
||||
<div className='modal-add-account-channel-body' style={{ height: '60%' }}>
|
||||
{validSender ? (
|
||||
<span className='chatbot-modal-description'>Chat As</span>
|
||||
) : (
|
||||
<span className='chatbot-modal-description-warning'>
|
||||
Select an account or channel
|
||||
</span>
|
||||
)}
|
||||
<div className='chatbot-modal-pages'>
|
||||
{sortPages().map((page, index) => (
|
||||
<div className={'chatbot-modal-page'} key={index}>
|
||||
<button
|
||||
className='chatbot-modal-page-button'
|
||||
onClick={() => selectSender(page)}
|
||||
style={{
|
||||
backgroundColor:
|
||||
page.display === sender.display ? '#85c742' : '',
|
||||
}}
|
||||
>
|
||||
{page.display}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ height: '29px' }}></div>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ModalNewRuleReview(props) {
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const back = () => {
|
||||
props.onBack();
|
||||
};
|
||||
|
||||
const submit = () => {
|
||||
props.onSubmit();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
cancelButton={'Back'}
|
||||
onCancel={back}
|
||||
onClose={props.onClose}
|
||||
show={props.show}
|
||||
submitButton={'Submit'}
|
||||
onSubmit={submit}
|
||||
style={{ height: '480px', minHeight: '480px', width: '360px', minWidth: '360px' }}
|
||||
>
|
||||
<div className='modal-add-account-channel'>
|
||||
<div className='modal-add-account-channel-header'>
|
||||
<span className='modal-add-account-channel-title'>Review</span>
|
||||
</div>
|
||||
<div className='modal-add-account-channel-body'>
|
||||
<div className='chatbot-modal-setting'>
|
||||
<label className='chatbot-modal-setting-description'>Trigger</label>
|
||||
<label className='chatbot-modal-setting-description'>Timer</label>
|
||||
</div>
|
||||
<div className='chatbot-modal-setting'>
|
||||
<label className='chatbot-modal-setting-description'>Chat As</label>
|
||||
<label className='chatbot-modal-setting-description'>tylertravisty</label>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ height: '29px' }}></div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function Timer(props) {
|
||||
const updateTimerBackspace = (e) => {
|
||||
if (props.timer.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.keyCode === 8) {
|
||||
props.setTimer(props.timer.substring(0, props.timer.length - 1));
|
||||
}
|
||||
};
|
||||
|
||||
const updateTimer = (e) => {
|
||||
let nums = '0123456789';
|
||||
let digit = e.target.value;
|
||||
|
||||
if (!nums.includes(digit)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let interval = timerValToInterval(props.timer + digit);
|
||||
if (interval >= 360000) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.timer.length === 0 && digit === '0') {
|
||||
return;
|
||||
}
|
||||
|
||||
props.setTimer(props.timer + digit);
|
||||
};
|
||||
|
||||
const timerValToInterval = (val) => {
|
||||
let prefix = '0'.repeat(6 - val.length);
|
||||
let t = prefix + val;
|
||||
|
||||
let hours = parseInt(t.substring(0, 2));
|
||||
let minutes = parseInt(t.substring(2, 4));
|
||||
let seconds = parseInt(t.substring(4, 6));
|
||||
|
||||
return hours * 3600 + minutes * 60 + seconds;
|
||||
};
|
||||
|
||||
const printTimer = () => {
|
||||
if (props.timer === '') {
|
||||
return '00:00:00';
|
||||
}
|
||||
|
||||
let prefix = '0'.repeat(6 - props.timer.length);
|
||||
let t = prefix + props.timer;
|
||||
|
||||
return t.substring(0, 2) + ':' + t.substring(2, 4) + ':' + t.substring(4, 6);
|
||||
};
|
||||
|
||||
return (
|
||||
<input
|
||||
className={
|
||||
props.timer === ''
|
||||
? 'timer-input timer-input-zero'
|
||||
: 'timer-input timer-input-value'
|
||||
}
|
||||
onKeyDown={updateTimerBackspace}
|
||||
onInput={updateTimer}
|
||||
placeholder={printTimer()}
|
||||
size='8'
|
||||
type='text'
|
||||
value={''}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -5,7 +5,7 @@ export function Modal(props) {
|
|||
return (
|
||||
<div
|
||||
className='modal-background'
|
||||
onClick={props.onClose}
|
||||
onClick={props.backgroundClose && props.onClose}
|
||||
style={{ zIndex: props.show ? 8 : -8 }}
|
||||
>
|
||||
<div
|
||||
|
|
|
@ -192,7 +192,7 @@
|
|||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
/* min-height: 80px; */
|
||||
min-height: 55px;
|
||||
justify-content: space-between;
|
||||
padding: 10px 20px;
|
||||
width: 100%;
|
||||
|
|
|
@ -295,6 +295,7 @@ function PageDetails(props) {
|
|||
<>
|
||||
{openLogin && (
|
||||
<Modal
|
||||
backgroundClose={true}
|
||||
cancelButton={'Cancel'}
|
||||
onCancel={closeLogin}
|
||||
onClose={closeLogin}
|
||||
|
@ -345,6 +346,7 @@ function PageDetails(props) {
|
|||
)}
|
||||
{openDelete && (
|
||||
<Modal
|
||||
backgroundClose={true}
|
||||
cancelButton={'Cancel'}
|
||||
onCancel={resetDelete}
|
||||
onClose={resetDelete}
|
||||
|
@ -384,6 +386,7 @@ function PageDetails(props) {
|
|||
)}
|
||||
{openApi && (
|
||||
<Modal
|
||||
backgroundClose={true}
|
||||
cancelButton={'Cancel'}
|
||||
onCancel={closeEditApi}
|
||||
onClose={closeEditApi}
|
||||
|
|
|
@ -24,7 +24,7 @@ function PageSideBar(props) {
|
|||
const [accounts, setAccounts] = useState({});
|
||||
const [error, setError] = useState('');
|
||||
const [addOpen, setAddOpen] = useState(false);
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
// const [refresh, setRefresh] = useState(false);
|
||||
const [scrollY, setScrollY] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -41,7 +41,7 @@ function PageSideBar(props) {
|
|||
.catch((error) => {
|
||||
setError(error);
|
||||
});
|
||||
}, [refresh]);
|
||||
}, []);
|
||||
|
||||
const sortAccounts = () => {
|
||||
let keys = Object.keys(accounts);
|
||||
|
@ -354,7 +354,7 @@ function ModalAdd(props) {
|
|||
.then(() => {
|
||||
reset();
|
||||
props.onClose();
|
||||
props.onRefresh();
|
||||
//props.onRefresh();
|
||||
})
|
||||
.catch((error) => {
|
||||
setAddAccountLoading(false);
|
||||
|
@ -369,7 +369,7 @@ function ModalAdd(props) {
|
|||
.then(() => {
|
||||
reset();
|
||||
props.onClose();
|
||||
props.onRefresh();
|
||||
//props.onRefresh();
|
||||
})
|
||||
.catch((error) => {
|
||||
setAddChannelLoading(false);
|
||||
|
|
|
@ -4,13 +4,14 @@ import { CircleGreenBackground, Heart } from '../assets';
|
|||
import PageDetails from '../components/PageDetails';
|
||||
import PageSideBar from '../components/PageSideBar';
|
||||
import './Dashboard.css';
|
||||
import ChatBot from '../components/ChatBot';
|
||||
|
||||
function Dashboard() {
|
||||
return (
|
||||
<div className='dashboard'>
|
||||
<PageSideBar />
|
||||
<PageDetails />
|
||||
<div style={{ backgroundColor: '#344453', width: '100%', height: '100%' }}></div>
|
||||
<ChatBot />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ toolchain go1.22.0
|
|||
|
||||
require (
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/tylertravisty/rumble-livestream-lib-go v0.3.5
|
||||
github.com/tylertravisty/rumble-livestream-lib-go v0.5.1
|
||||
github.com/wailsapp/wails/v2 v2.8.0
|
||||
)
|
||||
|
||||
|
@ -36,10 +36,10 @@ require (
|
|||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.10 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/net v0.22.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
|
||||
gopkg.in/sourcemap.v1 v1.0.5 // indirect
|
||||
|
|
16
v1/go.sum
16
v1/go.sum
|
@ -60,8 +60,8 @@ github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQ
|
|||
github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
|
||||
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909 h1:xrjIFqzGQXlCrCdMPpW6+SodGFSlrQ3ZNUCr3f5tF1g=
|
||||
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909/go.mod h1:2W31Jhs9YSy7y500wsCOW0bcamGi9foQV1CKrfvfTxk=
|
||||
github.com/tylertravisty/rumble-livestream-lib-go v0.3.5 h1:mAf4oYuQ55pXTPsIMVztOlYM8oGsBgsNMJvel2VLgsQ=
|
||||
github.com/tylertravisty/rumble-livestream-lib-go v0.3.5/go.mod h1:rUET5uInouMfB4ekqdGiYeoN5ibOdzU9cCgRE0i57Wg=
|
||||
github.com/tylertravisty/rumble-livestream-lib-go v0.5.1 h1:vq65n/8MOvvg6tHiaHFFfYf25w7yuR1viSoBCjY2DSg=
|
||||
github.com/tylertravisty/rumble-livestream-lib-go v0.5.1/go.mod h1:Odkqvsn+2eoWV3ePcj257Ga0bdOqV4JBTfOJcQ+Sqf8=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
|
@ -74,14 +74,14 @@ github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4X
|
|||
github.com/wailsapp/wails/v2 v2.8.0 h1:b2NNn99uGPiN6P5bDsnPwOJZWtAOUhNLv7Vl+YxMTr4=
|
||||
github.com/wailsapp/wails/v2 v2.8.0/go.mod h1:EFUGWkUX3KofO4fmKR/GmsLy3HhPH7NbyOEaMt8lBF0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -93,8 +93,8 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
|
|
|
@ -74,6 +74,7 @@ func (ap *ApiProducer) Shutdown() error {
|
|||
timer := time.NewTimer(3 * time.Second)
|
||||
select {
|
||||
case <-ap.closeCh:
|
||||
timer.Stop()
|
||||
close(ap.Ch)
|
||||
case <-timer.C:
|
||||
return pkgErr("", fmt.Errorf("not all producers were stopped"))
|
||||
|
@ -128,7 +129,7 @@ func (ap *ApiProducer) Stop(name string) error {
|
|||
}
|
||||
|
||||
func (ap *ApiProducer) run(ctx context.Context, producer *apiProducer) {
|
||||
client := &rumblelivestreamlib.Client{StreamKey: producer.url}
|
||||
client := &rumblelivestreamlib.Client{ApiKey: producer.url}
|
||||
for {
|
||||
resp, err := apiQuery(client)
|
||||
if err != nil {
|
||||
|
|
205
v1/internal/events/chat.go
Normal file
205
v1/internal/events/chat.go
Normal file
|
@ -0,0 +1,205 @@
|
|||
package events
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go"
|
||||
)
|
||||
|
||||
type Chat struct {
|
||||
Message rumblelivestreamlib.ChatView
|
||||
Stop bool
|
||||
Url string
|
||||
}
|
||||
|
||||
type chatProducer struct {
|
||||
cancel context.CancelFunc
|
||||
cancelMu sync.Mutex
|
||||
client *rumblelivestreamlib.Client
|
||||
url string
|
||||
}
|
||||
|
||||
type chatProducerValFunc func(*chatProducer) error
|
||||
|
||||
func runChatProducerValFuncs(c *chatProducer, fns ...chatProducerValFunc) error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("chat producer is nil")
|
||||
}
|
||||
|
||||
for _, fn := range fns {
|
||||
err := fn(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func chatProducerRequireClient(c *chatProducer) error {
|
||||
if c.client == nil {
|
||||
return fmt.Errorf("client is nil")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ChatProducer struct {
|
||||
Ch chan Chat
|
||||
close bool
|
||||
closeMu sync.Mutex
|
||||
closeCh chan bool
|
||||
logError *log.Logger
|
||||
logInfo *log.Logger
|
||||
producers map[string]*chatProducer
|
||||
producersMu sync.Mutex
|
||||
}
|
||||
|
||||
func NewChatProducer(logError *log.Logger, logInfo *log.Logger) *ChatProducer {
|
||||
return &ChatProducer{
|
||||
Ch: make(chan Chat, 10),
|
||||
closeCh: make(chan bool),
|
||||
logError: logError,
|
||||
logInfo: logInfo,
|
||||
producers: map[string]*chatProducer{},
|
||||
}
|
||||
}
|
||||
|
||||
// func (cp *ChatProducer) Active(url string) bool {
|
||||
// cp.producersMu.Lock()
|
||||
// defer cp.producersMu.Unlock()
|
||||
// _, active := cp.producers[url]
|
||||
|
||||
// return active
|
||||
// }
|
||||
|
||||
func (cp *ChatProducer) Start(liveStreamUrl string) (string, error) {
|
||||
if liveStreamUrl == "" {
|
||||
return "", pkgErr("", fmt.Errorf("url is empty"))
|
||||
}
|
||||
|
||||
client, err := rumblelivestreamlib.NewClient(rumblelivestreamlib.NewClientOptions{LiveStreamUrl: liveStreamUrl})
|
||||
if err != nil {
|
||||
return "", pkgErr("error creating new rumble client", err)
|
||||
}
|
||||
|
||||
chatInfo, err := client.ChatInfo(false)
|
||||
if err != nil {
|
||||
return "", pkgErr("error getting chat info", err)
|
||||
}
|
||||
chatStreamUrl := chatInfo.StreamUrl()
|
||||
|
||||
cp.producersMu.Lock()
|
||||
defer cp.producersMu.Unlock()
|
||||
if _, active := cp.producers[chatStreamUrl]; active {
|
||||
return chatStreamUrl, nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
producer := &chatProducer{
|
||||
cancel: cancel,
|
||||
client: client,
|
||||
url: chatStreamUrl,
|
||||
}
|
||||
cp.producers[chatStreamUrl] = producer
|
||||
go cp.run(ctx, producer)
|
||||
|
||||
return chatStreamUrl, nil
|
||||
}
|
||||
|
||||
func (cp *ChatProducer) Stop(chatStreamUrl string) error {
|
||||
cp.producersMu.Lock()
|
||||
producer, exists := cp.producers[chatStreamUrl]
|
||||
if !exists {
|
||||
return pkgErr("", fmt.Errorf("producer does not exist for chat stream: %s", chatStreamUrl))
|
||||
}
|
||||
cp.producersMu.Unlock()
|
||||
|
||||
producer.cancelMu.Lock()
|
||||
if producer.cancel != nil {
|
||||
producer.cancel()
|
||||
}
|
||||
producer.cancelMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *ChatProducer) run(ctx context.Context, producer *chatProducer) {
|
||||
err := runChatProducerValFuncs(
|
||||
producer,
|
||||
chatProducerRequireClient,
|
||||
)
|
||||
if err != nil {
|
||||
cp.logError.Println(pkgErr("invalid chat producer", err))
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
err = producer.client.StartChatStream(cp.handleChat(producer), cp.handleError(producer))
|
||||
if err != nil {
|
||||
cp.logError.Println(pkgErr("error starting chat stream", err))
|
||||
cp.stop(producer)
|
||||
return
|
||||
}
|
||||
|
||||
timer := time.NewTimer(90 * time.Minute)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
timer.Stop()
|
||||
producer.client.StopChatStream()
|
||||
cp.stop(producer)
|
||||
return
|
||||
case <-timer.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cp *ChatProducer) handleChat(p *chatProducer) func(cv rumblelivestreamlib.ChatView) {
|
||||
return func(cv rumblelivestreamlib.ChatView) {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
|
||||
cp.Ch <- Chat{Message: cv, Url: p.url}
|
||||
}
|
||||
}
|
||||
|
||||
func (cp *ChatProducer) handleError(p *chatProducer) func(err error) {
|
||||
return func(err error) {
|
||||
cp.logError.Println(pkgErr("chat stream returned error", err))
|
||||
p.cancelMu.Lock()
|
||||
if p.cancel != nil {
|
||||
p.cancel()
|
||||
}
|
||||
p.cancelMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (cp *ChatProducer) stop(p *chatProducer) {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
|
||||
cp.Ch <- Chat{Stop: true, Url: p.url}
|
||||
|
||||
cp.producersMu.Lock()
|
||||
delete(cp.producers, p.url)
|
||||
remaining := len(cp.producers)
|
||||
cp.producersMu.Unlock()
|
||||
|
||||
cp.closeMu.Lock()
|
||||
if remaining == 0 && cp.close {
|
||||
select {
|
||||
case cp.closeCh <- true:
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
cp.closeMu.Unlock()
|
||||
|
||||
return
|
||||
}
|
|
@ -9,6 +9,7 @@ type Producers struct {
|
|||
logError *log.Logger
|
||||
logInfo *log.Logger
|
||||
ApiP *ApiProducer
|
||||
ChatP *ChatProducer
|
||||
}
|
||||
|
||||
func (p *Producers) Startup() error {
|
||||
|
@ -61,3 +62,11 @@ func WithApiProducer() ProducersInit {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithChatProducer() ProducersInit {
|
||||
return func(p *Producers) error {
|
||||
p.ChatP = NewChatProducer(p.logError, p.logInfo)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -325,7 +325,7 @@ type accountValFunc func(*Account) error
|
|||
|
||||
func runAccountValFuncs(a *Account, fns ...accountValFunc) error {
|
||||
if a == nil {
|
||||
return fmt.Errorf("account cannot be nil")
|
||||
return fmt.Errorf("account is nil")
|
||||
}
|
||||
|
||||
for _, fn := range fns {
|
||||
|
|
|
@ -337,7 +337,7 @@ type channelValFunc func(*Channel) error
|
|||
|
||||
func runChannelValFuncs(c *Channel, fns ...channelValFunc) error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("channel cannot be nil")
|
||||
return fmt.Errorf("channel is nil")
|
||||
}
|
||||
|
||||
for _, fn := range fns {
|
||||
|
|
313
v1/internal/models/chatbot.go
Normal file
313
v1/internal/models/chatbot.go
Normal file
|
@ -0,0 +1,313 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
chatbotColumns = "id, name, url"
|
||||
chatbotTable = "chatbot"
|
||||
)
|
||||
|
||||
type Chatbot struct {
|
||||
ID *int64 `json:"id"`
|
||||
Name *string `json:"name"`
|
||||
Url *string `json:"url"`
|
||||
}
|
||||
|
||||
func (c *Chatbot) values() []any {
|
||||
return []any{c.ID, c.Name, c.Url}
|
||||
}
|
||||
|
||||
func (c *Chatbot) valuesNoID() []any {
|
||||
return c.values()[1:]
|
||||
}
|
||||
|
||||
func (c *Chatbot) valuesEndID() []any {
|
||||
vals := c.values()
|
||||
return append(vals[1:], vals[0])
|
||||
}
|
||||
|
||||
type sqlChatbot struct {
|
||||
id sql.NullInt64
|
||||
name sql.NullString
|
||||
url sql.NullString
|
||||
}
|
||||
|
||||
func (sc *sqlChatbot) scan(r Row) error {
|
||||
return r.Scan(&sc.id, &sc.name, &sc.url)
|
||||
}
|
||||
|
||||
func (sc sqlChatbot) toChatbot() *Chatbot {
|
||||
var c Chatbot
|
||||
c.ID = toInt64(sc.id)
|
||||
c.Name = toString(sc.name)
|
||||
c.Url = toString(sc.url)
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
type ChatbotService interface {
|
||||
All() ([]Chatbot, error)
|
||||
AutoMigrate() error
|
||||
ByID(id int64) (*Chatbot, error)
|
||||
ByName(name string) (*Chatbot, error)
|
||||
Create(c *Chatbot) (int64, error)
|
||||
Delete(c *Chatbot) error
|
||||
DestructiveReset() error
|
||||
Update(c *Chatbot) error
|
||||
}
|
||||
|
||||
func NewChatbotService(db *sql.DB) ChatbotService {
|
||||
return &chatbotService{
|
||||
Database: db,
|
||||
}
|
||||
}
|
||||
|
||||
var _ ChatbotService = &chatbotService{}
|
||||
|
||||
type chatbotService struct {
|
||||
Database *sql.DB
|
||||
}
|
||||
|
||||
func (cs *chatbotService) All() ([]Chatbot, error) {
|
||||
selectQ := fmt.Sprintf(`
|
||||
SELECT %s
|
||||
FROM "%s"
|
||||
`, chatbotColumns, chatbotTable)
|
||||
|
||||
rows, err := cs.Database.Query(selectQ)
|
||||
if err != nil {
|
||||
return nil, pkgErr("error executing select query", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
chatbots := []Chatbot{}
|
||||
for rows.Next() {
|
||||
sc := &sqlChatbot{}
|
||||
|
||||
err = sc.scan(rows)
|
||||
if err != nil {
|
||||
return nil, pkgErr("error scanning row", err)
|
||||
}
|
||||
|
||||
chatbots = append(chatbots, *sc.toChatbot())
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, pkgErr("error iterating over rows", err)
|
||||
}
|
||||
|
||||
return chatbots, nil
|
||||
}
|
||||
|
||||
func (cs *chatbotService) AutoMigrate() error {
|
||||
err := cs.createChatbotTable()
|
||||
if err != nil {
|
||||
return pkgErr(fmt.Sprintf("error creating %s table", chatbotTable), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *chatbotService) createChatbotTable() error {
|
||||
createQ := fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS "%s" (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
url TEXT
|
||||
)
|
||||
`, chatbotTable)
|
||||
|
||||
_, err := cs.Database.Exec(createQ)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error executing create query: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *chatbotService) ByID(id int64) (*Chatbot, error) {
|
||||
err := runChatbotValFuncs(
|
||||
&Chatbot{ID: &id},
|
||||
chatbotRequireID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, pkgErr("", err)
|
||||
}
|
||||
|
||||
selectQ := fmt.Sprintf(`
|
||||
SELECT %s
|
||||
FROM "%s"
|
||||
WHERE id=?
|
||||
`, chatbotColumns, chatbotTable)
|
||||
|
||||
var sc sqlChatbot
|
||||
row := cs.Database.QueryRow(selectQ, id)
|
||||
err = sc.scan(row)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, pkgErr("error executing select query", err)
|
||||
}
|
||||
|
||||
return sc.toChatbot(), nil
|
||||
}
|
||||
|
||||
func (cs *chatbotService) ByName(name string) (*Chatbot, error) {
|
||||
err := runChatbotValFuncs(
|
||||
&Chatbot{Name: &name},
|
||||
chatbotRequireName,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, pkgErr("", err)
|
||||
}
|
||||
|
||||
selectQ := fmt.Sprintf(`
|
||||
SELECT %s
|
||||
FROM "%s"
|
||||
WHERE name=?
|
||||
`, chatbotColumns, chatbotTable)
|
||||
|
||||
var sc sqlChatbot
|
||||
row := cs.Database.QueryRow(selectQ, name)
|
||||
err = sc.scan(row)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, pkgErr("error executing select query", err)
|
||||
}
|
||||
|
||||
return sc.toChatbot(), nil
|
||||
}
|
||||
|
||||
func (cs *chatbotService) Create(c *Chatbot) (int64, error) {
|
||||
err := runChatbotValFuncs(
|
||||
c,
|
||||
chatbotRequireName,
|
||||
)
|
||||
if err != nil {
|
||||
return -1, pkgErr("invalid chatbot", err)
|
||||
}
|
||||
|
||||
columns := columnsNoID(chatbotColumns)
|
||||
insertQ := fmt.Sprintf(`
|
||||
INSERT INTO "%s" (%s)
|
||||
VALUES (%s)
|
||||
RETURNING id
|
||||
`, chatbotTable, columns, values(columns))
|
||||
|
||||
var id int64
|
||||
row := cs.Database.QueryRow(insertQ, c.valuesNoID()...)
|
||||
err = row.Scan(&id)
|
||||
if err != nil {
|
||||
return -1, pkgErr("error executing insert query", err)
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (cs *chatbotService) Delete(c *Chatbot) error {
|
||||
err := runChatbotValFuncs(
|
||||
c,
|
||||
chatbotRequireID,
|
||||
)
|
||||
if err != nil {
|
||||
return pkgErr("invalid chatbot", err)
|
||||
}
|
||||
|
||||
deleteQ := fmt.Sprintf(`
|
||||
DELETE FROM "%s"
|
||||
WHERE id=?
|
||||
`, chatbotTable)
|
||||
|
||||
_, err = cs.Database.Exec(deleteQ, c.ID)
|
||||
if err != nil {
|
||||
return pkgErr("error executing delete query", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *chatbotService) DestructiveReset() error {
|
||||
err := cs.dropChatbotTable()
|
||||
if err != nil {
|
||||
return pkgErr(fmt.Sprintf("error dropping %s table", chatbotTable), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *chatbotService) dropChatbotTable() error {
|
||||
dropQ := fmt.Sprintf(`
|
||||
DROP TABLE IF EXISTS "%s"
|
||||
`, chatbotTable)
|
||||
|
||||
_, err := cs.Database.Exec(dropQ)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error executing drop query: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *chatbotService) Update(c *Chatbot) error {
|
||||
err := runChatbotValFuncs(
|
||||
c,
|
||||
chatbotRequireID,
|
||||
chatbotRequireName,
|
||||
)
|
||||
if err != nil {
|
||||
return pkgErr("invalid chatbot", err)
|
||||
}
|
||||
|
||||
columns := columnsNoID(chatbotColumns)
|
||||
updateQ := fmt.Sprintf(`
|
||||
UPDATE "%s"
|
||||
SET %s
|
||||
WHERE id=?
|
||||
`, chatbotTable, set(columns))
|
||||
|
||||
_, err = cs.Database.Exec(updateQ, c.valuesEndID()...)
|
||||
if err != nil {
|
||||
return pkgErr("error executing update query", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type chatbotValFunc func(*Chatbot) error
|
||||
|
||||
func runChatbotValFuncs(c *Chatbot, fns ...chatbotValFunc) error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("chatbot is nil")
|
||||
}
|
||||
|
||||
for _, fn := range fns {
|
||||
err := fn(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func chatbotRequireID(c *Chatbot) error {
|
||||
if c.ID == nil || *c.ID < 1 {
|
||||
return ErrChatbotInvalidID
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func chatbotRequireName(c *Chatbot) error {
|
||||
if c.Name == nil || *c.Name == "" {
|
||||
return ErrChatbotInvalidName
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
224
v1/internal/models/chatbotrule.go
Normal file
224
v1/internal/models/chatbotrule.go
Normal file
|
@ -0,0 +1,224 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
chatbotRuleColumns = "id, chatbot_id, name, rule"
|
||||
chatbotRuleTable = "chatbot_rule"
|
||||
)
|
||||
|
||||
type ChatbotRule struct {
|
||||
ID *int64 `json:"id"`
|
||||
ChatbotID *int64 `json:"chatbot_id"`
|
||||
Rule *string `json:"rule"`
|
||||
}
|
||||
|
||||
func (c *ChatbotRule) values() []any {
|
||||
return []any{c.ID, c.ChatbotID, c.Rule}
|
||||
}
|
||||
|
||||
func (c *ChatbotRule) valuesNoID() []any {
|
||||
return c.values()[1:]
|
||||
}
|
||||
|
||||
func (c *ChatbotRule) valuesEndID() []any {
|
||||
vals := c.values()
|
||||
return append(vals[1:], vals[0])
|
||||
}
|
||||
|
||||
type sqlChatbotRule struct {
|
||||
id sql.NullInt64
|
||||
chatbotID sql.NullInt64
|
||||
rule sql.NullString
|
||||
}
|
||||
|
||||
func (sc *sqlChatbotRule) scan(r Row) error {
|
||||
return r.Scan(&sc.id, &sc.chatbotID, &sc.rule)
|
||||
}
|
||||
|
||||
func (sc sqlChatbotRule) toChatbotRule() *ChatbotRule {
|
||||
var c ChatbotRule
|
||||
c.ID = toInt64(sc.id)
|
||||
c.ChatbotID = toInt64(sc.chatbotID)
|
||||
c.Rule = toString(sc.rule)
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
type ChatbotRuleService interface {
|
||||
AutoMigrate() error
|
||||
Create(c *ChatbotRule) (int64, error)
|
||||
Delete(c *ChatbotRule) error
|
||||
DestructiveReset() error
|
||||
Update(c *ChatbotRule) error
|
||||
}
|
||||
|
||||
func NewChatbotRuleService(db *sql.DB) ChatbotRuleService {
|
||||
return &chatbotRuleService{
|
||||
Database: db,
|
||||
}
|
||||
}
|
||||
|
||||
var _ ChatbotRuleService = &chatbotRuleService{}
|
||||
|
||||
type chatbotRuleService struct {
|
||||
Database *sql.DB
|
||||
}
|
||||
|
||||
func (cs *chatbotRuleService) AutoMigrate() error {
|
||||
err := cs.createChatbotRuleTable()
|
||||
if err != nil {
|
||||
return pkgErr(fmt.Sprintf("error creating %s table", chatbotRuleTable), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *chatbotRuleService) createChatbotRuleTable() error {
|
||||
createQ := fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS "%s" (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
chatbot_id INTEGER NOT NULL,
|
||||
rule TEXT NOT NULL
|
||||
FOREIGN KEY (chatbot_id) REFERENCES "%s" (id)
|
||||
)
|
||||
`, chatbotRuleTable, chatbotTable)
|
||||
|
||||
_, err := cs.Database.Exec(createQ)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error executing create query: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *chatbotRuleService) Create(c *ChatbotRule) (int64, error) {
|
||||
err := runChatbotRuleValFuncs(
|
||||
c,
|
||||
chatbotRuleRequireRule,
|
||||
)
|
||||
if err != nil {
|
||||
return -1, pkgErr("invalid chat rule", err)
|
||||
}
|
||||
|
||||
columns := columnsNoID(chatbotRuleColumns)
|
||||
insertQ := fmt.Sprintf(`
|
||||
INSERT INTO "%s" (%s)
|
||||
VALUES (%s)
|
||||
RETURNING id
|
||||
`, chatbotRuleTable, columns, values(columns))
|
||||
|
||||
var id int64
|
||||
row := cs.Database.QueryRow(insertQ, c.valuesNoID()...)
|
||||
err = row.Scan(&id)
|
||||
if err != nil {
|
||||
return -1, pkgErr("error executing insert query", err)
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (cs *chatbotRuleService) Delete(c *ChatbotRule) error {
|
||||
err := runChatbotRuleValFuncs(
|
||||
c,
|
||||
chatbotRuleRequireID,
|
||||
)
|
||||
if err != nil {
|
||||
return pkgErr("invalid chat rule", err)
|
||||
}
|
||||
|
||||
deleteQ := fmt.Sprintf(`
|
||||
DELETE FROM "%s"
|
||||
WHERE id=?
|
||||
`, chatbotRuleTable)
|
||||
|
||||
_, err = cs.Database.Exec(deleteQ, c.ID)
|
||||
if err != nil {
|
||||
return pkgErr("error executing delete query", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *chatbotRuleService) DestructiveReset() error {
|
||||
err := cs.dropChatbotRuleTable()
|
||||
if err != nil {
|
||||
return pkgErr(fmt.Sprintf("error dropping %s table", chatbotRuleTable), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *chatbotRuleService) dropChatbotRuleTable() error {
|
||||
dropQ := fmt.Sprintf(`
|
||||
DROP TABLE IF EXISTS "%s"
|
||||
`, chatbotRuleTable)
|
||||
|
||||
_, err := cs.Database.Exec(dropQ)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error executing drop query: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *chatbotRuleService) Update(c *ChatbotRule) error {
|
||||
err := runChatbotRuleValFuncs(
|
||||
c,
|
||||
chatbotRuleRequireID,
|
||||
chatbotRuleRequireRule,
|
||||
)
|
||||
if err != nil {
|
||||
return pkgErr("invalid chat rule", err)
|
||||
}
|
||||
|
||||
columns := columnsNoID(chatbotRuleColumns)
|
||||
updateQ := fmt.Sprintf(`
|
||||
UPDATE "%s"
|
||||
SET %s
|
||||
WHERE id=?
|
||||
`, chatbotRuleTable, set(columns))
|
||||
|
||||
_, err = cs.Database.Exec(updateQ, c.valuesEndID()...)
|
||||
if err != nil {
|
||||
return pkgErr("error executing update query", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type chatbotRuleValFunc func(*ChatbotRule) error
|
||||
|
||||
func runChatbotRuleValFuncs(c *ChatbotRule, fns ...chatbotRuleValFunc) error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("chat rule is nil")
|
||||
}
|
||||
|
||||
for _, fn := range fns {
|
||||
err := fn(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func chatbotRuleRequireID(c *ChatbotRule) error {
|
||||
if c.ID == nil || *c.ID < 1 {
|
||||
return ErrChatbotRuleInvalidID
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func chatbotRuleRequireRule(c *ChatbotRule) error {
|
||||
if c.Rule == nil || *c.Rule == "" {
|
||||
return ErrChatbotRuleInvalidRule
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -13,6 +13,12 @@ const (
|
|||
ErrChannelInvalidCID ValidatorError = "invalid channel CID"
|
||||
ErrChannelInvalidID ValidatorError = "invalid channel ID"
|
||||
ErrChannelInvalidName ValidatorError = "invalid channel name"
|
||||
|
||||
ErrChatbotInvalidID ValidatorError = "invalid chatbot id"
|
||||
ErrChatbotInvalidName ValidatorError = "invalid chatbot name"
|
||||
|
||||
ErrChatbotRuleInvalidID ValidatorError = "invalid chatbot rule id"
|
||||
ErrChatbotRuleInvalidRule ValidatorError = "invalid chatbot rule rule"
|
||||
)
|
||||
|
||||
func pkgErr(prefix string, err error) error {
|
||||
|
|
|
@ -17,6 +17,7 @@ type Services struct {
|
|||
AccountS AccountService
|
||||
AccountChannelS AccountChannelService
|
||||
ChannelS ChannelService
|
||||
ChatbotS ChatbotService
|
||||
Database *sql.DB
|
||||
tables []table
|
||||
}
|
||||
|
@ -106,3 +107,12 @@ func WithChannelService() ServicesInit {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithChatbotService() ServicesInit {
|
||||
return func(s *Services) error {
|
||||
s.ChatbotS = NewChatbotService(s.Database)
|
||||
s.tables = append(s.tables, table{chatbotTable, s.ChatbotS.AutoMigrate, s.ChatbotS.DestructiveReset})
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
85
v1/vendor/github.com/tylertravisty/rumble-livestream-lib-go/chat.go
generated
vendored
85
v1/vendor/github.com/tylertravisty/rumble-livestream-lib-go/chat.go
generated
vendored
|
@ -7,6 +7,7 @@ import (
|
|||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -14,13 +15,15 @@ import (
|
|||
|
||||
"github.com/r3labs/sse/v2"
|
||||
"github.com/tylertravisty/go-utils/random"
|
||||
"golang.org/x/net/html"
|
||||
"gopkg.in/cenkalti/backoff.v1"
|
||||
)
|
||||
|
||||
type ChatInfo struct {
|
||||
UrlPrefix string
|
||||
ChatID string
|
||||
ChannelID int
|
||||
ChatID string
|
||||
Page string
|
||||
UrlPrefix string
|
||||
}
|
||||
|
||||
func (ci *ChatInfo) MessageUrl() string {
|
||||
|
@ -31,33 +34,37 @@ func (ci *ChatInfo) StreamUrl() string {
|
|||
return fmt.Sprintf("%s/chat/%s/stream", ci.UrlPrefix, ci.ChatID)
|
||||
}
|
||||
|
||||
func (c *Client) ChatInfo() error {
|
||||
ci, err := c.getChatInfo()
|
||||
func (c *Client) ChatInfo(reload bool) (*ChatInfo, error) {
|
||||
var ci *ChatInfo
|
||||
var err error
|
||||
|
||||
if c.chatInfo == nil || reload {
|
||||
ci, err = c.getChatInfo()
|
||||
if err != nil {
|
||||
return pkgErr("error getting chat info", err)
|
||||
return nil, pkgErr("error getting chat info", err)
|
||||
}
|
||||
}
|
||||
|
||||
c.chatInfo = ci
|
||||
return nil
|
||||
return ci, nil
|
||||
}
|
||||
|
||||
func (c *Client) getChatInfo() (*ChatInfo, error) {
|
||||
if c.StreamUrl == "" {
|
||||
if c.LiveStreamUrl == "" {
|
||||
return nil, fmt.Errorf("stream url is empty")
|
||||
}
|
||||
|
||||
resp, err := c.getWebpage(c.StreamUrl)
|
||||
resp, err := c.getWebpage(c.LiveStreamUrl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting stream webpage: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var chatInfo *ChatInfo
|
||||
var page string
|
||||
r := bufio.NewReader(resp.Body)
|
||||
//line, _, err := r.ReadLine()
|
||||
lineS, err := r.ReadString('\n')
|
||||
//var lineS string
|
||||
for err == nil {
|
||||
//lineS = string(line)
|
||||
if strings.Contains(lineS, "RumbleChat(") {
|
||||
start := strings.Index(lineS, "RumbleChat(") + len("RumbleChat(")
|
||||
if start == -1 {
|
||||
|
@ -81,16 +88,33 @@ func (c *Client) getChatInfo() (*ChatInfo, error) {
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("error converting channel ID argument string to int: %v", err)
|
||||
}
|
||||
return &ChatInfo{info[0], info[1], channelID}, nil
|
||||
}
|
||||
//line, _, err = r.ReadLine()
|
||||
lineS, err = r.ReadString('\n')
|
||||
}
|
||||
chatInfo = &ChatInfo{ChannelID: channelID, ChatID: info[1], UrlPrefix: info[0]}
|
||||
} else if strings.Contains(lineS, "media-by--a") && strings.Contains(lineS, "author") {
|
||||
r := strings.NewReader(lineS)
|
||||
node, err := html.Parse(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading line from stream webpage: %v", err)
|
||||
return nil, fmt.Errorf("error parsing html tag with page name: %v", err)
|
||||
}
|
||||
if node.FirstChild != nil && node.FirstChild.LastChild != nil && node.FirstChild.LastChild.FirstChild != nil {
|
||||
for _, attr := range node.FirstChild.LastChild.FirstChild.Attr {
|
||||
if attr.Key == "href" {
|
||||
page = attr.Val
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lineS, err = r.ReadString('\n')
|
||||
}
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, fmt.Errorf("error reading line from stream webpage: %v", err)
|
||||
}
|
||||
if chatInfo == nil {
|
||||
return nil, fmt.Errorf("did not find RumbleChat function call")
|
||||
}
|
||||
|
||||
chatInfo.Page = page
|
||||
return chatInfo, nil
|
||||
}
|
||||
|
||||
type ChatMessage struct {
|
||||
|
@ -118,20 +142,17 @@ type ChatResponse struct {
|
|||
Errors []Error `json:"errors"`
|
||||
}
|
||||
|
||||
func (c *Client) Chat(asChannel bool, message string) error {
|
||||
func (c *Client) Chat(message string, channelID *int) error {
|
||||
if c.httpClient == nil {
|
||||
return pkgErr("", fmt.Errorf("http client is nil"))
|
||||
}
|
||||
|
||||
// chatInfo, err := c.streamChatInfo()
|
||||
// if err != nil {
|
||||
// return pkgErr("error getting stream chat info", err)
|
||||
// }
|
||||
if c.chatInfo == nil {
|
||||
err := c.ChatInfo()
|
||||
ci, err := c.getChatInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
return pkgErr("error getting chat info", err)
|
||||
}
|
||||
c.chatInfo = ci
|
||||
}
|
||||
|
||||
requestID, err := random.String(32)
|
||||
|
@ -145,12 +166,12 @@ func (c *Client) Chat(asChannel bool, message string) error {
|
|||
Text: message,
|
||||
},
|
||||
Rant: nil,
|
||||
ChannelID: nil,
|
||||
ChannelID: channelID,
|
||||
},
|
||||
}
|
||||
if asChannel {
|
||||
body.Data.ChannelID = &c.chatInfo.ChannelID
|
||||
}
|
||||
// if asChannel {
|
||||
// body.Data.ChannelID = &c.chatInfo.ChannelID
|
||||
// }
|
||||
|
||||
bodyB, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
|
@ -260,6 +281,14 @@ func (c *Client) StartChatStream(handle func(cv ChatView), handleError func(err
|
|||
return pkgErr("", fmt.Errorf("chat stream already started"))
|
||||
}
|
||||
sseEvent := make(chan *sse.Event)
|
||||
|
||||
if c.chatInfo == nil {
|
||||
ci, err := c.getChatInfo()
|
||||
if err != nil {
|
||||
return pkgErr("error getting chat info", err)
|
||||
}
|
||||
c.chatInfo = ci
|
||||
}
|
||||
sseCl := sse.NewClient(c.chatInfo.StreamUrl())
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
sseCl.ReconnectStrategy = backoff.WithContext(
|
||||
|
|
10
v1/vendor/github.com/tylertravisty/rumble-livestream-lib-go/client.go
generated
vendored
10
v1/vendor/github.com/tylertravisty/rumble-livestream-lib-go/client.go
generated
vendored
|
@ -27,8 +27,8 @@ type Client struct {
|
|||
chatInfo *ChatInfo
|
||||
chatStream *ChatStream
|
||||
chatStreamMu sync.Mutex
|
||||
StreamKey string
|
||||
StreamUrl string
|
||||
ApiKey string
|
||||
LiveStreamUrl string
|
||||
}
|
||||
|
||||
func (c *Client) cookies() ([]*http.Cookie, error) {
|
||||
|
@ -54,8 +54,8 @@ func (c *Client) PrintCookies() error {
|
|||
|
||||
type NewClientOptions struct {
|
||||
Cookies []*http.Cookie `json:"cookies"`
|
||||
StreamKey string `json:"stream_key"`
|
||||
StreamUrl string `json:"stream_url"`
|
||||
ApiKey string `json:"stream_key"`
|
||||
LiveStreamUrl string `json:"stream_url"`
|
||||
}
|
||||
|
||||
func NewClient(opts NewClientOptions) (*Client, error) {
|
||||
|
@ -64,7 +64,7 @@ func NewClient(opts NewClientOptions) (*Client, error) {
|
|||
return nil, pkgErr("error creating http client", err)
|
||||
}
|
||||
|
||||
return &Client{httpClient: cl, StreamKey: opts.StreamKey, StreamUrl: opts.StreamUrl}, nil
|
||||
return &Client{httpClient: cl, ApiKey: opts.ApiKey, LiveStreamUrl: opts.LiveStreamUrl}, nil
|
||||
}
|
||||
|
||||
func newHttpClient(cookies []*http.Cookie) (*http.Client, error) {
|
||||
|
|
2
v1/vendor/github.com/tylertravisty/rumble-livestream-lib-go/livestream.go
generated
vendored
2
v1/vendor/github.com/tylertravisty/rumble-livestream-lib-go/livestream.go
generated
vendored
|
@ -95,7 +95,7 @@ type LivestreamResponse struct {
|
|||
|
||||
func (c *Client) Request() (*LivestreamResponse, error) {
|
||||
hcl := http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := hcl.Get(c.StreamKey)
|
||||
resp, err := hcl.Get(c.ApiKey)
|
||||
if err != nil {
|
||||
return nil, pkgErr("http Get request returned error", err)
|
||||
}
|
||||
|
|
31
v1/vendor/golang.org/x/net/http2/frame.go
generated
vendored
31
v1/vendor/golang.org/x/net/http2/frame.go
generated
vendored
|
@ -1564,6 +1564,7 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
|
|||
if size > remainSize {
|
||||
hdec.SetEmitEnabled(false)
|
||||
mh.Truncated = true
|
||||
remainSize = 0
|
||||
return
|
||||
}
|
||||
remainSize -= size
|
||||
|
@ -1576,6 +1577,36 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
|
|||
var hc headersOrContinuation = hf
|
||||
for {
|
||||
frag := hc.HeaderBlockFragment()
|
||||
|
||||
// Avoid parsing large amounts of headers that we will then discard.
|
||||
// If the sender exceeds the max header list size by too much,
|
||||
// skip parsing the fragment and close the connection.
|
||||
//
|
||||
// "Too much" is either any CONTINUATION frame after we've already
|
||||
// exceeded the max header list size (in which case remainSize is 0),
|
||||
// or a frame whose encoded size is more than twice the remaining
|
||||
// header list bytes we're willing to accept.
|
||||
if int64(len(frag)) > int64(2*remainSize) {
|
||||
if VerboseLogs {
|
||||
log.Printf("http2: header list too large")
|
||||
}
|
||||
// It would be nice to send a RST_STREAM before sending the GOAWAY,
|
||||
// but the structure of the server's frame writer makes this difficult.
|
||||
return nil, ConnectionError(ErrCodeProtocol)
|
||||
}
|
||||
|
||||
// Also close the connection after any CONTINUATION frame following an
|
||||
// invalid header, since we stop tracking the size of the headers after
|
||||
// an invalid one.
|
||||
if invalid != nil {
|
||||
if VerboseLogs {
|
||||
log.Printf("http2: invalid header: %v", invalid)
|
||||
}
|
||||
// It would be nice to send a RST_STREAM before sending the GOAWAY,
|
||||
// but the structure of the server's frame writer makes this difficult.
|
||||
return nil, ConnectionError(ErrCodeProtocol)
|
||||
}
|
||||
|
||||
if _, err := hdec.Write(frag); err != nil {
|
||||
return nil, ConnectionError(ErrCodeCompression)
|
||||
}
|
||||
|
|
11
v1/vendor/golang.org/x/net/http2/pipe.go
generated
vendored
11
v1/vendor/golang.org/x/net/http2/pipe.go
generated
vendored
|
@ -77,7 +77,10 @@ func (p *pipe) Read(d []byte) (n int, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
var errClosedPipeWrite = errors.New("write on closed buffer")
|
||||
var (
|
||||
errClosedPipeWrite = errors.New("write on closed buffer")
|
||||
errUninitializedPipeWrite = errors.New("write on uninitialized buffer")
|
||||
)
|
||||
|
||||
// Write copies bytes from p into the buffer and wakes a reader.
|
||||
// It is an error to write more data than the buffer can hold.
|
||||
|
@ -91,6 +94,12 @@ func (p *pipe) Write(d []byte) (n int, err error) {
|
|||
if p.err != nil || p.breakErr != nil {
|
||||
return 0, errClosedPipeWrite
|
||||
}
|
||||
// pipe.setBuffer is never invoked, leaving the buffer uninitialized.
|
||||
// We shouldn't try to write to an uninitialized pipe,
|
||||
// but returning an error is better than panicking.
|
||||
if p.b == nil {
|
||||
return 0, errUninitializedPipeWrite
|
||||
}
|
||||
return p.b.Write(d)
|
||||
}
|
||||
|
||||
|
|
13
v1/vendor/golang.org/x/net/http2/server.go
generated
vendored
13
v1/vendor/golang.org/x/net/http2/server.go
generated
vendored
|
@ -124,6 +124,7 @@ type Server struct {
|
|||
// IdleTimeout specifies how long until idle clients should be
|
||||
// closed with a GOAWAY frame. PING frames are not considered
|
||||
// activity for the purposes of IdleTimeout.
|
||||
// If zero or negative, there is no timeout.
|
||||
IdleTimeout time.Duration
|
||||
|
||||
// MaxUploadBufferPerConnection is the size of the initial flow
|
||||
|
@ -434,7 +435,7 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
|
|||
// passes the connection off to us with the deadline already set.
|
||||
// Write deadlines are set per stream in serverConn.newStream.
|
||||
// Disarm the net.Conn write deadline here.
|
||||
if sc.hs.WriteTimeout != 0 {
|
||||
if sc.hs.WriteTimeout > 0 {
|
||||
sc.conn.SetWriteDeadline(time.Time{})
|
||||
}
|
||||
|
||||
|
@ -924,7 +925,7 @@ func (sc *serverConn) serve() {
|
|||
sc.setConnState(http.StateActive)
|
||||
sc.setConnState(http.StateIdle)
|
||||
|
||||
if sc.srv.IdleTimeout != 0 {
|
||||
if sc.srv.IdleTimeout > 0 {
|
||||
sc.idleTimer = time.AfterFunc(sc.srv.IdleTimeout, sc.onIdleTimer)
|
||||
defer sc.idleTimer.Stop()
|
||||
}
|
||||
|
@ -1637,7 +1638,7 @@ func (sc *serverConn) closeStream(st *stream, err error) {
|
|||
delete(sc.streams, st.id)
|
||||
if len(sc.streams) == 0 {
|
||||
sc.setConnState(http.StateIdle)
|
||||
if sc.srv.IdleTimeout != 0 {
|
||||
if sc.srv.IdleTimeout > 0 {
|
||||
sc.idleTimer.Reset(sc.srv.IdleTimeout)
|
||||
}
|
||||
if h1ServerKeepAlivesDisabled(sc.hs) {
|
||||
|
@ -2017,7 +2018,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
|
|||
// similar to how the http1 server works. Here it's
|
||||
// technically more like the http1 Server's ReadHeaderTimeout
|
||||
// (in Go 1.8), though. That's a more sane option anyway.
|
||||
if sc.hs.ReadTimeout != 0 {
|
||||
if sc.hs.ReadTimeout > 0 {
|
||||
sc.conn.SetReadDeadline(time.Time{})
|
||||
st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout, st.onReadTimeout)
|
||||
}
|
||||
|
@ -2038,7 +2039,7 @@ func (sc *serverConn) upgradeRequest(req *http.Request) {
|
|||
|
||||
// Disable any read deadline set by the net/http package
|
||||
// prior to the upgrade.
|
||||
if sc.hs.ReadTimeout != 0 {
|
||||
if sc.hs.ReadTimeout > 0 {
|
||||
sc.conn.SetReadDeadline(time.Time{})
|
||||
}
|
||||
|
||||
|
@ -2116,7 +2117,7 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream
|
|||
st.flow.conn = &sc.flow // link to conn-level counter
|
||||
st.flow.add(sc.initialStreamSendWindowSize)
|
||||
st.inflow.init(sc.srv.initialStreamRecvWindowSize())
|
||||
if sc.hs.WriteTimeout != 0 {
|
||||
if sc.hs.WriteTimeout > 0 {
|
||||
st.writeDeadline = time.AfterFunc(sc.hs.WriteTimeout, st.onWriteTimeout)
|
||||
}
|
||||
|
||||
|
|
331
v1/vendor/golang.org/x/net/http2/testsync.go
generated
vendored
Normal file
331
v1/vendor/golang.org/x/net/http2/testsync.go
generated
vendored
Normal file
|
@ -0,0 +1,331 @@
|
|||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
package http2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// testSyncHooks coordinates goroutines in tests.
|
||||
//
|
||||
// For example, a call to ClientConn.RoundTrip involves several goroutines, including:
|
||||
// - the goroutine running RoundTrip;
|
||||
// - the clientStream.doRequest goroutine, which writes the request; and
|
||||
// - the clientStream.readLoop goroutine, which reads the response.
|
||||
//
|
||||
// Using testSyncHooks, a test can start a RoundTrip and identify when all these goroutines
|
||||
// are blocked waiting for some condition such as reading the Request.Body or waiting for
|
||||
// flow control to become available.
|
||||
//
|
||||
// The testSyncHooks also manage timers and synthetic time in tests.
|
||||
// This permits us to, for example, start a request and cause it to time out waiting for
|
||||
// response headers without resorting to time.Sleep calls.
|
||||
type testSyncHooks struct {
|
||||
// active/inactive act as a mutex and condition variable.
|
||||
//
|
||||
// - neither chan contains a value: testSyncHooks is locked.
|
||||
// - active contains a value: unlocked, and at least one goroutine is not blocked
|
||||
// - inactive contains a value: unlocked, and all goroutines are blocked
|
||||
active chan struct{}
|
||||
inactive chan struct{}
|
||||
|
||||
// goroutine counts
|
||||
total int // total goroutines
|
||||
condwait map[*sync.Cond]int // blocked in sync.Cond.Wait
|
||||
blocked []*testBlockedGoroutine // otherwise blocked
|
||||
|
||||
// fake time
|
||||
now time.Time
|
||||
timers []*fakeTimer
|
||||
|
||||
// Transport testing: Report various events.
|
||||
newclientconn func(*ClientConn)
|
||||
newstream func(*clientStream)
|
||||
}
|
||||
|
||||
// testBlockedGoroutine is a blocked goroutine.
|
||||
type testBlockedGoroutine struct {
|
||||
f func() bool // blocked until f returns true
|
||||
ch chan struct{} // closed when unblocked
|
||||
}
|
||||
|
||||
func newTestSyncHooks() *testSyncHooks {
|
||||
h := &testSyncHooks{
|
||||
active: make(chan struct{}, 1),
|
||||
inactive: make(chan struct{}, 1),
|
||||
condwait: map[*sync.Cond]int{},
|
||||
}
|
||||
h.inactive <- struct{}{}
|
||||
h.now = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
return h
|
||||
}
|
||||
|
||||
// lock acquires the testSyncHooks mutex.
|
||||
func (h *testSyncHooks) lock() {
|
||||
select {
|
||||
case <-h.active:
|
||||
case <-h.inactive:
|
||||
}
|
||||
}
|
||||
|
||||
// waitInactive waits for all goroutines to become inactive.
|
||||
func (h *testSyncHooks) waitInactive() {
|
||||
for {
|
||||
<-h.inactive
|
||||
if !h.unlock() {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unlock releases the testSyncHooks mutex.
|
||||
// It reports whether any goroutines are active.
|
||||
func (h *testSyncHooks) unlock() (active bool) {
|
||||
// Look for a blocked goroutine which can be unblocked.
|
||||
blocked := h.blocked[:0]
|
||||
unblocked := false
|
||||
for _, b := range h.blocked {
|
||||
if !unblocked && b.f() {
|
||||
unblocked = true
|
||||
close(b.ch)
|
||||
} else {
|
||||
blocked = append(blocked, b)
|
||||
}
|
||||
}
|
||||
h.blocked = blocked
|
||||
|
||||
// Count goroutines blocked on condition variables.
|
||||
condwait := 0
|
||||
for _, count := range h.condwait {
|
||||
condwait += count
|
||||
}
|
||||
|
||||
if h.total > condwait+len(blocked) {
|
||||
h.active <- struct{}{}
|
||||
return true
|
||||
} else {
|
||||
h.inactive <- struct{}{}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// goRun starts a new goroutine.
|
||||
func (h *testSyncHooks) goRun(f func()) {
|
||||
h.lock()
|
||||
h.total++
|
||||
h.unlock()
|
||||
go func() {
|
||||
defer func() {
|
||||
h.lock()
|
||||
h.total--
|
||||
h.unlock()
|
||||
}()
|
||||
f()
|
||||
}()
|
||||
}
|
||||
|
||||
// blockUntil indicates that a goroutine is blocked waiting for some condition to become true.
|
||||
// It waits until f returns true before proceeding.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// h.blockUntil(func() bool {
|
||||
// // Is the context done yet?
|
||||
// select {
|
||||
// case <-ctx.Done():
|
||||
// default:
|
||||
// return false
|
||||
// }
|
||||
// return true
|
||||
// })
|
||||
// // Wait for the context to become done.
|
||||
// <-ctx.Done()
|
||||
//
|
||||
// The function f passed to blockUntil must be non-blocking and idempotent.
|
||||
func (h *testSyncHooks) blockUntil(f func() bool) {
|
||||
if f() {
|
||||
return
|
||||
}
|
||||
ch := make(chan struct{})
|
||||
h.lock()
|
||||
h.blocked = append(h.blocked, &testBlockedGoroutine{
|
||||
f: f,
|
||||
ch: ch,
|
||||
})
|
||||
h.unlock()
|
||||
<-ch
|
||||
}
|
||||
|
||||
// broadcast is sync.Cond.Broadcast.
|
||||
func (h *testSyncHooks) condBroadcast(cond *sync.Cond) {
|
||||
h.lock()
|
||||
delete(h.condwait, cond)
|
||||
h.unlock()
|
||||
cond.Broadcast()
|
||||
}
|
||||
|
||||
// broadcast is sync.Cond.Wait.
|
||||
func (h *testSyncHooks) condWait(cond *sync.Cond) {
|
||||
h.lock()
|
||||
h.condwait[cond]++
|
||||
h.unlock()
|
||||
}
|
||||
|
||||
// newTimer creates a new fake timer.
|
||||
func (h *testSyncHooks) newTimer(d time.Duration) timer {
|
||||
h.lock()
|
||||
defer h.unlock()
|
||||
t := &fakeTimer{
|
||||
hooks: h,
|
||||
when: h.now.Add(d),
|
||||
c: make(chan time.Time),
|
||||
}
|
||||
h.timers = append(h.timers, t)
|
||||
return t
|
||||
}
|
||||
|
||||
// afterFunc creates a new fake AfterFunc timer.
|
||||
func (h *testSyncHooks) afterFunc(d time.Duration, f func()) timer {
|
||||
h.lock()
|
||||
defer h.unlock()
|
||||
t := &fakeTimer{
|
||||
hooks: h,
|
||||
when: h.now.Add(d),
|
||||
f: f,
|
||||
}
|
||||
h.timers = append(h.timers, t)
|
||||
return t
|
||||
}
|
||||
|
||||
func (h *testSyncHooks) contextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
t := h.afterFunc(d, cancel)
|
||||
return ctx, func() {
|
||||
t.Stop()
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *testSyncHooks) timeUntilEvent() time.Duration {
|
||||
h.lock()
|
||||
defer h.unlock()
|
||||
var next time.Time
|
||||
for _, t := range h.timers {
|
||||
if next.IsZero() || t.when.Before(next) {
|
||||
next = t.when
|
||||
}
|
||||
}
|
||||
if d := next.Sub(h.now); d > 0 {
|
||||
return d
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// advance advances time and causes synthetic timers to fire.
|
||||
func (h *testSyncHooks) advance(d time.Duration) {
|
||||
h.lock()
|
||||
defer h.unlock()
|
||||
h.now = h.now.Add(d)
|
||||
timers := h.timers[:0]
|
||||
for _, t := range h.timers {
|
||||
t := t // remove after go.mod depends on go1.22
|
||||
t.mu.Lock()
|
||||
switch {
|
||||
case t.when.After(h.now):
|
||||
timers = append(timers, t)
|
||||
case t.when.IsZero():
|
||||
// stopped timer
|
||||
default:
|
||||
t.when = time.Time{}
|
||||
if t.c != nil {
|
||||
close(t.c)
|
||||
}
|
||||
if t.f != nil {
|
||||
h.total++
|
||||
go func() {
|
||||
defer func() {
|
||||
h.lock()
|
||||
h.total--
|
||||
h.unlock()
|
||||
}()
|
||||
t.f()
|
||||
}()
|
||||
}
|
||||
}
|
||||
t.mu.Unlock()
|
||||
}
|
||||
h.timers = timers
|
||||
}
|
||||
|
||||
// A timer wraps a time.Timer, or a synthetic equivalent in tests.
|
||||
// Unlike time.Timer, timer is single-use: The timer channel is closed when the timer expires.
|
||||
type timer interface {
|
||||
C() <-chan time.Time
|
||||
Stop() bool
|
||||
Reset(d time.Duration) bool
|
||||
}
|
||||
|
||||
// timeTimer implements timer using real time.
|
||||
type timeTimer struct {
|
||||
t *time.Timer
|
||||
c chan time.Time
|
||||
}
|
||||
|
||||
// newTimeTimer creates a new timer using real time.
|
||||
func newTimeTimer(d time.Duration) timer {
|
||||
ch := make(chan time.Time)
|
||||
t := time.AfterFunc(d, func() {
|
||||
close(ch)
|
||||
})
|
||||
return &timeTimer{t, ch}
|
||||
}
|
||||
|
||||
// newTimeAfterFunc creates an AfterFunc timer using real time.
|
||||
func newTimeAfterFunc(d time.Duration, f func()) timer {
|
||||
return &timeTimer{
|
||||
t: time.AfterFunc(d, f),
|
||||
}
|
||||
}
|
||||
|
||||
func (t timeTimer) C() <-chan time.Time { return t.c }
|
||||
func (t timeTimer) Stop() bool { return t.t.Stop() }
|
||||
func (t timeTimer) Reset(d time.Duration) bool { return t.t.Reset(d) }
|
||||
|
||||
// fakeTimer implements timer using fake time.
|
||||
type fakeTimer struct {
|
||||
hooks *testSyncHooks
|
||||
|
||||
mu sync.Mutex
|
||||
when time.Time // when the timer will fire
|
||||
c chan time.Time // closed when the timer fires; mutually exclusive with f
|
||||
f func() // called when the timer fires; mutually exclusive with c
|
||||
}
|
||||
|
||||
func (t *fakeTimer) C() <-chan time.Time { return t.c }
|
||||
|
||||
func (t *fakeTimer) Stop() bool {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
stopped := t.when.IsZero()
|
||||
t.when = time.Time{}
|
||||
return stopped
|
||||
}
|
||||
|
||||
func (t *fakeTimer) Reset(d time.Duration) bool {
|
||||
if t.c != nil || t.f == nil {
|
||||
panic("fakeTimer only supports Reset on AfterFunc timers")
|
||||
}
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.hooks.lock()
|
||||
defer t.hooks.unlock()
|
||||
active := !t.when.IsZero()
|
||||
t.when = t.hooks.now.Add(d)
|
||||
if !active {
|
||||
t.hooks.timers = append(t.hooks.timers, t)
|
||||
}
|
||||
return active
|
||||
}
|
296
v1/vendor/golang.org/x/net/http2/transport.go
generated
vendored
296
v1/vendor/golang.org/x/net/http2/transport.go
generated
vendored
|
@ -147,6 +147,12 @@ type Transport struct {
|
|||
// waiting for their turn.
|
||||
StrictMaxConcurrentStreams bool
|
||||
|
||||
// IdleConnTimeout is the maximum amount of time an idle
|
||||
// (keep-alive) connection will remain idle before closing
|
||||
// itself.
|
||||
// Zero means no limit.
|
||||
IdleConnTimeout time.Duration
|
||||
|
||||
// ReadIdleTimeout is the timeout after which a health check using ping
|
||||
// frame will be carried out if no frame is received on the connection.
|
||||
// Note that a ping response will is considered a received frame, so if
|
||||
|
@ -178,6 +184,8 @@ type Transport struct {
|
|||
|
||||
connPoolOnce sync.Once
|
||||
connPoolOrDef ClientConnPool // non-nil version of ConnPool
|
||||
|
||||
syncHooks *testSyncHooks
|
||||
}
|
||||
|
||||
func (t *Transport) maxHeaderListSize() uint32 {
|
||||
|
@ -302,7 +310,7 @@ type ClientConn struct {
|
|||
readerErr error // set before readerDone is closed
|
||||
|
||||
idleTimeout time.Duration // or 0 for never
|
||||
idleTimer *time.Timer
|
||||
idleTimer timer
|
||||
|
||||
mu sync.Mutex // guards following
|
||||
cond *sync.Cond // hold mu; broadcast on flow/closed changes
|
||||
|
@ -344,6 +352,60 @@ type ClientConn struct {
|
|||
werr error // first write error that has occurred
|
||||
hbuf bytes.Buffer // HPACK encoder writes into this
|
||||
henc *hpack.Encoder
|
||||
|
||||
syncHooks *testSyncHooks // can be nil
|
||||
}
|
||||
|
||||
// Hook points used for testing.
|
||||
// Outside of tests, cc.syncHooks is nil and these all have minimal implementations.
|
||||
// Inside tests, see the testSyncHooks function docs.
|
||||
|
||||
// goRun starts a new goroutine.
|
||||
func (cc *ClientConn) goRun(f func()) {
|
||||
if cc.syncHooks != nil {
|
||||
cc.syncHooks.goRun(f)
|
||||
return
|
||||
}
|
||||
go f()
|
||||
}
|
||||
|
||||
// condBroadcast is cc.cond.Broadcast.
|
||||
func (cc *ClientConn) condBroadcast() {
|
||||
if cc.syncHooks != nil {
|
||||
cc.syncHooks.condBroadcast(cc.cond)
|
||||
}
|
||||
cc.cond.Broadcast()
|
||||
}
|
||||
|
||||
// condWait is cc.cond.Wait.
|
||||
func (cc *ClientConn) condWait() {
|
||||
if cc.syncHooks != nil {
|
||||
cc.syncHooks.condWait(cc.cond)
|
||||
}
|
||||
cc.cond.Wait()
|
||||
}
|
||||
|
||||
// newTimer creates a new time.Timer, or a synthetic timer in tests.
|
||||
func (cc *ClientConn) newTimer(d time.Duration) timer {
|
||||
if cc.syncHooks != nil {
|
||||
return cc.syncHooks.newTimer(d)
|
||||
}
|
||||
return newTimeTimer(d)
|
||||
}
|
||||
|
||||
// afterFunc creates a new time.AfterFunc timer, or a synthetic timer in tests.
|
||||
func (cc *ClientConn) afterFunc(d time.Duration, f func()) timer {
|
||||
if cc.syncHooks != nil {
|
||||
return cc.syncHooks.afterFunc(d, f)
|
||||
}
|
||||
return newTimeAfterFunc(d, f)
|
||||
}
|
||||
|
||||
func (cc *ClientConn) contextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
|
||||
if cc.syncHooks != nil {
|
||||
return cc.syncHooks.contextWithTimeout(ctx, d)
|
||||
}
|
||||
return context.WithTimeout(ctx, d)
|
||||
}
|
||||
|
||||
// clientStream is the state for a single HTTP/2 stream. One of these
|
||||
|
@ -425,7 +487,7 @@ func (cs *clientStream) abortStreamLocked(err error) {
|
|||
// TODO(dneil): Clean up tests where cs.cc.cond is nil.
|
||||
if cs.cc.cond != nil {
|
||||
// Wake up writeRequestBody if it is waiting on flow control.
|
||||
cs.cc.cond.Broadcast()
|
||||
cs.cc.condBroadcast()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -435,7 +497,7 @@ func (cs *clientStream) abortRequestBodyWrite() {
|
|||
defer cc.mu.Unlock()
|
||||
if cs.reqBody != nil && cs.reqBodyClosed == nil {
|
||||
cs.closeReqBodyLocked()
|
||||
cc.cond.Broadcast()
|
||||
cc.condBroadcast()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -445,10 +507,10 @@ func (cs *clientStream) closeReqBodyLocked() {
|
|||
}
|
||||
cs.reqBodyClosed = make(chan struct{})
|
||||
reqBodyClosed := cs.reqBodyClosed
|
||||
go func() {
|
||||
cs.cc.goRun(func() {
|
||||
cs.reqBody.Close()
|
||||
close(reqBodyClosed)
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
type stickyErrWriter struct {
|
||||
|
@ -537,15 +599,6 @@ func authorityAddr(scheme string, authority string) (addr string) {
|
|||
return net.JoinHostPort(host, port)
|
||||
}
|
||||
|
||||
var retryBackoffHook func(time.Duration) *time.Timer
|
||||
|
||||
func backoffNewTimer(d time.Duration) *time.Timer {
|
||||
if retryBackoffHook != nil {
|
||||
return retryBackoffHook(d)
|
||||
}
|
||||
return time.NewTimer(d)
|
||||
}
|
||||
|
||||
// RoundTripOpt is like RoundTrip, but takes options.
|
||||
func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
||||
if !(req.URL.Scheme == "https" || (req.URL.Scheme == "http" && t.AllowHTTP)) {
|
||||
|
@ -573,13 +626,27 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
|
|||
backoff := float64(uint(1) << (uint(retry) - 1))
|
||||
backoff += backoff * (0.1 * mathrand.Float64())
|
||||
d := time.Second * time.Duration(backoff)
|
||||
timer := backoffNewTimer(d)
|
||||
var tm timer
|
||||
if t.syncHooks != nil {
|
||||
tm = t.syncHooks.newTimer(d)
|
||||
t.syncHooks.blockUntil(func() bool {
|
||||
select {
|
||||
case <-timer.C:
|
||||
case <-tm.C():
|
||||
case <-req.Context().Done():
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
tm = newTimeTimer(d)
|
||||
}
|
||||
select {
|
||||
case <-tm.C():
|
||||
t.vlogf("RoundTrip retrying after failure: %v", roundTripErr)
|
||||
continue
|
||||
case <-req.Context().Done():
|
||||
timer.Stop()
|
||||
tm.Stop()
|
||||
err = req.Context().Err()
|
||||
}
|
||||
}
|
||||
|
@ -658,6 +725,9 @@ func canRetryError(err error) bool {
|
|||
}
|
||||
|
||||
func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) {
|
||||
if t.syncHooks != nil {
|
||||
return t.newClientConn(nil, singleUse, t.syncHooks)
|
||||
}
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -666,7 +736,7 @@ func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse b
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t.newClientConn(tconn, singleUse)
|
||||
return t.newClientConn(tconn, singleUse, nil)
|
||||
}
|
||||
|
||||
func (t *Transport) newTLSConfig(host string) *tls.Config {
|
||||
|
@ -732,10 +802,10 @@ func (t *Transport) maxEncoderHeaderTableSize() uint32 {
|
|||
}
|
||||
|
||||
func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
|
||||
return t.newClientConn(c, t.disableKeepAlives())
|
||||
return t.newClientConn(c, t.disableKeepAlives(), nil)
|
||||
}
|
||||
|
||||
func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, error) {
|
||||
func (t *Transport) newClientConn(c net.Conn, singleUse bool, hooks *testSyncHooks) (*ClientConn, error) {
|
||||
cc := &ClientConn{
|
||||
t: t,
|
||||
tconn: c,
|
||||
|
@ -750,10 +820,15 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
|
|||
wantSettingsAck: true,
|
||||
pings: make(map[[8]byte]chan struct{}),
|
||||
reqHeaderMu: make(chan struct{}, 1),
|
||||
syncHooks: hooks,
|
||||
}
|
||||
if hooks != nil {
|
||||
hooks.newclientconn(cc)
|
||||
c = cc.tconn
|
||||
}
|
||||
if d := t.idleConnTimeout(); d != 0 {
|
||||
cc.idleTimeout = d
|
||||
cc.idleTimer = time.AfterFunc(d, cc.onIdleTimeout)
|
||||
cc.idleTimer = cc.afterFunc(d, cc.onIdleTimeout)
|
||||
}
|
||||
if VerboseLogs {
|
||||
t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr())
|
||||
|
@ -818,7 +893,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
|
|||
return nil, cc.werr
|
||||
}
|
||||
|
||||
go cc.readLoop()
|
||||
cc.goRun(cc.readLoop)
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
|
@ -826,7 +901,7 @@ func (cc *ClientConn) healthCheck() {
|
|||
pingTimeout := cc.t.pingTimeout()
|
||||
// We don't need to periodically ping in the health check, because the readLoop of ClientConn will
|
||||
// trigger the healthCheck again if there is no frame received.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), pingTimeout)
|
||||
ctx, cancel := cc.contextWithTimeout(context.Background(), pingTimeout)
|
||||
defer cancel()
|
||||
cc.vlogf("http2: Transport sending health check")
|
||||
err := cc.Ping(ctx)
|
||||
|
@ -1056,7 +1131,7 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
|
|||
// Wait for all in-flight streams to complete or connection to close
|
||||
done := make(chan struct{})
|
||||
cancelled := false // guarded by cc.mu
|
||||
go func() {
|
||||
cc.goRun(func() {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
for {
|
||||
|
@ -1068,9 +1143,9 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
|
|||
if cancelled {
|
||||
break
|
||||
}
|
||||
cc.cond.Wait()
|
||||
cc.condWait()
|
||||
}
|
||||
}()
|
||||
})
|
||||
shutdownEnterWaitStateHook()
|
||||
select {
|
||||
case <-done:
|
||||
|
@ -1080,7 +1155,7 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
|
|||
cc.mu.Lock()
|
||||
// Free the goroutine above
|
||||
cancelled = true
|
||||
cc.cond.Broadcast()
|
||||
cc.condBroadcast()
|
||||
cc.mu.Unlock()
|
||||
return ctx.Err()
|
||||
}
|
||||
|
@ -1118,7 +1193,7 @@ func (cc *ClientConn) closeForError(err error) {
|
|||
for _, cs := range cc.streams {
|
||||
cs.abortStreamLocked(err)
|
||||
}
|
||||
cc.cond.Broadcast()
|
||||
cc.condBroadcast()
|
||||
cc.mu.Unlock()
|
||||
cc.closeConn()
|
||||
}
|
||||
|
@ -1215,6 +1290,10 @@ func (cc *ClientConn) decrStreamReservationsLocked() {
|
|||
}
|
||||
|
||||
func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return cc.roundTrip(req, nil)
|
||||
}
|
||||
|
||||
func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream)) (*http.Response, error) {
|
||||
ctx := req.Context()
|
||||
cs := &clientStream{
|
||||
cc: cc,
|
||||
|
@ -1229,9 +1308,23 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||
respHeaderRecv: make(chan struct{}),
|
||||
donec: make(chan struct{}),
|
||||
}
|
||||
go cs.doRequest(req)
|
||||
cc.goRun(func() {
|
||||
cs.doRequest(req)
|
||||
})
|
||||
|
||||
waitDone := func() error {
|
||||
if cc.syncHooks != nil {
|
||||
cc.syncHooks.blockUntil(func() bool {
|
||||
select {
|
||||
case <-cs.donec:
|
||||
case <-ctx.Done():
|
||||
case <-cs.reqCancel:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
select {
|
||||
case <-cs.donec:
|
||||
return nil
|
||||
|
@ -1292,7 +1385,24 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||
return err
|
||||
}
|
||||
|
||||
if streamf != nil {
|
||||
streamf(cs)
|
||||
}
|
||||
|
||||
for {
|
||||
if cc.syncHooks != nil {
|
||||
cc.syncHooks.blockUntil(func() bool {
|
||||
select {
|
||||
case <-cs.respHeaderRecv:
|
||||
case <-cs.abort:
|
||||
case <-ctx.Done():
|
||||
case <-cs.reqCancel:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
select {
|
||||
case <-cs.respHeaderRecv:
|
||||
return handleResponseHeaders()
|
||||
|
@ -1348,6 +1458,21 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
|
|||
if cc.reqHeaderMu == nil {
|
||||
panic("RoundTrip on uninitialized ClientConn") // for tests
|
||||
}
|
||||
var newStreamHook func(*clientStream)
|
||||
if cc.syncHooks != nil {
|
||||
newStreamHook = cc.syncHooks.newstream
|
||||
cc.syncHooks.blockUntil(func() bool {
|
||||
select {
|
||||
case cc.reqHeaderMu <- struct{}{}:
|
||||
<-cc.reqHeaderMu
|
||||
case <-cs.reqCancel:
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
select {
|
||||
case cc.reqHeaderMu <- struct{}{}:
|
||||
case <-cs.reqCancel:
|
||||
|
@ -1372,6 +1497,10 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
|
|||
}
|
||||
cc.mu.Unlock()
|
||||
|
||||
if newStreamHook != nil {
|
||||
newStreamHook(cs)
|
||||
}
|
||||
|
||||
// TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
|
||||
if !cc.t.disableCompression() &&
|
||||
req.Header.Get("Accept-Encoding") == "" &&
|
||||
|
@ -1452,15 +1581,30 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
|
|||
var respHeaderTimer <-chan time.Time
|
||||
var respHeaderRecv chan struct{}
|
||||
if d := cc.responseHeaderTimeout(); d != 0 {
|
||||
timer := time.NewTimer(d)
|
||||
timer := cc.newTimer(d)
|
||||
defer timer.Stop()
|
||||
respHeaderTimer = timer.C
|
||||
respHeaderTimer = timer.C()
|
||||
respHeaderRecv = cs.respHeaderRecv
|
||||
}
|
||||
// Wait until the peer half-closes its end of the stream,
|
||||
// or until the request is aborted (via context, error, or otherwise),
|
||||
// whichever comes first.
|
||||
for {
|
||||
if cc.syncHooks != nil {
|
||||
cc.syncHooks.blockUntil(func() bool {
|
||||
select {
|
||||
case <-cs.peerClosed:
|
||||
case <-respHeaderTimer:
|
||||
case <-respHeaderRecv:
|
||||
case <-cs.abort:
|
||||
case <-ctx.Done():
|
||||
case <-cs.reqCancel:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
select {
|
||||
case <-cs.peerClosed:
|
||||
return nil
|
||||
|
@ -1609,7 +1753,7 @@ func (cc *ClientConn) awaitOpenSlotForStreamLocked(cs *clientStream) error {
|
|||
return nil
|
||||
}
|
||||
cc.pendingRequests++
|
||||
cc.cond.Wait()
|
||||
cc.condWait()
|
||||
cc.pendingRequests--
|
||||
select {
|
||||
case <-cs.abort:
|
||||
|
@ -1871,10 +2015,26 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error)
|
|||
cs.flow.take(take)
|
||||
return take, nil
|
||||
}
|
||||
cc.cond.Wait()
|
||||
cc.condWait()
|
||||
}
|
||||
}
|
||||
|
||||
func validateHeaders(hdrs http.Header) string {
|
||||
for k, vv := range hdrs {
|
||||
if !httpguts.ValidHeaderFieldName(k) {
|
||||
return fmt.Sprintf("name %q", k)
|
||||
}
|
||||
for _, v := range vv {
|
||||
if !httpguts.ValidHeaderFieldValue(v) {
|
||||
// Don't include the value in the error,
|
||||
// because it may be sensitive.
|
||||
return fmt.Sprintf("value for header %q", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var errNilRequestURL = errors.New("http2: Request.URI is nil")
|
||||
|
||||
// requires cc.wmu be held.
|
||||
|
@ -1912,19 +2072,14 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
|
|||
}
|
||||
}
|
||||
|
||||
// Check for any invalid headers and return an error before we
|
||||
// Check for any invalid headers+trailers and return an error before we
|
||||
// potentially pollute our hpack state. (We want to be able to
|
||||
// continue to reuse the hpack encoder for future requests)
|
||||
for k, vv := range req.Header {
|
||||
if !httpguts.ValidHeaderFieldName(k) {
|
||||
return nil, fmt.Errorf("invalid HTTP header name %q", k)
|
||||
}
|
||||
for _, v := range vv {
|
||||
if !httpguts.ValidHeaderFieldValue(v) {
|
||||
// Don't include the value in the error, because it may be sensitive.
|
||||
return nil, fmt.Errorf("invalid HTTP header value for header %q", k)
|
||||
}
|
||||
if err := validateHeaders(req.Header); err != "" {
|
||||
return nil, fmt.Errorf("invalid HTTP header %s", err)
|
||||
}
|
||||
if err := validateHeaders(req.Trailer); err != "" {
|
||||
return nil, fmt.Errorf("invalid HTTP trailer %s", err)
|
||||
}
|
||||
|
||||
enumerateHeaders := func(f func(name, value string)) {
|
||||
|
@ -2143,7 +2298,7 @@ func (cc *ClientConn) forgetStreamID(id uint32) {
|
|||
}
|
||||
// Wake up writeRequestBody via clientStream.awaitFlowControl and
|
||||
// wake up RoundTrip if there is a pending request.
|
||||
cc.cond.Broadcast()
|
||||
cc.condBroadcast()
|
||||
|
||||
closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil
|
||||
if closeOnIdle && cc.streamsReserved == 0 && len(cc.streams) == 0 {
|
||||
|
@ -2231,7 +2386,7 @@ func (rl *clientConnReadLoop) cleanup() {
|
|||
cs.abortStreamLocked(err)
|
||||
}
|
||||
}
|
||||
cc.cond.Broadcast()
|
||||
cc.condBroadcast()
|
||||
cc.mu.Unlock()
|
||||
}
|
||||
|
||||
|
@ -2266,10 +2421,9 @@ func (rl *clientConnReadLoop) run() error {
|
|||
cc := rl.cc
|
||||
gotSettings := false
|
||||
readIdleTimeout := cc.t.ReadIdleTimeout
|
||||
var t *time.Timer
|
||||
var t timer
|
||||
if readIdleTimeout != 0 {
|
||||
t = time.AfterFunc(readIdleTimeout, cc.healthCheck)
|
||||
defer t.Stop()
|
||||
t = cc.afterFunc(readIdleTimeout, cc.healthCheck)
|
||||
}
|
||||
for {
|
||||
f, err := cc.fr.ReadFrame()
|
||||
|
@ -2684,7 +2838,7 @@ func (rl *clientConnReadLoop) processData(f *DataFrame) error {
|
|||
})
|
||||
return nil
|
||||
}
|
||||
if !cs.firstByte {
|
||||
if !cs.pastHeaders {
|
||||
cc.logf("protocol error: received DATA before a HEADERS frame")
|
||||
rl.endStreamError(cs, StreamError{
|
||||
StreamID: f.StreamID,
|
||||
|
@ -2867,7 +3021,7 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
|
|||
for _, cs := range cc.streams {
|
||||
cs.flow.add(delta)
|
||||
}
|
||||
cc.cond.Broadcast()
|
||||
cc.condBroadcast()
|
||||
|
||||
cc.initialWindowSize = s.Val
|
||||
case SettingHeaderTableSize:
|
||||
|
@ -2922,7 +3076,7 @@ func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error {
|
|||
|
||||
return ConnectionError(ErrCodeFlowControl)
|
||||
}
|
||||
cc.cond.Broadcast()
|
||||
cc.condBroadcast()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -2964,24 +3118,38 @@ func (cc *ClientConn) Ping(ctx context.Context) error {
|
|||
}
|
||||
cc.mu.Unlock()
|
||||
}
|
||||
errc := make(chan error, 1)
|
||||
go func() {
|
||||
var pingError error
|
||||
errc := make(chan struct{})
|
||||
cc.goRun(func() {
|
||||
cc.wmu.Lock()
|
||||
defer cc.wmu.Unlock()
|
||||
if err := cc.fr.WritePing(false, p); err != nil {
|
||||
errc <- err
|
||||
if pingError = cc.fr.WritePing(false, p); pingError != nil {
|
||||
close(errc)
|
||||
return
|
||||
}
|
||||
if err := cc.bw.Flush(); err != nil {
|
||||
errc <- err
|
||||
if pingError = cc.bw.Flush(); pingError != nil {
|
||||
close(errc)
|
||||
return
|
||||
}
|
||||
}()
|
||||
})
|
||||
if cc.syncHooks != nil {
|
||||
cc.syncHooks.blockUntil(func() bool {
|
||||
select {
|
||||
case <-c:
|
||||
case <-errc:
|
||||
case <-ctx.Done():
|
||||
case <-cc.readerDone:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
select {
|
||||
case <-c:
|
||||
return nil
|
||||
case err := <-errc:
|
||||
return err
|
||||
case <-errc:
|
||||
return pingError
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-cc.readerDone:
|
||||
|
@ -3150,9 +3318,17 @@ func (rt noDialH2RoundTripper) RoundTrip(req *http.Request) (*http.Response, err
|
|||
}
|
||||
|
||||
func (t *Transport) idleConnTimeout() time.Duration {
|
||||
// to keep things backwards compatible, we use non-zero values of
|
||||
// IdleConnTimeout, followed by using the IdleConnTimeout on the underlying
|
||||
// http1 transport, followed by 0
|
||||
if t.IdleConnTimeout != 0 {
|
||||
return t.IdleConnTimeout
|
||||
}
|
||||
|
||||
if t.t1 != nil {
|
||||
return t.t1.IdleConnTimeout
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
2
v1/vendor/golang.org/x/sys/unix/mmap_nomremap.go
generated
vendored
2
v1/vendor/golang.org/x/sys/unix/mmap_nomremap.go
generated
vendored
|
@ -2,7 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || openbsd || solaris
|
||||
//go:build aix || darwin || dragonfly || freebsd || openbsd || solaris || zos
|
||||
|
||||
package unix
|
||||
|
||||
|
|
8
v1/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go
generated
vendored
8
v1/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go
generated
vendored
|
@ -1520,6 +1520,14 @@ func (m *mmapper) Munmap(data []byte) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) {
|
||||
return mapper.Mmap(fd, offset, length, prot, flags)
|
||||
}
|
||||
|
||||
func Munmap(b []byte) (err error) {
|
||||
return mapper.Munmap(b)
|
||||
}
|
||||
|
||||
func Read(fd int, p []byte) (n int, err error) {
|
||||
n, err = read(fd, p)
|
||||
if raceenabled {
|
||||
|
|
82
v1/vendor/golang.org/x/sys/windows/syscall_windows.go
generated
vendored
82
v1/vendor/golang.org/x/sys/windows/syscall_windows.go
generated
vendored
|
@ -165,6 +165,7 @@ func NewCallbackCDecl(fn interface{}) uintptr {
|
|||
//sys CreateFile(name *uint16, access uint32, mode uint32, sa *SecurityAttributes, createmode uint32, attrs uint32, templatefile Handle) (handle Handle, err error) [failretval==InvalidHandle] = CreateFileW
|
||||
//sys CreateNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *SecurityAttributes) (handle Handle, err error) [failretval==InvalidHandle] = CreateNamedPipeW
|
||||
//sys ConnectNamedPipe(pipe Handle, overlapped *Overlapped) (err error)
|
||||
//sys DisconnectNamedPipe(pipe Handle) (err error)
|
||||
//sys GetNamedPipeInfo(pipe Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error)
|
||||
//sys GetNamedPipeHandleState(pipe Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
|
||||
//sys SetNamedPipeHandleState(pipe Handle, state *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32) (err error) = SetNamedPipeHandleState
|
||||
|
@ -348,8 +349,19 @@ func NewCallbackCDecl(fn interface{}) uintptr {
|
|||
//sys SetProcessPriorityBoost(process Handle, disable bool) (err error) = kernel32.SetProcessPriorityBoost
|
||||
//sys GetProcessWorkingSetSizeEx(hProcess Handle, lpMinimumWorkingSetSize *uintptr, lpMaximumWorkingSetSize *uintptr, flags *uint32)
|
||||
//sys SetProcessWorkingSetSizeEx(hProcess Handle, dwMinimumWorkingSetSize uintptr, dwMaximumWorkingSetSize uintptr, flags uint32) (err error)
|
||||
//sys ClearCommBreak(handle Handle) (err error)
|
||||
//sys ClearCommError(handle Handle, lpErrors *uint32, lpStat *ComStat) (err error)
|
||||
//sys EscapeCommFunction(handle Handle, dwFunc uint32) (err error)
|
||||
//sys GetCommState(handle Handle, lpDCB *DCB) (err error)
|
||||
//sys GetCommModemStatus(handle Handle, lpModemStat *uint32) (err error)
|
||||
//sys GetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error)
|
||||
//sys PurgeComm(handle Handle, dwFlags uint32) (err error)
|
||||
//sys SetCommBreak(handle Handle) (err error)
|
||||
//sys SetCommMask(handle Handle, dwEvtMask uint32) (err error)
|
||||
//sys SetCommState(handle Handle, lpDCB *DCB) (err error)
|
||||
//sys SetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error)
|
||||
//sys SetupComm(handle Handle, dwInQueue uint32, dwOutQueue uint32) (err error)
|
||||
//sys WaitCommEvent(handle Handle, lpEvtMask *uint32, lpOverlapped *Overlapped) (err error)
|
||||
//sys GetActiveProcessorCount(groupNumber uint16) (ret uint32)
|
||||
//sys GetMaximumProcessorCount(groupNumber uint16) (ret uint32)
|
||||
//sys EnumWindows(enumFunc uintptr, param unsafe.Pointer) (err error) = user32.EnumWindows
|
||||
|
@ -1834,3 +1846,73 @@ func ResizePseudoConsole(pconsole Handle, size Coord) error {
|
|||
// accept arguments that can be casted to uintptr, and Coord can't.
|
||||
return resizePseudoConsole(pconsole, *((*uint32)(unsafe.Pointer(&size))))
|
||||
}
|
||||
|
||||
// DCB constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-dcb.
|
||||
const (
|
||||
CBR_110 = 110
|
||||
CBR_300 = 300
|
||||
CBR_600 = 600
|
||||
CBR_1200 = 1200
|
||||
CBR_2400 = 2400
|
||||
CBR_4800 = 4800
|
||||
CBR_9600 = 9600
|
||||
CBR_14400 = 14400
|
||||
CBR_19200 = 19200
|
||||
CBR_38400 = 38400
|
||||
CBR_57600 = 57600
|
||||
CBR_115200 = 115200
|
||||
CBR_128000 = 128000
|
||||
CBR_256000 = 256000
|
||||
|
||||
DTR_CONTROL_DISABLE = 0x00000000
|
||||
DTR_CONTROL_ENABLE = 0x00000010
|
||||
DTR_CONTROL_HANDSHAKE = 0x00000020
|
||||
|
||||
RTS_CONTROL_DISABLE = 0x00000000
|
||||
RTS_CONTROL_ENABLE = 0x00001000
|
||||
RTS_CONTROL_HANDSHAKE = 0x00002000
|
||||
RTS_CONTROL_TOGGLE = 0x00003000
|
||||
|
||||
NOPARITY = 0
|
||||
ODDPARITY = 1
|
||||
EVENPARITY = 2
|
||||
MARKPARITY = 3
|
||||
SPACEPARITY = 4
|
||||
|
||||
ONESTOPBIT = 0
|
||||
ONE5STOPBITS = 1
|
||||
TWOSTOPBITS = 2
|
||||
)
|
||||
|
||||
// EscapeCommFunction constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-escapecommfunction.
|
||||
const (
|
||||
SETXOFF = 1
|
||||
SETXON = 2
|
||||
SETRTS = 3
|
||||
CLRRTS = 4
|
||||
SETDTR = 5
|
||||
CLRDTR = 6
|
||||
SETBREAK = 8
|
||||
CLRBREAK = 9
|
||||
)
|
||||
|
||||
// PurgeComm constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-purgecomm.
|
||||
const (
|
||||
PURGE_TXABORT = 0x0001
|
||||
PURGE_RXABORT = 0x0002
|
||||
PURGE_TXCLEAR = 0x0004
|
||||
PURGE_RXCLEAR = 0x0008
|
||||
)
|
||||
|
||||
// SetCommMask constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setcommmask.
|
||||
const (
|
||||
EV_RXCHAR = 0x0001
|
||||
EV_RXFLAG = 0x0002
|
||||
EV_TXEMPTY = 0x0004
|
||||
EV_CTS = 0x0008
|
||||
EV_DSR = 0x0010
|
||||
EV_RLSD = 0x0020
|
||||
EV_BREAK = 0x0040
|
||||
EV_ERR = 0x0080
|
||||
EV_RING = 0x0100
|
||||
)
|
||||
|
|
24
v1/vendor/golang.org/x/sys/windows/types_windows.go
generated
vendored
24
v1/vendor/golang.org/x/sys/windows/types_windows.go
generated
vendored
|
@ -3380,3 +3380,27 @@ type BLOB struct {
|
|||
Size uint32
|
||||
BlobData *byte
|
||||
}
|
||||
|
||||
type ComStat struct {
|
||||
Flags uint32
|
||||
CBInQue uint32
|
||||
CBOutQue uint32
|
||||
}
|
||||
|
||||
type DCB struct {
|
||||
DCBlength uint32
|
||||
BaudRate uint32
|
||||
Flags uint32
|
||||
wReserved uint16
|
||||
XonLim uint16
|
||||
XoffLim uint16
|
||||
ByteSize uint8
|
||||
Parity uint8
|
||||
StopBits uint8
|
||||
XonChar byte
|
||||
XoffChar byte
|
||||
ErrorChar byte
|
||||
EofChar byte
|
||||
EvtChar byte
|
||||
wReserved1 uint16
|
||||
}
|
||||
|
|
126
v1/vendor/golang.org/x/sys/windows/zsyscall_windows.go
generated
vendored
126
v1/vendor/golang.org/x/sys/windows/zsyscall_windows.go
generated
vendored
|
@ -188,6 +188,8 @@ var (
|
|||
procAssignProcessToJobObject = modkernel32.NewProc("AssignProcessToJobObject")
|
||||
procCancelIo = modkernel32.NewProc("CancelIo")
|
||||
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
|
||||
procClearCommBreak = modkernel32.NewProc("ClearCommBreak")
|
||||
procClearCommError = modkernel32.NewProc("ClearCommError")
|
||||
procCloseHandle = modkernel32.NewProc("CloseHandle")
|
||||
procClosePseudoConsole = modkernel32.NewProc("ClosePseudoConsole")
|
||||
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
|
||||
|
@ -212,7 +214,9 @@ var (
|
|||
procDeleteProcThreadAttributeList = modkernel32.NewProc("DeleteProcThreadAttributeList")
|
||||
procDeleteVolumeMountPointW = modkernel32.NewProc("DeleteVolumeMountPointW")
|
||||
procDeviceIoControl = modkernel32.NewProc("DeviceIoControl")
|
||||
procDisconnectNamedPipe = modkernel32.NewProc("DisconnectNamedPipe")
|
||||
procDuplicateHandle = modkernel32.NewProc("DuplicateHandle")
|
||||
procEscapeCommFunction = modkernel32.NewProc("EscapeCommFunction")
|
||||
procExitProcess = modkernel32.NewProc("ExitProcess")
|
||||
procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW")
|
||||
procFindClose = modkernel32.NewProc("FindClose")
|
||||
|
@ -236,6 +240,8 @@ var (
|
|||
procGenerateConsoleCtrlEvent = modkernel32.NewProc("GenerateConsoleCtrlEvent")
|
||||
procGetACP = modkernel32.NewProc("GetACP")
|
||||
procGetActiveProcessorCount = modkernel32.NewProc("GetActiveProcessorCount")
|
||||
procGetCommModemStatus = modkernel32.NewProc("GetCommModemStatus")
|
||||
procGetCommState = modkernel32.NewProc("GetCommState")
|
||||
procGetCommTimeouts = modkernel32.NewProc("GetCommTimeouts")
|
||||
procGetCommandLineW = modkernel32.NewProc("GetCommandLineW")
|
||||
procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW")
|
||||
|
@ -322,6 +328,7 @@ var (
|
|||
procProcess32NextW = modkernel32.NewProc("Process32NextW")
|
||||
procProcessIdToSessionId = modkernel32.NewProc("ProcessIdToSessionId")
|
||||
procPulseEvent = modkernel32.NewProc("PulseEvent")
|
||||
procPurgeComm = modkernel32.NewProc("PurgeComm")
|
||||
procQueryDosDeviceW = modkernel32.NewProc("QueryDosDeviceW")
|
||||
procQueryFullProcessImageNameW = modkernel32.NewProc("QueryFullProcessImageNameW")
|
||||
procQueryInformationJobObject = modkernel32.NewProc("QueryInformationJobObject")
|
||||
|
@ -335,6 +342,9 @@ var (
|
|||
procResetEvent = modkernel32.NewProc("ResetEvent")
|
||||
procResizePseudoConsole = modkernel32.NewProc("ResizePseudoConsole")
|
||||
procResumeThread = modkernel32.NewProc("ResumeThread")
|
||||
procSetCommBreak = modkernel32.NewProc("SetCommBreak")
|
||||
procSetCommMask = modkernel32.NewProc("SetCommMask")
|
||||
procSetCommState = modkernel32.NewProc("SetCommState")
|
||||
procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts")
|
||||
procSetConsoleCursorPosition = modkernel32.NewProc("SetConsoleCursorPosition")
|
||||
procSetConsoleMode = modkernel32.NewProc("SetConsoleMode")
|
||||
|
@ -342,7 +352,6 @@ var (
|
|||
procSetDefaultDllDirectories = modkernel32.NewProc("SetDefaultDllDirectories")
|
||||
procSetDllDirectoryW = modkernel32.NewProc("SetDllDirectoryW")
|
||||
procSetEndOfFile = modkernel32.NewProc("SetEndOfFile")
|
||||
procSetFileValidData = modkernel32.NewProc("SetFileValidData")
|
||||
procSetEnvironmentVariableW = modkernel32.NewProc("SetEnvironmentVariableW")
|
||||
procSetErrorMode = modkernel32.NewProc("SetErrorMode")
|
||||
procSetEvent = modkernel32.NewProc("SetEvent")
|
||||
|
@ -351,6 +360,7 @@ var (
|
|||
procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle")
|
||||
procSetFilePointer = modkernel32.NewProc("SetFilePointer")
|
||||
procSetFileTime = modkernel32.NewProc("SetFileTime")
|
||||
procSetFileValidData = modkernel32.NewProc("SetFileValidData")
|
||||
procSetHandleInformation = modkernel32.NewProc("SetHandleInformation")
|
||||
procSetInformationJobObject = modkernel32.NewProc("SetInformationJobObject")
|
||||
procSetNamedPipeHandleState = modkernel32.NewProc("SetNamedPipeHandleState")
|
||||
|
@ -361,6 +371,7 @@ var (
|
|||
procSetStdHandle = modkernel32.NewProc("SetStdHandle")
|
||||
procSetVolumeLabelW = modkernel32.NewProc("SetVolumeLabelW")
|
||||
procSetVolumeMountPointW = modkernel32.NewProc("SetVolumeMountPointW")
|
||||
procSetupComm = modkernel32.NewProc("SetupComm")
|
||||
procSizeofResource = modkernel32.NewProc("SizeofResource")
|
||||
procSleepEx = modkernel32.NewProc("SleepEx")
|
||||
procTerminateJobObject = modkernel32.NewProc("TerminateJobObject")
|
||||
|
@ -379,6 +390,7 @@ var (
|
|||
procVirtualQueryEx = modkernel32.NewProc("VirtualQueryEx")
|
||||
procVirtualUnlock = modkernel32.NewProc("VirtualUnlock")
|
||||
procWTSGetActiveConsoleSessionId = modkernel32.NewProc("WTSGetActiveConsoleSessionId")
|
||||
procWaitCommEvent = modkernel32.NewProc("WaitCommEvent")
|
||||
procWaitForMultipleObjects = modkernel32.NewProc("WaitForMultipleObjects")
|
||||
procWaitForSingleObject = modkernel32.NewProc("WaitForSingleObject")
|
||||
procWriteConsoleW = modkernel32.NewProc("WriteConsoleW")
|
||||
|
@ -1641,6 +1653,22 @@ func CancelIoEx(s Handle, o *Overlapped) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func ClearCommBreak(handle Handle) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procClearCommBreak.Addr(), 1, uintptr(handle), 0, 0)
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ClearCommError(handle Handle, lpErrors *uint32, lpStat *ComStat) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procClearCommError.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(lpErrors)), uintptr(unsafe.Pointer(lpStat)))
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func CloseHandle(handle Handle) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procCloseHandle.Addr(), 1, uintptr(handle), 0, 0)
|
||||
if r1 == 0 {
|
||||
|
@ -1845,6 +1873,14 @@ func DeviceIoControl(handle Handle, ioControlCode uint32, inBuffer *byte, inBuff
|
|||
return
|
||||
}
|
||||
|
||||
func DisconnectNamedPipe(pipe Handle) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procDisconnectNamedPipe.Addr(), 1, uintptr(pipe), 0, 0)
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetProcessHandle Handle, lpTargetHandle *Handle, dwDesiredAccess uint32, bInheritHandle bool, dwOptions uint32) (err error) {
|
||||
var _p0 uint32
|
||||
if bInheritHandle {
|
||||
|
@ -1857,6 +1893,14 @@ func DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetP
|
|||
return
|
||||
}
|
||||
|
||||
func EscapeCommFunction(handle Handle, dwFunc uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procEscapeCommFunction.Addr(), 2, uintptr(handle), uintptr(dwFunc), 0)
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ExitProcess(exitcode uint32) {
|
||||
syscall.Syscall(procExitProcess.Addr(), 1, uintptr(exitcode), 0, 0)
|
||||
return
|
||||
|
@ -2058,6 +2102,22 @@ func GetActiveProcessorCount(groupNumber uint16) (ret uint32) {
|
|||
return
|
||||
}
|
||||
|
||||
func GetCommModemStatus(handle Handle, lpModemStat *uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procGetCommModemStatus.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(lpModemStat)), 0)
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetCommState(handle Handle, lpDCB *DCB) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procGetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(lpDCB)), 0)
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procGetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0)
|
||||
if r1 == 0 {
|
||||
|
@ -2810,6 +2870,14 @@ func PulseEvent(event Handle) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func PurgeComm(handle Handle, dwFlags uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procPurgeComm.Addr(), 2, uintptr(handle), uintptr(dwFlags), 0)
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func QueryDosDevice(deviceName *uint16, targetPath *uint16, max uint32) (n uint32, err error) {
|
||||
r0, _, e1 := syscall.Syscall(procQueryDosDeviceW.Addr(), 3, uintptr(unsafe.Pointer(deviceName)), uintptr(unsafe.Pointer(targetPath)), uintptr(max))
|
||||
n = uint32(r0)
|
||||
|
@ -2924,6 +2992,30 @@ func ResumeThread(thread Handle) (ret uint32, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func SetCommBreak(handle Handle) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procSetCommBreak.Addr(), 1, uintptr(handle), 0, 0)
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SetCommMask(handle Handle, dwEvtMask uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procSetCommMask.Addr(), 2, uintptr(handle), uintptr(dwEvtMask), 0)
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SetCommState(handle Handle, lpDCB *DCB) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procSetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(lpDCB)), 0)
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procSetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0)
|
||||
if r1 == 0 {
|
||||
|
@ -2989,14 +3081,6 @@ func SetEndOfFile(handle Handle) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func SetFileValidData(handle Handle, validDataLength int64) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procSetFileValidData.Addr(), 2, uintptr(handle), uintptr(validDataLength), 0)
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SetEnvironmentVariable(name *uint16, value *uint16) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procSetEnvironmentVariableW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(value)), 0)
|
||||
if r1 == 0 {
|
||||
|
@ -3060,6 +3144,14 @@ func SetFileTime(handle Handle, ctime *Filetime, atime *Filetime, wtime *Filetim
|
|||
return
|
||||
}
|
||||
|
||||
func SetFileValidData(handle Handle, validDataLength int64) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procSetFileValidData.Addr(), 2, uintptr(handle), uintptr(validDataLength), 0)
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SetHandleInformation(handle Handle, mask uint32, flags uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procSetHandleInformation.Addr(), 3, uintptr(handle), uintptr(mask), uintptr(flags))
|
||||
if r1 == 0 {
|
||||
|
@ -3145,6 +3237,14 @@ func SetVolumeMountPoint(volumeMountPoint *uint16, volumeName *uint16) (err erro
|
|||
return
|
||||
}
|
||||
|
||||
func SetupComm(handle Handle, dwInQueue uint32, dwOutQueue uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procSetupComm.Addr(), 3, uintptr(handle), uintptr(dwInQueue), uintptr(dwOutQueue))
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SizeofResource(module Handle, resInfo Handle) (size uint32, err error) {
|
||||
r0, _, e1 := syscall.Syscall(procSizeofResource.Addr(), 2, uintptr(module), uintptr(resInfo), 0)
|
||||
size = uint32(r0)
|
||||
|
@ -3291,6 +3391,14 @@ func WTSGetActiveConsoleSessionId() (sessionID uint32) {
|
|||
return
|
||||
}
|
||||
|
||||
func WaitCommEvent(handle Handle, lpEvtMask *uint32, lpOverlapped *Overlapped) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procWaitCommEvent.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(lpEvtMask)), uintptr(unsafe.Pointer(lpOverlapped)))
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func waitForMultipleObjects(count uint32, handles uintptr, waitAll bool, waitMilliseconds uint32) (event uint32, err error) {
|
||||
var _p0 uint32
|
||||
if waitAll {
|
||||
|
|
8
v1/vendor/modules.txt
vendored
8
v1/vendor/modules.txt
vendored
|
@ -77,7 +77,7 @@ github.com/tkrajina/go-reflector/reflector
|
|||
# github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909
|
||||
## explicit; go 1.16
|
||||
github.com/tylertravisty/go-utils/random
|
||||
# github.com/tylertravisty/rumble-livestream-lib-go v0.3.5
|
||||
# github.com/tylertravisty/rumble-livestream-lib-go v0.5.1
|
||||
## explicit; go 1.19
|
||||
github.com/tylertravisty/rumble-livestream-lib-go
|
||||
# github.com/valyala/bytebufferpool v1.0.0
|
||||
|
@ -140,14 +140,14 @@ github.com/wailsapp/wails/v2/pkg/options/linux
|
|||
github.com/wailsapp/wails/v2/pkg/options/mac
|
||||
github.com/wailsapp/wails/v2/pkg/options/windows
|
||||
github.com/wailsapp/wails/v2/pkg/runtime
|
||||
# golang.org/x/crypto v0.21.0
|
||||
# golang.org/x/crypto v0.22.0
|
||||
## explicit; go 1.18
|
||||
golang.org/x/crypto/acme
|
||||
golang.org/x/crypto/acme/autocert
|
||||
# golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
||||
## explicit; go 1.20
|
||||
golang.org/x/exp/constraints
|
||||
# golang.org/x/net v0.22.0
|
||||
# golang.org/x/net v0.24.0
|
||||
## explicit; go 1.18
|
||||
golang.org/x/net/context
|
||||
golang.org/x/net/html
|
||||
|
@ -158,7 +158,7 @@ golang.org/x/net/http2/h2c
|
|||
golang.org/x/net/http2/hpack
|
||||
golang.org/x/net/idna
|
||||
golang.org/x/net/websocket
|
||||
# golang.org/x/sys v0.18.0
|
||||
# golang.org/x/sys v0.19.0
|
||||
## explicit; go 1.18
|
||||
golang.org/x/sys/unix
|
||||
golang.org/x/sys/windows
|
||||
|
|
Loading…
Reference in a new issue