Refactored the code

This commit is contained in:
2025-06-20 23:16:32 +02:00
parent 7ea5713bf5
commit be38f711b7
4 changed files with 437 additions and 466 deletions

View File

@@ -1,11 +1,7 @@
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"math"
"net/http"
"os"
@@ -13,14 +9,9 @@ import (
"strconv"
"strings"
"time"
"github.com/mdp/qrterminal/v3"
_ "github.com/mattn/go-sqlite3"
"google.golang.org/protobuf/proto"
"go.mau.fi/whatsmeow"
"go.mau.fi/whatsmeow/types"
"go.mau.fi/whatsmeow/store/sqlstore"
waProto "go.mau.fi/whatsmeow/binary/proto"
waLog "go.mau.fi/whatsmeow/util/log"
"github.com/napnap75/multiarch-docker-files/immich-souvenirs/internals/immich"
"github.com/napnap75/multiarch-docker-files/immich-souvenirs/internals/whatsapp"
)
type Parameters struct {
@@ -33,487 +24,214 @@ type Parameters struct {
HealthchecksURL string
}
type Album struct {
ID string `json:"id"`
Name string `json:"albumName"`
Description string `json:"description"`
Shared bool `json:"shared"`
HasSharedLink bool `json:"hasSharedLink"`
StartDate time.Time `json:"startDate"`
CreatedAt time.Time `json:"createdAt"`
AlbumThumbnailAssetId string `json:"albumThumbnailAssetId"`
func parseTime(timeStr string) (int, int, error) {
parts := strings.Split(timeStr, ":")
if len(parts) != 2 {
return 0, 0, fmt.Errorf("invalid format: %s", timeStr)
}
hours, err := strconv.Atoi(parts[0])
if err != nil {
return 0, 0, err
}
minutes, err := strconv.Atoi(parts[1])
if err != nil {
return 0, 0, err
}
return hours, minutes, nil
}
type Key struct {
ID string `json:"id"`
Key string `json:"key"`
Album *Album `json:"album"`
}
func connect(param Parameters) (*whatsmeow.Client, error) {
dbLog := waLog.Stdout("Database", "ERROR", true)
container, err := sqlstore.New(context.Background(), "sqlite3", "file:" + param.WhatsappSessionFile + "?_foreign_keys=on", dbLog)
if err != nil {
return nil, err
}
deviceStore, err := container.GetFirstDevice(context.Background())
if err != nil {
return nil, err
}
clientLog := waLog.Stdout("Client", "ERROR", true)
client := whatsmeow.NewClient(deviceStore, clientLog)
if client.Store.ID == nil {
// No ID stored, new login
qrChan, _ := client.GetQRChannel(context.Background())
err = client.Connect()
if err != nil {
return nil, err
}
for evt := range qrChan {
if evt.Event == "code" {
qrterminal.GenerateHalfBlock(evt.Code, qrterminal.L, os.Stdout)
fmt.Println("QR code:", evt.Code)
} else {
fmt.Println("Login event:", evt.Event)
}
}
} else {
// Already logged in, just connect
err = client.Connect()
if err != nil {
return nil, err
}
}
return client, nil
}
func sendMessage(client *whatsmeow.Client, group string, title string, description string, message string, url string, thumbnail []byte) error {
jid, err := types.ParseJID(group)
if err != nil {
return fmt.Errorf("Incorrect group identifier '%s': %v", group, err)
}
msg := &waProto.Message{
ExtendedTextMessage: &waProto.ExtendedTextMessage{
Title: proto.String(title),
Description: proto.String(description),
Text: proto.String(message),
MatchedText: proto.String(url),
JPEGThumbnail: thumbnail,
},
}
ts, err := client.SendMessage(context.Background(), jid, msg)
if err != nil {
return fmt.Errorf("Error sending message with title '%s': %v", title, err)
}
fmt.Fprintf(os.Stdout, "Message with title '%s' sent (timestamp: %s)\n", title, ts)
return nil
}
func testConnexions(param Parameters) error {
// Create new WhatsApp connection and connect
client, err := connect(param)
if err != nil {
return fmt.Errorf("Error connecting to WhatsApp: %v", err)
}
<-time.After(3 * time.Second)
defer client.Disconnect()
// Prints the available groups if none provided
if param.WhatsappGroup == "" {
fmt.Fprintf(os.Stdout, "No WhatsApp group provided, showing all available groups\n")
groups, err := client.GetJoinedGroups()
if err != nil {
return fmt.Errorf("Error getting groups list: %v", err)
}
for _, groupInfo := range groups {
fmt.Fprintf(os.Stdout, "%s | %s\n", groupInfo.JID, groupInfo.GroupName)
}
return fmt.Errorf("No WhatsApp group provided")
} else {
jid, err := types.ParseJID(param.WhatsappGroup)
if err != nil {
return fmt.Errorf("Incorrect group identifier '%s': %v", param.WhatsappGroup, err)
}
_, err = client.GetGroupInfo(jid)
if err != nil {
return fmt.Errorf("Unknown WhatsApp group %s", param.WhatsappGroup)
}
}
// Connects to Immich and load albums
spaceClient := http.Client{
Timeout: time.Second * 10,
}
req, err := http.NewRequest(http.MethodGet, param.ImmichURL + "/api/albums", nil)
if err != nil {
return fmt.Errorf("Error connecting to Immich with URL '%s': %v", param.ImmichURL, err)
}
req.Header.Set("x-api-key", param.ImmichKey)
req.Header.Set("Accept", "application/json")
res, err := spaceClient.Do(req)
if err != nil {
return fmt.Errorf("Error connecting to Immich with URL '%s': %v", param.ImmichURL, err)
}
if res.Body != nil {
defer res.Body.Close()
}
if res.StatusCode != 200 {
return fmt.Errorf("Error connecting to Immich with URL '%s': Status code %d", param.ImmichURL, res.StatusCode)
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("Error connecting to Immich with URL '%s': %v", param.ImmichURL, err)
}
var albums []Album
err = json.Unmarshal([]byte(body), &albums)
if err != nil {
return fmt.Errorf("Error connecting to Immich with URL '%s': %v", param.ImmichURL, err)
}
return nil
}
func getSharingKey(album Album, param Parameters) (string, error) {
spaceClient := http.Client{
Timeout: time.Second * 10,
}
if (album.HasSharedLink) {
// Retrieve the existing key
req, err := http.NewRequest(http.MethodGet, param.ImmichURL + "/api/shared-links", nil)
if err != nil {
return "", fmt.Errorf("Error retrieving sharing key for album '%s': %v", album.Name, err)
}
req.Header.Set("x-api-key", param.ImmichKey)
req.Header.Set("Accept", "application/json")
res, err := spaceClient.Do(req)
if err != nil {
return "", fmt.Errorf("Error retrieving sharing key for album '%s': %v", album.Name, err)
}
if res.Body != nil {
defer res.Body.Close()
}
if res.StatusCode != 200 {
return "", fmt.Errorf("Error retrieving sharing key for album '%s': Status code %d", album.Name, res.StatusCode)
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", fmt.Errorf("Error retrieving sharing key for album '%s': %v", album.Name, err)
}
var keys []Key
err = json.Unmarshal([]byte(body), &keys)
if err != nil {
return "", fmt.Errorf("Error retrieving sharing key for album '%s': %v", album.Name, err)
}
for _, key := range keys {
if (key.Album != nil && album.ID == key.Album.ID) {
return key.Key, nil
}
}
return "", fmt.Errorf("Error retrieving sharing key for album '%s': no key found for this albume", album.Name)
} else {
// Create the missing key
var jsonData = []byte(`{
"albumId": "` + album.ID + `",
"allowDownload": true,
"allowUpload": false,
"showMetadata": true,
"type": "ALBUM"
}`)
req, err := http.NewRequest(http.MethodPost, param.ImmichURL + "/api/shared-links", bytes.NewBuffer(jsonData))
if err != nil {
return "", fmt.Errorf("Error creating missing key for album '%s': %v", album.Name, err)
}
req.Header.Set("x-api-key", param.ImmichKey)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/octet-stream")
res, err := spaceClient.Do(req)
if err != nil {
return "", fmt.Errorf("Error creating missing key for album '%s': %v", album.Name, err)
}
if res.Body != nil {
defer res.Body.Close()
}
if res.StatusCode != 201 {
return "", fmt.Errorf("Error creating missing key for album '%s': Status code %d", album.Name, res.StatusCode)
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", fmt.Errorf("Error creating missing key for album '%s': %v", album.Name, err)
}
var key Key
err = json.Unmarshal([]byte(body), &key)
if err != nil {
return "", fmt.Errorf("Error creating missing key for album '%s': %v", album.Name, err)
}
return key.Key, nil
}
}
func getThumbnail(album Album, param Parameters) ([]byte, error) {
spaceClient := http.Client{
Timeout: time.Second * 10,
}
req, err := http.NewRequest(http.MethodGet, param.ImmichURL + "/api/assets/" + album.AlbumThumbnailAssetId + "/thumbnail?size=preview", nil)
if err != nil {
return nil, fmt.Errorf("Error retrieving thumbnail for album '%s': %v", album.Name, err)
}
req.Header.Set("x-api-key", param.ImmichKey)
req.Header.Set("Accept", "application/octet-stream")
res, err := spaceClient.Do(req)
if err != nil {
return nil, fmt.Errorf("Error retrieving thumbnail for album '%s': %v", album.Name, err)
}
if res.Body != nil {
defer res.Body.Close()
}
if res.StatusCode != 200 {
return nil, fmt.Errorf("Error retrieving thumbnail for album '%s': Status code %d", album.Name, res.StatusCode)
}
thumbnail, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("Error retrieving thumbnail for album '%s': %v", album.Name, err)
}
return thumbnail, nil
}
func eventHandler(evt interface{}) {
fmt.Println("Received a message!", evt)
}
func listen(param Parameters) error {
// Create new WhatsApp connection and connect
client, err := connect(param)
if err != nil {
return fmt.Errorf("Error creating connection to WhatsApp: %v", err)
}
<-time.After(3 * time.Second)
defer client.Disconnect()
client.AddEventHandler(eventHandler)
func main() {
// Capture interrupt signal for graceful shutdown
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
<-c
fmt.Println("Got interrupt signal. Exiting...")
os.Exit(0)
}()
// Load parameters from environment variables
param := &Parameters{
ImmichURL: os.Getenv("IMMICH-URL"),
ImmichKey: os.Getenv("IMMICH-KEY"),
WhatsappSessionFile: os.Getenv("WHATSAPP-SESSION-FILE"),
WhatsappGroup: os.Getenv("WHATSAPP-GROUP"),
TimeToRun: os.Getenv("TIME-TO-RUN"),
DevelopmentMode: os.Getenv("DEVELOPMENT-MODE"),
HealthchecksURL: os.Getenv("HEALTHCHECKS-URL"),
}
// Initialize WhatsApp connection
wac, err := whatsapp.New(param.WhatsappSessionFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Error initializing WhatsApp: %v\n", err)
return
}
// Initialize Immich client
immichClient := immich.New(param.ImmichURL, param.ImmichKey)
switch param.DevelopmentMode {
case "run-once", "run-last":
if err := runLoop(wac, immichClient, param); err != nil {
fmt.Fprintf(os.Stderr, "Error in runLoop: %v\n", err)
}
case "listen":
if err := listen(wac); err != nil {
fmt.Fprintf(os.Stderr, "Error in listen: %v\n", err)
}
default:
// Test connection on startup
if err := testConnections(wac, immichClient, param); err != nil {
fmt.Fprintf(os.Stderr, "Connection test failed: %v\n", err)
return
}
// Main scheduled loop
for {
hours, minutes, err := parseTime(param.TimeToRun)
if err != nil {
fmt.Fprintf(os.Stderr, "Invalid time format: %v\n", err)
return
}
now := time.Now()
target := time.Date(now.Year(), now.Month(), now.Day(), hours, minutes, 0, 0, now.Location())
if target.Before(now) {
target = target.Add(24 * time.Hour)
}
sleepDuration := target.Sub(now)
fmt.Printf("Sleeping for: %v\n", sleepDuration)
time.Sleep(sleepDuration)
// Retry logic
for i := 0; i < 3; i++ {
err := runLoop(wac, immichClient, param)
if err == nil {
break
}
fmt.Fprintf(os.Stderr, "Attempt %d failed: %v\n", i+1, err)
time.Sleep(time.Duration(math.Pow(2, float64(i))) * 30 * time.Second)
}
if err != nil {
fmt.Fprintf(os.Stderr, "Error after retries: %v\n", err)
} else if param.HealthchecksURL != "" {
client := &http.Client{Timeout: 10 * time.Second}
_, err := client.Head(param.HealthchecksURL)
if err != nil {
fmt.Fprintf(os.Stderr, "Healthcheck error: %v\n", err)
}
}
}
}
}
// Fonction pour tester la connexion à WhatsApp et Immich
func testConnections(wac *whatsapp.WhatsAppClient, immichClient *immich.ImmichClient, param *Parameters) error {
fmt.Println("Testing connections...")
// Test WhatsApp
if err := wac.Client.Connect(); err != nil {
return fmt.Errorf("WhatsApp connection failed: %v", err)
}
defer wac.Client.Disconnect()
fmt.Println("WhatsApp connected.")
// Test Immich
albums, err := immichClient.FetchAlbums()
if err != nil {
return fmt.Errorf("Immich connection failed: %v", err)
}
fmt.Printf("Fetched %d albums from Immich.\n", len(albums))
return nil
}
// Fonction principale pour exécuter la logique
func runLoop(wac *whatsapp.WhatsAppClient, immichClient *immich.ImmichClient, param *Parameters) error {
// Connecter WhatsApp si nécessaire
if !wac.Client.IsConnected() {
if err := wac.Client.Connect(); err != nil {
return err
}
}
defer wac.Client.Disconnect()
func runLoop(param Parameters) error {
// Create new WhatsApp connection and connect
client, err := connect(param)
// Charger albums depuis Immich
albums, err := immichClient.FetchAlbums()
if err != nil {
return fmt.Errorf("Error creating connection to WhatsApp: %v", err)
}
<-time.After(3 * time.Second)
defer client.Disconnect()
// Connects to Immich and load albums
spaceClient := http.Client{
Timeout: time.Second * 10,
}
req, err := http.NewRequest(http.MethodGet, param.ImmichURL + "/api/albums", nil)
if err != nil {
return fmt.Errorf("Error connecting to Immich with URL '%s': %v", param.ImmichURL, err)
}
req.Header.Set("x-api-key", param.ImmichKey)
req.Header.Set("Accept", "application/json")
res, err := spaceClient.Do(req)
if err != nil {
return fmt.Errorf("Error connecting to Immich with URL '%s': %v", param.ImmichURL, err)
}
if res.Body != nil {
defer res.Body.Close()
}
if res.StatusCode != 200 {
return fmt.Errorf("Error connecting to Immich with URL '%s': Status code %d", param.ImmichURL, res.StatusCode)
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("Error connecting to Immich with URL '%s': %v", param.ImmichURL, err)
}
var albums []Album
err = json.Unmarshal([]byte(body), &albums)
if err != nil {
return fmt.Errorf("Error connecting to Immich with URL '%s': %v", param.ImmichURL, err)
return err
}
for _, album := range albums {
if album.Shared {
// Get albums from x years ago
if param.DevelopmentMode == "run-last" || (album.StartDate.Month() == time.Now().Month()) && (album.StartDate.Day() == time.Now().Day()) {
// Retrieve the sharing key
sharingKey, err := getSharingKey(album, param)
// Récupérer les albums anniversaire
if param.DevelopmentMode == "run-last" || (album.StartDate.Month() == time.Now().Month() && album.StartDate.Day() == time.Now().Day()) {
// Obtenir la clé de partage
sharingKey, err := immichClient.GetSharingKey(album)
if err != nil {
return fmt.Errorf("Error retrieving the sharing key for album '%s': %v", album.Name, err)
}
// Retrieve the thumbnail
thumbnail, err := getThumbnail(album, param)
if err != nil {
return fmt.Errorf("Error retrieving thumbnail for album '%s': %v", album.Name, err)
}
// Send the message
link := param.ImmichURL + "/share/" + sharingKey
err = sendMessage(client, param.WhatsappGroup, album.Name, album.Description, fmt.Sprintf("Il y a %d an(s) : %s", time.Now().Year()-album.StartDate.Year(), link), link, thumbnail)
if err != nil {
fmt.Fprintf(os.Stderr, "Error sending message to WhatsApp for album '%s': %v\n", album.Name, err)
fmt.Fprintf(os.Stderr, "Erreur récupération clé partage: %v\n", err)
continue
}
link := param.ImmichURL + "/share/" + sharingKey
// Récupérer la miniature
thumbnail, err := immichClient.GetThumbnail(album.AlbumThumbnailAssetId)
if err != nil {
fmt.Fprintf(os.Stderr, "Erreur miniature: %v\n", err)
continue
}
// Envoyer le message
err = wac.SendMessage(param.WhatsappGroup, album.Name, album.Description, fmt.Sprintf("Il y a %d an(s) : %s", time.Now().Year()-album.StartDate.Year(), link), link, thumbnail)
if err != nil {
fmt.Fprintf(os.Stderr, "Erreur envoi WhatsApp: %v\n", err)
}
}
if param.DevelopmentMode == "run-last" {
return nil
}
// Get albums created yesterday
// Récupérer les albums de la veille
if album.CreatedAt.Year() == time.Now().AddDate(0, 0, -1).Year() && (album.CreatedAt.Month() == time.Now().AddDate(0, 0, -1).Month()) && (album.CreatedAt.Day() == time.Now().AddDate(0, 0, -1).Day()) {
// Retrieve the sharing key
sharingKey, err := getSharingKey(album, param)
// Obtenir la clé de partage
sharingKey, err := immichClient.GetSharingKey(album)
if err != nil {
return fmt.Errorf("Error retrieving the sharing key for album '%s': %v", album.Name, err)
}
// Retrieve the thumbnail
thumbnail, err := getThumbnail(album, param)
if err != nil {
return fmt.Errorf("Error retrieving thumbnail for album '%s': %v", album.Name, err)
}
// Send the message
link := param.ImmichURL + "/share/" + sharingKey
err = sendMessage(client, param.WhatsappGroup, album.Name, album.Description, fmt.Sprintf("Nouvel album : %s", link), link, thumbnail)
if err != nil {
fmt.Fprintf(os.Stderr, "Error sending message to WhatsApp for album '%s': %v\n", album.Name, err)
fmt.Fprintf(os.Stderr, "Erreur récupération clé partage: %v\n", err)
continue
}
}
}
link := param.ImmichURL + "/share/" + sharingKey
// Récupérer la miniature
thumbnail, err := immichClient.GetThumbnail(album.AlbumThumbnailAssetId)
if err != nil {
fmt.Fprintf(os.Stderr, "Erreur miniature: %v\n", err)
continue
}
// Envoyer le message
err = wac.SendMessage(param.WhatsappGroup, album.Name, album.Description, fmt.Sprintf("Nouvel album : %s", link), link, thumbnail)
if err != nil {
fmt.Fprintf(os.Stderr, "Erreur envoi WhatsApp: %v\n", err)
}
}
}
}
return nil
}
func parseTime(timeStr string) (int, int, error) {
// Split the string into hours and minutes
parts := strings.Split(timeStr, ":")
if len(parts) != 2 {
return 0, 0, fmt.Errorf("invalid format: %s", timeStr)
// Fonction pour écouter les événements WhatsApp
func listen(wac *whatsapp.WhatsAppClient) error {
if err := wac.Client.Connect(); err != nil {
return err
}
defer wac.Client.Disconnect()
// Convert the parts to integers
hours, err := strconv.Atoi(parts[0])
if err != nil {
return 0, 0, fmt.Errorf("error converting hours: %v", err)
}
wac.Client.AddEventHandler(func(evt interface{}) {
fmt.Println("Réception d'un message:", evt)
})
minutes, err := strconv.Atoi(parts[1])
if err != nil {
return 0, 0, fmt.Errorf("error converting minutes: %v", err)
}
return hours, minutes, nil
}
func main() {
// Handle interrupts to clean properly
c := make(chan os.Signal)
// Attendre une interruption
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
select {
case sig := <-c:
fmt.Printf("Got %s signal. Aborting...\n", sig)
os.Exit(1)
}
}()
// Load the parameters
param := new(Parameters)
param.ImmichURL = os.Getenv("IMMICH-URL")
param.ImmichKey = os.Getenv("IMMICH-KEY")
param.WhatsappSessionFile = os.Getenv("WHATSAPP-SESSION-FILE")
param.WhatsappGroup = os.Getenv("WHATSAPP-GROUP")
param.TimeToRun = os.Getenv("TIME-TO-RUN")
param.HealthchecksURL = os.Getenv("HEALTHCHECKS-URL")
param.DevelopmentMode = os.Getenv("DEVELOPMENT-MODE")
if param.DevelopmentMode == "run-once" || param.DevelopmentMode == "run-last" {
err := runLoop(*param)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
}
} else if param.DevelopmentMode == "listen" {
err := listen(*param)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
}
} else {
// Test the connexion on startup
err := testConnexions(*param)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to connect: %v\n", err)
return
}
// Run the loop everyday
for {
// First, wait for the appropriate time
hours, minutes, err := parseTime(param.TimeToRun)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to read the time provided: %v\n", err)
return
}
t := time.Now()
n := time.Date(t.Year(), t.Month(), t.Day(), hours, minutes, 0, 0, t.Location())
d := n.Sub(t)
if d < 0 {
n = n.Add(24 * time.Hour)
d = n.Sub(t)
}
fmt.Fprintf(os.Stderr, "Sleeping for: %s\n", d)
time.Sleep(d)
// Then try to run it 3 times in case of error
for i := 0; i < 3; i++ {
err = runLoop(*param)
if err == nil {
break
}
// Print the error and attempt number
fmt.Fprintf(os.Stderr, "Attempt %d failed: %v\n", i+1, err)
// Wait before the next attempt
time.Sleep(time.Duration(math.Pow(2, float64(i))) * 30 * time.Second)
}
// Manage the error
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
continue
} else {
if param.HealthchecksURL != "" {
client := http.Client{
Timeout: time.Second * 10,
}
_, err := client.Head(param.HealthchecksURL)
if err != nil {
fmt.Fprintf(os.Stderr, "%s", err)
}
}
}
}
}
<-c
fmt.Println("Arrêt demandé.")
return nil
}

