Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,16 @@ Response:

You can create a user with optional proxy and S3 storage configuration. All fields are optional and backward compatible. If you do not provide these fields, the user will be created with default settings.

The optional `skipMedia` (boolean) field controls the per-instance media download setting. When `true`, the instance does not automatically download media from incoming messages. It defaults to `false` (media is downloaded) and can be changed later via [`/session/skipmedia`](#user-content-set-media-download-setting).

### Example Payload

```json
{
"name": "test_user",
"token": "user_token",
"history": 0,
"skipMedia": false,
"proxyConfig": {
"enabled": true,
"proxyURL": "socks5://user:pass@host:port"
Expand Down Expand Up @@ -485,6 +489,65 @@ Response:

---

## Get Media Download Setting

Retrieves the per-instance `skip_media` setting. When `skip_media` is `true`, the server does not automatically download media (images, audio, documents, etc.) from incoming messages for this instance. It defaults to `false` (media is downloaded), preserving the existing behavior.

This per-instance setting is independent from the global `-skipmedia` flag. Media is only downloaded when both the global flag is unset **and** the instance `skip_media` is `false`.

Endpoint: _/session/skipmedia_

Method: **GET**

```
curl -s -X GET -H 'Token: 1234ABCD' http://localhost:8080/session/skipmedia
```

Response:

```json
{
"code": 200,
"data": {
"skip_media": false
},
"success": true
}
```

---

## Set Media Download Setting

Updates the per-instance `skip_media` setting. Set `skip_media` to `true` to disable automatic media download for incoming messages on this instance, or `false` (the default) to enable it.

Endpoint: _/session/skipmedia_

Method: **POST**

Parameters:

* `skip_media` (boolean, required): `true` to skip downloading media, `false` to download media automatically (default).

```
curl -s -X POST -H 'Token: 1234ABCD' -H 'Content-Type: application/json' --data '{"skip_media":true}' http://localhost:8080/session/skipmedia
```

Response:

```json
{
"code": 200,
"data": {
"Details": "Skip media configured successfully",
"skip_media": true
},
"success": true
}
```

---

## User

The following _user_ endpoints are used to gather information about Whatsapp users.
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,17 @@ you can use to alter behaviour
* -logtype : format for logs, either console (default) or json
* -color : enable colored output for console logs
* -osname : Connection OS Name in Whatsapp
* -skipmedia : Skip downloading media from messages
* -skipmedia : Skip downloading media from messages (global, applies to all instances)
* -wadebug : enable whatsmeow debug, either INFO or DEBUG levels are suported

* -sslcertificate : SSL Certificate File
* -sslprivatekey : SSL Private Key File

> Media download can also be controlled **per instance** via the `/session/skipmedia` endpoint
> (see [API.md](./API.md)). This setting defaults to `skip_media = false`, preserving the existing
> behavior (media is downloaded). Media is downloaded only when the global `-skipmedia` flag is
> unset **and** the instance `skip_media` is `false`.

Example:

To have colored logs:
Expand Down
91 changes: 86 additions & 5 deletions handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,16 +161,16 @@ func (s *server) authalice(next http.Handler) http.Handler {
if !found {
log.Info().Msg("Looking for user information in DB")
// Checks DB from matching user and store user values in context
rows, err := s.db.Query("SELECT id,name,webhook,jid,events,proxy_url,qrcode,history,hmac_key IS NOT NULL AND length(hmac_key) > 0,CASE WHEN s3_enabled THEN 'true' ELSE 'false' END,COALESCE(media_delivery, 'base64') FROM users WHERE token=$1 LIMIT 1", token)
rows, err := s.db.Query("SELECT id,name,webhook,jid,events,proxy_url,qrcode,history,hmac_key IS NOT NULL AND length(hmac_key) > 0,CASE WHEN s3_enabled THEN 'true' ELSE 'false' END,COALESCE(media_delivery, 'base64'),CASE WHEN COALESCE(skip_media, false) THEN 'true' ELSE 'false' END FROM users WHERE token=$1 LIMIT 1", token)
if err != nil {
s.Respond(w, r, http.StatusInternalServerError, err)
return
}
defer rows.Close()
var history sql.NullInt64
var s3Enabled, mediaDelivery string
var s3Enabled, mediaDelivery, skipMedia string
for rows.Next() {
err = rows.Scan(&txtid, &name, &webhook, &jid, &events, &proxy_url, &qrcode, &history, &hasHmac, &s3Enabled, &mediaDelivery)
err = rows.Scan(&txtid, &name, &webhook, &jid, &events, &proxy_url, &qrcode, &history, &hasHmac, &s3Enabled, &mediaDelivery, &skipMedia)
if err != nil {
s.Respond(w, r, http.StatusInternalServerError, err)
return
Expand All @@ -196,6 +196,7 @@ func (s *server) authalice(next http.Handler) http.Handler {
"HasHmac": strconv.FormatBool(hasHmac),
"S3Enabled": s3Enabled,
"MediaDelivery": mediaDelivery,
"SkipMedia": skipMedia,
}}

userinfocache.Set(token, v, cache.NoExpiration)
Expand Down Expand Up @@ -827,6 +828,7 @@ func (s *server) GetStatus() http.HandlerFunc {
"proxy_url": userInfo.Get("Proxy"),
"qrcode": userInfo.Get("Qrcode"),
"history": userInfo.Get("History"),
"skip_media": userInfo.Get("SkipMedia") == "true",
"proxy_config": proxyConfig,
"s3_config": s3Config,
"hmac_configured": hmacConfigured,
Expand Down Expand Up @@ -5374,6 +5376,7 @@ func (s *server) AddUser() http.HandlerFunc {
S3Config *S3Config `json:"s3Config,omitempty"`
HmacKey string `json:"hmacKey,omitempty"`
History int `json:"history,omitempty"`
SkipMedia bool `json:"skipMedia,omitempty"`
}

if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
Expand Down Expand Up @@ -5480,9 +5483,9 @@ func (s *server) AddUser() http.HandlerFunc {

// Insert user with all proxy, S3 and HMAC fields
if _, err = s.db.Exec(
"INSERT INTO users (id, name, token, webhook, expiration, events, jid, qrcode, proxy_url, s3_enabled, s3_endpoint, s3_region, s3_bucket, s3_access_key, s3_secret_key, s3_path_style, s3_public_url, media_delivery, s3_retention_days, hmac_key, history) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)",
"INSERT INTO users (id, name, token, webhook, expiration, events, jid, qrcode, proxy_url, s3_enabled, s3_endpoint, s3_region, s3_bucket, s3_access_key, s3_secret_key, s3_path_style, s3_public_url, media_delivery, s3_retention_days, hmac_key, history, skip_media) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)",
id, user.Name, user.Token, user.Webhook, user.Expiration, user.Events, "", "", user.ProxyConfig.ProxyURL,
user.S3Config.Enabled, user.S3Config.Endpoint, user.S3Config.Region, user.S3Config.Bucket, user.S3Config.AccessKey, user.S3Config.SecretKey, user.S3Config.PathStyle, user.S3Config.PublicURL, user.S3Config.MediaDelivery, user.S3Config.RetentionDays, encryptedHmacKey, user.History,
user.S3Config.Enabled, user.S3Config.Endpoint, user.S3Config.Region, user.S3Config.Bucket, user.S3Config.AccessKey, user.S3Config.SecretKey, user.S3Config.PathStyle, user.S3Config.PublicURL, user.S3Config.MediaDelivery, user.S3Config.RetentionDays, encryptedHmacKey, user.History, user.SkipMedia,
); err != nil {
log.Error().Str("error", fmt.Sprintf("%v", err)).Msg("admin DB error")
s.respondWithJSON(w, http.StatusInternalServerError, map[string]interface{}{
Expand Down Expand Up @@ -5536,6 +5539,7 @@ func (s *server) AddUser() http.HandlerFunc {
"proxy_config": proxyConfig,
"s3_config": s3Config,
"hmac_key": user.HmacKey != "",
"skip_media": user.SkipMedia,
}
s.respondWithJSON(w, http.StatusCreated, map[string]interface{}{
"code": http.StatusCreated,
Expand Down Expand Up @@ -6053,6 +6057,83 @@ func (s *server) SetHistory() http.HandlerFunc {
}
}

// GetSkipMedia returns the per-instance skip_media setting. When enabled the
// server does not automatically download media from incoming messages for this
// instance. It defaults to false (media is downloaded).
func (s *server) GetSkipMedia() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
txtid := r.Context().Value("userinfo").(Values).Get("Id")

var skipMedia bool
err := s.db.QueryRow("SELECT COALESCE(skip_media, false) FROM users WHERE id = $1", txtid).Scan(&skipMedia)
if err != nil {
s.Respond(w, r, http.StatusInternalServerError, errors.New("failed to read skip_media configuration"))
return
}

response := map[string]interface{}{
"skip_media": skipMedia,
}
responseJson, err := json.Marshal(response)
if err != nil {
s.Respond(w, r, http.StatusInternalServerError, err)
} else {
s.Respond(w, r, http.StatusOK, string(responseJson))
}
}
}

// SetSkipMedia updates the per-instance skip_media setting. When true, media is
// not automatically downloaded for incoming messages on this instance; set it
// to false (the default) to enable automatic media download.
func (s *server) SetSkipMedia() http.HandlerFunc {
type skipMediaStruct struct {
SkipMedia *bool `json:"skip_media"`
}

return func(w http.ResponseWriter, r *http.Request) {
txtid := r.Context().Value("userinfo").(Values).Get("Id")

decoder := json.NewDecoder(r.Body)
var t skipMediaStruct
err := decoder.Decode(&t)
if err != nil {
s.Respond(w, r, http.StatusBadRequest, errors.New("could not decode payload"))
return
}

if t.SkipMedia == nil {
s.Respond(w, r, http.StatusBadRequest, errors.New("skip_media is required"))
return
}

// Store skip_media configuration in database
_, err = s.db.Exec("UPDATE users SET skip_media = $1 WHERE id = $2", *t.SkipMedia, txtid)
if err != nil {
s.Respond(w, r, http.StatusInternalServerError, errors.New("failed to save skip_media configuration"))
return
}

token := r.Context().Value("userinfo").(Values).Get("Token")
if cachedUserInfo, found := userinfocache.Get(token); found {
updatedUserInfo := updateUserInfo(cachedUserInfo, "SkipMedia", strconv.FormatBool(*t.SkipMedia)).(Values)
userinfocache.Set(token, updatedUserInfo, cache.NoExpiration)
log.Info().Str("userID", txtid).Bool("skip_media", *t.SkipMedia).Msg("User info cache updated with SkipMedia configuration")
}

response := map[string]interface{}{
"Details": "Skip media configured successfully",
"skip_media": *t.SkipMedia,
}
responseJson, err := json.Marshal(response)
if err != nil {
s.Respond(w, r, http.StatusInternalServerError, err)
} else {
s.Respond(w, r, http.StatusOK, string(responseJson))
}
}
}

// Set proxy
func (s *server) SetProxy() http.HandlerFunc {
type proxyStruct struct {
Expand Down
27 changes: 27 additions & 0 deletions migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ var migrations = []Migration{
Name: "add_whatsmeow_message_secrets_message_id_idx",
UpSQL: addWhatsmeowMessageSecretsMessageIDIndexSQL,
},
{
ID: 10,
Name: "add_skip_media",
UpSQL: addSkipMediaSQL,
},
}

const changeIDToStringSQL = `
Expand Down Expand Up @@ -219,6 +224,21 @@ END $$;
-- SQLite version (handled in code)
`

const addSkipMediaSQL = `
-- PostgreSQL version
DO $$
BEGIN
-- Per-instance toggle to skip automatic media download on incoming messages.
-- Defaults to FALSE to preserve existing behavior (media is downloaded
-- unless explicitly disabled for the instance).
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'skip_media') THEN
ALTER TABLE users ADD COLUMN skip_media BOOLEAN DEFAULT FALSE;
END IF;
END $$;

