From a7795dc2d01aff2aa65af8f3f208faa817b787a5 Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Thu, 22 Feb 2024 09:41:53 +0100 Subject: [PATCH 01/36] Update main.go --- main.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/main.go b/main.go index e38c8cf3..c378c9b1 100644 --- a/main.go +++ b/main.go @@ -335,7 +335,7 @@ func decode(ctx context.Context, method int, protoData *ProtoData) { return fmt.Sprintf("#%d", method) } - if method != int(pogo.InternalPlatformClientAction_INTERNAL_PROXY_SOCIAL_ACTION) && protoData.Level < 30 { + if method != int(pogo.InternalPlatformClientAction_INTERNAL_PROXY_SOCIAL_ACTION) && protoData.Level < 20 { statsCollector.IncDecodeMethods("error", "low_level", getMethodName(method, true)) log.Debugf("Insufficient Level %d Did not process hook type %s", protoData.Level, pogo.Method(method)) return @@ -366,13 +366,19 @@ func decode(ctx context.Context, method int, protoData *ProtoData) { result = decodeGetGymInfo(ctx, protoData.Data) processed = true case pogo.Method_METHOD_ENCOUNTER: - if getScanParameters(protoData).ProcessPokemon { - result = decodeEncounter(ctx, protoData.Data, protoData.Account) + if protoData.Level >= 30 { + if getScanParameters(protoData).ProcessPokemon { + result = decodeEncounter(ctx, protoData.Data, protoData.Account) + } + processed = true } - processed = true + break case pogo.Method_METHOD_DISK_ENCOUNTER: - result = decodeDiskEncounter(ctx, protoData.Data, protoData.Account) - processed = true + if protoData.Level >= 30 { + result = decodeDiskEncounter(ctx, protoData.Data, protoData.Account) + processed = true + } + break case pogo.Method_METHOD_FORT_SEARCH: result = decodeQuest(ctx, protoData.Data, protoData.HaveAr) processed = true From f3c31eb0069fd6d7b1a1dd1700625448f307a3dd Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Thu, 22 Feb 2024 09:43:12 +0100 Subject: [PATCH 02/36] Update main.go --- main.go | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/main.go b/main.go index c378c9b1..e38c8cf3 100644 --- a/main.go +++ b/main.go @@ -335,7 +335,7 @@ func decode(ctx context.Context, method int, protoData *ProtoData) { return fmt.Sprintf("#%d", method) } - if method != int(pogo.InternalPlatformClientAction_INTERNAL_PROXY_SOCIAL_ACTION) && protoData.Level < 20 { + if method != int(pogo.InternalPlatformClientAction_INTERNAL_PROXY_SOCIAL_ACTION) && protoData.Level < 30 { statsCollector.IncDecodeMethods("error", "low_level", getMethodName(method, true)) log.Debugf("Insufficient Level %d Did not process hook type %s", protoData.Level, pogo.Method(method)) return @@ -366,19 +366,13 @@ func decode(ctx context.Context, method int, protoData *ProtoData) { result = decodeGetGymInfo(ctx, protoData.Data) processed = true case pogo.Method_METHOD_ENCOUNTER: - if protoData.Level >= 30 { - if getScanParameters(protoData).ProcessPokemon { - result = decodeEncounter(ctx, protoData.Data, protoData.Account) - } - processed = true + if getScanParameters(protoData).ProcessPokemon { + result = decodeEncounter(ctx, protoData.Data, protoData.Account) } - break + processed = true case pogo.Method_METHOD_DISK_ENCOUNTER: - if protoData.Level >= 30 { - result = decodeDiskEncounter(ctx, protoData.Data, protoData.Account) - processed = true - } - break + result = decodeDiskEncounter(ctx, protoData.Data, protoData.Account) + processed = true case pogo.Method_METHOD_FORT_SEARCH: result = decodeQuest(ctx, protoData.Data, protoData.HaveAr) processed = true From a3f98e308e3d2e2f188ac53359f22bffe5326e4a Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Tue, 25 Feb 2025 10:09:53 +0100 Subject: [PATCH 03/36] Update publish.yml --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a78a71f3..2e025749 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,7 +4,7 @@ on: [push] env: REGISTRY: ghcr.io - GHCR_REPO: ghcr.io/unownhash/golbat + IMAGE_NAME: ${{ github.repository }} GO_VERSION: '1.24.x' jobs: From 1d39cec82591382b611c30d462badd0cdc222f3c Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Tue, 25 Feb 2025 10:18:33 +0100 Subject: [PATCH 04/36] Update publish.yml --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2e025749..b1ef409c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,7 +4,7 @@ on: [push] env: REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} + GHCR_REPO: ${{ github.repository }} GO_VERSION: '1.24.x' jobs: From cc39da36aefe1af77d8f17b87bf38e58bd1e977f Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Tue, 25 Feb 2025 10:27:40 +0100 Subject: [PATCH 05/36] Update publish.yml --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b1ef409c..249fe885 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,7 +4,7 @@ on: [push] env: REGISTRY: ghcr.io - GHCR_REPO: ${{ github.repository }} + GHCR_REPO: downcase ${{ github.repository }} GO_VERSION: '1.24.x' jobs: From 92da3e5c01a0227765af18f5faa0f9c45b03a1c7 Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Tue, 25 Feb 2025 10:37:49 +0100 Subject: [PATCH 06/36] Update publish.yml --- .github/workflows/publish.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 249fe885..4cfd30a1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,7 +4,9 @@ on: [push] env: REGISTRY: ghcr.io - GHCR_REPO: downcase ${{ github.repository }} + GHCR_REPO: downcase GHCR_REPO + run: | + echo "GHCR_REPO=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} GO_VERSION: '1.24.x' jobs: From f441404b394290aa925a50c43600f3b37d07ea20 Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Tue, 25 Feb 2025 10:48:40 +0100 Subject: [PATCH 07/36] Update publish.yml --- .github/workflows/publish.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4cfd30a1..8baaa351 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,9 +4,7 @@ on: [push] env: REGISTRY: ghcr.io - GHCR_REPO: downcase GHCR_REPO - run: | - echo "GHCR_REPO=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} + GHCR_REPO: ghcr.io/reuschelcgn/golbat GO_VERSION: '1.24.x' jobs: From 5352c2c86dafb542e11a81d226cae5a278bd9d70 Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Tue, 25 Feb 2025 11:18:20 +0100 Subject: [PATCH 08/36] Update publish.yml --- .github/workflows/publish.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8baaa351..9847e14f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,7 +4,6 @@ on: [push] env: REGISTRY: ghcr.io - GHCR_REPO: ghcr.io/reuschelcgn/golbat GO_VERSION: '1.24.x' jobs: @@ -40,6 +39,9 @@ jobs: run: | platform=${{ matrix.arch }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + - name: downcase GHCR_REPO + run: | + echo "GHCR_REPO=ghcr.io/${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 @@ -94,6 +96,9 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: downcase GHCR_REPO + run: | + echo "GHCR_REPO=ghcr.io/${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} - name: Docker meta id: meta uses: docker/metadata-action@v5 From 26e759d7c19f028cf2708eb2b721aa8c6c216b0b Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Tue, 25 Feb 2025 11:24:18 +0100 Subject: [PATCH 09/36] Update publish.yml --- .github/workflows/publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9847e14f..742ea0b5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -41,7 +41,7 @@ jobs: echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: downcase GHCR_REPO run: | - echo "GHCR_REPO=ghcr.io/${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} + echo "GHCR_REPO=${{ env.REGISTRY }}/${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 @@ -98,7 +98,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: downcase GHCR_REPO run: | - echo "GHCR_REPO=ghcr.io/${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} + echo "GHCR_REPO=${{ env.REGISTRY }}/${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} - name: Docker meta id: meta uses: docker/metadata-action@v5 From 9b8702e8cba96a93bde136b9c4de9bafd2aab48e Mon Sep 17 00:00:00 2001 From: Magik <30931287+unseenmagik@users.noreply.github.com> Date: Wed, 29 Jan 2025 21:13:12 +0000 Subject: [PATCH 10/36] Initial station webhook --- config.toml.example | 2 +- decoder/station.go | 66 ++++++++++++++++++++++++++++++++++++++++++--- webhooks/webhook.go | 3 +++ 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/config.toml.example b/config.toml.example index 5384be59..fc58c595 100644 --- a/config.toml.example +++ b/config.toml.example @@ -45,7 +45,7 @@ include_hundos_under_cap = false [[webhooks]] url = "http://localhost:4201" # types if specified can be... -# types = ["pokemon", "pokemon_iv", "pokemon_no_iv", "gym", "invasion", "quest", "pokestop", "raid", "weather", "fort_update"] +# types = ["pokemon", "pokemon_iv", "pokemon_no_iv", "gym", "invasion", "quest", "pokestop", "raid", "weather", "fort_update", "station"] # "pokemon" includes both with ivs and without. "pokemon_iv" will only be encountered pokemon. "pokemon_no_iv" may be nearby pokemon that have not been encountered (yet). #[[webhooks]] diff --git a/decoder/station.go b/decoder/station.go index 03534a3f..ae5a0c15 100644 --- a/decoder/station.go +++ b/decoder/station.go @@ -46,6 +46,19 @@ type Station struct { StationedPokemon null.String `db:"stationed_pokemon"` } +type webhookLineup struct { + PokemonId null.Int `json:"pokemon_id"` + Form null.Int `json:"form"` + Costume null.Int `json:"costume"` + Gender null.Int `json:"gender"` + Alignment null.Int `json:"alignment"` + Mode null.Int `json:"mode"` + Move1 null.Int `json:"move1"` + Move2 null.Int `json:"move2"` + TotalStationedPokemon null.Int `json:"totalstationedpokemon"` + StationedPokemon null.String `json:"staitionedpokemon"` +} + func getStationRecord(ctx context.Context, db db.DbDetails, stationId string) (*Station, error) { inMemoryStation := stationCache.Get(stationId) if inMemoryStation != nil { @@ -135,7 +148,7 @@ func saveStationRecord(ctx context.Context, db db.DbDetails, station *Station) { } stationCache.Set(station.Id, *station, ttlcache.DefaultTTL) - createStationWebhooks(oldStation, station) + createStationWebhooks(ctx, db, oldStation, station) } @@ -287,6 +300,53 @@ func UpdateStationWithStationDetails(ctx context.Context, db db.DbDetails, reque return fmt.Sprintf("StationedPokemonDetails %s", stationId) } -func createStationWebhooks(oldStation *Station, station *Station) { - //TODO we need to define webhooks, are they needed for stations, or only for battles? +func createStationWebhooks(ctx context.Context, db db.DbDetails, oldStation *Station, station *Station) { + if oldStation == nil || (oldStation.EndTime != station.EndTime || oldStation.StationedPokemon != station.StationedPokemon || oldStation.BattlePokemonId != station.BattlePokemonId) { + station, _ := getStationRecord(ctx, db, station.Id) + if station == nil { + station = &Station{} + } + + stationHook := map[string]interface{}{ + "id": station.Id, + "latitude": station.Lat, + "longitude": station.Lon, + "name": func() string { + if station.Name.Valid { + return station.Name.String + } else { + return "Unknown" + } + }(), + //"url": station.Url.ValueOrZero(), + "start_time": station.StartTime, + "end_time": station.EndTime, + "is_battle_available": station.IsBattleAvailable, + "battle_level": station.BattleLevel, + "battle_start": station.BattleStart, + "battle_end": station.BattleEnd, + "updated": station.Updated, + "lineup": nil, + } + + if station.BattlePokemonId.Valid { + stationHook["lineup"] = []webhookLineup{ + { + PokemonId: station.Id, + Form: station.BattlePokemonForm, + Costume: station.BattlePokemonCostume, + Gender: station.BattlePokemonGender, + Alignment: station.BattlePokemonAlignment, + Mode: station.BattlePokemonBreadMode, + Move1: station.BattlePokemonMove1, + Move2: station.BattlePokemonMove2, + TotalStationedPokemon: station.TotalStationedPokemon, + StationedPokemon: station.StationedPokemon, + }, + } + } + areas := MatchStatsGeofence(station.Lat, station.Lon) + webhooksSender.AddMessage(webhooks.Station, stationHook, areas) + statsCollector.UpdateStationCount(areas) + } } diff --git a/webhooks/webhook.go b/webhooks/webhook.go index d26609de..b38384d0 100644 --- a/webhooks/webhook.go +++ b/webhooks/webhook.go @@ -26,6 +26,7 @@ const ( FortUpdate PokemonIV PokemonNoIV + Station // this magically becomes the number of types we have webhookTypesLength ) @@ -42,6 +43,7 @@ func init() { webhookTypeToPayloadType[FortUpdate] = "fort_update" webhookTypeToPayloadType[PokemonIV] = "pokemon" webhookTypeToPayloadType[PokemonNoIV] = "pokemon" + webhookTypeToPayloadType[Station] = "station" // if we add more types, make sure one has added everything here for _, str := range webhookTypeToPayloadType { @@ -62,6 +64,7 @@ var webhookConfigStringToType = map[string][]WebhookType{ "pokemon_iv": []WebhookType{PokemonIV}, "pokemon_no_iv": []WebhookType{PokemonNoIV}, "pokemon": []WebhookType{PokemonIV, PokemonNoIV}, + "station": []WebhookType{Station}, } type webhook struct { From 0404773078c7a50d8f2406639500712eb7345070 Mon Sep 17 00:00:00 2001 From: Fabio1988 Date: Sat, 1 Feb 2025 17:36:08 +0100 Subject: [PATCH 11/36] fix: webhook sending --- config.toml.example | 2 +- decoder/station.go | 94 +++++++++++------------------- stats_collector/noop.go | 1 + stats_collector/prometheus.go | 21 ++++++- stats_collector/stats_collector.go | 6 +- webhooks/webhook.go | 6 +- 6 files changed, 63 insertions(+), 67 deletions(-) diff --git a/config.toml.example b/config.toml.example index fc58c595..e3ccc7d5 100644 --- a/config.toml.example +++ b/config.toml.example @@ -45,7 +45,7 @@ include_hundos_under_cap = false [[webhooks]] url = "http://localhost:4201" # types if specified can be... -# types = ["pokemon", "pokemon_iv", "pokemon_no_iv", "gym", "invasion", "quest", "pokestop", "raid", "weather", "fort_update", "station"] +# types = ["pokemon", "pokemon_iv", "pokemon_no_iv", "gym", "invasion", "quest", "pokestop", "raid", "weather", "fort_update", "max_battle"] # "pokemon" includes both with ivs and without. "pokemon_iv" will only be encountered pokemon. "pokemon_no_iv" may be nearby pokemon that have not been encountered (yet). #[[webhooks]] diff --git a/decoder/station.go b/decoder/station.go index ae5a0c15..b5283aee 100644 --- a/decoder/station.go +++ b/decoder/station.go @@ -9,6 +9,7 @@ import ( "golbat/db" "golbat/pogo" "golbat/util" + "golbat/webhooks" "time" "github.com/jellydator/ttlcache/v3" @@ -46,19 +47,6 @@ type Station struct { StationedPokemon null.String `db:"stationed_pokemon"` } -type webhookLineup struct { - PokemonId null.Int `json:"pokemon_id"` - Form null.Int `json:"form"` - Costume null.Int `json:"costume"` - Gender null.Int `json:"gender"` - Alignment null.Int `json:"alignment"` - Mode null.Int `json:"mode"` - Move1 null.Int `json:"move1"` - Move2 null.Int `json:"move2"` - TotalStationedPokemon null.Int `json:"totalstationedpokemon"` - StationedPokemon null.String `json:"staitionedpokemon"` -} - func getStationRecord(ctx context.Context, db db.DbDetails, stationId string) (*Station, error) { inMemoryStation := stationCache.Get(stationId) if inMemoryStation != nil { @@ -148,7 +136,7 @@ func saveStationRecord(ctx context.Context, db db.DbDetails, station *Station) { } stationCache.Set(station.Id, *station, ttlcache.DefaultTTL) - createStationWebhooks(ctx, db, oldStation, station) + createStationWebhooks(oldStation, station) } @@ -300,53 +288,39 @@ func UpdateStationWithStationDetails(ctx context.Context, db db.DbDetails, reque return fmt.Sprintf("StationedPokemonDetails %s", stationId) } -func createStationWebhooks(ctx context.Context, db db.DbDetails, oldStation *Station, station *Station) { - if oldStation == nil || (oldStation.EndTime != station.EndTime || oldStation.StationedPokemon != station.StationedPokemon || oldStation.BattlePokemonId != station.BattlePokemonId) { - station, _ := getStationRecord(ctx, db, station.Id) - if station == nil { - station = &Station{} - } - - stationHook := map[string]interface{}{ - "id": station.Id, - "latitude": station.Lat, - "longitude": station.Lon, - "name": func() string { - if station.Name.Valid { - return station.Name.String - } else { - return "Unknown" - } - }(), - //"url": station.Url.ValueOrZero(), - "start_time": station.StartTime, - "end_time": station.EndTime, - "is_battle_available": station.IsBattleAvailable, - "battle_level": station.BattleLevel, - "battle_start": station.BattleStart, - "battle_end": station.BattleEnd, - "updated": station.Updated, - "lineup": nil, - } - - if station.BattlePokemonId.Valid { - stationHook["lineup"] = []webhookLineup{ - { - PokemonId: station.Id, - Form: station.BattlePokemonForm, - Costume: station.BattlePokemonCostume, - Gender: station.BattlePokemonGender, - Alignment: station.BattlePokemonAlignment, - Mode: station.BattlePokemonBreadMode, - Move1: station.BattlePokemonMove1, - Move2: station.BattlePokemonMove2, - TotalStationedPokemon: station.TotalStationedPokemon, - StationedPokemon: station.StationedPokemon, - }, - } +func createStationWebhooks(oldStation *Station, station *Station) { + if oldStation == nil || station.BattlePokemonId.Valid && (oldStation.EndTime != station.EndTime || + oldStation.BattleEnd != station.BattleEnd || + oldStation.BattlePokemonId != station.BattlePokemonId || + oldStation.BattlePokemonForm != station.BattlePokemonForm || + oldStation.BattlePokemonCostume != station.BattlePokemonCostume || + oldStation.BattlePokemonGender != station.BattlePokemonGender || + oldStation.BattlePokemonBreadMode != station.BattlePokemonBreadMode) { + stationHook := map[string]any{ + "id": station.Id, + "latitude": station.Lat, + "longitude": station.Lon, + "name": station.Name, + "start_time": station.StartTime, + "end_time": station.EndTime, + "is_battle_available": station.IsBattleAvailable, + "battle_level": station.BattleLevel, + "battle_start": station.BattleStart, + "battle_end": station.BattleEnd, + "battle_pokemon_id": station.BattlePokemonId, + "battle_pokemon_form": station.BattlePokemonForm, + "battle_pokemon_costume": station.BattlePokemonCostume, + "battle_pokemon_gender": station.BattlePokemonGender, + "battle_pokemon_alignment": station.BattlePokemonAlignment, + "battle_pokemon_bread_mode": station.BattlePokemonBreadMode, + "battle_pokemon_move_1": station.BattlePokemonMove1, + "battle_pokemon_move_2": station.BattlePokemonMove2, + "total_stationed_pokemon": station.TotalStationedPokemon, + "total_stationed_gmax": station.TotalStationedGmax, + "updated": station.Updated, } areas := MatchStatsGeofence(station.Lat, station.Lon) - webhooksSender.AddMessage(webhooks.Station, stationHook, areas) - statsCollector.UpdateStationCount(areas) + webhooksSender.AddMessage(webhooks.MaxBattle, stationHook, areas) + statsCollector.UpdateMaxBattleCount(areas, station.BattleLevel.ValueOrZero()) } } diff --git a/stats_collector/noop.go b/stats_collector/noop.go index 28061f7a..e726b008 100644 --- a/stats_collector/noop.go +++ b/stats_collector/noop.go @@ -48,6 +48,7 @@ func (col *noopCollector) SetLures(int32, float64) func (col *noopCollector) SetQuests(float64, float64) {} func (col *noopCollector) IncPokemons(bool, null.String) {} func (col *noopCollector) DecPokemons(bool, null.String) {} +func (col *noopCollector) UpdateMaxBattleCount([]geo.AreaName, int64) {} func NewNoopStatsCollector() StatsCollector { return &noopCollector{} diff --git a/stats_collector/prometheus.go b/stats_collector/prometheus.go index 17c0b55a..4b31acfe 100644 --- a/stats_collector/prometheus.go +++ b/stats_collector/prometheus.go @@ -318,6 +318,14 @@ var ( }, []string{"level"}, ) + maxBattleCount = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: ns, + Name: "max_battle_count", + Help: "Total number of created max battles", + }, + []string{"area", "level"}, + ) ) var _ StatsCollector = (*promCollector)(nil) @@ -564,6 +572,17 @@ func (col *promCollector) DecPokemons(hasIv bool, seenType null.String) { pokemons.WithLabelValues(hasIvStr, seenType.ValueOrZero()).Dec() } +func (col *promCollector) UpdateMaxBattleCount(areas []geo.AreaName, level int64) { + processed := make(map[string]bool) + for _, area := range areas { + areaName := area.String() + if !processed[areaName] { + maxBattleCount.WithLabelValues(areaName, strconv.FormatInt(level, 10)).Inc() + processed[areaName] = true + } + } +} + func initPrometheus() { prometheus.MustRegister( rawRequests, decodeMethods, decodeFortDetails, decodeGetMapForts, decodeGetGymInfo, decodeEncounter, @@ -575,7 +594,7 @@ func initPrometheus() { pokemonCountNew, pokemonCountIv, pokemonCountHundo, pokemonCountNundo, pokemonCountShiny, pokemonCountNonShiny, pokemonCountShundo, pokemonCountSnundo, - verifiedPokemonTTL, verifiedPokemonTTLCounter, raidCount, fortCount, incidentCount, + verifiedPokemonTTL, verifiedPokemonTTLCounter, raidCount, fortCount, incidentCount, maxBattleCount, duplicateEncounters, dbQueries, gyms, incidents, pokemons, lures, quests, raids, diff --git a/stats_collector/stats_collector.go b/stats_collector/stats_collector.go index 7afd6844..c307b063 100644 --- a/stats_collector/stats_collector.go +++ b/stats_collector/stats_collector.go @@ -1,11 +1,12 @@ package stats_collector import ( + "golbat/config" + "golbat/geo" + "github.com/Depado/ginprom" "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" - "golbat/config" - "golbat/geo" "gopkg.in/guregu/null.v4" ) @@ -47,6 +48,7 @@ type StatsCollector interface { SetQuests(ar float64, noAr float64) IncPokemons(hasIv bool, seenType null.String) DecPokemons(hasIv bool, seenType null.String) + UpdateMaxBattleCount(areas []geo.AreaName, level int64) } type Config interface { diff --git a/webhooks/webhook.go b/webhooks/webhook.go index b38384d0..57f280c4 100644 --- a/webhooks/webhook.go +++ b/webhooks/webhook.go @@ -26,7 +26,7 @@ const ( FortUpdate PokemonIV PokemonNoIV - Station + MaxBattle // this magically becomes the number of types we have webhookTypesLength ) @@ -43,7 +43,7 @@ func init() { webhookTypeToPayloadType[FortUpdate] = "fort_update" webhookTypeToPayloadType[PokemonIV] = "pokemon" webhookTypeToPayloadType[PokemonNoIV] = "pokemon" - webhookTypeToPayloadType[Station] = "station" + webhookTypeToPayloadType[MaxBattle] = "max_battle" // if we add more types, make sure one has added everything here for _, str := range webhookTypeToPayloadType { @@ -64,7 +64,7 @@ var webhookConfigStringToType = map[string][]WebhookType{ "pokemon_iv": []WebhookType{PokemonIV}, "pokemon_no_iv": []WebhookType{PokemonNoIV}, "pokemon": []WebhookType{PokemonIV, PokemonNoIV}, - "station": []WebhookType{Station}, + "max_battle": []WebhookType{MaxBattle}, } type webhook struct { From de3ff21c7eceab91e2ad30d50f477edae14aa1cd Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Fri, 25 Apr 2025 16:05:28 +0200 Subject: [PATCH 12/36] Update station.go --- decoder/station.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decoder/station.go b/decoder/station.go index b5283aee..b6bf9aa3 100644 --- a/decoder/station.go +++ b/decoder/station.go @@ -297,7 +297,7 @@ func createStationWebhooks(oldStation *Station, station *Station) { oldStation.BattlePokemonGender != station.BattlePokemonGender || oldStation.BattlePokemonBreadMode != station.BattlePokemonBreadMode) { stationHook := map[string]any{ - "id": station.Id, + "station_id": station.Id, "latitude": station.Lat, "longitude": station.Lon, "name": station.Name, From 3866a9c6baa41a797917bd8bae2b96c16259e1c3 Mon Sep 17 00:00:00 2001 From: Magik <30931287+unseenmagik@users.noreply.github.com> Date: Wed, 29 Jan 2025 21:13:12 +0000 Subject: [PATCH 13/36] Initial station webhook --- config.toml.example | 2 +- decoder/station.go | 66 ++++++++++++++++++++++++++++++++++++++++++--- webhooks/webhook.go | 3 +++ 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/config.toml.example b/config.toml.example index 868b0efc..7b8137b6 100644 --- a/config.toml.example +++ b/config.toml.example @@ -46,7 +46,7 @@ include_hundos_under_cap = false [[webhooks]] url = "http://localhost:4201" # types if specified can be... -# types = ["pokemon", "pokemon_iv", "pokemon_no_iv", "gym", "invasion", "quest", "pokestop", "raid", "weather", "fort_update"] +# types = ["pokemon", "pokemon_iv", "pokemon_no_iv", "gym", "invasion", "quest", "pokestop", "raid", "weather", "fort_update", "station"] # "pokemon" includes both with ivs and without. "pokemon_iv" will only be encountered pokemon. "pokemon_no_iv" may be nearby pokemon that have not been encountered (yet). #[[webhooks]] diff --git a/decoder/station.go b/decoder/station.go index 03534a3f..ae5a0c15 100644 --- a/decoder/station.go +++ b/decoder/station.go @@ -46,6 +46,19 @@ type Station struct { StationedPokemon null.String `db:"stationed_pokemon"` } +type webhookLineup struct { + PokemonId null.Int `json:"pokemon_id"` + Form null.Int `json:"form"` + Costume null.Int `json:"costume"` + Gender null.Int `json:"gender"` + Alignment null.Int `json:"alignment"` + Mode null.Int `json:"mode"` + Move1 null.Int `json:"move1"` + Move2 null.Int `json:"move2"` + TotalStationedPokemon null.Int `json:"totalstationedpokemon"` + StationedPokemon null.String `json:"staitionedpokemon"` +} + func getStationRecord(ctx context.Context, db db.DbDetails, stationId string) (*Station, error) { inMemoryStation := stationCache.Get(stationId) if inMemoryStation != nil { @@ -135,7 +148,7 @@ func saveStationRecord(ctx context.Context, db db.DbDetails, station *Station) { } stationCache.Set(station.Id, *station, ttlcache.DefaultTTL) - createStationWebhooks(oldStation, station) + createStationWebhooks(ctx, db, oldStation, station) } @@ -287,6 +300,53 @@ func UpdateStationWithStationDetails(ctx context.Context, db db.DbDetails, reque return fmt.Sprintf("StationedPokemonDetails %s", stationId) } -func createStationWebhooks(oldStation *Station, station *Station) { - //TODO we need to define webhooks, are they needed for stations, or only for battles? +func createStationWebhooks(ctx context.Context, db db.DbDetails, oldStation *Station, station *Station) { + if oldStation == nil || (oldStation.EndTime != station.EndTime || oldStation.StationedPokemon != station.StationedPokemon || oldStation.BattlePokemonId != station.BattlePokemonId) { + station, _ := getStationRecord(ctx, db, station.Id) + if station == nil { + station = &Station{} + } + + stationHook := map[string]interface{}{ + "id": station.Id, + "latitude": station.Lat, + "longitude": station.Lon, + "name": func() string { + if station.Name.Valid { + return station.Name.String + } else { + return "Unknown" + } + }(), + //"url": station.Url.ValueOrZero(), + "start_time": station.StartTime, + "end_time": station.EndTime, + "is_battle_available": station.IsBattleAvailable, + "battle_level": station.BattleLevel, + "battle_start": station.BattleStart, + "battle_end": station.BattleEnd, + "updated": station.Updated, + "lineup": nil, + } + + if station.BattlePokemonId.Valid { + stationHook["lineup"] = []webhookLineup{ + { + PokemonId: station.Id, + Form: station.BattlePokemonForm, + Costume: station.BattlePokemonCostume, + Gender: station.BattlePokemonGender, + Alignment: station.BattlePokemonAlignment, + Mode: station.BattlePokemonBreadMode, + Move1: station.BattlePokemonMove1, + Move2: station.BattlePokemonMove2, + TotalStationedPokemon: station.TotalStationedPokemon, + StationedPokemon: station.StationedPokemon, + }, + } + } + areas := MatchStatsGeofence(station.Lat, station.Lon) + webhooksSender.AddMessage(webhooks.Station, stationHook, areas) + statsCollector.UpdateStationCount(areas) + } } diff --git a/webhooks/webhook.go b/webhooks/webhook.go index d26609de..b38384d0 100644 --- a/webhooks/webhook.go +++ b/webhooks/webhook.go @@ -26,6 +26,7 @@ const ( FortUpdate PokemonIV PokemonNoIV + Station // this magically becomes the number of types we have webhookTypesLength ) @@ -42,6 +43,7 @@ func init() { webhookTypeToPayloadType[FortUpdate] = "fort_update" webhookTypeToPayloadType[PokemonIV] = "pokemon" webhookTypeToPayloadType[PokemonNoIV] = "pokemon" + webhookTypeToPayloadType[Station] = "station" // if we add more types, make sure one has added everything here for _, str := range webhookTypeToPayloadType { @@ -62,6 +64,7 @@ var webhookConfigStringToType = map[string][]WebhookType{ "pokemon_iv": []WebhookType{PokemonIV}, "pokemon_no_iv": []WebhookType{PokemonNoIV}, "pokemon": []WebhookType{PokemonIV, PokemonNoIV}, + "station": []WebhookType{Station}, } type webhook struct { From fbfc6e539cf303e4ab3265d6d867bb17cba169cc Mon Sep 17 00:00:00 2001 From: Fabio1988 Date: Sat, 1 Feb 2025 17:36:08 +0100 Subject: [PATCH 14/36] fix: webhook sending --- config.toml.example | 2 +- decoder/station.go | 94 +++++++++++------------------- stats_collector/noop.go | 1 + stats_collector/prometheus.go | 21 ++++++- stats_collector/stats_collector.go | 6 +- webhooks/webhook.go | 6 +- 6 files changed, 63 insertions(+), 67 deletions(-) diff --git a/config.toml.example b/config.toml.example index 7b8137b6..f51825fa 100644 --- a/config.toml.example +++ b/config.toml.example @@ -46,7 +46,7 @@ include_hundos_under_cap = false [[webhooks]] url = "http://localhost:4201" # types if specified can be... -# types = ["pokemon", "pokemon_iv", "pokemon_no_iv", "gym", "invasion", "quest", "pokestop", "raid", "weather", "fort_update", "station"] +# types = ["pokemon", "pokemon_iv", "pokemon_no_iv", "gym", "invasion", "quest", "pokestop", "raid", "weather", "fort_update", "max_battle"] # "pokemon" includes both with ivs and without. "pokemon_iv" will only be encountered pokemon. "pokemon_no_iv" may be nearby pokemon that have not been encountered (yet). #[[webhooks]] diff --git a/decoder/station.go b/decoder/station.go index ae5a0c15..b5283aee 100644 --- a/decoder/station.go +++ b/decoder/station.go @@ -9,6 +9,7 @@ import ( "golbat/db" "golbat/pogo" "golbat/util" + "golbat/webhooks" "time" "github.com/jellydator/ttlcache/v3" @@ -46,19 +47,6 @@ type Station struct { StationedPokemon null.String `db:"stationed_pokemon"` } -type webhookLineup struct { - PokemonId null.Int `json:"pokemon_id"` - Form null.Int `json:"form"` - Costume null.Int `json:"costume"` - Gender null.Int `json:"gender"` - Alignment null.Int `json:"alignment"` - Mode null.Int `json:"mode"` - Move1 null.Int `json:"move1"` - Move2 null.Int `json:"move2"` - TotalStationedPokemon null.Int `json:"totalstationedpokemon"` - StationedPokemon null.String `json:"staitionedpokemon"` -} - func getStationRecord(ctx context.Context, db db.DbDetails, stationId string) (*Station, error) { inMemoryStation := stationCache.Get(stationId) if inMemoryStation != nil { @@ -148,7 +136,7 @@ func saveStationRecord(ctx context.Context, db db.DbDetails, station *Station) { } stationCache.Set(station.Id, *station, ttlcache.DefaultTTL) - createStationWebhooks(ctx, db, oldStation, station) + createStationWebhooks(oldStation, station) } @@ -300,53 +288,39 @@ func UpdateStationWithStationDetails(ctx context.Context, db db.DbDetails, reque return fmt.Sprintf("StationedPokemonDetails %s", stationId) } -func createStationWebhooks(ctx context.Context, db db.DbDetails, oldStation *Station, station *Station) { - if oldStation == nil || (oldStation.EndTime != station.EndTime || oldStation.StationedPokemon != station.StationedPokemon || oldStation.BattlePokemonId != station.BattlePokemonId) { - station, _ := getStationRecord(ctx, db, station.Id) - if station == nil { - station = &Station{} - } - - stationHook := map[string]interface{}{ - "id": station.Id, - "latitude": station.Lat, - "longitude": station.Lon, - "name": func() string { - if station.Name.Valid { - return station.Name.String - } else { - return "Unknown" - } - }(), - //"url": station.Url.ValueOrZero(), - "start_time": station.StartTime, - "end_time": station.EndTime, - "is_battle_available": station.IsBattleAvailable, - "battle_level": station.BattleLevel, - "battle_start": station.BattleStart, - "battle_end": station.BattleEnd, - "updated": station.Updated, - "lineup": nil, - } - - if station.BattlePokemonId.Valid { - stationHook["lineup"] = []webhookLineup{ - { - PokemonId: station.Id, - Form: station.BattlePokemonForm, - Costume: station.BattlePokemonCostume, - Gender: station.BattlePokemonGender, - Alignment: station.BattlePokemonAlignment, - Mode: station.BattlePokemonBreadMode, - Move1: station.BattlePokemonMove1, - Move2: station.BattlePokemonMove2, - TotalStationedPokemon: station.TotalStationedPokemon, - StationedPokemon: station.StationedPokemon, - }, - } +func createStationWebhooks(oldStation *Station, station *Station) { + if oldStation == nil || station.BattlePokemonId.Valid && (oldStation.EndTime != station.EndTime || + oldStation.BattleEnd != station.BattleEnd || + oldStation.BattlePokemonId != station.BattlePokemonId || + oldStation.BattlePokemonForm != station.BattlePokemonForm || + oldStation.BattlePokemonCostume != station.BattlePokemonCostume || + oldStation.BattlePokemonGender != station.BattlePokemonGender || + oldStation.BattlePokemonBreadMode != station.BattlePokemonBreadMode) { + stationHook := map[string]any{ + "id": station.Id, + "latitude": station.Lat, + "longitude": station.Lon, + "name": station.Name, + "start_time": station.StartTime, + "end_time": station.EndTime, + "is_battle_available": station.IsBattleAvailable, + "battle_level": station.BattleLevel, + "battle_start": station.BattleStart, + "battle_end": station.BattleEnd, + "battle_pokemon_id": station.BattlePokemonId, + "battle_pokemon_form": station.BattlePokemonForm, + "battle_pokemon_costume": station.BattlePokemonCostume, + "battle_pokemon_gender": station.BattlePokemonGender, + "battle_pokemon_alignment": station.BattlePokemonAlignment, + "battle_pokemon_bread_mode": station.BattlePokemonBreadMode, + "battle_pokemon_move_1": station.BattlePokemonMove1, + "battle_pokemon_move_2": station.BattlePokemonMove2, + "total_stationed_pokemon": station.TotalStationedPokemon, + "total_stationed_gmax": station.TotalStationedGmax, + "updated": station.Updated, } areas := MatchStatsGeofence(station.Lat, station.Lon) - webhooksSender.AddMessage(webhooks.Station, stationHook, areas) - statsCollector.UpdateStationCount(areas) + webhooksSender.AddMessage(webhooks.MaxBattle, stationHook, areas) + statsCollector.UpdateMaxBattleCount(areas, station.BattleLevel.ValueOrZero()) } } diff --git a/stats_collector/noop.go b/stats_collector/noop.go index 28061f7a..e726b008 100644 --- a/stats_collector/noop.go +++ b/stats_collector/noop.go @@ -48,6 +48,7 @@ func (col *noopCollector) SetLures(int32, float64) func (col *noopCollector) SetQuests(float64, float64) {} func (col *noopCollector) IncPokemons(bool, null.String) {} func (col *noopCollector) DecPokemons(bool, null.String) {} +func (col *noopCollector) UpdateMaxBattleCount([]geo.AreaName, int64) {} func NewNoopStatsCollector() StatsCollector { return &noopCollector{} diff --git a/stats_collector/prometheus.go b/stats_collector/prometheus.go index 17c0b55a..4b31acfe 100644 --- a/stats_collector/prometheus.go +++ b/stats_collector/prometheus.go @@ -318,6 +318,14 @@ var ( }, []string{"level"}, ) + maxBattleCount = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: ns, + Name: "max_battle_count", + Help: "Total number of created max battles", + }, + []string{"area", "level"}, + ) ) var _ StatsCollector = (*promCollector)(nil) @@ -564,6 +572,17 @@ func (col *promCollector) DecPokemons(hasIv bool, seenType null.String) { pokemons.WithLabelValues(hasIvStr, seenType.ValueOrZero()).Dec() } +func (col *promCollector) UpdateMaxBattleCount(areas []geo.AreaName, level int64) { + processed := make(map[string]bool) + for _, area := range areas { + areaName := area.String() + if !processed[areaName] { + maxBattleCount.WithLabelValues(areaName, strconv.FormatInt(level, 10)).Inc() + processed[areaName] = true + } + } +} + func initPrometheus() { prometheus.MustRegister( rawRequests, decodeMethods, decodeFortDetails, decodeGetMapForts, decodeGetGymInfo, decodeEncounter, @@ -575,7 +594,7 @@ func initPrometheus() { pokemonCountNew, pokemonCountIv, pokemonCountHundo, pokemonCountNundo, pokemonCountShiny, pokemonCountNonShiny, pokemonCountShundo, pokemonCountSnundo, - verifiedPokemonTTL, verifiedPokemonTTLCounter, raidCount, fortCount, incidentCount, + verifiedPokemonTTL, verifiedPokemonTTLCounter, raidCount, fortCount, incidentCount, maxBattleCount, duplicateEncounters, dbQueries, gyms, incidents, pokemons, lures, quests, raids, diff --git a/stats_collector/stats_collector.go b/stats_collector/stats_collector.go index 7afd6844..c307b063 100644 --- a/stats_collector/stats_collector.go +++ b/stats_collector/stats_collector.go @@ -1,11 +1,12 @@ package stats_collector import ( + "golbat/config" + "golbat/geo" + "github.com/Depado/ginprom" "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" - "golbat/config" - "golbat/geo" "gopkg.in/guregu/null.v4" ) @@ -47,6 +48,7 @@ type StatsCollector interface { SetQuests(ar float64, noAr float64) IncPokemons(hasIv bool, seenType null.String) DecPokemons(hasIv bool, seenType null.String) + UpdateMaxBattleCount(areas []geo.AreaName, level int64) } type Config interface { diff --git a/webhooks/webhook.go b/webhooks/webhook.go index b38384d0..57f280c4 100644 --- a/webhooks/webhook.go +++ b/webhooks/webhook.go @@ -26,7 +26,7 @@ const ( FortUpdate PokemonIV PokemonNoIV - Station + MaxBattle // this magically becomes the number of types we have webhookTypesLength ) @@ -43,7 +43,7 @@ func init() { webhookTypeToPayloadType[FortUpdate] = "fort_update" webhookTypeToPayloadType[PokemonIV] = "pokemon" webhookTypeToPayloadType[PokemonNoIV] = "pokemon" - webhookTypeToPayloadType[Station] = "station" + webhookTypeToPayloadType[MaxBattle] = "max_battle" // if we add more types, make sure one has added everything here for _, str := range webhookTypeToPayloadType { @@ -64,7 +64,7 @@ var webhookConfigStringToType = map[string][]WebhookType{ "pokemon_iv": []WebhookType{PokemonIV}, "pokemon_no_iv": []WebhookType{PokemonNoIV}, "pokemon": []WebhookType{PokemonIV, PokemonNoIV}, - "station": []WebhookType{Station}, + "max_battle": []WebhookType{MaxBattle}, } type webhook struct { From 776ebad27d03b440a9ef0a76fb1bc412cb6ea2c8 Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Wed, 7 May 2025 14:57:32 +0200 Subject: [PATCH 15/36] Update station.go --- decoder/station.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decoder/station.go b/decoder/station.go index b6bf9aa3..b5283aee 100644 --- a/decoder/station.go +++ b/decoder/station.go @@ -297,7 +297,7 @@ func createStationWebhooks(oldStation *Station, station *Station) { oldStation.BattlePokemonGender != station.BattlePokemonGender || oldStation.BattlePokemonBreadMode != station.BattlePokemonBreadMode) { stationHook := map[string]any{ - "station_id": station.Id, + "id": station.Id, "latitude": station.Lat, "longitude": station.Lon, "name": station.Name, From 78a0e1c1b4593639022d0e80393c4001cdf473d0 Mon Sep 17 00:00:00 2001 From: James Berry Date: Fri, 16 May 2025 10:08:06 +0100 Subject: [PATCH 16/36] Initial test --- decoder/gym.go | 65 ++++++++++++++++++++++++++++++++++++++++++--- main.go | 37 ++++++++++++++++++++++++++ sql/46_rsvps.up.sql | 2 ++ 3 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 sql/46_rsvps.up.sql diff --git a/decoder/gym.go b/decoder/gym.go index 12d0d7fc..aaede83d 100644 --- a/decoder/gym.go +++ b/decoder/gym.go @@ -1,10 +1,12 @@ package decoder import ( + "cmp" "context" "database/sql" "encoding/json" "fmt" + "slices" "time" "golbat/geo" @@ -63,6 +65,7 @@ type Gym struct { PowerUpEndTimestamp null.Int `db:"power_up_end_timestamp"` Description null.String `db:"description"` Defenders null.String `db:"defenders"` + Rsvps null.String `db:"rsvps"` //`id` varchar(35) NOT NULL, //`lat` double(18,14) NOT NULL, //`lon` double(18,14) NOT NULL, @@ -380,6 +383,37 @@ func (gym *Gym) updateGymFromGetMapFortsOutProto(fortData *pogo.GetMapFortsOutPr return gym } +func (gym *Gym) updateGymFromRsvpProto(fortData *pogo.GetEventRsvpsOutProto) *Gym { + type rsvpTimeslot struct { + Timeslot int64 `json:"timeslot"` + GoingCount int32 `json:"going_count"` + MaybeCount int32 `json:"maybe_count"` + } + + timeslots := make([]rsvpTimeslot, 0) + + for _, timeslot := range fortData.RsvpTimeslots { + timeslots = append(timeslots, rsvpTimeslot{ + Timeslot: timeslot.TimeSlot, + GoingCount: timeslot.GoingCount, + MaybeCount: timeslot.MaybeCount, + }) + } + + if len(timeslots) == 0 { + gym.Rsvps = null.NewString("", false) + } else { + slices.SortFunc(timeslots, func(a, b rsvpTimeslot) int { + return cmp.Compare(a.Timeslot, b.Timeslot) + }) + + bRsvps, _ := json.Marshal(timeslots) + gym.Rsvps = null.StringFrom(string(bRsvps)) + } + + return gym +} + // hasChangesGym compares two Gym structs // Float tolerance: Lat, Lon func hasChangesGym(old *Gym, new *Gym) bool { @@ -420,7 +454,8 @@ func hasChangesGym(old *Gym, new *Gym) bool { old.PowerUpEndTimestamp != new.PowerUpEndTimestamp || old.Description != new.Description || !floatAlmostEqual(old.Lat, new.Lat, floatTolerance) || - !floatAlmostEqual(old.Lon, new.Lon, floatTolerance) + !floatAlmostEqual(old.Lon, new.Lon, floatTolerance) || + old.Rsvps != new.Rsvps } // hasChangesInternalGym compares two Gym structs for changes that will be stored in memory @@ -586,8 +621,8 @@ func saveGymRecord(ctx context.Context, db db.DbDetails, gym *Gym) { //log.Traceln(cmp.Diff(oldGym, gym)) if oldGym == nil { - res, err := db.GeneralDb.NamedExecContext(ctx, "INSERT INTO gym (id,lat,lon,name,url,last_modified_timestamp,raid_end_timestamp,raid_spawn_timestamp,raid_battle_timestamp,updated,raid_pokemon_id,guarding_pokemon_id,guarding_pokemon_display,available_slots,team_id,raid_level,enabled,ex_raid_eligible,in_battle,raid_pokemon_move_1,raid_pokemon_move_2,raid_pokemon_form,raid_pokemon_alignment,raid_pokemon_cp,raid_is_exclusive,cell_id,deleted,total_cp,first_seen_timestamp,raid_pokemon_gender,sponsor_id,partner_id,raid_pokemon_costume,raid_pokemon_evolution,ar_scan_eligible,power_up_level,power_up_points,power_up_end_timestamp,description, defenders) "+ - "VALUES (:id,:lat,:lon,:name,:url,UNIX_TIMESTAMP(),:raid_end_timestamp,:raid_spawn_timestamp,:raid_battle_timestamp,:updated,:raid_pokemon_id,:guarding_pokemon_id,:guarding_pokemon_display,:available_slots,:team_id,:raid_level,:enabled,:ex_raid_eligible,:in_battle,:raid_pokemon_move_1,:raid_pokemon_move_2,:raid_pokemon_form,:raid_pokemon_alignment,:raid_pokemon_cp,:raid_is_exclusive,:cell_id,0,:total_cp,UNIX_TIMESTAMP(),:raid_pokemon_gender,:sponsor_id,:partner_id,:raid_pokemon_costume,:raid_pokemon_evolution,:ar_scan_eligible,:power_up_level,:power_up_points,:power_up_end_timestamp,:description, :defenders)", gym) + res, err := db.GeneralDb.NamedExecContext(ctx, "INSERT INTO gym (id,lat,lon,name,url,last_modified_timestamp,raid_end_timestamp,raid_spawn_timestamp,raid_battle_timestamp,updated,raid_pokemon_id,guarding_pokemon_id,guarding_pokemon_display,available_slots,team_id,raid_level,enabled,ex_raid_eligible,in_battle,raid_pokemon_move_1,raid_pokemon_move_2,raid_pokemon_form,raid_pokemon_alignment,raid_pokemon_cp,raid_is_exclusive,cell_id,deleted,total_cp,first_seen_timestamp,raid_pokemon_gender,sponsor_id,partner_id,raid_pokemon_costume,raid_pokemon_evolution,ar_scan_eligible,power_up_level,power_up_points,power_up_end_timestamp,description, defenders, rsvps) "+ + "VALUES (:id,:lat,:lon,:name,:url,UNIX_TIMESTAMP(),:raid_end_timestamp,:raid_spawn_timestamp,:raid_battle_timestamp,:updated,:raid_pokemon_id,:guarding_pokemon_id,:guarding_pokemon_display,:available_slots,:team_id,:raid_level,:enabled,:ex_raid_eligible,:in_battle,:raid_pokemon_move_1,:raid_pokemon_move_2,:raid_pokemon_form,:raid_pokemon_alignment,:raid_pokemon_cp,:raid_is_exclusive,:cell_id,0,:total_cp,UNIX_TIMESTAMP(),:raid_pokemon_gender,:sponsor_id,:partner_id,:raid_pokemon_costume,:raid_pokemon_evolution,:ar_scan_eligible,:power_up_level,:power_up_points,:power_up_end_timestamp,:description, :defenders, :rsvps)", gym) statsCollector.IncDbQuery("insert gym", err) if err != nil { @@ -635,7 +670,8 @@ func saveGymRecord(ctx context.Context, db db.DbDetails, gym *Gym) { "power_up_points = :power_up_points, "+ "power_up_end_timestamp = :power_up_end_timestamp,"+ "description = :description,"+ - "defenders = :defenders "+ + "defenders = :defenders,"+ + "rsvps = :rsvps "+ "WHERE id = :id", gym, ) statsCollector.IncDbQuery("update gym", err) @@ -722,3 +758,24 @@ func UpdateGymRecordWithGetMapFortsOutProto(ctx context.Context, db db.DbDetails saveGymRecord(ctx, db, gym) return true, fmt.Sprintf("%s %s", gym.Id, gym.Name.ValueOrZero()) } + +func UpdateGymRecordWithRsvpProto(ctx context.Context, db db.DbDetails, req *pogo.RaidDetails, resp *pogo.GetEventRsvpsOutProto) string { + gymMutex, _ := gymStripedMutex.GetLock(req.FortId) + gymMutex.Lock() + defer gymMutex.Unlock() + + gym, err := getGymRecord(ctx, db, req.FortId) + if err != nil { + return err.Error() + } + + if gym == nil { + // Do not add RSVP details to unknown gyms + return fmt.Sprintf("%s Gym not present", req.FortId) + } + gym.updateGymFromRsvpProto(resp) + + saveGymRecord(ctx, db, gym) + + return fmt.Sprintf("%s %s", gym.Id, gym.Name.ValueOrZero()) +} diff --git a/main.go b/main.go index c79e1f99..cdde0b90 100644 --- a/main.go +++ b/main.go @@ -447,6 +447,14 @@ func decode(ctx context.Context, method int, protoData *ProtoData) { result = decodeTappable(ctx, protoData.Request, protoData.Data, protoData.Account, protoData.TimestampMs) } processed = true + case pogo.Method_METHOD_GET_EVENT_RSVPS: + if getScanParameters(protoData).ProcessGyms { + result = decodeGetEventRsvp(ctx, protoData.Request, protoData.Data) + } + processed = true + case pogo.Method_METHOD_GET_EVENT_RSVP_COUNT: + // We don't process the count, only the detail + ignore = true default: log.Debugf("Did not know hook type %s", pogo.Method(method)) } @@ -980,3 +988,32 @@ func decodeTappable(ctx context.Context, request, data []byte, username string, } return result + " " + decoder.UpdateTappable(ctx, dbDetails, &tappableRequest, &tappable, timestampMs) } + +func decodeGetEventRsvp(ctx context.Context, request []byte, data []byte) string { + var rsvp pogo.GetEventRsvpsOutProto + if err := proto.Unmarshal(data, &rsvp); err != nil { + log.Errorf("Failed to parse %s", err) + return fmt.Sprintf("Failed to parse GetEventRsvpsOutProto %s", err) + } + + var rsvpRequest pogo.GetEventRsvpsProto + if request != nil { + if err := proto.Unmarshal(request, &rsvpRequest); err != nil { + log.Errorf("Failed to parse %s", err) + return fmt.Sprintf("Failed to parse GetEventRsvpsProto %s", err) + } + } + + if rsvp.Status != pogo.GetEventRsvpsOutProto_SUCCESS { + return fmt.Sprintf("Ignored GetEventRsvpsOutProto non-success status %s", rsvp.Status) + } + + switch op := rsvpRequest.EventDetails.(type) { + case *pogo.GetEventRsvpsProto_Raid: + return decoder.UpdateGymRecordWithRsvpProto(ctx, dbDetails, op.Raid, &rsvp) + case *pogo.GetEventRsvpsProto_GmaxBattle: + return "Unsupported GmaxBattle Rsvp received" + } + + return "Failed to parse GetEventRsvpsProto - unknown event type" +} diff --git a/sql/46_rsvps.up.sql b/sql/46_rsvps.up.sql new file mode 100644 index 00000000..a54ab1e8 --- /dev/null +++ b/sql/46_rsvps.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE gym + add `rsvps` TEXT default NULL; From a7bbd6c57885519e6c6b356b6e78be3a73853d0a Mon Sep 17 00:00:00 2001 From: James Berry Date: Fri, 16 May 2025 10:12:22 +0100 Subject: [PATCH 17/36] Only include timeslots with attendees --- decoder/gym.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/decoder/gym.go b/decoder/gym.go index aaede83d..f9703dba 100644 --- a/decoder/gym.go +++ b/decoder/gym.go @@ -393,11 +393,13 @@ func (gym *Gym) updateGymFromRsvpProto(fortData *pogo.GetEventRsvpsOutProto) *Gy timeslots := make([]rsvpTimeslot, 0) for _, timeslot := range fortData.RsvpTimeslots { - timeslots = append(timeslots, rsvpTimeslot{ - Timeslot: timeslot.TimeSlot, - GoingCount: timeslot.GoingCount, - MaybeCount: timeslot.MaybeCount, - }) + if timeslot.GoingCount > 0 || timeslot.MaybeCount > 0 { + timeslots = append(timeslots, rsvpTimeslot{ + Timeslot: timeslot.TimeSlot, + GoingCount: timeslot.GoingCount, + MaybeCount: timeslot.MaybeCount, + }) + } } if len(timeslots) == 0 { From f3458b5baf5002e5389efac9fcd5918da4de261a Mon Sep 17 00:00:00 2001 From: James Berry Date: Fri, 16 May 2025 16:44:42 +0100 Subject: [PATCH 18/36] Update protos.md --- protos.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/protos.md b/protos.md index ef121a63..747c215a 100644 --- a/protos.md +++ b/protos.md @@ -56,6 +56,18 @@ requires the proto request to be present in the raw. - Decode top 3 player scores for a contest. This requires the proto request to be present in the raw. +`Method_METHOD_PROCESS_TAPPABLE = 1408` + +- Decode pokemon encounters and items hidden behind tappable objects +- +`Method_METHOD_GET_EVENT_RSVPS = 3031` + +- Decode the number of players and time that they have indicated they will present at a raid +- +`Method_REQUEST_TYPE_METHOD_GET_EVENT_RSVP_COUNT = 3036` + +- This proto is ignored + # Social actions - The master `ClientAction_CLIENT_ACTION_PROXY_SOCIAL_ACTION` proto will be From 0509bdf4bd771ec6ea6be5ab58bd0b5129a47b21 Mon Sep 17 00:00:00 2001 From: James Berry Date: Sat, 17 May 2025 13:59:46 +0100 Subject: [PATCH 19/36] Change order of compare --- decoder/gym.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/decoder/gym.go b/decoder/gym.go index f9703dba..aa911e0b 100644 --- a/decoder/gym.go +++ b/decoder/gym.go @@ -455,9 +455,10 @@ func hasChangesGym(old *Gym, new *Gym) bool { old.PowerUpPoints != new.PowerUpPoints || old.PowerUpEndTimestamp != new.PowerUpEndTimestamp || old.Description != new.Description || + old.Rsvps != new.Rsvps || !floatAlmostEqual(old.Lat, new.Lat, floatTolerance) || - !floatAlmostEqual(old.Lon, new.Lon, floatTolerance) || - old.Rsvps != new.Rsvps + !floatAlmostEqual(old.Lon, new.Lon, floatTolerance) + } // hasChangesInternalGym compares two Gym structs for changes that will be stored in memory From 045391a5325bce7dffeb5a068001451161bb2b74 Mon Sep 17 00:00:00 2001 From: James Berry Date: Tue, 20 May 2025 09:38:27 +0100 Subject: [PATCH 20/36] Add rsvps to raid webhook --- decoder/gym.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/decoder/gym.go b/decoder/gym.go index aa911e0b..faf53a79 100644 --- a/decoder/gym.go +++ b/decoder/gym.go @@ -550,7 +550,7 @@ func createGymWebhooks(oldGym *Gym, gym *Gym, areas []geo.AreaName) { if gym.RaidSpawnTimestamp.ValueOrZero() > 0 && (oldGym == nil || oldGym.RaidLevel != gym.RaidLevel || oldGym.RaidPokemonId != gym.RaidPokemonId || - oldGym.RaidSpawnTimestamp != gym.RaidSpawnTimestamp) { + oldGym.RaidSpawnTimestamp != gym.RaidSpawnTimestamp || oldGym.Rsvps != gym.Rsvps) { raidBattleTime := gym.RaidBattleTimestamp.ValueOrZero() raidEndTime := gym.RaidEndTimestamp.ValueOrZero() now := time.Now().Unix() @@ -591,6 +591,13 @@ func createGymWebhooks(oldGym *Gym, gym *Gym, areas []geo.AreaName) { "power_up_level": gym.PowerUpLevel.ValueOrZero(), "power_up_end_timestamp": gym.PowerUpEndTimestamp.ValueOrZero(), "ar_scan_eligible": gym.ArScanEligible.ValueOrZero(), + "rsvps": func() any { + if !gym.Rsvps.Valid { + return nil + } else { + return json.RawMessage(gym.Rsvps.ValueOrZero()) + } + }(), } webhooksSender.AddMessage(webhooks.Raid, raidHook, areas) From c951c3936ab7b63fa91135495aaadeb88b2cfaef Mon Sep 17 00:00:00 2001 From: James Berry Date: Wed, 21 May 2025 17:59:50 +0100 Subject: [PATCH 21/36] Clear rsvps from count proto --- decoder/gym.go | 24 ++++++++++++++++++++++++ main.go | 28 ++++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/decoder/gym.go b/decoder/gym.go index faf53a79..18059881 100644 --- a/decoder/gym.go +++ b/decoder/gym.go @@ -789,3 +789,27 @@ func UpdateGymRecordWithRsvpProto(ctx context.Context, db db.DbDetails, req *pog return fmt.Sprintf("%s %s", gym.Id, gym.Name.ValueOrZero()) } + +func ClearGymRsvp(ctx context.Context, db db.DbDetails, fortId string) string { + gymMutex, _ := gymStripedMutex.GetLock(fortId) + gymMutex.Lock() + defer gymMutex.Unlock() + + gym, err := getGymRecord(ctx, db, fortId) + if err != nil { + return err.Error() + } + + if gym == nil { + // Do not add RSVP details to unknown gyms + return fmt.Sprintf("%s Gym not present", fortId) + } + + if gym.Rsvps.Valid { + gym.Rsvps = null.NewString("", false) + + saveGymRecord(ctx, db, gym) + } + + return fmt.Sprintf("%s %s", gym.Id, gym.Name.ValueOrZero()) +} diff --git a/main.go b/main.go index cdde0b90..c1dfbfb4 100644 --- a/main.go +++ b/main.go @@ -453,8 +453,10 @@ func decode(ctx context.Context, method int, protoData *ProtoData) { } processed = true case pogo.Method_METHOD_GET_EVENT_RSVP_COUNT: - // We don't process the count, only the detail - ignore = true + if getScanParameters(protoData).ProcessGyms { + result = decodeGetEventRsvpCount(ctx, protoData.Data) + } + processed = true default: log.Debugf("Did not know hook type %s", pogo.Method(method)) } @@ -1017,3 +1019,25 @@ func decodeGetEventRsvp(ctx context.Context, request []byte, data []byte) string return "Failed to parse GetEventRsvpsProto - unknown event type" } + +func decodeGetEventRsvpCount(ctx context.Context, data []byte) string { + var rsvp pogo.GetEventRsvpCountOutProto + if err := proto.Unmarshal(data, &rsvp); err != nil { + log.Errorf("Failed to parse %s", err) + return fmt.Sprintf("Failed to parse GetEventRsvpCountOutProto %s", err) + } + + if rsvp.Status != pogo.GetEventRsvpCountOutProto_SUCCESS { + return fmt.Sprintf("Ignored GetEventRsvpCountOutProto non-success status %s", rsvp.Status) + } + + var clearLocations []string + for _, rsvpDetails := range rsvp.RsvpDetails { + if rsvpDetails.MaybeCount == 0 || rsvpDetails.GoingCount == 0 { + clearLocations = append(clearLocations, rsvpDetails.LocationId) + decoder.ClearGymRsvp(ctx, dbDetails, rsvpDetails.LocationId) + } + } + + return "Cleared RSVP @ " + strings.Join(clearLocations, ", ") +} From 5bc44741f7a504740f6d334fd85742db12e86190 Mon Sep 17 00:00:00 2001 From: James Berry Date: Wed, 21 May 2025 18:59:19 +0100 Subject: [PATCH 22/36] Clear rsvp data when new raid is first seen --- decoder/gym.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/decoder/gym.go b/decoder/gym.go index 18059881..bbf8717e 100644 --- a/decoder/gym.go +++ b/decoder/gym.go @@ -240,7 +240,14 @@ func (gym *Gym) updateGymFromFort(fortData *pogo.PokemonFortProto, cellId uint64 if fortData.RaidInfo != nil { gym.RaidEndTimestamp = null.IntFrom(int64(fortData.RaidInfo.RaidEndMs) / 1000) gym.RaidSpawnTimestamp = null.IntFrom(int64(fortData.RaidInfo.RaidSpawnMs) / 1000) - gym.RaidBattleTimestamp = null.IntFrom(int64(fortData.RaidInfo.RaidBattleMs) / 1000) + raidBattleTimestamp := int64(fortData.RaidInfo.RaidBattleMs) + + if gym.RaidBattleTimestamp.ValueOrZero() != raidBattleTimestamp { + // We are reporting a new raid, clear rsvp data + gym.Rsvps = null.NewString("", false) + } + gym.RaidBattleTimestamp = null.IntFrom(raidBattleTimestamp) + gym.RaidLevel = null.IntFrom(int64(fortData.RaidInfo.RaidLevel)) if fortData.RaidInfo.RaidPokemon != nil { gym.RaidPokemonId = null.IntFrom(int64(fortData.RaidInfo.RaidPokemon.PokemonId)) From 549620168d8e998cbaf4a37043ff089b588c3866 Mon Sep 17 00:00:00 2001 From: James Berry Date: Wed, 21 May 2025 19:06:39 +0100 Subject: [PATCH 23/36] Fix! --- decoder/gym.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decoder/gym.go b/decoder/gym.go index bbf8717e..52d1e095 100644 --- a/decoder/gym.go +++ b/decoder/gym.go @@ -240,7 +240,7 @@ func (gym *Gym) updateGymFromFort(fortData *pogo.PokemonFortProto, cellId uint64 if fortData.RaidInfo != nil { gym.RaidEndTimestamp = null.IntFrom(int64(fortData.RaidInfo.RaidEndMs) / 1000) gym.RaidSpawnTimestamp = null.IntFrom(int64(fortData.RaidInfo.RaidSpawnMs) / 1000) - raidBattleTimestamp := int64(fortData.RaidInfo.RaidBattleMs) + raidBattleTimestamp := int64(fortData.RaidInfo.RaidBattleMs) / 1000 if gym.RaidBattleTimestamp.ValueOrZero() != raidBattleTimestamp { // We are reporting a new raid, clear rsvp data From af05da41896441a1f42d33742c5aea5109e635f8 Mon Sep 17 00:00:00 2001 From: James Berry Date: Fri, 23 May 2025 14:06:01 +0100 Subject: [PATCH 24/36] Fix clear logic --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index c1dfbfb4..e8b64442 100644 --- a/main.go +++ b/main.go @@ -1033,7 +1033,7 @@ func decodeGetEventRsvpCount(ctx context.Context, data []byte) string { var clearLocations []string for _, rsvpDetails := range rsvp.RsvpDetails { - if rsvpDetails.MaybeCount == 0 || rsvpDetails.GoingCount == 0 { + if rsvpDetails.MaybeCount == 0 && rsvpDetails.GoingCount == 0 { clearLocations = append(clearLocations, rsvpDetails.LocationId) decoder.ClearGymRsvp(ctx, dbDetails, rsvpDetails.LocationId) } From 367264f0089ff8506a3339cdf5278b3c9ab59cb6 Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Mon, 26 May 2025 10:59:08 +0200 Subject: [PATCH 25/36] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 056a5a92..4be3889c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Golbat +# Golbat Golbat is an experimental raw data processor for Pokemon Go. Initially designed to be database compatible with RDM, it will From 9df0babd13e70f9f65f63686c102aa76e66fd30a Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Mon, 26 May 2025 11:15:53 +0200 Subject: [PATCH 26/36] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4be3889c..056a5a92 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Golbat +# Golbat Golbat is an experimental raw data processor for Pokemon Go. Initially designed to be database compatible with RDM, it will From 93c304b57e9455265c3d2a7eef41ff6acc38cd59 Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Mon, 23 Jun 2025 13:00:55 +0200 Subject: [PATCH 27/36] Update pokemon.go --- decoder/pokemon.go | 1 + 1 file changed, 1 insertion(+) diff --git a/decoder/pokemon.go b/decoder/pokemon.go index 553b8cfd..1f9020f4 100644 --- a/decoder/pokemon.go +++ b/decoder/pokemon.go @@ -772,6 +772,7 @@ func (pokemon *Pokemon) setDittoAttributes(mode string, isDitto bool, old, new * pokemon.IsDitto = true pokemon.DisplayPokemonId = null.IntFrom(int64(pokemon.PokemonId)) pokemon.PokemonId = int16(pogo.HoloPokemonId_DITTO) + pokemon.Form = null.IntFrom(0) } else { log.Debugf("[POKEMON] %d: %s not Ditto found %s -> %s", pokemon.Id, mode, old, new) } From c3650bca536d4554e774790ae870b8a182e35538 Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Wed, 15 Oct 2025 12:02:40 +0200 Subject: [PATCH 28/36] Update pokemon.go --- decoder/pokemon.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/decoder/pokemon.go b/decoder/pokemon.go index 40326679..fbed4934 100644 --- a/decoder/pokemon.go +++ b/decoder/pokemon.go @@ -68,6 +68,7 @@ type Pokemon struct { IsDitto bool `db:"is_ditto" json:"is_ditto"` SeenType null.String `db:"seen_type" json:"seen_type"` Shiny null.Bool `db:"shiny" json:"shiny"` + Background null.Int `db:"background" json:"background"` Username null.String `db:"username" json:"username"` Capture1 null.Float `db:"capture_1" json:"capture_1"` Capture2 null.Float `db:"capture_2" json:"capture_2"` @@ -109,6 +110,7 @@ type Pokemon struct { //`display_pokemon_id` smallint unsigned DEFAULT NULL, //`seen_type` enum('wild','encounter','nearby_stop','nearby_cell') DEFAULT NULL, //`shiny` tinyint(1) DEFAULT '0', +//`background` smallint unsigned DEFAULT NULL, //`username` varchar(32) DEFAULT NULL, //`capture_1` double(18,14) DEFAULT NULL, //`capture_2` double(18,14) DEFAULT NULL, @@ -148,7 +150,7 @@ func getPokemonRecord(ctx context.Context, db db.DbDetails, encounterId uint64) "SELECT id, pokemon_id, lat, lon, spawn_id, expire_timestamp, atk_iv, def_iv, sta_iv, golbat_internal, iv, "+ "move_1, move_2, gender, form, cp, level, strong, weather, costume, weight, height, size, "+ "display_pokemon_id, is_ditto, pokestop_id, updated, first_seen_timestamp, changed, cell_id, "+ - "expire_timestamp_verified, shiny, username, pvp, is_event, seen_type "+ + "expire_timestamp_verified, shiny, background, username, pvp, is_event, seen_type "+ "FROM pokemon WHERE id = ?", strconv.FormatUint(encounterId, 10)) statsCollector.IncDbQuery("select pokemon", err) @@ -211,6 +213,7 @@ func hasChangesPokemon(old *Pokemon, new *Pokemon) bool { old.IsDitto != new.IsDitto || old.SeenType != new.SeenType || old.Shiny != new.Shiny || + old.Background != new.Background || old.IsEvent != new.IsEvent || !floatAlmostEqual(old.Lat, new.Lat, floatTolerance) || !floatAlmostEqual(old.Lon, new.Lon, floatTolerance) || @@ -315,12 +318,12 @@ func savePokemonRecordAsAtTime(ctx context.Context, db db.DbDetails, pokemon *Po "spawn_id, expire_timestamp, atk_iv, def_iv, sta_iv, golbat_internal, iv, move_1, move_2,"+ "gender, form, cp, level, strong, weather, costume, weight, height, size,"+ "display_pokemon_id, is_ditto, pokestop_id, updated, first_seen_timestamp, changed, cell_id,"+ - "expire_timestamp_verified, shiny, username, %s is_event, seen_type) "+ + "expire_timestamp_verified, shiny, background, username, %s is_event, seen_type) "+ "VALUES (\"%d\", :pokemon_id, :lat, :lon, :spawn_id, :expire_timestamp, :atk_iv, :def_iv, :sta_iv,"+ ":golbat_internal, :iv, :move_1, :move_2, :gender, :form, :cp, :level, :strong, :weather, :costume,"+ ":weight, :height, :size, :display_pokemon_id, :is_ditto, :pokestop_id, :updated,"+ - ":first_seen_timestamp, :changed, :cell_id, :expire_timestamp_verified, :shiny, :username, %s :is_event,"+ - ":seen_type)", pvpField, pokemon.Id, pvpValue), pokemon) + ":first_seen_timestamp, :changed, :cell_id, :expire_timestamp_verified, :shiny, :background, :username,"+ + "%s :is_event, :seen_type)", pvpField, pokemon.Id, pvpValue), pokemon) statsCollector.IncDbQuery("insert pokemon", err) if err != nil { @@ -369,6 +372,7 @@ func savePokemonRecordAsAtTime(ctx context.Context, db db.DbDetails, pokemon *Po "is_ditto = :is_ditto, "+ "seen_type = :seen_type, "+ "shiny = :shiny, "+ + "background = :background, "+ "username = :username, "+ "%s"+ "is_event = :is_event "+ @@ -474,6 +478,7 @@ func createPokemonWebhooks(ctx context.Context, db db.DbDetails, old *Pokemon, n "capture_2": new.Capture2.ValueOrZero(), "capture_3": new.Capture3.ValueOrZero(), "shiny": new.Shiny, + "background": new.Background, "username": new.Username, "display_pokemon_id": new.DisplayPokemonId, "is_event": new.IsEvent, @@ -1104,6 +1109,9 @@ func (pokemon *Pokemon) clearIv(cp bool) { func (pokemon *Pokemon) addEncounterPokemon(ctx context.Context, db db.DbDetails, proto *pogo.PokemonProto, username string) { pokemon.Username = null.StringFrom(username) pokemon.Shiny = null.BoolFrom(proto.PokemonDisplay.Shiny) + if proto.PokemonDisplay.LocationCard != nil { + pokemon.Background = null.IntFrom(int64(proto.PokemonDisplay.LocationCard.LocationCard.Number())) + } pokemon.Cp = null.IntFrom(int64(proto.Cp)) pokemon.Move1 = null.IntFrom(int64(proto.Move1)) pokemon.Move2 = null.IntFrom(int64(proto.Move2)) @@ -1254,6 +1262,7 @@ func (pokemon *Pokemon) setPokemonDisplay(pokemonId int16, display *pogo.Pokemon pokemon.Move2 = null.NewInt(0, false) pokemon.Cp = null.NewInt(0, false) pokemon.Shiny = null.NewBool(false, false) + pokemon.Background = null.NewInt(0, false) pokemon.IsDitto = false pokemon.DisplayPokemonId = null.NewInt(0, false) pokemon.Pvp = null.NewString("", false) From 159667c949a7aa0d8da115067582b8d11e9e7444 Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Wed, 15 Oct 2025 12:03:05 +0200 Subject: [PATCH 29/36] Add files via upload --- sql/50_pokemon_background.up.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 sql/50_pokemon_background.up.sql diff --git a/sql/50_pokemon_background.up.sql b/sql/50_pokemon_background.up.sql new file mode 100644 index 00000000..8dcf5d0a --- /dev/null +++ b/sql/50_pokemon_background.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE pokemon + add `background` smallint unsigned DEFAULT NULL AFTER `shiny`; From 2257b42067e3b87b61b6a315fbe976b88737193a Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Fri, 17 Oct 2025 11:05:42 +0200 Subject: [PATCH 30/36] Update gym.go --- decoder/gym.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/decoder/gym.go b/decoder/gym.go index b3963c77..c1fbd164 100644 --- a/decoder/gym.go +++ b/decoder/gym.go @@ -173,15 +173,15 @@ func calculatePowerUpPoints(fortData *pogo.PokemonFortProto) (null.Int, null.Int func (gym *Gym) updateGymFromFort(fortData *pogo.PokemonFortProto, cellId uint64) *Gym { type pokemonDisplay struct { - Form int `json:"form,omitempty"` - Costume int `json:"costume,omitempty"` - Gender int `json:"gender"` - Shiny bool `json:"shiny,omitempty"` - TempEvolution int `json:"temp_evolution,omitempty"` - TempEvolutionFinishMs int64 `json:"temp_evolution_finish_ms,omitempty"` - Alignment int `json:"alignment,omitempty"` - Badge int `json:"badge,omitempty"` - LocationCard int `json:"location_card,omitempty"` + Form int `json:"form,omitempty"` + Costume int `json:"costume,omitempty"` + Gender int `json:"gender"` + Shiny bool `json:"shiny,omitempty"` + TempEvolution int `json:"temp_evolution,omitempty"` + TempEvolutionFinishMs int64 `json:"temp_evolution_finish_ms,omitempty"` + Alignment int `json:"alignment,omitempty"` + Badge int `json:"badge,omitempty"` + Background *int64 `json:"background,omitempty"` } gym.Id = fortData.FortId gym.Lat = fortData.Latitude //fmt.Sprintf("%f", fortData.Latitude) @@ -200,7 +200,7 @@ func (gym *Gym) updateGymFromFort(fortData *pogo.PokemonFortProto, cellId uint64 TempEvolutionFinishMs: fortData.GuardPokemonDisplay.TemporaryEvolutionFinishMs, Alignment: int(fortData.GuardPokemonDisplay.Alignment), Badge: int(fortData.GuardPokemonDisplay.PokemonBadge), - LocationCard: util.ExtractLocationCardFromDisplay(fortData.GuardPokemonDisplay), + Background: util.ExtractBackgroundFromDisplay(fortData.GuardPokemonDisplay), }) gym.GuardingPokemonDisplay = null.StringFrom(string(display)) } @@ -331,7 +331,7 @@ func (gym *Gym) updateGymFromGymInfoOutProto(gymData *pogo.GymGetInfoOutProto) * TempEvolutionFinishMs int64 `json:"temp_evolution_finish_ms,omitempty"` Alignment int `json:"alignment,omitempty"` Badge int `json:"badge,omitempty"` - LocationCard int `json:"location_card,omitempty"` + Background *int64 `json:"background,omitempty"` DeployedMs int64 `json:"deployed_ms,omitempty"` DeployedTime int64 `json:"deployed_time,omitempty"` BattlesWon int32 `json:"battles_won"` @@ -364,7 +364,7 @@ func (gym *Gym) updateGymFromGymInfoOutProto(gymData *pogo.GymGetInfoOutProto) * TempEvolutionFinishMs: pokemonDisplay.TemporaryEvolutionFinishMs, Alignment: int(pokemonDisplay.Alignment), Badge: int(pokemonDisplay.PokemonBadge), - LocationCard: util.ExtractLocationCardFromDisplay(pokemonDisplay), + Background: util.ExtractBackgroundFromDisplay(pokemonDisplay), Shiny: pokemonDisplay.Shiny, MotivationNow: util.RoundedFloat4(motivatedPokemon.MotivationNow), CpNow: motivatedPokemon.CpNow, From 31d7c863da4059802f7655ed0257c8042d836982 Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Fri, 17 Oct 2025 11:11:11 +0200 Subject: [PATCH 31/36] Update pokemon.go --- decoder/pokemon.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/decoder/pokemon.go b/decoder/pokemon.go index fbed4934..5e44db81 100644 --- a/decoder/pokemon.go +++ b/decoder/pokemon.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "golbat/util" "strconv" "strings" "sync" @@ -1250,6 +1251,7 @@ func (pokemon *Pokemon) setPokemonDisplay(pokemonId int16, display *pogo.Pokemon if oldId != pokemonId || pokemon.Form != null.IntFrom(int64(display.Form)) || pokemon.Costume != null.IntFrom(int64(display.Costume)) || pokemon.Gender != null.IntFrom(int64(display.Gender)) || + pokemon.Background != null.IntFromPtr(util.ExtractBackgroundFromDisplay(display)) || pokemon.IsStrong.ValueOrZero() != display.IsStrongPokemon { log.Debugf("Pokemon %d changed from (%d,%d,%d,%d,%t) to (%d,%d,%d,%d,%t)", pokemon.Id, oldId, pokemon.Form.ValueOrZero(), pokemon.Costume.ValueOrZero(), pokemon.Gender.ValueOrZero(), @@ -1274,6 +1276,7 @@ func (pokemon *Pokemon) setPokemonDisplay(pokemonId int16, display *pogo.Pokemon pokemon.Gender = null.IntFrom(int64(display.Gender)) pokemon.Form = null.IntFrom(int64(display.Form)) pokemon.Costume = null.IntFrom(int64(display.Costume)) + pokemon.Background = null.IntFromPtr(util.ExtractBackgroundFromDisplay(display)) if !pokemon.isNewRecord() { pokemon.repopulateIv(int64(display.WeatherBoostedCondition), display.IsStrongPokemon) } From 8ff3831c727c1b69853a35e57ca23d5ff2873349 Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Fri, 17 Oct 2025 11:15:33 +0200 Subject: [PATCH 32/36] Update pokemon.go --- decoder/pokemon.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/decoder/pokemon.go b/decoder/pokemon.go index 5e44db81..6488965d 100644 --- a/decoder/pokemon.go +++ b/decoder/pokemon.go @@ -1110,9 +1110,6 @@ func (pokemon *Pokemon) clearIv(cp bool) { func (pokemon *Pokemon) addEncounterPokemon(ctx context.Context, db db.DbDetails, proto *pogo.PokemonProto, username string) { pokemon.Username = null.StringFrom(username) pokemon.Shiny = null.BoolFrom(proto.PokemonDisplay.Shiny) - if proto.PokemonDisplay.LocationCard != nil { - pokemon.Background = null.IntFrom(int64(proto.PokemonDisplay.LocationCard.LocationCard.Number())) - } pokemon.Cp = null.IntFrom(int64(proto.Cp)) pokemon.Move1 = null.IntFrom(int64(proto.Move1)) pokemon.Move2 = null.IntFrom(int64(proto.Move2)) @@ -1264,7 +1261,6 @@ func (pokemon *Pokemon) setPokemonDisplay(pokemonId int16, display *pogo.Pokemon pokemon.Move2 = null.NewInt(0, false) pokemon.Cp = null.NewInt(0, false) pokemon.Shiny = null.NewBool(false, false) - pokemon.Background = null.NewInt(0, false) pokemon.IsDitto = false pokemon.DisplayPokemonId = null.NewInt(0, false) pokemon.Pvp = null.NewString("", false) From dacee147e56e29df43bb3fba304cdd62d718df4f Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Fri, 17 Oct 2025 11:17:09 +0200 Subject: [PATCH 33/36] Update pokestop.go --- decoder/pokestop.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/decoder/pokestop.go b/decoder/pokestop.go index f0afa989..ee910222 100644 --- a/decoder/pokestop.go +++ b/decoder/pokestop.go @@ -454,8 +454,8 @@ func (stop *Pokestop) updatePokestopFromQuestProto(questProto *pogo.FortSearchOu if display.Shiny { infoData["shiny"] = display.Shiny } - if locationCard := util.ExtractLocationCardFromDisplay(display); locationCard != 0 { - infoData["location_card"] = locationCard + if background := util.ExtractBackgroundFromDisplay(display); background != nil { + infoData["background"] = background } if breadMode := int(display.BreadModeEnum); breadMode != 0 { infoData["bread_mode"] = breadMode @@ -609,7 +609,7 @@ func (stop *Pokestop) updatePokestopFromGetPokemonSizeContestEntryOutProto(conte TempEvolutionFinishMs int64 `json:"temp_evolution_finish_ms"` Alignment int `json:"alignment"` Badge int `json:"badge"` - LocationCard int `json:"location_card"` + Background *int64 `json:"background,omitempty"` } type contestJson struct { TotalEntries int `json:"total_entries"` @@ -637,7 +637,7 @@ func (stop *Pokestop) updatePokestopFromGetPokemonSizeContestEntryOutProto(conte TempEvolutionFinishMs: entry.GetPokemonDisplay().TemporaryEvolutionFinishMs, Alignment: int(entry.GetPokemonDisplay().Alignment), Badge: int(entry.GetPokemonDisplay().PokemonBadge), - LocationCard: util.ExtractLocationCardFromDisplay(entry.PokemonDisplay), + Background: util.ExtractBackgroundFromDisplay(entry.PokemonDisplay), }) } From e2e331c128f72fe25dab6607538f45ada7dd021b Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Fri, 17 Oct 2025 11:18:39 +0200 Subject: [PATCH 34/36] Update pogo.go --- util/pogo.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/util/pogo.go b/util/pogo.go index dc92af7b..a4634634 100644 --- a/util/pogo.go +++ b/util/pogo.go @@ -27,9 +27,10 @@ var IncidentTypeToName = map[int8]string{ 9: "showcase", } -func ExtractLocationCardFromDisplay(display *pogo.PokemonDisplayProto) int { +func ExtractBackgroundFromDisplay(display *pogo.PokemonDisplayProto) *int64 { if display.LocationCard == nil { - return 0 + return nil } - return int(display.LocationCard.LocationCard) + result := int64(display.LocationCard.LocationCard) + return &result } From fb187255a85a607a36bead30b2f7bb13bbc75266 Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Sun, 19 Oct 2025 11:46:26 +0200 Subject: [PATCH 35/36] Update api_pokemon_common.go --- decoder/api_pokemon_common.go | 107 +++++++++++++++++----------------- 1 file changed, 53 insertions(+), 54 deletions(-) diff --git a/decoder/api_pokemon_common.go b/decoder/api_pokemon_common.go index d492a0f0..d62381a0 100644 --- a/decoder/api_pokemon_common.go +++ b/decoder/api_pokemon_common.go @@ -1,61 +1,60 @@ package decoder import ( - "github.com/UnownHash/gohbem" + "github.com/UnownHash/gohbem" ) func buildApiPokemonResult(pokemon *Pokemon) ApiPokemonResult { - return ApiPokemonResult{ - Id: pokemon.Id, - PokestopId: pokemon.PokestopId, - SpawnId: pokemon.SpawnId, - Lat: pokemon.Lat, - Lon: pokemon.Lon, - Weight: pokemon.Weight, - Size: pokemon.Size, - Height: pokemon.Height, - ExpireTimestamp: pokemon.ExpireTimestamp, - Updated: pokemon.Updated, - PokemonId: pokemon.PokemonId, - Move1: pokemon.Move1, - Move2: pokemon.Move2, - Gender: pokemon.Gender, - Cp: pokemon.Cp, - AtkIv: pokemon.AtkIv, - DefIv: pokemon.DefIv, - StaIv: pokemon.StaIv, - Iv: pokemon.Iv, - Form: pokemon.Form, - Level: pokemon.Level, - Weather: pokemon.Weather, - Costume: pokemon.Costume, - FirstSeenTimestamp: pokemon.FirstSeenTimestamp, - Changed: pokemon.Changed, - CellId: pokemon.CellId, - ExpireTimestampVerified: pokemon.ExpireTimestampVerified, - DisplayPokemonId: pokemon.DisplayPokemonId, - IsDitto: pokemon.IsDitto, - SeenType: pokemon.SeenType, - Shiny: pokemon.Shiny, - Username: pokemon.Username, - Pvp: func() map[string][]gohbem.PokemonEntry { - if ohbem != nil { - pvp, err := ohbem.QueryPvPRank(int(pokemon.PokemonId), - int(pokemon.Form.ValueOrZero()), - int(pokemon.Costume.ValueOrZero()), - int(pokemon.Gender.ValueOrZero()), - int(pokemon.AtkIv.ValueOrZero()), - int(pokemon.DefIv.ValueOrZero()), - int(pokemon.StaIv.ValueOrZero()), - float64(pokemon.Level.ValueOrZero())) - if err != nil { - return nil - } - return pvp - } - return nil - }(), - } + return ApiPokemonResult{ + Id: pokemon.Id, + PokestopId: pokemon.PokestopId, + SpawnId: pokemon.SpawnId, + Lat: pokemon.Lat, + Lon: pokemon.Lon, + Weight: pokemon.Weight, + Size: pokemon.Size, + Height: pokemon.Height, + ExpireTimestamp: pokemon.ExpireTimestamp, + Updated: pokemon.Updated, + PokemonId: pokemon.PokemonId, + Move1: pokemon.Move1, + Move2: pokemon.Move2, + Gender: pokemon.Gender, + Cp: pokemon.Cp, + AtkIv: pokemon.AtkIv, + DefIv: pokemon.DefIv, + StaIv: pokemon.StaIv, + Iv: pokemon.Iv, + Form: pokemon.Form, + Level: pokemon.Level, + Weather: pokemon.Weather, + Costume: pokemon.Costume, + FirstSeenTimestamp: pokemon.FirstSeenTimestamp, + Changed: pokemon.Changed, + CellId: pokemon.CellId, + ExpireTimestampVerified: pokemon.ExpireTimestampVerified, + DisplayPokemonId: pokemon.DisplayPokemonId, + IsDitto: pokemon.IsDitto, + SeenType: pokemon.SeenType, + Shiny: pokemon.Shiny, + Background: pokemon.Background, + Username: pokemon.Username, + Pvp: func() map[string][]gohbem.PokemonEntry { + if ohbem != nil { + pvp, err := ohbem.QueryPvPRank(int(pokemon.PokemonId), + int(pokemon.Form.ValueOrZero()), + int(pokemon.Costume.ValueOrZero()), + int(pokemon.Gender.ValueOrZero()), + int(pokemon.AtkIv.ValueOrZero()), + int(pokemon.DefIv.ValueOrZero()), + int(pokemon.StaIv.ValueOrZero()), + float64(pokemon.Level.ValueOrZero())) + if err != nil { + return nil + } + return pvp + } + return nil + }(), + } } - - From 9f8b30a6d4a79284dfd62f1525f59d0d3f81ac6d Mon Sep 17 00:00:00 2001 From: ReuschelCGN <82573872+ReuschelCGN@users.noreply.github.com> Date: Sun, 19 Oct 2025 11:47:24 +0200 Subject: [PATCH 36/36] Update api_pokemon_scan_v1.go --- decoder/api_pokemon_scan_v1.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/decoder/api_pokemon_scan_v1.go b/decoder/api_pokemon_scan_v1.go index 073c6b1d..de72c8ff 100644 --- a/decoder/api_pokemon_scan_v1.go +++ b/decoder/api_pokemon_scan_v1.go @@ -48,6 +48,7 @@ type ApiPokemonResult struct { IsDitto bool `json:"is_ditto"` SeenType null.String `json:"seen_type"` Shiny null.Bool `json:"shiny"` + Background null.Int `json:"background"` Username null.String `json:"username"` Capture1 null.Float `json:"capture_1"` Capture2 null.Float `json:"capture_2"` @@ -308,6 +309,7 @@ func GetPokemonInArea(retrieveParameters ApiPokemonScan) []*ApiPokemonResult { IsDitto: pokemon.IsDitto, SeenType: pokemon.SeenType, Shiny: pokemon.Shiny, + Background: pokemon.Background, Username: pokemon.Username, Pvp: func() map[string][]gohbem.PokemonEntry { if ohbem != nil {