mirror of
https://github.com/mautrix/whatsapp.git
synced 2026-08-24 04:54:56 -04:00
361 lines
14 KiB
Go
361 lines
14 KiB
Go
package connector
|
|
|
|
import (
|
|
_ "embed"
|
|
"fmt"
|
|
"strings"
|
|
"text/template"
|
|
"time"
|
|
|
|
up "go.mau.fi/util/configupgrade"
|
|
"go.mau.fi/whatsmeow/types"
|
|
"gopkg.in/yaml.v3"
|
|
"maunium.net/go/mautrix/event"
|
|
|
|
"go.mau.fi/mautrix-whatsapp/pkg/msgconv"
|
|
)
|
|
|
|
type MediaRequestMethod string
|
|
|
|
const (
|
|
MediaRequestMethodImmediate MediaRequestMethod = "immediate"
|
|
MediaRequestMethodLocalTime MediaRequestMethod = "local_time"
|
|
)
|
|
|
|
//go:embed example-config.yaml
|
|
var ExampleConfig string
|
|
|
|
type Config struct {
|
|
OSName string `yaml:"os_name"`
|
|
BrowserName string `yaml:"browser_name"`
|
|
|
|
Proxy string `yaml:"proxy"`
|
|
GetProxyURL string `yaml:"get_proxy_url"`
|
|
ProxyOnlyLogin bool `yaml:"proxy_only_login"`
|
|
|
|
DisplaynameTemplate string `yaml:"displayname_template"`
|
|
|
|
CallStartNotices bool `yaml:"call_start_notices"`
|
|
IdentityChangeNotices bool `yaml:"identity_change_notices"`
|
|
SendPresenceOnTyping bool `yaml:"send_presence_on_typing"`
|
|
EnableStatusBroadcast bool `yaml:"enable_status_broadcast"`
|
|
DisableStatusBroadcastSend bool `yaml:"disable_status_broadcast_send"`
|
|
MuteStatusBroadcast bool `yaml:"mute_status_broadcast"`
|
|
StatusBroadcastTag event.RoomTag `yaml:"status_broadcast_tag"`
|
|
PinnedTag event.RoomTag `yaml:"pinned_tag"`
|
|
ArchiveTag event.RoomTag `yaml:"archive_tag"`
|
|
WhatsappThumbnail bool `yaml:"whatsapp_thumbnail"`
|
|
URLPreviews bool `yaml:"url_previews"`
|
|
ExtEvPolls bool `yaml:"extev_polls"`
|
|
DisableViewOnce bool `yaml:"disable_view_once"`
|
|
ForceActiveDeliveryReceipts bool `yaml:"force_active_delivery_receipts"`
|
|
DirectMediaAutoRequest bool `yaml:"direct_media_auto_request"`
|
|
InitialAutoReconnect bool `yaml:"initial_auto_reconnect"`
|
|
UseWhatsAppRetryStore bool `yaml:"use_whatsapp_retry_store"`
|
|
|
|
AnimatedSticker msgconv.AnimatedStickerConfig `yaml:"animated_sticker"`
|
|
VOIP VOIPConfig `yaml:"voip"`
|
|
|
|
HistorySync struct {
|
|
MaxInitialConversations int `yaml:"max_initial_conversations"`
|
|
RequestFullSync bool `yaml:"request_full_sync"`
|
|
DispatchWait time.Duration `yaml:"dispatch_wait"`
|
|
FullSyncConfig struct {
|
|
DaysLimit uint32 `yaml:"days_limit"`
|
|
SizeLimit uint32 `yaml:"size_mb_limit"`
|
|
StorageQuota uint32 `yaml:"storage_quota_mb"`
|
|
} `yaml:"full_sync_config"`
|
|
|
|
MediaRequests struct {
|
|
AutoRequestMedia bool `yaml:"auto_request_media"`
|
|
RequestMethod MediaRequestMethod `yaml:"request_method"`
|
|
RequestLocalTime int `yaml:"request_local_time"`
|
|
MaxAsyncHandle int64 `yaml:"max_async_handle"`
|
|
} `yaml:"media_requests"`
|
|
|
|
BackwardsOnDemand bool `yaml:"backwards_on_demand"`
|
|
} `yaml:"history_sync"`
|
|
|
|
displaynameTemplate *template.Template `yaml:"-"`
|
|
}
|
|
|
|
type VOIPConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
MatrixSurface string `yaml:"matrix_surface"`
|
|
IncomingPolicy string `yaml:"incoming_policy"`
|
|
MaxActiveCallsPerLogin int `yaml:"max_active_calls_per_login"`
|
|
MatrixRTC MatrixRTCConfig `yaml:"matrixrtc"`
|
|
LiveKit LiveKitConfig `yaml:"livekit"`
|
|
Audio VOIPAudioConfig `yaml:"audio"`
|
|
Video VOIPVideoConfig `yaml:"video"`
|
|
Diagnostics VOIPDiagnostics `yaml:"diagnostics"`
|
|
}
|
|
|
|
type MatrixRTCConfig struct {
|
|
LiveKitServiceURL string `yaml:"livekit_service_url"`
|
|
RequireLiveKitFocus bool `yaml:"require_livekit_focus"`
|
|
MembershipEventCompat string `yaml:"membership_event_compat"`
|
|
NotificationEventCompat string `yaml:"notification_event_compat"`
|
|
UseDelayedEvents bool `yaml:"use_delayed_events"`
|
|
ParticipantMode string `yaml:"participant_mode"`
|
|
FallbackParticipantMXID string `yaml:"fallback_participant_mxid"`
|
|
}
|
|
|
|
type LiveKitConfig struct {
|
|
ConnectTimeout time.Duration `yaml:"connect_timeout"`
|
|
PublishSilenceBeforeWhatsAppAnswer bool `yaml:"publish_silence_before_whatsapp_answer"`
|
|
AutoSubscribe bool `yaml:"auto_subscribe"`
|
|
AudioUplinkPolicy string `yaml:"audio_uplink_policy"`
|
|
SelectedParticipantTimeout time.Duration `yaml:"selected_participant_timeout"`
|
|
}
|
|
|
|
type VOIPAudioConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
JitterBuffer time.Duration `yaml:"jitter_buffer_ms"`
|
|
OpusBackend string `yaml:"opus_backend"`
|
|
SilenceOnUnderrun bool `yaml:"silence_on_underrun"`
|
|
MaxMixParticipants int `yaml:"max_mix_participants"`
|
|
}
|
|
|
|
type VOIPVideoConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
SelectedSourcePolicy string `yaml:"selected_source_policy"`
|
|
MaxWidth int `yaml:"max_width"`
|
|
MaxHeight int `yaml:"max_height"`
|
|
MaxFPS int `yaml:"max_fps"`
|
|
}
|
|
|
|
type VOIPDiagnostics struct {
|
|
HealthcheckFocusOnStartup bool `yaml:"healthcheck_focus_on_startup"`
|
|
EnableMeowcallerDiagnostics bool `yaml:"enable_meowcaller_diagnostics"`
|
|
MediaTraceDir string `yaml:"media_trace_dir"`
|
|
}
|
|
|
|
type umConfig Config
|
|
|
|
func (c *Config) UnmarshalYAML(node *yaml.Node) error {
|
|
err := node.Decode((*umConfig)(c))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.PostProcess()
|
|
}
|
|
|
|
func (c *Config) PostProcess() error {
|
|
var err error
|
|
c.displaynameTemplate, err = template.New("displayname").Parse(c.DisplaynameTemplate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Try to execute template to make sure it's valid
|
|
_, err = c.formatDisplayname(types.PSAJID, "", types.ContactInfo{})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to execute displayname template: %w", err)
|
|
}
|
|
if err = c.validateVOIP(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) validateVOIP() error {
|
|
if !c.VOIP.Enabled {
|
|
return nil
|
|
}
|
|
if c.VOIP.MatrixSurface != "matrixrtc_livekit" {
|
|
return fmt.Errorf("voip.matrix_surface must be matrixrtc_livekit")
|
|
}
|
|
if !oneOf(c.VOIP.IncomingPolicy, "notice", "ring", "auto_answer") {
|
|
return fmt.Errorf("voip.incoming_policy must be one of notice, ring, auto_answer")
|
|
}
|
|
if c.VOIP.MaxActiveCallsPerLogin <= 0 {
|
|
return fmt.Errorf("voip.max_active_calls_per_login must be greater than 0")
|
|
}
|
|
if !oneOf(c.VOIP.MatrixRTC.MembershipEventCompat, "auto", "msc4143", "msc3401") {
|
|
return fmt.Errorf("voip.matrixrtc.membership_event_compat must be one of auto, msc4143, msc3401")
|
|
}
|
|
if !oneOf(c.VOIP.MatrixRTC.NotificationEventCompat, "auto", "disabled") {
|
|
return fmt.Errorf("voip.matrixrtc.notification_event_compat must be one of auto, disabled")
|
|
}
|
|
if !oneOf(c.VOIP.MatrixRTC.ParticipantMode, "whatsapp_ghost", "bridge_user") {
|
|
return fmt.Errorf("voip.matrixrtc.participant_mode must be one of whatsapp_ghost, bridge_user")
|
|
}
|
|
if c.VOIP.LiveKit.ConnectTimeout <= 0 {
|
|
return fmt.Errorf("voip.livekit.connect_timeout must be greater than 0")
|
|
}
|
|
if !oneOf(c.VOIP.LiveKit.AudioUplinkPolicy, "dominant_speaker", "mix_all", "selected_participant") {
|
|
return fmt.Errorf("voip.livekit.audio_uplink_policy must be one of dominant_speaker, mix_all, selected_participant")
|
|
}
|
|
if c.VOIP.Audio.Enabled {
|
|
if c.VOIP.Audio.JitterBuffer <= 0 {
|
|
return fmt.Errorf("voip.audio.jitter_buffer_ms must be greater than 0")
|
|
}
|
|
if c.VOIP.Audio.OpusBackend == "" {
|
|
return fmt.Errorf("voip.audio.opus_backend must be set")
|
|
}
|
|
if c.VOIP.Audio.MaxMixParticipants <= 0 {
|
|
return fmt.Errorf("voip.audio.max_mix_participants must be greater than 0")
|
|
}
|
|
}
|
|
if c.VOIP.Video.Enabled {
|
|
if !oneOf(c.VOIP.Video.SelectedSourcePolicy, "active_speaker", "selected_participant") {
|
|
return fmt.Errorf("voip.video.selected_source_policy must be one of active_speaker, selected_participant")
|
|
}
|
|
if c.VOIP.Video.MaxWidth <= 0 || c.VOIP.Video.MaxHeight <= 0 || c.VOIP.Video.MaxFPS <= 0 {
|
|
return fmt.Errorf("voip.video max_width, max_height and max_fps must be greater than 0")
|
|
}
|
|
}
|
|
if c.VOIP.Diagnostics.EnableMeowcallerDiagnostics && c.VOIP.Diagnostics.MediaTraceDir == "" {
|
|
return fmt.Errorf("voip.diagnostics.media_trace_dir must be set when meowcaller diagnostics are enabled")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func oneOf(value string, allowed ...string) bool {
|
|
for _, item := range allowed {
|
|
if value == item {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func upgradeConfig(helper up.Helper) {
|
|
helper.Copy(up.Str, "os_name")
|
|
helper.Copy(up.Str, "browser_name")
|
|
|
|
helper.Copy(up.Str|up.Null, "proxy")
|
|
helper.Copy(up.Str|up.Null, "get_proxy_url")
|
|
helper.Copy(up.Bool, "proxy_only_login")
|
|
|
|
helper.Copy(up.Str, "displayname_template")
|
|
|
|
helper.Copy(up.Bool, "call_start_notices")
|
|
helper.Copy(up.Bool, "identity_change_notices")
|
|
helper.Copy(up.Bool, "send_presence_on_typing")
|
|
helper.Copy(up.Bool, "enable_status_broadcast")
|
|
helper.Copy(up.Bool, "disable_status_broadcast_send")
|
|
helper.Copy(up.Bool, "mute_status_broadcast")
|
|
helper.Copy(up.Str|up.Null, "status_broadcast_tag")
|
|
helper.Copy(up.Str|up.Null, "pinned_tag")
|
|
helper.Copy(up.Str|up.Null, "archive_tag")
|
|
helper.Copy(up.Bool, "whatsapp_thumbnail")
|
|
helper.Copy(up.Bool, "url_previews")
|
|
helper.Copy(up.Bool, "extev_polls")
|
|
helper.Copy(up.Bool, "disable_view_once")
|
|
helper.Copy(up.Bool, "force_active_delivery_receipts")
|
|
helper.Copy(up.Bool, "direct_media_auto_request")
|
|
helper.Copy(up.Bool, "initial_auto_reconnect")
|
|
helper.Copy(up.Bool, "use_whatsapp_retry_store")
|
|
|
|
helper.Copy(up.Str, "animated_sticker", "target")
|
|
helper.Copy(up.Int, "animated_sticker", "args", "width")
|
|
helper.Copy(up.Int, "animated_sticker", "args", "height")
|
|
helper.Copy(up.Int, "animated_sticker", "args", "fps")
|
|
|
|
helper.Copy(up.Bool, "voip", "enabled")
|
|
helper.Copy(up.Str, "voip", "matrix_surface")
|
|
helper.Copy(up.Str, "voip", "incoming_policy")
|
|
helper.Copy(up.Int, "voip", "max_active_calls_per_login")
|
|
helper.Copy(up.Str|up.Null, "voip", "matrixrtc", "livekit_service_url")
|
|
helper.Copy(up.Bool, "voip", "matrixrtc", "require_livekit_focus")
|
|
helper.Copy(up.Str, "voip", "matrixrtc", "membership_event_compat")
|
|
helper.Copy(up.Str, "voip", "matrixrtc", "notification_event_compat")
|
|
helper.Copy(up.Bool, "voip", "matrixrtc", "use_delayed_events")
|
|
helper.Copy(up.Str, "voip", "matrixrtc", "participant_mode")
|
|
helper.Copy(up.Str|up.Null, "voip", "matrixrtc", "fallback_participant_mxid")
|
|
helper.Copy(up.Str|up.Int, "voip", "livekit", "connect_timeout")
|
|
helper.Copy(up.Bool, "voip", "livekit", "publish_silence_before_whatsapp_answer")
|
|
helper.Copy(up.Bool, "voip", "livekit", "auto_subscribe")
|
|
helper.Copy(up.Str, "voip", "livekit", "audio_uplink_policy")
|
|
helper.Copy(up.Str|up.Int, "voip", "livekit", "selected_participant_timeout")
|
|
helper.Copy(up.Bool, "voip", "audio", "enabled")
|
|
helper.Copy(up.Str|up.Int, "voip", "audio", "jitter_buffer_ms")
|
|
helper.Copy(up.Str, "voip", "audio", "opus_backend")
|
|
helper.Copy(up.Bool, "voip", "audio", "silence_on_underrun")
|
|
helper.Copy(up.Int, "voip", "audio", "max_mix_participants")
|
|
helper.Copy(up.Bool, "voip", "video", "enabled")
|
|
helper.Copy(up.Str, "voip", "video", "selected_source_policy")
|
|
helper.Copy(up.Int, "voip", "video", "max_width")
|
|
helper.Copy(up.Int, "voip", "video", "max_height")
|
|
helper.Copy(up.Int, "voip", "video", "max_fps")
|
|
helper.Copy(up.Bool, "voip", "diagnostics", "healthcheck_focus_on_startup")
|
|
helper.Copy(up.Bool, "voip", "diagnostics", "enable_meowcaller_diagnostics")
|
|
helper.Copy(up.Str|up.Null, "voip", "diagnostics", "media_trace_dir")
|
|
|
|
helper.Copy(up.Int, "history_sync", "max_initial_conversations")
|
|
helper.Copy(up.Bool, "history_sync", "request_full_sync")
|
|
helper.Copy(up.Str|up.Int, "history_sync", "dispatch_wait")
|
|
helper.Copy(up.Int|up.Null, "history_sync", "full_sync_config", "days_limit")
|
|
helper.Copy(up.Int|up.Null, "history_sync", "full_sync_config", "size_mb_limit")
|
|
helper.Copy(up.Int|up.Null, "history_sync", "full_sync_config", "storage_quota_mb")
|
|
helper.Copy(up.Bool, "history_sync", "media_requests", "auto_request_media")
|
|
helper.Copy(up.Str, "history_sync", "media_requests", "request_method")
|
|
helper.Copy(up.Int, "history_sync", "media_requests", "request_local_time")
|
|
helper.Copy(up.Int, "history_sync", "media_requests", "max_async_handle")
|
|
helper.Copy(up.Bool, "history_sync", "backwards_on_demand")
|
|
}
|
|
|
|
type DisplaynameParams struct {
|
|
types.ContactInfo
|
|
Phone string
|
|
|
|
// Deprecated legacy fields
|
|
JID string
|
|
Notify string
|
|
VName string
|
|
Name string
|
|
Short string
|
|
}
|
|
|
|
func (c *Config) formatDisplayname(jid types.JID, phone string, contact types.ContactInfo) (string, error) {
|
|
var nameBuf strings.Builder
|
|
if phone == "" && jid.Server == types.DefaultUserServer {
|
|
phone = "+" + jid.User
|
|
}
|
|
if contact.RedactedPhone == "" && phone != "" {
|
|
contact.RedactedPhone = redactPhone(phone)
|
|
}
|
|
err := c.displaynameTemplate.Execute(&nameBuf, &DisplaynameParams{
|
|
ContactInfo: contact,
|
|
Phone: phone,
|
|
|
|
// Deprecated legacy fields
|
|
JID: phone,
|
|
Notify: contact.PushName,
|
|
VName: contact.BusinessName,
|
|
Name: contact.FullName,
|
|
Short: contact.FirstName,
|
|
})
|
|
return nameBuf.String(), err
|
|
}
|
|
|
|
func (c *Config) FormatDisplayname(jid types.JID, phone string, contact types.ContactInfo) string {
|
|
name, err := c.formatDisplayname(jid, phone, contact)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return name
|
|
}
|
|
|
|
func redactPhone(phone string) string {
|
|
if len(phone) <= 4 {
|
|
return phone
|
|
}
|
|
// This doesn't keep 2+ digit country codes properly, but whatever
|
|
return phone[:2] + strings.Repeat("∙", len(phone)-4) + phone[len(phone)-2:]
|
|
}
|
|
|
|
func (wa *WhatsAppConnector) GetConfig() (string, any, up.Upgrader) {
|
|
return ExampleConfig, &wa.Config, &up.StructUpgrader{
|
|
SimpleUpgrader: up.SimpleUpgrader(upgradeConfig),
|
|
Blocks: [][]string{
|
|
{"proxy"},
|
|
{"displayname_template"},
|
|
{"call_start_notices"},
|
|
{"voip"},
|
|
{"history_sync"},
|
|
},
|
|
Base: ExampleConfig,
|
|
}
|
|
}
|