From 999be22fc7412d5a2899aeeebc5e2c06597feb0d Mon Sep 17 00:00:00 2001 From: Martin Bruse Date: Sun, 3 May 2020 02:14:54 +0200 Subject: [PATCH 1/4] Some telegram work. --- game/handler.go | 14 ++++++++++---- game/telegram.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 game/telegram.go diff --git a/game/handler.go b/game/handler.go index 0ff4160..09b8721 100644 --- a/game/handler.go +++ b/game/handler.go @@ -567,10 +567,11 @@ func createAllocation(w ResponseWriter, r Request) (*Allocation, error) { } type configuration struct { - OAuth *auth.OAuth - FCMConf *FCMConf - SendGrid *SendGrid - Superusers *auth.Superusers + OAuth *auth.OAuth + FCMConf *FCMConf + SendGrid *SendGrid + Superusers *auth.Superusers + TelegramConf *TelegramConf } func handleConfigure(w ResponseWriter, r Request) error { @@ -600,6 +601,11 @@ func handleConfigure(w ResponseWriter, r Request) error { return err } } + if conf.TelegramConf != nil { + if err := SetTelegramConf(ctx, conf.TelegramConf); err != nil { + return err + } + } return nil } diff --git a/game/telegram.go b/game/telegram.go new file mode 100644 index 0000000..51d83d9 --- /dev/null +++ b/game/telegram.go @@ -0,0 +1,33 @@ +package game + +import ( + "net/http" + + "golang.org/x/net/context" + "google.golang.org/appengine/datastore" +) + +const ( + telegramConfKind = "TelegramConf" +) + +type TelegramConf struct { + BotToken string +} + +func getTelegramConfKey(ctx context.Context) *datastore.Key { + return datastore.NewKey(ctx, telegramConfKind, prodKey, 0, nil) +} + +func SetTelegramConf(ctx context.Context, telegramConf *TelegramConf) error { + return datastore.RunInTransaction(ctx, func(ctx context.Context) error { + currentTelegramConf := &TelegramConf{} + if err := datastore.Get(ctx, getTelegramConfKey(ctx), currentTelegramConf); err == nil { + return HTTPErr{"TelegramConf already configured", http.StatusBadRequest} + } + if _, err := datastore.Put(ctx, getTelegramConfKey(ctx), telegramConf); err != nil { + return err + } + return nil + }, &datastore.TransactionOptions{XG: false}) +} From 05b1102aaa17d5bc987d70000caf8be3eef1b419 Mon Sep 17 00:00:00 2001 From: Martin Bruse Date: Sun, 3 May 2020 03:13:24 +0200 Subject: [PATCH 2/4] More telegram work. --- auth/user_config.go | 48 +++++++++++++++++++++++++++++++++++++---- game/handler.go | 2 ++ game/telegram.go | 52 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 4 deletions(-) diff --git a/auth/user_config.go b/auth/user_config.go index af99caf..2a8e752 100644 --- a/auth/user_config.go +++ b/auth/user_config.go @@ -124,6 +124,29 @@ func (m *MailNotificationConfig) Customize(ctx context.Context, msg *sendgrid.SG } } +type TelegramNotificationConfig struct { + BodyTemplate string `methods:"PUT" datastore:",noindex"` +} + +func (m *TelegramNotificationConfig) Validate() error { + if m.BodyTemplate != "" { + if _, err := raymond.Parse(m.BodyTemplate); err != nil { + return err + } + } + return nil +} + +func (m *TelegramNotificationConfig) Customize(ctx context.Context, msg *string, data interface{}) { + if m.BodyTemplate != "" { + if customTextBody, err := raymond.Render(m.BodyTemplate, data); err == nil { + *msg = customTextBody + } else { + log.Infof(ctx, "Broken BodyTemplate %q: %v", m.BodyTemplate, err) + } + } +} + type FCMToken struct { Value string `methods:"PUT"` Disabled bool `methods:"PUT"` @@ -173,12 +196,29 @@ func (m *MailConfig) Validate() error { return nil } +type TelegramConfig struct { + Enabled bool `methods:"PUT"` + MessageConfig TelegramNotificationConfig `methods:"PUT"` + PhaseConfig TelegramNotificationConfig `methods:"PUT"` +} + +func (m *TelegramConfig) Validate() error { + if err := m.MessageConfig.Validate(); err != nil { + return err + } + if err := m.PhaseConfig.Validate(); err != nil { + return err + } + return nil +} + type UserConfig struct { UserId string - FCMTokens []FCMToken `methods:"PUT"` - MailConfig MailConfig `methods:"PUT"` - Colors []string `methods:"PUT"` - PhaseDeadlineWarningMinutesAhead int `methods:"PUT"` + FCMTokens []FCMToken `methods:"PUT"` + MailConfig MailConfig `methods:"PUT"` + TelegramConfig TelegramConfig `methods:"PUT"` + Colors []string `methods:"PUT"` + PhaseDeadlineWarningMinutesAhead int `methods:"PUT"` } var UserConfigResource = &Resource{ diff --git a/game/handler.go b/game/handler.go index 09b8721..1700351 100644 --- a/game/handler.go +++ b/game/handler.go @@ -110,6 +110,7 @@ const ( RemoveDIASFromSoloGamesRoute = "RemoveDIASFromSoloGamesRoute" ReComputeAllDIASUsersRoute = "ReComputeAllDIASUsers" SendSystemMessageRoute = "SendSystemMessage" + TelegramWebhookRoute = "TelegramWebhook" ) type userStatsHandler struct { @@ -1262,6 +1263,7 @@ func ejectMember(ctx context.Context, gameID *datastore.Key, userId string) erro func SetupRouter(r *mux.Router) { router = r + Handle(r, "/_telegram_webhook", []string{"POST"}, TelegramWebhookRoute, handleTelegramWebhook) Handle(r, "/_reap-inactive-waiting-players", []string{"GET"}, ReapInactiveWaitingPlayersRoute, handleReapInactiveWaitingPlayers) Handle(r, "/_test_reap-inactive-waiting-players", []string{"GET"}, TestReapInactiveWaitingPlayersRoute, handleTestReapInactiveWaitingPlayers) Handle(r, "/_re-save", []string{"GET"}, ReSaveRoute, handleReSave) diff --git a/game/telegram.go b/game/telegram.go index 51d83d9..b8d4889 100644 --- a/game/telegram.go +++ b/game/telegram.go @@ -1,10 +1,15 @@ package game import ( + "encoding/json" "net/http" "golang.org/x/net/context" + "google.golang.org/appengine" "google.golang.org/appengine/datastore" + "google.golang.org/appengine/log" + + . "github.com/zond/goaeoas" ) const ( @@ -31,3 +36,50 @@ func SetTelegramConf(ctx context.Context, telegramConf *TelegramConf) error { return nil }, &datastore.TransactionOptions{XG: false}) } + +type TelegramUser struct { + ID int64 `json:"id"` + IsBot bool `json:"is_bot"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + LanguageCode string `json:"language_code"` +} + +type TelegramChat struct { + ID int64 `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Type string `json:"type"` +} + +type TelegramEntity struct { + Offset int64 `json:"offset"` + Length int64 `json:"length"` + Type string `json:"type"` +} + +type TelegramMessage struct { + MessageID int64 `json:"message"` + From TelegramUser `json:"from"` + Chat TelegramChat `json:"chat"` + Date int64 `json:"date"` + Text string `json:"text"` + Entities []TelegramEntity `json:"entities"` +} + +type TelegramUpdate struct { + UpdateID int64 `json:"update_id"` + Message TelegramMessage `json:"message"` +} + +func handleTelegramWebhook(w ResponseWriter, r Request) error { + ctx := appengine.NewContext(r.Req()) + + update := &TelegramUpdate{} + if err := json.NewDecoder(r.Req().Body).Decode(update); err != nil { + return err + } + + log.Infof(ctx, "Got telegram update %+v", update) + return nil +} From 798191fb0246bfde1e847491d0791eec552eb80e Mon Sep 17 00:00:00 2001 From: Martin Bruse Date: Wed, 6 May 2020 11:17:43 +0200 Subject: [PATCH 3/4] Added possibility to change SC colors, and outline colors of units and orders. Fixes #147, #148, and #149. --- static/js/dippymap.js | 266 +++++++++++++++++++++++------------------- 1 file changed, 143 insertions(+), 123 deletions(-) diff --git a/static/js/dippymap.js b/static/js/dippymap.js index 8f33888..41c182c 100644 --- a/static/js/dippymap.js +++ b/static/js/dippymap.js @@ -10,109 +10,109 @@ function dippyMap(container) { }; // Based on http://godsnotwheregodsnot.blogspot.se/2012/09/color-distribution-methodology.html. that.contrasts = [ - "#F44336", - "#2196F3", - "#80DEEA", - "#90A4AE", - "#4CAF50", - "#FFC107", - "#F5F5F5", - "#009688", - "#FFEB3B", - "#795548", - "#E91E63", - "#CDDC39", - "#FF9800", - "#D05CE3", - "#9A67EA", - "#FF6090", - "#6EC6FF", - "#80E27E", - "#A98274", - "#CFCFCF", - "#FF34FF", - "#1CE6FF", - "#FFDBE5", - "#FF7961", - "#C66900", - "#9C27B0", - "#3F51B5", - "#C8B900", - "#C2185B", - "#BA000D", - "#607D8B", - "#087F23", - "#673AB7", - "#0069C0", - "#34515E", - "#002984", - "#004C40", - "#FFFF6E", - "#B4FFFF", - "#6A0080", - "#757DE8", - "#04F757", - "#CEFDAE", - "#974D2B", - "#974D2B", - "#FF2F80", - "#0CBD66", - "#FF90C9", - "#BEC459", - "#0086ED", - "#FFB500", - "#0AA6D8", - "#A05837", - "#EEC3FF", - "#456648", - "#D790FF", - "#6A3A4C", - "#324E72", - "#A4E804", - "#CB7E98", - "#0089A3", - "#404E55", - "#FDE8DC", - "#5B4534", - "#922329", - "#3A2465", - "#99ADC0", - "#BC23FF", - "#72418F", - "#201625", - "#FFF69F", - "#549E79", - "#9B9700", - "#772600", - "#6B002C", - "#6367A9", - "#A77500", - "#7900D7", - "#1E6E00", - "#C8A1A1", - "#885578", - "#788D66", - "#7A87A1", - "#B77B68", - "#456D75", - "#6F0062", - "#00489C", - "#001E09", - "#C2FF99", - "#C0B9B2", - "#CC0744", - "#A079BF", - "#C2FFED", - "#372101", - "#00846F", - "#013349", - "#300018", - "#A1C299", - "#7B4F4B", - "#000035", - "#DDEFFF", - "#D16100", - "#B903AA" + "#F44336", + "#2196F3", + "#80DEEA", + "#90A4AE", + "#4CAF50", + "#FFC107", + "#F5F5F5", + "#009688", + "#FFEB3B", + "#795548", + "#E91E63", + "#CDDC39", + "#FF9800", + "#D05CE3", + "#9A67EA", + "#FF6090", + "#6EC6FF", + "#80E27E", + "#A98274", + "#CFCFCF", + "#FF34FF", + "#1CE6FF", + "#FFDBE5", + "#FF7961", + "#C66900", + "#9C27B0", + "#3F51B5", + "#C8B900", + "#C2185B", + "#BA000D", + "#607D8B", + "#087F23", + "#673AB7", + "#0069C0", + "#34515E", + "#002984", + "#004C40", + "#FFFF6E", + "#B4FFFF", + "#6A0080", + "#757DE8", + "#04F757", + "#CEFDAE", + "#974D2B", + "#974D2B", + "#FF2F80", + "#0CBD66", + "#FF90C9", + "#BEC459", + "#0086ED", + "#FFB500", + "#0AA6D8", + "#A05837", + "#EEC3FF", + "#456648", + "#D790FF", + "#6A3A4C", + "#324E72", + "#A4E804", + "#CB7E98", + "#0089A3", + "#404E55", + "#FDE8DC", + "#5B4534", + "#922329", + "#3A2465", + "#99ADC0", + "#BC23FF", + "#72418F", + "#201625", + "#FFF69F", + "#549E79", + "#9B9700", + "#772600", + "#6B002C", + "#6367A9", + "#A77500", + "#7900D7", + "#1E6E00", + "#C8A1A1", + "#885578", + "#788D66", + "#7A87A1", + "#B77B68", + "#456D75", + "#6F0062", + "#00489C", + "#001E09", + "#C2FF99", + "#C0B9B2", + "#CC0744", + "#A079BF", + "#C2FFED", + "#372101", + "#00846F", + "#013349", + "#300018", + "#A1C299", + "#7B4F4B", + "#000035", + "#DDEFFF", + "#D16100", + "#B903AA" ]; that.contrastNeutral = "#f4d7b5"; that.Poi = function(x, y) { @@ -183,6 +183,9 @@ function dippyMap(container) { container[0].appendChild(source[0]); el = container.find("svg")[0]; }; + that.colorSC = function(province, color) { + $(el).find("#" + province + "Center")[0].style.stroke = color; + }; that.colorProvince = function(province, color) { var path = $(el).find("#" + that.selEscape(province))[0]; path.removeAttribute("style"); @@ -297,7 +300,7 @@ function dippyMap(container) { }); } }; - that.addBox = function(province, corners, color) { + that.addBox = function(province, corners, color, opts = {}) { var loc = that.centerOf(province); loc.x -= 3; loc.y -= 3; @@ -313,7 +316,9 @@ function dippyMap(container) { "style", "fill-rule:evenodd;fill:" + color + - ";stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-opacity:1.0;fill-opacity:0.9;" + ";stroke:" + + (opts.stroke || "#000000") + + ";stroke-width:0.5;stroke-miterlimit:4;stroke-opacity:1.0;fill-opacity:0.9;" ); var d = ""; var subBox = function(boundF) { @@ -339,7 +344,7 @@ function dippyMap(container) { .find("#orders")[0] .appendChild(path); }; - that.addArrow = function(provs, color) { + that.addArrow = function(provs, color, opts = {}) { var start = null; var middle = null; var end = null; @@ -388,7 +393,9 @@ function dippyMap(container) { "style", "fill:" + color + - ";stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-opacity:1.0;fill-opacity:0.7;" + ";stroke:" + + (opts.stroke || "#000000") + + ";stroke-width:0.5;stroke-miterlimit:4;stroke-opacity:1.0;fill-opacity:0.7;" ); var d = "M " + start0.x + "," + start0.y; d += @@ -427,7 +434,7 @@ function dippyMap(container) { .find("#orders")[0] .appendChild(path); }; - that.addCross = function(province, color) { + that.addCross = function(province, color, opts = {}) { var bound = 14; var width = 4; var loc = that.centerOf(province); @@ -438,7 +445,9 @@ function dippyMap(container) { "style", "fill:" + color + - ";stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-opacity:1.0;fill-opacity:0.9;" + ";stroke:" + + (opts.stroke || "#000000") + + ";stroke-width:0.5;stroke-miterlimit:4;stroke-opacity:1.0;fill-opacity:0.9;" ); var d = "M " + @@ -511,28 +520,36 @@ function dippyMap(container) { .find("#orders") .empty(); }; - that.addOrder = function(order, color) { + that.addOrder = function(order, color, opts = {}) { if (order[1] == "Hold") { - addBox(order[0], 4, color); + addBox(order[0], 4, color, opts); } else if (order[1] == "Move") { - addArrow([order[0], order[2]], color); + addArrow([order[0], order[2]], color, opts); } else if (order[1] == "MoveViaConvoy") { - addArrow([order[0], order[2]], color); - addBox(order[0], 5, color); + addArrow([order[0], order[2]], color, opts); + addBox(order[0], 5, color, opts); } else if (order[1] == "Build") { - addUnit("unit" + order[2], order[0], color, false, true, "#orders"); + addUnit( + "unit" + order[2], + order[0], + color, + false, + true, + "#orders", + opts + ); } else if (order[1] == "Disband") { - addCross(order[0], color); + addCross(order[0], color, opts); } else if (order[1] == "Convoy") { - addBox(order[0], 5, color); - addArrow([order[2], order[0], order[3]], color); + addBox(order[0], 5, color, opts); + addArrow([order[2], order[0], order[3]], color, opts); } else if (order[1] == "Support") { if (order.length == 3) { - addBox(order[0], 3, color); - addArrow([order[2], order[3]], color); + addBox(order[0], 3, color, opts); + addArrow([order[2], order[3]], color, opts); } else { addBox(order[0], 3, color); - addArrow([order[0], order[2], order[3]], color); + addArrow([order[0], order[2], order[3]], color, opts); } } }; @@ -550,7 +567,8 @@ function dippyMap(container) { color, dislodged, build, - layer + layer, + opts = {} ) { if (typeof layer === "undefined") { layer = "#units"; @@ -590,7 +608,9 @@ function dippyMap(container) { color + ";fill-opacity:" + opacity + - ";stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" + ";stroke:" + + (opts.stroke || "#000000") + + ";stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" ); $(el) .find(layer)[0] From 3d30b4242a160365a497056bbf7d170d918a2eaa Mon Sep 17 00:00:00 2001 From: Martin Bruse Date: Thu, 7 May 2020 10:53:11 +0200 Subject: [PATCH 4/4] Made the FCM data content 'gameDesc' be the customized desc for the recipient. --- game/phase.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/phase.go b/game/phase.go index 4fef96a..9023d04 100644 --- a/game/phase.go +++ b/game/phase.go @@ -167,7 +167,7 @@ func getPhaseNotificationContext(ctx context.Context, host string, gameID *datas res.fcmData = map[string]interface{}{ "type": "phase", "gameID": res.game.ID, - "gameDesc": res.game.Desc, + "gameDesc": res.game.DescFor(res.member.Nation), "phaseMeta": res.phase.PhaseMeta, }