-- SQLite version (handled in code)
`

const addWhatsmeowMessageSecretsMessageIDIndexSQL = `
-- PostgreSQL version
DO $$
Expand Down Expand Up @@ -456,6 +476,13 @@ func applyMigration(db *sqlx.DB, migration Migration) error {
} else {
_, err = tx.Exec(migration.UpSQL)
}
} else if migration.ID == 10 {
if db.DriverName() == "sqlite" {
// Per-instance skip_media toggle, defaults to disabled (0 = download).
err = addColumnIfNotExistsSQLite(tx, "users", "skip_media", "BOOLEAN DEFAULT 0")
} else {
_, err = tx.Exec(migration.UpSQL)
}
} else {
_, err = tx.Exec(migration.UpSQL)
}
Expand Down
3 changes: 3 additions & 0 deletions routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ func (s *server) routes() {
s.router.Handle("/session/proxy", c.Then(s.SetProxy())).Methods("POST")
s.router.Handle("/session/history", c.Then(s.SetHistory())).Methods("POST")

s.router.Handle("/session/skipmedia", c.Then(s.GetSkipMedia())).Methods("GET")
s.router.Handle("/session/skipmedia", c.Then(s.SetSkipMedia())).Methods("POST")

s.router.Handle("/session/s3/config", c.Then(s.ConfigureS3())).Methods("POST")
s.router.Handle("/session/s3/config", c.Then(s.GetS3Config())).Methods("GET")
s.router.Handle("/session/s3/config", c.Then(s.DeleteS3Config())).Methods("DELETE")
Expand Down
83 changes: 83 additions & 0 deletions static/api/spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,89 @@ paths:
description: Bad request or session not connected
'500':
description: Internal server error
/session/skipmedia:
get:
tags:
- Session
summary: Get media download setting
description: >-
Returns the per-instance `skip_media` setting. When `true` the server
does not automatically download media from incoming messages for this
instance. Defaults to `false` (media is downloaded).
security:
- ApiKeyAuth: []
responses:
'200':
description: Current skip_media setting
content:
application/json:
schema:
type: object
properties:
code:
type: integer
example: 200
success:
type: boolean
example: true
data:
type: object
properties:
skip_media:
type: boolean
example: false
'500':
description: Internal server error
post:
tags:
- Session
summary: Set media download setting
description: >-
Updates the per-instance `skip_media` setting. Set `skip_media` to
`true` to disable automatic media download for incoming messages on this
instance, or `false` (the default) to enable it.
security:
- ApiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- skip_media
properties:
skip_media:
type: boolean
description: true to skip downloading media, false to download automatically (default)
example: true
responses:
'200':
description: skip_media setting updated successfully
content:
application/json:
schema:
type: object
properties:
code:
type: integer
example: 200
success:
type: boolean
example: true
data:
type: object
properties:
Details:
type: string
example: 'Skip media configured successfully'
skip_media:
type: boolean
example: true
'400':
description: Bad request (missing or invalid skip_media)
'500':
description: Internal server error
/session/hmac/config:
post:
tags:
Expand Down
Loading
Loading