From 04a93e820c55d4380914e2c9e0aa50f79bcb132d Mon Sep 17 00:00:00 2001 From: doggyhaha <75123663+doggyhaha@users.noreply.github.com> Date: Thu, 19 Feb 2026 23:16:03 +0100 Subject: [PATCH 1/3] fix: update libheif and ffmpeg versions in Dockerfiles fix: implement new IGram extractor (should work until IGram decides to change signature again) --- Dockerfile | 6 +- Dockerfile.dev | 4 +- go.mod | 2 +- go.sum | 4 +- internal/extractors/instagram/main.go | 2 +- internal/extractors/instagram/util.go | 116 +++++++++++++++++--------- 6 files changed, 87 insertions(+), 47 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7e8ec2f..ad485db 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN --mount=type=cache,target=/var/cache/apk,sharing=locked \ --repository="https://dl-cdn.alpinelinux.org/alpine/edge/main" \ --repository="https://dl-cdn.alpinelinux.org/alpine/edge/community" \ "build-base=0.5-r3" \ - "libheif-dev=1.20.2-r1" + "libheif-dev=1.21.2-r1" WORKDIR /app @@ -38,8 +38,8 @@ RUN --mount=type=cache,target=/var/cache/apk,sharing=locked \ apk add --no-cache \ --repository="https://dl-cdn.alpinelinux.org/alpine/edge/main" \ --repository="https://dl-cdn.alpinelinux.org/alpine/edge/community" \ - "ffmpeg=8.0.1-r0" \ - "libheif=1.20.2-r1" + "ffmpeg=8.0.1-r1" \ + "libheif=1.21.2-r1" COPY --from=builder /app/govd ./govd diff --git a/Dockerfile.dev b/Dockerfile.dev index ffd93af..47b6f9e 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -6,8 +6,8 @@ RUN --mount=type=cache,target=/var/cache/apk,sharing=locked \ --repository="https://dl-cdn.alpinelinux.org/alpine/edge/main" \ --repository="https://dl-cdn.alpinelinux.org/alpine/edge/community" \ "build-base=0.5-r3" \ - "libheif-dev=1.20.2-r1" \ - "ffmpeg=8.0.1-r0" + "libheif-dev=1.21.2-r1" \ + "ffmpeg=8.0.1-r1" WORKDIR /app diff --git a/go.mod b/go.mod index cb9b630..7125d88 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/nicksnyder/go-i18n/v2 v2.6.0 github.com/pressly/goose/v3 v3.24.3 github.com/prometheus/client_golang v1.23.2 - github.com/strukturag/libheif v1.20.2 + github.com/strukturag/libheif v1.21.2 github.com/sunfish-shogi/bufseekio v0.1.0 github.com/titanous/json5 v1.0.0 github.com/u2takey/ffmpeg-go v0.5.0 diff --git a/go.sum b/go.sum index 4345235..0ce4de4 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/strukturag/libheif v1.20.2 h1:6te1PczCHlF//Uc9E5xb/mXb5+y67vrwssKwLS0ng30= -github.com/strukturag/libheif v1.20.2/go.mod h1:E/PNRlmVtrtj9j2AvBZlrO4dsBDu6KfwDZn7X1Ce8Ks= +github.com/strukturag/libheif v1.21.2 h1:YFD3crf+d33cFVQh3aTkkVGwJFyWpfqVT4XhzHWU6mA= +github.com/strukturag/libheif v1.21.2/go.mod h1:E/PNRlmVtrtj9j2AvBZlrO4dsBDu6KfwDZn7X1Ce8Ks= github.com/sunfish-shogi/bufseekio v0.0.0-20210207115823-a4185644b365/go.mod h1:dEzdXgvImkQ3WLI+0KQpmEx8T/C/ma9KeS3AfmU899I= github.com/sunfish-shogi/bufseekio v0.1.0 h1:zu38kFbv0KuuiwZQeuYeS02U9AM14j0pVA9xkHOCJ2A= github.com/sunfish-shogi/bufseekio v0.1.0/go.mod h1:dEzdXgvImkQ3WLI+0KQpmEx8T/C/ma9KeS3AfmU899I= diff --git a/internal/extractors/instagram/main.go b/internal/extractors/instagram/main.go index ca7254c..86b0ee1 100644 --- a/internal/extractors/instagram/main.go +++ b/internal/extractors/instagram/main.go @@ -226,7 +226,7 @@ func GetPostFromIGram(ctx *models.ExtractorContext) (*IGramResponse, error) { } headers := map[string]string{ - "Content-Type": "application/x-www-form-urlencoded", + "Content-Type": "application/json", } maps.Copy(headers, igramHeaders) diff --git a/internal/extractors/instagram/util.go b/internal/extractors/instagram/util.go index cfac223..7c7276d 100644 --- a/internal/extractors/instagram/util.go +++ b/internal/extractors/instagram/util.go @@ -1,12 +1,12 @@ package instagram import ( + "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/hex" "fmt" "io" - "maps" "math/big" "net/http" "net/url" @@ -29,9 +29,10 @@ const ( graphQLEndpoint = "https://www.instagram.com/graphql/query/" polarisAction = "PolarisPostActionLoadPostQueryQuery" - igramHostname = "api-wh.igram.world" - igramKey = "241c28282e4ce419ce73ca61555a5a0c7faf887c5ccf9305c55484f701ba883a" - igramTimestamp = "1766415734394" + igramHostname = "api-wh.igram.world" + igramAPIBase = "api.igram.world" + igramHMACKey = "75f2d70d3724f98e4a7d1ffd0ba9cfd907f3ae2632ee159980e2c521bff62358" + igramStaticTS = 1771418815381 // parseInt("mls10xp1", 36) ) var ( @@ -157,57 +158,96 @@ func ParseEmbedGQL(body []byte) (*Media, error) { } func IGramBodyFromURL(contentURL string) (io.Reader, error) { - timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10) + return igramBuildPayload(map[string]string{ + "target_url": contentURL, + }) +} - hash := sha256.New() - _, err := io.WriteString(hash, contentURL+timestamp+igramKey) - if err != nil { - return nil, fmt.Errorf("error writing to SHA256 hash: %w", err) - } +func IGramBodyFromParams(params map[string]string) (io.Reader, error) { + return igramBuildPayload(params) +} - secretBytes := hash.Sum(nil) - secretString := hex.EncodeToString(secretBytes) - secretString = strings.ToLower(secretString) +func igramBuildPayload(urlParams map[string]string) (io.Reader, error) { + nowMs := time.Now().UnixMilli() + serverMs := getIGramServerTime() - payload := url.Values{} - payload.Set("sf_url", contentURL) - payload.Set("ts", timestamp) - payload.Set("_ts", igramTimestamp) - payload.Set("_tsc", "0") // ? - payload.Set("_s", secretString) + drift := serverMs - nowMs + var correction int64 + if drift >= 60000 || drift <= -60000 { + correction = drift + } + ts := nowMs + correction - return strings.NewReader(payload.Encode()), nil -} + // partial payload fields that get signed + partial := map[string]any{ + "_sc": 0, + "_ef": 0, + "_df": 0, + } + for k, v := range urlParams { + partial[k] = v + } -func IGramBodyFromParams(params map[string]string) (io.Reader, error) { - timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10) + sig, err := igramSign(partial, ts) + if err != nil { + return nil, err + } - paramsStr, err := sonic.ConfigFastest.Marshal(params) + // assemble final payload + final := make(map[string]any, len(partial)+5) + for k, v := range partial { + final[k] = v + } + final["ts"] = ts + final["_ts"] = igramStaticTS + final["_tsc"] = correction + final["_sv"] = 2 + final["_s"] = sig + + jsonBytes, err := sonic.ConfigFastest.Marshal(final) if err != nil { return nil, fmt.Errorf("failed to marshal payload: %w", err) } - hash := sha256.New() - _, err = io.WriteString(hash, string(paramsStr)+timestamp+igramKey) + return strings.NewReader(string(jsonBytes)), nil +} + +func igramSign(partial map[string]any, ts int64) (string, error) { + // sonic.ConfigStd sorts map keys alphabetically, matching + // the signing: JSON.stringify(sorted_partial) + String(ts) + jsonBytes, err := sonic.ConfigStd.Marshal(partial) if err != nil { - return nil, fmt.Errorf("error writing to SHA256 hash: %w", err) + return "", fmt.Errorf("failed to marshal partial payload: %w", err) } - secretBytes := hash.Sum(nil) - secretString := hex.EncodeToString(secretBytes) - secretString = strings.ToLower(secretString) + data := string(jsonBytes) + strconv.FormatInt(ts, 10) - data := map[string]string{ - "ts": timestamp, - "_ts": igramTimestamp, - "_tsc": "0", // ? - "_s": secretString, + keyBytes, err := hex.DecodeString(igramHMACKey) + if err != nil { + return "", fmt.Errorf("failed to decode HMAC key: %w", err) } - maps.Copy(data, params) - parsedData, _ := sonic.ConfigFastest.Marshal(data) + mac := hmac.New(sha256.New, keyBytes) + mac.Write([]byte(data)) + return hex.EncodeToString(mac.Sum(nil)), nil +} + +func getIGramServerTime() int64 { + apiURL := fmt.Sprintf("https://%s/msec", igramAPIBase) + resp, err := http.Get(apiURL) + if err != nil { + return time.Now().UnixMilli() + } + defer resp.Body.Close() - return strings.NewReader(string(parsedData)), nil + var result struct { + Msec float64 `json:"msec"` + } + decoder := sonic.ConfigFastest.NewDecoder(resp.Body) + if err := decoder.Decode(&result); err != nil { + return time.Now().UnixMilli() + } + return int64(result.Msec * 1000) } func ParseIGramResponse(body []byte) (*IGramResponse, error) { From cb79d831aadd76844cac539b7bf20f48036a83ba Mon Sep 17 00:00:00 2001 From: doggyhaha <75123663+doggyhaha@users.noreply.github.com> Date: Thu, 19 Feb 2026 23:46:12 +0100 Subject: [PATCH 2/3] bump go version and toolchain to avoid CI errors --- go.mod | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 7125d88..5b42411 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/govdbot/govd -go 1.24.0 +go 1.26 -toolchain go1.24.2 +toolchain go1.26.0 require ( github.com/BurntSushi/toml v1.5.0 From e93c7bb23cc2a2c91bc98568c263d79c688b01a6 Mon Sep 17 00:00:00 2001 From: doggyhaha <75123663+doggyhaha@users.noreply.github.com> Date: Fri, 20 Feb 2026 00:02:45 +0100 Subject: [PATCH 3/3] bump go image version and change golangci-lint mode to goinstall since the prebuilt version doesn't support go1.26 --- .github/workflows/golangci-lint.yaml | 1 + Dockerfile | 2 +- Dockerfile.dev | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/golangci-lint.yaml b/.github/workflows/golangci-lint.yaml index d7e4383..db429e5 100644 --- a/.github/workflows/golangci-lint.yaml +++ b/.github/workflows/golangci-lint.yaml @@ -22,6 +22,7 @@ jobs: with: version: v2.1 args: --build-tags=lint + install-mode: goinstall # apparently needed since prebuilt doesn't support go1.26 yet # skip cache to avoid flakes (and avoid using gh-action storage) skip-cache: true skip-save-cache: true \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ad485db..e8decc5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.25-alpine AS builder +FROM golang:1.26-alpine AS builder ENV GOCACHE=/root/.cache/go-build diff --git a/Dockerfile.dev b/Dockerfile.dev index 47b6f9e..bc1ee93 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,4 +1,4 @@ -FROM golang:1.25-alpine +FROM golang:1.26-alpine RUN --mount=type=cache,target=/var/cache/apk,sharing=locked \ --mount=type=cache,target=/var/lib/apk,sharing=locked \