Skip to content

Commit 1a87425

Browse files
authored
feat: store multiple prices per entity (depending on the url/location)
Merge pull request #24 from d-Rickyy-b/fix-urls
2 parents 800c472 + adb3b26 commit 1a87425

19 files changed

Lines changed: 673 additions & 388 deletions

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99
### Added
10+
- Store prices per location per entity
11+
- Allow skinflint.co.uk links
12+
- Ability to disable price agents (no UI yet)
13+
1014
### Changed
15+
- Display price agent name (& link) in price history graph caption
16+
- Display currency in price history graph
17+
- Send new message with price agent details when clicking on the button for the price notification message
18+
1119
### Fixed
12-
### Docs
20+
- Improve graph visibility
1321

1422
## [1.1.0] - 2022-03-28
1523

cmd/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import (
44
"GoGeizhalsBot/internal/bot"
55
"GoGeizhalsBot/internal/config"
66
"GoGeizhalsBot/internal/database"
7-
"GoGeizhalsBot/internal/geizhals"
87
"GoGeizhalsBot/internal/logging"
8+
"GoGeizhalsBot/internal/proxy"
99
"flag"
1010
"log"
1111
"net/url"
@@ -35,6 +35,6 @@ func main() {
3535
log.Println("Loaded proxies:", len(proxies))
3636
}
3737

38-
geizhals.InitProxies(proxies)
38+
proxy.InitProxies(proxies)
3939
bot.Start(botConfig)
4040
}

internal/bot/bot.go