View File

@@ -0,0 +1,169 @@
package immich
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
)
type Album struct {
ID string `json:"id"`
Name string `json:"albumName"`
Description string `json:"description"`
Shared bool `json:"shared"`
HasSharedLink bool `json:"hasSharedLink"`
StartDate time.Time `json:"startDate"`
CreatedAt time.Time `json:"createdAt"`
AlbumThumbnailAssetId string `json:"albumThumbnailAssetId"`
}
type Key struct {
ID string `json:"id"`
Key string `json:"key"`
Album *Album `json:"album"`
}
type ImmichClient struct {
BaseURL string
APIKey string
}
// Fonction pour créer une instance d'ImmichClient
func New(baseURL string, apiKey string) *ImmichClient {
return &ImmichClient{
BaseURL: baseURL,
APIKey: apiKey,
}
}
// Récupérer la liste des albums
func (ic *ImmichClient) FetchAlbums() ([]Album, error) {
client := &http.Client{Timeout: 10 * time.Second}
req, err := http.NewRequest(http.MethodGet, ic.BaseURL+"/api/albums", nil)
if err != nil {
return nil, err
}
req.Header.Set("x-api-key", ic.APIKey)
req.Header.Set("Accept", "application/json")
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != 200 {
return nil, fmt.Errorf("Status code %d", res.StatusCode)
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
var albums []Album
if err := json.Unmarshal(body, &albums); err != nil {
return nil, err
}
return albums, nil
}
// Obtenir la clé de partage pour un album
func (ic *ImmichClient) GetSharingKey(album Album) (string, error) {
client := &http.Client{Timeout: 10 * time.Second}
if album.HasSharedLink {
// Récupérer la clé existante
req, err := http.NewRequest(http.MethodGet, ic.BaseURL+"/api/shared-links", nil)
if err != nil {
return "", err
}
req.Header.Set("x-api-key", ic.APIKey)
req.Header.Set("Accept", "application/json")
res, err := client.Do(req)
if err != nil {
return "", err
}
defer res.Body.Close()
if res.StatusCode != 200 {
return "", fmt.Errorf("Status code %d", res.StatusCode)
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", err
}
var keys []Key
if err := json.Unmarshal(body, &keys); err != nil {
return "", err
}
for _, key := range keys {
if key.Album != nil && key.Album.ID == album.ID {
return key.Key, nil
}
}
return "", fmt.Errorf("No sharing key found for album '%s'", album.Name)
} else {
// Créer un nouveau lien partagé
jsonData := []byte(`{
"albumId": "` + album.ID + `",
"allowDownload": true,
"allowUpload": false,
"showMetadata": true,
"type": "ALBUM"
}`)
req, err := http.NewRequest(http.MethodPost, ic.BaseURL+"/api/shared-links", bytes.NewBuffer(jsonData))
if err != nil {
return "", err
}
req.Header.Set("x-api-key", ic.APIKey)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/octet-stream")
res, err := client.Do(req)
if err != nil {
return "", err
}
defer res.Body.Close()
if res.StatusCode != 201 {
return "", fmt.Errorf("Status code %d", res.StatusCode)
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", err
}
var key Key
if err := json.Unmarshal(body, &key); err != nil {
return "", err
}
return key.Key, nil
}
}
// Récupérer la miniature d'un album
func (ic *ImmichClient) GetThumbnail(assetID string) ([]byte, error) {
client := &http.Client{Timeout: 10 * time.Second}
req, err := http.NewRequest(http.MethodGet, ic.BaseURL+"/api/assets/"+assetID+"/thumbnail?size=preview", nil)
if err != nil {
return nil, err
}
req.Header.Set("x-api-key", ic.APIKey)
req.Header.Set("Accept", "application/octet-stream")
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != 200 {
return nil, fmt.Errorf("Status code %d", res.StatusCode)
}
return ioutil.ReadAll(res.Body)
}

View File

@@ -0,0 +1,84 @@
package whatsapp
import (
"context"
"fmt"
"os"
_ "github.com/mattn/go-sqlite3"
"github.com/mdp/qrterminal/v3"
"go.mau.fi/whatsmeow"
"go.mau.fi/whatsmeow/store/sqlstore"
waLog "go.mau.fi/whatsmeow/util/log"
"go.mau.fi/whatsmeow/types"
waProto "go.mau.fi/whatsmeow/binary/proto"
"google.golang.org/protobuf/proto"
)
type WhatsAppClient struct {
Client *whatsmeow.Client
}
// Fonction pour initialiser la connexion et retourner une instance de WhatsAppClient
func New(sessionFile string) (*WhatsAppClient, error) {
dbLog := waLog.Stdout("Database", "ERROR", true)
container, err := sqlstore.New(context.Background(), "sqlite3", "file:"+sessionFile+"?_foreign_keys=on", dbLog)
if err != nil {
return nil, err
}
deviceStore, err := container.GetFirstDevice(context.Background())
if err != nil {
return nil, err
}
clientLog := waLog.Stdout("Client", "ERROR", true)
client := whatsmeow.NewClient(deviceStore, clientLog)
if client.Store.ID == nil {
// No ID stored, nouvelle connexion
qrChan, _ := client.GetQRChannel(context.Background())
err = client.Connect()
if err != nil {
return nil, err
}
for evt := range qrChan {
if evt.Event == "code" {
qrterminal.GenerateHalfBlock(evt.Code, qrterminal.L, os.Stdout)
fmt.Println("QR code:", evt.Code)
} else {
fmt.Println("Event:", evt.Event)
}
}
} else {
// Connexion existante
err = client.Connect()
if err != nil {
return nil, err
}
}
return &WhatsAppClient{Client: client}, nil
}
// Méthode pour envoyer un message
func (wac *WhatsAppClient) SendMessage(group string, title string, description string, message string, url string, thumbnail []byte) error {
jid, err := types.ParseJID(group)
if err != nil {
return fmt.Errorf("Incorrect group identifier '%s': %v", group, err)
}
msg := &waProto.Message{
ExtendedTextMessage: &waProto.ExtendedTextMessage{
Title: proto.String(title),
Description: proto.String(description),
Text: proto.String(message),
MatchedText: proto.String(url),
JPEGThumbnail: thumbnail,
},
}
ts, err := wac.Client.SendMessage(context.Background(), jid, msg)
if err != nil {
return fmt.Errorf("Error sending message with title '%s': %v", title, err)
}
fmt.Printf("Message with title '%s' sent (timestamp: %s)\n", title, ts)
return nil
}

View File

@@ -7,4 +7,4 @@ echo "go mod tidy"
echo "env CGO_ENABLED=1 env DEVELOPMENT-MODE=run-once go run immich-souvenirs.go"
echo "-----------------------------------------------------------------------"
docker run -it -v $(pwd)/immich-souvenirs.go:/app/immich-souvenirs.go -w /app -v immich-souvenirs_config:/config --env-file test.env --rm golang:1.23-alpine /bin/sh
docker run -it -v $(pwd)/immich-souvenirs.go:/app/immich-souvenirs.go -v $(pwd)/internals:/app/internals -w /app -v immich-souvenirs_config:/config --env-file test.env --rm golang:1.23-alpine /bin/sh