bridgev2/commands: Add captcha step type

This commit is contained in:
Radon Rosborough 2026-02-23 10:39:51 -08:00
commit 09b991fc16
2 changed files with 91 additions and 0 deletions

View file

@ -18,6 +18,7 @@ import (
"github.com/skip2/go-qrcode"
"go.mau.fi/util/curl"
"go.mau.fi/util/exmime"
"maunium.net/go/mautrix/bridgev2"
"maunium.net/go/mautrix/bridgev2/networkid"
@ -273,6 +274,44 @@ func sendQR(ce *Event, qr string, prevEventID *id.EventID) error {
return nil
}
func sendCaptcha(ce *Event, params *bridgev2.LoginCaptchaParams) error {
if params.ImageMimeType != "" {
filename := "captcha" + exmime.ExtensionFromMimetype(params.ImageMimeType)
mxc, file, err := ce.Bot.UploadMedia(ce.Ctx, ce.RoomID, params.ImageData, filename, params.ImageMimeType)
if err != nil {
return fmt.Errorf("failed to upload image: %w", err)
}
content := &event.MessageEventContent{
MsgType: event.MsgImage,
FileName: filename,
URL: mxc,
File: file,
}
_, err = ce.Bot.SendMessage(ce.Ctx, ce.RoomID, event.EventMessage, &event.Content{Parsed: content}, nil)
if err != nil {
return nil
}
}
if params.AudioMimeType != "" {
filename := "captcha" + exmime.ExtensionFromMimetype(params.AudioMimeType)
mxc, file, err := ce.Bot.UploadMedia(ce.Ctx, ce.RoomID, params.AudioData, filename, params.AudioMimeType)
if err != nil {
return fmt.Errorf("failed to upload audio: %w", err)
}
content := &event.MessageEventContent{
MsgType: event.MsgAudio,
FileName: filename,
URL: mxc,
File: file,
}
_, err = ce.Bot.SendMessage(ce.Ctx, ce.RoomID, event.EventMessage, &event.Content{Parsed: content}, nil)
if err != nil {
return nil
}
}
return nil
}
type contextKey int
const (
@ -463,6 +502,31 @@ func maybeURLDecodeCookie(val string, field *bridgev2.LoginCookieField) string {
return decoded
}
type captchaLoginCommandState struct {
Login bridgev2.LoginProcessCaptcha
Data *bridgev2.LoginCaptchaParams
Override *bridgev2.UserLogin
}
func (clcs *captchaLoginCommandState) prompt(ce *Event) {
ce.Reply("Please enter the captcha code")
StoreCommandState(ce.User, &CommandState{
Next: MinimalCommandHandlerFunc(clcs.submit),
Action: "Login",
Meta: clcs,
Cancel: clcs.Login.Cancel,
})
}
func (clcs *captchaLoginCommandState) submit(ce *Event) {
StoreCommandState(ce.User, nil)
if nextStep, err := clcs.Login.SubmitCaptcha(ce.Ctx, ce.RawArgs); err != nil {
ce.Reply("Failed to submit captcha: %v", err)
} else {
doLoginStep(ce, clcs.Login, nextStep, clcs.Override)
}
}
func doLoginStep(ce *Event, login bridgev2.LoginProcess, step *bridgev2.LoginStep, override *bridgev2.UserLogin) {
if step.Instructions != "" {
ce.Reply(step.Instructions)
@ -484,6 +548,18 @@ func doLoginStep(ce *Event, login bridgev2.LoginProcess, step *bridgev2.LoginSte
Data: make(map[string]string),
Override: override,
}).promptNext(ce)
case bridgev2.LoginStepTypeCaptcha:
err := sendCaptcha(ce, step.CaptchaParams)
if err != nil {
ce.Reply("Failed to send captcha: %v", err)
login.Cancel()
return
}
(&captchaLoginCommandState{
Login: login.(bridgev2.LoginProcessCaptcha),
Data: step.CaptchaParams,
Override: override,
}).prompt(ce)
case bridgev2.LoginStepTypeComplete:
if override != nil && override.ID != step.CompleteParams.UserLoginID {
ce.Log.Info().

View file

@ -58,6 +58,11 @@ type LoginProcessCookies interface {
SubmitCookies(ctx context.Context, cookies map[string]string) (*LoginStep, error)
}
type LoginProcessCaptcha interface {
LoginProcess
SubmitCaptcha(ctx context.Context, code string) (*LoginStep, error)
}
type LoginFlow struct {
Name string `json:"name"`
Description string `json:"description"`
@ -70,6 +75,7 @@ const (
LoginStepTypeUserInput LoginStepType = "user_input"
LoginStepTypeCookies LoginStepType = "cookies"
LoginStepTypeDisplayAndWait LoginStepType = "display_and_wait"
LoginStepTypeCaptcha LoginStepType = "captcha"
LoginStepTypeComplete LoginStepType = "complete"
)
@ -99,6 +105,7 @@ type LoginStep struct {
DisplayAndWaitParams *LoginDisplayAndWaitParams `json:"display_and_wait,omitempty"`
CookiesParams *LoginCookiesParams `json:"cookies,omitempty"`
UserInputParams *LoginUserInputParams `json:"user_input,omitempty"`
CaptchaParams *LoginCaptchaParams `json:"captcha,omitempty"`
CompleteParams *LoginCompleteParams `json:"complete,omitempty"`
}
@ -273,6 +280,14 @@ type LoginUserInputParams struct {
Fields []LoginInputDataField `json:"fields"`
}
type LoginCaptchaParams struct {
ImageData []byte `json:"image,omitempty"`
ImageMimeType string `json:"image_content_type"`
AudioData []byte `json:"audio,omitempty"`
AudioMimeType string `json:"audio_content_type,omitempty"`
}
type LoginCompleteParams struct {
UserLoginID networkid.UserLoginID `json:"user_login_id"`
UserLogin *UserLogin `json:"-"`