Lines changed: 40 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -179,16 +179,11 @@ func mainMenuHandler(b *gotgbot.Bot, ctx *ext.Context) error {
179179
func showPriceagentDetail(b *gotgbot.Bot, ctx *ext.Context) error {
180180
cb := ctx.Update.CallbackQuery
181181

182-
menu, parseErr := models.NewMenu(cb.Data)
182+
menu, priceagent, parseErr := parseMenuPriceagent(ctx)
183183
if parseErr != nil {
184184
return fmt.Errorf("showPriceagentDetail: failed to parse callback data: %w", parseErr)
185185
}
186186

187-
priceagent, dbErr := database.GetPriceagentForUserByID(ctx.EffectiveUser.Id, menu.PriceAgent)
188-
if dbErr != nil {
189-
return fmt.Errorf("showPriceagentDetail: failed to get priceagent from database: %w", dbErr)
190-
}
191-
192187
var backCallbackData string
193188
switch {
194189
case priceagent.Entity.Type == geizhals.Wishlist:
@@ -205,8 +200,9 @@ func showPriceagentDetail(b *gotgbot.Bot, ctx *ext.Context) error {
205200

206201
notificationButtonText := fmt.Sprintf("⏰ %s", priceagent.NotificationSettings.String())
207202

208-
linkName := createLink(priceagent.Entity.URL, priceagent.Entity.Name)
209-
editedText := fmt.Sprintf("%s kostet aktuell %s", linkName, bold(createPrice(priceagent.Entity.Price)))
203+
linkName := createLink(priceagent.EntityURL(), priceagent.Entity.Name)
204+
price := priceagent.CurrentEntityPrice()
205+
editedText := fmt.Sprintf("%s kostet aktuell %s", linkName, bold(price.String()))
210206
markup := gotgbot.InlineKeyboardMarkup{
211207
InlineKeyboard: [][]gotgbot.InlineKeyboardButton{
212208
{
@@ -224,19 +220,19 @@ func showPriceagentDetail(b *gotgbot.Bot, ctx *ext.Context) error {
224220
case "00":
225221
_, err := cb.Message.EditText(b, editedText, &gotgbot.EditMessageTextOpts{ReplyMarkup: markup, ParseMode: "HTML"})
226222
if err != nil {
227-
return fmt.Errorf("showPriceagent: failed to edit message text: %w", err)
223+
return fmt.Errorf("showPriceagentDetail: failed to edit message text: %w", err)
228224
}
229225
case "01":
230226
bot.DeleteMessage(ctx.EffectiveChat.Id, cb.Message.MessageId)
231227

232228
_, err := b.SendMessage(ctx.EffectiveChat.Id, editedText, &gotgbot.SendMessageOpts{ReplyMarkup: markup, ParseMode: "HTML"})
233229
if err != nil {
234-
return fmt.Errorf("showPriceagent: failed to send new message: %w", err)
230+
return fmt.Errorf("showPriceagentDetail: failed to send new message: %w", err)
235231
}
236232
case "02":
237233
_, err := b.SendMessage(ctx.EffectiveChat.Id, editedText, &gotgbot.SendMessageOpts{ReplyMarkup: markup, ParseMode: "HTML"})
238234
if err != nil {
239-
return fmt.Errorf("showPriceagent: failed to send new message: %w", err)
235+
return fmt.Errorf("showPriceagentDetail: failed to send new message: %w", err)
240236
}
241237
}
242238
return nil
@@ -247,22 +243,18 @@ func showPriceagentDetail(b *gotgbot.Bot, ctx *ext.Context) error {
247243
func changePriceagentSettingsHandler(b *gotgbot.Bot, ctx *ext.Context) error {
248244
cb := ctx.Update.CallbackQuery
249245

250-
priceagentID, parseErr := parseIDFromCallbackData(cb.Data, "m04_00_")
246+
_, priceagent, parseErr := parseMenuPriceagent(ctx)
251247
if parseErr != nil {
252-
return fmt.Errorf("changePriceagentSettingsHandler: failed to parse priceagentID from callback data: %w", parseErr)
253-
}
254-
255-
priceagent, dbErr := database.GetPriceagentForUserByID(ctx.EffectiveUser.Id, priceagentID)
256-
if dbErr != nil {
257-
return fmt.Errorf("setNotifPriceagentHandler: failed to get priceagent from database: %w", dbErr)
248+
return fmt.Errorf("changePriceagentSettingsHandler: failed to parse callback data: %w", parseErr)
258249
}
259250

260251
if _, err := cb.Answer(b, &gotgbot.AnswerCallbackQueryOpts{}); err != nil {
261252
return fmt.Errorf("changePriceagentSettingsHandler: failed to answer callback query: %w", err)
262253
}
263254

264-
linkName := createLink(priceagent.Entity.URL, priceagent.Entity.Name)
265-
editedText := fmt.Sprintf("%s\n\nWann möchtest du für %s alarmiert werden?\n\nAktuelle Einstellung: %s\nAktueller Preis: %s", bold("Benachrichtigungseinstellungen"), linkName, bold(priceagent.NotificationSettings.String()), bold(createPrice(priceagent.Entity.Price)))
255+
linkName := createLink(priceagent.EntityURL(), priceagent.Entity.Name)
256+
price := priceagent.CurrentEntityPrice()
257+
editedText := fmt.Sprintf("%s\n\nWann möchtest du für %s alarmiert werden?\n\nAktuelle Einstellung: %s\nAktueller Preis: %s", bold("Benachrichtigungseinstellungen"), linkName, bold(priceagent.NotificationSettings.String()), bold(price.String()))
266258
markup := gotgbot.InlineKeyboardMarkup{
267259
InlineKeyboard: [][]gotgbot.InlineKeyboardButton{
268260
{
@@ -284,21 +276,16 @@ func changePriceagentSettingsHandler(b *gotgbot.Bot, ctx *ext.Context) error {
284276
func deletePriceagentConfirmationHandler(b *gotgbot.Bot, ctx *ext.Context) error {
285277
cb := ctx.Update.CallbackQuery
286278

287-
priceagentID, parseErr := parseIDFromCallbackData(cb.Data, "m04_98_")
279+
_, priceagent, parseErr := parseMenuPriceagent(ctx)
288280
if parseErr != nil {
289-
return fmt.Errorf("deletePriceagentConfirmationHandler: failed to parse priceagentID from callback data: %w", parseErr)
290-
}
291-
292-
priceagent, dbErr := database.GetPriceagentForUserByID(ctx.EffectiveUser.Id, priceagentID)
293-
if dbErr != nil {
294-
return fmt.Errorf("deletePriceagentConfirmationHandler: failed to get priceagent from database: %w", dbErr)
281+
return fmt.Errorf("deletePriceagentConfirmationHandler: failed to parse callback data: %w", parseErr)
295282
}
296283

297284
if _, err := cb.Answer(b, &gotgbot.AnswerCallbackQueryOpts{}); err != nil {
298285
return fmt.Errorf("deletePriceagentConfirmationHandler: failed to answer callback query: %w", err)
299286
}
300287

301-
linkName := createLink(priceagent.Entity.URL, priceagent.Entity.Name)
288+
linkName := createLink(priceagent.EntityURL(), priceagent.Entity.Name)
302289
editedText := fmt.Sprintf("%s\n\nMöchtest du den Preisagenten für %s wirklich löschen?", bold("Löschen bestätigen"), linkName)
303290
markup := gotgbot.InlineKeyboardMarkup{
304291
InlineKeyboard: [][]gotgbot.InlineKeyboardButton{
@@ -324,15 +311,10 @@ func deletePriceagentHandler(b *gotgbot.Bot, ctx *ext.Context) error {
324311
}
325312

326313
// Get Priceagent from DB
327-
priceagentID, parseErr := parseIDFromCallbackData(cb.Data, "m04_99_")
314+
_, priceagent, parseErr := parseMenuPriceagent(ctx)
328315
if parseErr != nil {
329-
return fmt.Errorf("deletePriceagentHandler: failed to parse priceagentID from callback data: %w", parseErr)
330-
}
331-
332-
priceagent, dbErr := database.GetPriceagentForUserByID(ctx.EffectiveUser.Id, priceagentID)
333-
if dbErr != nil {
334316
ctx.EffectiveMessage.Reply(b, "Der Preisagent existiert nicht mehr, vielleicht wurde er schon gelöscht?", &gotgbot.SendMessageOpts{})
335-
return fmt.Errorf("deletePriceagentHandler: failed to get priceagent from database: %w", dbErr)
317+
return fmt.Errorf("deletePriceagentHandler: failed to parse callback data: %w", parseErr)
336318
}
337319

338320
deleteErr := database.DeletePriceAgentForUser(priceagent)
@@ -341,11 +323,11 @@ func deletePriceagentHandler(b *gotgbot.Bot, ctx *ext.Context) error {
341323
return fmt.Errorf("deletePriceagentHandler: failed to delete priceagent from database: %w", deleteErr)
342324
}
343325

344-
editText := fmt.Sprintf("Preisagent für %s wurde gelöscht!", bold(createLink(priceagent.Entity.URL, priceagent.Entity.Name)))
326+
editText := fmt.Sprintf("Preisagent für %s wurde gelöscht!", bold(createLink(priceagent.EntityURL(), priceagent.Entity.Name)))
345327

346328
_, err := cb.Message.EditText(b, editText, &gotgbot.EditMessageTextOpts{ParseMode: "HTML", DisableWebPagePreview: true})
347329
if err != nil {
348-
return fmt.Errorf("deletePriceagent: failed to edit message text: %w", err)
330+
return fmt.Errorf("deletePriceagentHandler: failed to edit message text: %w", err)
349331
}
350332
return nil
351333
}
@@ -468,14 +450,17 @@ func Start(botConfig config.Config) {
468450
}
469451
log.Printf("Starting webhook on '%s:%d%s'...\n", botConfig.Webhook.ListenIP, botConfig.Webhook.ListenPort, botConfig.Webhook.ListenPath)
470452
// TODO add support for custom certificates
471-
err := updater.StartWebhook(bot, ext.WebhookOpts{
453+
startErr := updater.StartWebhook(bot, ext.WebhookOpts{
472454
Listen: botConfig.Webhook.ListenIP,
473455
Port: botConfig.Webhook.ListenPort,
474456
URLPath: parsedURL.Path,
475457
})
476-
bot.SetWebhook(botConfig.Webhook.URL, &gotgbot.SetWebhookOpts{})
477-
if err != nil {
478-
panic("failed to start webhook: " + err.Error())
458+
if startErr != nil {
459+
panic("failed to start webhook: " + startErr.Error())
460+
}
461+
_, setWebhookErr := bot.SetWebhook(botConfig.Webhook.URL, &gotgbot.SetWebhookOpts{})
462+
if setWebhookErr != nil {
463+
panic("failed to set webhook: " + setWebhookErr.Error())
479464
}
480465
} else {
481466
log.Println("Start polling...")
@@ -510,3 +495,17 @@ func Start(botConfig config.Config) {
510495

511496
updater.Idle()
512497
}
498+
499+
func parseMenuPriceagent(ctx *ext.Context) (models.Menu, models.PriceAgent, error) {
500+
menu, parseMenuErr := models.NewMenu(ctx.CallbackQuery.Data)
501+
if parseMenuErr != nil {
502+
return models.Menu{}, models.PriceAgent{}, fmt.Errorf("invalid callback data: %s", ctx.CallbackQuery.Data)
503+
}
504+
505+
priceAgent, dbErr := database.GetPriceagentForUserByID(ctx.EffectiveUser.Id, menu.PriceAgentID)
506+
if dbErr != nil {
507+
return models.Menu{}, models.PriceAgent{}, fmt.Errorf("invalid callback data: %s", ctx.CallbackQuery.Data)
508+
}
509+
510+
return *menu, priceAgent, nil
511+
}

internal/bot/models/menu.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import (
77
)
88

99
type Menu struct {
10-
ID string
11-
SubMenu string
12-
PriceAgent int64
13-
Extra string
10+
ID string
11+
SubMenu string
12+
PriceAgentID int64
13+
Extra string
1414
}
1515

1616
// NewMenu returns a menu struct from a given menuData string. The menu follows the format <menuID>_<submenuID>_<priceagentID>[_<extraData>]
@@ -29,9 +29,9 @@ func NewMenu(menuData string) (*Menu, error) {
2929
}
3030

3131
menu := &Menu{
32-
ID: components[0],
33-
SubMenu: components[1],
34-
PriceAgent: int64(priceagentID),
32+
ID: components[0],
33+
SubMenu: components[1],
34+
PriceAgentID: int64(priceagentID),
3535
}
3636

3737
if len(components) == 4 {

internal/bot/models/priceagent.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,32 @@ type PriceAgent struct {
1414
User User `json:"user" gorm:"foreignkey:UserID"`
1515
EntityID int64 `json:"-" gorm:"index:user_entity_idx,unique"`
1616
Entity geizhals.Entity `json:"entity" gorm:"foreignkey:EntityID"`
17+
Location string `json:"location" gorm:"default:de"`
1718
NotificationID int64 `json:"-"`
1819
NotificationSettings NotificationSettings `json:"notificationSettings" gorm:"foreignkey:NotificationID;constraint:OnDelete:CASCADE;"`
20+
Enabled bool `json:"enabled" gorm:"default:1"`
1921
}
2022

2123
func (pa PriceAgent) String() string {
2224
return fmt.Sprintf("%d - '%s' (%s) | User: %d", pa.ID, pa.Name, pa.Entity.Name, pa.UserID)
2325
}
2426

27+
func (pa PriceAgent) EntityURL() string {
28+
return pa.Entity.FullURL(pa.Location)
29+
}
30+
31+
func (pa PriceAgent) CurrentEntityPrice() geizhals.EntityPrice {
32+
return pa.Entity.GetPrice(pa.Location)
33+
}
34+
35+
func (pa PriceAgent) CurrentPrice() float64 {
36+
return pa.Entity.GetPrice(pa.Location).Price
37+
}
38+
39+
func (pa PriceAgent) GetCurrency() geizhals.Currency {
40+
return pa.Entity.GetPrice(pa.Location).Currency
41+
}
42+
2543
type NotificationSettings struct {
2644
CreatedAt time.Time
2745
UpdatedAt time.Time

0 commit comments

Comments
 (0)