package utils import ( "encoding/json" "fmt" "github.com/aldinokemal/go-whatsapp-web-multidevice/config" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/appstate" waProto "go.mau.fi/whatsmeow/binary/proto" "go.mau.fi/whatsmeow/store" "go.mau.fi/whatsmeow/store/sqlstore" "go.mau.fi/whatsmeow/types" "go.mau.fi/whatsmeow/types/events" waLog "go.mau.fi/whatsmeow/util/log" "google.golang.org/protobuf/proto" "mime" "os" "strings" "sync/atomic" "time" ) var ( cli *whatsmeow.Client log waLog.Logger historySyncID int32 startupTime = time.Now().Unix() ) const logLevel = "DEBUG" func ParseJID(arg string) (types.JID, bool) { if arg[0] == '+' { arg = arg[1:] } if !strings.ContainsRune(arg, '@') { return types.NewJID(arg, types.DefaultUserServer), true } else { recipient, err := types.ParseJID(arg) if err != nil { _ = fmt.Errorf("Invalid JID %s: %v", arg, err) return recipient, false } else if recipient.User == "" { _ = fmt.Errorf("Invalid JID %s: no server specified", arg) return recipient, false } return recipient, true } } func InitWaDB() *sqlstore.Container { // Running Whatsapp log = waLog.Stdout("Main", logLevel, true) dbLog := waLog.Stdout("Database", logLevel, true) storeContainer, err := sqlstore.New("sqlite3", fmt.Sprintf("file:%s?_foreign_keys=off", config.DBName), dbLog) if err != nil { log.Errorf("Failed to connect to database: %v", err) panic(err) } return storeContainer } func InitWaCLI(storeContainer *sqlstore.Container) *whatsmeow.Client { device, err := storeContainer.GetFirstDevice() if err != nil { log.Errorf("Failed to get device: %v", err) panic(err) } store.CompanionProps.PlatformType = waProto.CompanionProps_UNKNOWN.Enum() store.CompanionProps.Os = proto.String("AldinoKemal") cli = whatsmeow.NewClient(device, waLog.Stdout("Client", logLevel, true)) cli.AddEventHandler(handler) return cli } func MustLogin(waCli *whatsmeow.Client) { if !waCli.IsConnected() { panic(AuthError{Message: "you are not connect to whatsapp server, please reconnect"}) } else if !waCli.IsLoggedIn() { panic(AuthError{Message: "you are not login"}) } } func handler(rawEvt interface{}) { switch evt := rawEvt.(type) { case *events.AppStateSyncComplete: if len(cli.Store.PushName) > 0 && evt.Name == appstate.WAPatchCriticalBlock { err := cli.SendPresence(types.PresenceAvailable) if err != nil { log.Warnf("Failed to send available presence: %v", err) } else { log.Infof("Marked self as available") } } case *events.Connected, *events.PushNameSetting: if len(cli.Store.PushName) == 0 { return } // Send presence available when connecting and when the pushname is changed. // This makes sure that outgoing messages always have the right pushname. err := cli.SendPresence(types.PresenceAvailable) if err != nil { log.Warnf("Failed to send available presence: %v", err) } else { log.Infof("Marked self as available") } case *events.StreamReplaced: os.Exit(0) case *events.Message: metaParts := []string{fmt.Sprintf("pushname: %s", evt.Info.PushName), fmt.Sprintf("timestamp: %s", evt.Info.Timestamp)} if evt.Info.Type != "" { metaParts = append(metaParts, fmt.Sprintf("type: %s", evt.Info.Type)) } if evt.Info.Category != "" { metaParts = append(metaParts, fmt.Sprintf("category: %s", evt.Info.Category)) } if evt.IsViewOnce { metaParts = append(metaParts, "view once") } if evt.IsViewOnce { metaParts = append(metaParts, "ephemeral") } log.Infof("Received message %s from %s (%s): %+v", evt.Info.ID, evt.Info.SourceString(), strings.Join(metaParts, ", "), evt.Message) img := evt.Message.GetImageMessage() if img != nil { data, err := cli.Download(img) if err != nil { log.Errorf("Failed to download image: %v", err) return } exts, _ := mime.ExtensionsByType(img.GetMimetype()) path := fmt.Sprintf("%s%s", evt.Info.ID, exts[0]) err = os.WriteFile(path, data, 0600) if err != nil { log.Errorf("Failed to save image: %v", err) return } log.Infof("Saved image in message to %s", path) } case *events.Receipt: if evt.Type == events.ReceiptTypeRead || evt.Type == events.ReceiptTypeReadSelf { log.Infof("%v was read by %s at %s", evt.MessageIDs, evt.SourceString(), evt.Timestamp) } else if evt.Type == events.ReceiptTypeDelivered { log.Infof("%s was delivered to %s at %s", evt.MessageIDs[0], evt.SourceString(), evt.Timestamp) } case *events.Presence: if evt.Unavailable { if evt.LastSeen.IsZero() { log.Infof("%s is now offline", evt.From) } else { log.Infof("%s is now offline (last seen: %s)", evt.From, evt.LastSeen) } } else { log.Infof("%s is now online", evt.From) } case *events.HistorySync: id := atomic.AddInt32(&historySyncID, 1) fileName := fmt.Sprintf("history-%d-%d.json", startupTime, id) file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE, 0600) if err != nil { log.Errorf("Failed to open file to write history sync: %v", err) return } enc := json.NewEncoder(file) enc.SetIndent("", " ") err = enc.Encode(evt.Data) if err != nil { log.Errorf("Failed to write history sync: %v", err) return } log.Infof("Wrote history sync to %s", fileName) _ = file.Close() case *events.AppState: log.Debugf("App state event: %+v / %+v", evt.Index, evt.SyncActionValue) } }