From e307989b9f73bc6faf6ceb73828317b64f9d3579 Mon Sep 17 00:00:00 2001 From: Arthur Khachaturov Date: Sat, 17 Aug 2024 01:20:24 +0300 Subject: [PATCH] init: mvp --- .gitignore | 2 + go.mod | 5 ++ go.sum | 2 + internal/clicker/clicker.go | 86 ++++++++++++++++++++++++ internal/clicker/const.go | 7 ++ internal/clicker/helpers.go | 16 +++++ internal/clicker/methods.go | 80 ++++++++++++++++++++++ internal/clicker/request.go | 50 ++++++++++++++ internal/clicker/request_types.go | 25 +++++++ internal/clicker/update.go | 56 ++++++++++++++++ internal/clicker/workers.go | 93 ++++++++++++++++++++++++++ internal/entity/boosts/boosts.go | 41 ++++++++++++ internal/entity/config/config.go | 54 +++++++++++++++ internal/entity/config/const.go | 8 +++ internal/entity/config/decodecipher.go | 12 ++++ internal/entity/upgrades/condition.go | 68 +++++++++++++++++++ internal/entity/upgrades/price.go | 37 ++++++++++ internal/entity/upgrades/upgrades.go | 76 +++++++++++++++++++++ internal/entity/user/user.go | 64 ++++++++++++++++++ main.go | 53 +++++++++++++++ 20 files changed, 835 insertions(+) create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/clicker/clicker.go create mode 100644 internal/clicker/const.go create mode 100644 internal/clicker/helpers.go create mode 100644 internal/clicker/methods.go create mode 100644 internal/clicker/request.go create mode 100644 internal/clicker/request_types.go create mode 100644 internal/clicker/update.go create mode 100644 internal/clicker/workers.go create mode 100644 internal/entity/boosts/boosts.go create mode 100644 internal/entity/config/config.go create mode 100644 internal/entity/config/const.go create mode 100644 internal/entity/config/decodecipher.go create mode 100644 internal/entity/upgrades/condition.go create mode 100644 internal/entity/upgrades/price.go create mode 100644 internal/entity/upgrades/upgrades.go create mode 100644 internal/entity/user/user.go create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53ed21b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +json/ +accounts.json diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..99716ba --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/wzrayyy/tappin + +go 1.19 + +require golang.org/x/sync v0.8.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e584c1b --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= diff --git a/internal/clicker/clicker.go b/internal/clicker/clicker.go new file mode 100644 index 0000000..ab0f85f --- /dev/null +++ b/internal/clicker/clicker.go @@ -0,0 +1,86 @@ +package clicker + +import ( + "net/http" + "net/http/cookiejar" + "net/url" + "sync" + + "github.com/wzrayyy/tappin/internal/entity/boosts" + "github.com/wzrayyy/tappin/internal/entity/config" + "github.com/wzrayyy/tappin/internal/entity/upgrades" + "github.com/wzrayyy/tappin/internal/entity/user" + "golang.org/x/sync/errgroup" +) + +type Clicker struct { + client *http.Client + authKey string + baseUrl *url.URL + clickerConfig *config.Response + user *user.Response + boosts *boosts.Response + upgrades *upgrades.Response + telegramUserID int + errorGroup errgroup.Group + + locks struct { + User sync.RWMutex + Boosts sync.RWMutex + Config sync.RWMutex + Upgrades sync.RWMutex + } + + channels struct { + Update EmptyChannel + Tap EmptyChannel + Upgrade EmptyChannel + Global EmptyChannel + } + + Config Config +} + +type Config struct { + UpdateFrequency int + TapsPerSecond int + TapInterval int +} + +func NewClicker(auth_key string, user_id int, config Config) (*Clicker, error) { + c := new(Clicker) + + var err error + + c.client = new(http.Client) + c.client.Jar = new(cookiejar.Jar) + c.authKey = auth_key + c.telegramUserID = user_id + + c.baseUrl, err = url.Parse(apiEndpoint) + if err != nil { + return c, err + } + + c.Config = config + + return c, c.Update() +} + +func (c *Clicker) Tick() { + c.locks.Config.Lock() + c.clickerConfig.Tick() + c.locks.Config.Unlock() + + c.locks.User.Lock() + c.user.Tick() + c.locks.User.Unlock() + + c.locks.Boosts.Lock() + c.boosts.Tick() + c.locks.Boosts.Unlock() + + c.locks.Upgrades.Lock() + c.upgrades.Tick() + c.locks.Upgrades.Unlock() +} diff --git a/internal/clicker/const.go b/internal/clicker/const.go new file mode 100644 index 0000000..53a8494 --- /dev/null +++ b/internal/clicker/const.go @@ -0,0 +1,7 @@ +package clicker + +const ( + apiEndpoint string = "https://api.hamsterkombatgame.io/clicker" +) + +type EmptyChannel chan struct{} diff --git a/internal/clicker/helpers.go b/internal/clicker/helpers.go new file mode 100644 index 0000000..d2a7745 --- /dev/null +++ b/internal/clicker/helpers.go @@ -0,0 +1,16 @@ +package clicker + +// this was added in go 1.21, which is not available on Debian 12 +func max[T int | int64 | float32 | float64](a T, b T) T { + if a > b { + return a + } + return b +} + +func min[T int | int64 | float32 | float64](a T, b T) T { + if a < b { + return a + } + return b +} diff --git a/internal/clicker/methods.go b/internal/clicker/methods.go new file mode 100644 index 0000000..ea0ed18 --- /dev/null +++ b/internal/clicker/methods.go @@ -0,0 +1,80 @@ +package clicker + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/wzrayyy/tappin/internal/entity/user" +) + +func (c *Clicker) ClaimDailyKeys() error { + m, err := json.Marshal(&claimDailyCipherRequest{ + Cipher: base64.StdEncoding.EncodeToString([]byte(strings.Repeat("0", 10) + fmt.Sprintf("|%d", c.telegramUserID))), + }) + + if err != nil { + return err + } + + err = c.requestAndDecode("start-keys-minigame", nil, nil) + + if err != nil { + return err + } + + return c.requestAndDecode("claim-daily-keys-minigame", m, nil) +} + +func (c *Clicker) ClaimDailyCipher() error { + cipher, err := c.clickerConfig.DailyCipher.Decode() + if err != nil { + return err + } + + m, err := json.Marshal(claimDailyCipherRequest{ + Cipher: cipher, + }) + + return c.requestAndDecode("claim-daily-cipher", m, nil) +} + +func (c *Clicker) Tap(taps int) error { + c.locks.User.RLock() + m, err := json.Marshal(tapRequest{ + Count: taps, + AvailableTaps: c.user.AvailableTaps - taps, + Timestamp: time.Now().UnixMilli(), + }) + c.locks.User.RUnlock() + + if err != nil { + return err + } + + var u user.Response + + err = c.requestAndDecode("tap", m, &u) + if err != nil { + return err + } + + c.locks.User.Lock() + c.user = &u + c.locks.User.Unlock() + + return nil +} + +func (c *Clicker) BuyBoost(boost_id string) error { + r, err := json.Marshal(buyBoostRequest{ + BoostID: boost_id, + Timestamp: time.Now().UnixMilli(), + }) + if err != nil { + return err + } + return c.requestAndDecode("buy-boost", r, nil) +} diff --git a/internal/clicker/request.go b/internal/clicker/request.go new file mode 100644 index 0000000..8eab0a4 --- /dev/null +++ b/internal/clicker/request.go @@ -0,0 +1,50 @@ +package clicker + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" +) + +func (c *Clicker) requestAndDecode(path string, data []byte, output any) error { + var r io.Reader + r = nil + if data != nil { + r = bytes.NewReader(data) + } + + req, err := http.NewRequest("POST", c.baseUrl.JoinPath(path).String(), r) + + if err != nil { + return err + } + + if data != nil { + req.Header.Set("Content-Type", "application/json") + } + + resp, err := c.doRequest(req) + defer resp.Body.Close() + if err != nil { + return err + } + + if output != nil { + return json.NewDecoder(resp.Body).Decode(output) + } + + return nil +} + +func (c *Clicker) doRequest(req *http.Request) (*http.Response, error) { + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.authKey)) + resp, err := c.client.Do(req) + if resp.StatusCode != 200 { + raw_body, _ := io.ReadAll(resp.Body) + body := string(raw_body) + err = fmt.Errorf("request: Request to %s failed with status code %d\n%s", req.URL.String(), resp.StatusCode, body) + } + return resp, err +} diff --git a/internal/clicker/request_types.go b/internal/clicker/request_types.go new file mode 100644 index 0000000..ee61c0f --- /dev/null +++ b/internal/clicker/request_types.go @@ -0,0 +1,25 @@ +package clicker + +type tapRequest struct { + Count int `json:"count"` + AvailableTaps int `json:"availableTaps"` + Timestamp int64 `json:"timestamp"` +} + +type buyUpgradeRequest struct { + UpgradeID string `json:"upgradeId"` + Timestamp int64 `json:"timestamp"` +} + +type buyBoostRequest struct { + BoostID string `json:"boostId"` + Timestamp int64 `json:"timestamp"` +} + +type checkTaskRequest struct { + TaskID string `json:"taskId"` +} + +type claimDailyCipherRequest struct { + Cipher string `json:"cipher"` +} diff --git a/internal/clicker/update.go b/internal/clicker/update.go new file mode 100644 index 0000000..9e5b6cb --- /dev/null +++ b/internal/clicker/update.go @@ -0,0 +1,56 @@ +package clicker + +import ( + "sync" + + "github.com/wzrayyy/tappin/internal/entity/boosts" + "github.com/wzrayyy/tappin/internal/entity/config" + "github.com/wzrayyy/tappin/internal/entity/upgrades" + "github.com/wzrayyy/tappin/internal/entity/user" + "golang.org/x/sync/errgroup" +) + +func fetchAndUpdate[T any](c *Clicker, endpoint string, lock *sync.RWMutex, setter func(*T)) error { + var resp T + + err := c.requestAndDecode(endpoint, nil, &resp) + if err != nil { + return err + } + + lock.Lock() + setter(&resp) + lock.Unlock() + + return nil +} + +func (c *Clicker) Update() error { + errs := errgroup.Group{} + + errs.Go(func() error { + return fetchAndUpdate(c, "sync", &c.locks.User, func(r *user.Response) { + c.user = r + }) + }) + + errs.Go(func() error { + return fetchAndUpdate(c, "config", &c.locks.Config, func(r *config.Response) { + c.clickerConfig = r + }) + }) + + errs.Go(func() error { + return fetchAndUpdate(c, "boosts-for-buy", &c.locks.Boosts, func(r *boosts.Response) { + c.boosts = r + }) + }) + + errs.Go(func() error { + return fetchAndUpdate(c, "upgrades-for-buy", &c.locks.Upgrades, func(r *upgrades.Response) { + c.upgrades = r + }) + }) + + return errs.Wait() +} diff --git a/internal/clicker/workers.go b/internal/clicker/workers.go new file mode 100644 index 0000000..831107c --- /dev/null +++ b/internal/clicker/workers.go @@ -0,0 +1,93 @@ +package clicker + +import ( + "fmt" + "time" +) + +func (c *Clicker) genericWorker(fn func() error, period *int, done EmptyChannel) error { + return c.genericWorkerWithChannel(func(EmptyChannel) error { return fn() }, period, done) +} + +func (c *Clicker) genericWorkerWithChannel(fn func(EmptyChannel) error, period *int, done EmptyChannel) error { + for { + select { + case <-done: + return nil + case <-c.channels.Global: + fmt.Println("got global stop") + if done != nil { + close(done) + } + return nil + case <-time.After(time.Second * time.Duration(*period)): + err := fn(done) + if err != nil { + close(c.channels.Global) + return err + } + } + } +} + +func (c *Clicker) tapWorker() error { + return c.genericWorkerWithChannel(func(done EmptyChannel) error { + c.locks.User.RLock() + taps_consumed := int(c.Config.TapInterval) * c.user.EarnPerTap + taps_left := c.user.AvailableTaps - taps_consumed + c.locks.User.RUnlock() + + fmt.Println("tap") + + if taps_left < 0 { + c.locks.Boosts.RLock() + b := c.boosts.SelectById("BoostFullAvailableTaps").CooldownSeconds + c.locks.Boosts.RUnlock() + + if b != nil && *b <= 0 { + fmt.Println("buy taps") + c.BuyBoost("BoostFullAvailableTaps") + } else { + c.locks.User.RLock() + time_sleep := (c.user.MaxTaps - c.user.AvailableTaps) / c.user.RecoverPerSecond + c.locks.User.RUnlock() + select { + case <-c.channels.Global: + return nil + case <-done: + return nil + case <-time.After(time.Duration(time_sleep) * time.Second): + break + } + } + } + return c.Tap(c.Config.TapInterval * c.Config.TapsPerSecond) + }, &c.Config.TapInterval, c.channels.Tap) +} + +func (c *Clicker) updateWorker() error { + return c.genericWorker(func() error { + return c.Update() + }, &c.Config.UpdateFrequency, c.channels.Update) +} + +func (c *Clicker) tickWorker() error { + interval := 1 + return c.genericWorker(func() error { + c.Tick() + return nil + }, &interval, nil) +} + +func (c *Clicker) Start() error { + c.errorGroup.Go(c.tapWorker) + c.errorGroup.Go(c.updateWorker) + c.errorGroup.Go(c.tickWorker) + + return c.errorGroup.Wait() +} + +func (c *Clicker) Stop() error { + close(c.channels.Global) + return c.errorGroup.Wait() +} diff --git a/internal/entity/boosts/boosts.go b/internal/entity/boosts/boosts.go new file mode 100644 index 0000000..d712254 --- /dev/null +++ b/internal/entity/boosts/boosts.go @@ -0,0 +1,41 @@ +package boosts + +type Item struct { + ID string `json:"id"` + Price int `json:"price"` + EarnPerTap int `json:"earnPerTap"` + MaxTaps int `json:"maxTaps"` + CooldownSeconds *int `json:"cooldownSeconds"` + TotalCooldownSeconds *int `json:"totalCooldownSeconds"` + Level int `json:"level"` + MaxTapsDelta int `json:"maxTapsDelta"` + EarnPerTapDelta int `json:"earnPerTapDelta"` + MaxLevel *int `json:"maxLevel,omitempty"` +} + +func (i *Item) Tick() { + if i.TotalCooldownSeconds != nil && *i.TotalCooldownSeconds > 0 { + (*i.TotalCooldownSeconds)-- + } +} + +type Boosts []*Item + +type Response struct { + Boosts `json:"boostsForBuy"` +} + +func (b *Boosts) Tick() { + for _, el := range *b { + el.Tick() + } +} + +func (b *Boosts) SelectById(id string) *Item { + for _, i := range *b { + if i.ID == id { + return i + } + } + return nil +} diff --git a/internal/entity/config/config.go b/internal/entity/config/config.go new file mode 100644 index 0000000..82d6d4f --- /dev/null +++ b/internal/entity/config/config.go @@ -0,0 +1,54 @@ +package config + +type Task struct { + ID string `json:"id"` + Link *string `json:"link"` + Reward int `json:"rewardCoins"` + Cycle Cycle `json:"periodicity"` + ChannelId *int `json:"channelId"` +} + +type ClickerConfig struct { + MaxPassive int `json:"maxPassiveDtSeconds"` + Tasks []Task `json:"tasks"` +} + +type DailyCipher struct { + Cipher string `json:"cipher"` + BonusCoins int `json:"bonusCoins"` + IsClaimed bool `json:"isClaimed"` + RemainSeconds int `json:"remainSeconds"` +} + +type DailyKeys struct { + StartDate string `json:"startDate"` + LevelConfig string `json:"levelConfig"` + BonusKeys int `json:"bonusKeys"` + IsClaimed bool `json:"isClaimed"` + SecondsToNext int `json:"totalSecondsToNextAttempt"` + RemainToGuess float32 `json:"remainSecondsToGuess"` + RemainSeconds float32 `json:"remainSeconds"` + RemainToNext float32 `json:"remainSecondsToNextAttempt"` +} + +type Response struct { + ClickerConfig ClickerConfig `json:"clickerConfig"` + DailyCipher DailyCipher `json:"dailyCipher"` + DailyKeys DailyKeys `json:"DailyKeysMiniGame"` +} + +func (c *DailyCipher) Tick() { + c.RemainSeconds-- +} + +func (k *DailyKeys) Tick() { + k.RemainToGuess-- + k.RemainToNext-- + k.RemainSeconds-- + k.SecondsToNext-- +} + +func (r *Response) Tick() { + r.DailyCipher.Tick() + r.DailyKeys.Tick() +} diff --git a/internal/entity/config/const.go b/internal/entity/config/const.go new file mode 100644 index 0000000..3ea300f --- /dev/null +++ b/internal/entity/config/const.go @@ -0,0 +1,8 @@ +package config + +type Cycle string + +const ( + Repeatedly Cycle = "repeatedly" + Once Cycle = "once" +) diff --git a/internal/entity/config/decodecipher.go b/internal/entity/config/decodecipher.go new file mode 100644 index 0000000..4b40627 --- /dev/null +++ b/internal/entity/config/decodecipher.go @@ -0,0 +1,12 @@ +package config + +import "encoding/base64" + +func (c *DailyCipher) Decode() (string, error) { + enc, err := base64.StdEncoding.DecodeString(c.Cipher[:3] + c.Cipher[4:]) + + if err != nil { + return "", err + } + return string(enc), nil +} diff --git a/internal/entity/upgrades/condition.go b/internal/entity/upgrades/condition.go new file mode 100644 index 0000000..4d42585 --- /dev/null +++ b/internal/entity/upgrades/condition.go @@ -0,0 +1,68 @@ +package upgrades + +import ( + "encoding/json" +) + +type Type string + +const ( + byUpgrade Type = "ByUpgrade" + referralCount Type = "ReferralCount" + moreReferralCount Type = "MoreReferralsCount" + subscribeTelegramChannel Type = "SubscribeTelegramChannel" +) + +type Condition struct { + Type Type + ByUpgrade *ByUpgrade + ReferralCount *ReferralCount + MoreReferralCount *MoreReferralCount + SubscribeTelegramChannel *SubscribeTelegramChannel +} + +type ByUpgrade struct { + Level int + UpgradeID string +} + +type ReferralCount struct { + ReferralCount int +} + +type MoreReferralCount struct { + MoreReferralCount int +} + +type SubscribeTelegramChannel struct { + ChannelID int + Link string +} + +func (c *Condition) UnmarshalJSON(d []byte) error { + type tmpStruct struct { + Type Type `json:"_type"` + ByUpgrade + ReferralCount + MoreReferralCount + SubscribeTelegramChannel + } + var tmp = new(tmpStruct) + if err := json.Unmarshal(d, tmp); err != nil { + return err + } + + c.Type = tmp.Type + switch tmp.Type { + case byUpgrade: + c.ByUpgrade = &tmp.ByUpgrade + case referralCount: + c.ReferralCount = &tmp.ReferralCount + case moreReferralCount: + c.MoreReferralCount = &tmp.MoreReferralCount + case subscribeTelegramChannel: + c.SubscribeTelegramChannel = &tmp.SubscribeTelegramChannel + } + + return nil +} diff --git a/internal/entity/upgrades/price.go b/internal/entity/upgrades/price.go new file mode 100644 index 0000000..3e6c471 --- /dev/null +++ b/internal/entity/upgrades/price.go @@ -0,0 +1,37 @@ +package upgrades + +import ( + "encoding/json" +) + +func (u *Upgrades) RecurseUnavailable(item *Item) []*Item { + if item.IsAvailable || (item.Condition == nil || item.Condition.ByUpgrade == nil) { + return []*Item{item} + } + + return append(u.RecurseUnavailable((*u)[item.Condition.ByUpgrade.UpgradeID]), item) +} + +func (r *Response) UnmarshalJSON(data []byte) error { + type tempType Response + var temp *tempType = (*tempType)(r) + + err := json.Unmarshal(data, temp) + if err != nil { + return err + } + + r.Upgrades = make(Upgrades) + + for _, i := range r.UpgradesArray { + r.Upgrades[i.ID] = i + } + + return nil +} + +func (r *Response) Tick() { + for _, e := range r.UpgradesArray { + e.Tick() + } +} diff --git a/internal/entity/upgrades/upgrades.go b/internal/entity/upgrades/upgrades.go new file mode 100644 index 0000000..7bf3ce6 --- /dev/null +++ b/internal/entity/upgrades/upgrades.go @@ -0,0 +1,76 @@ +package upgrades + +import "time" + +type Item struct { + ID string `json:"id"` + Name string `json:"name"` + Price int `json:"price"` + ProfitPerHour int `json:"profitPerHour"` + Condition *Condition `json:"condition,omitempty"` + Section string `json:"section"` + Level int `json:"level"` + CurrentProfit int `json:"currentProfitPerHour"` + ProfitDelta int `json:"profitPerHourDelta"` + IsAvailable bool `json:"isAvailable"` + IsExpired bool `json:"isExpired"` + Cooldown *int `json:"cooldownSeconds"` + TotalCooldown *int `json:"totalCooldownSeconds"` + ReleaseAt *string `json:"releaseAt"` + ExpiresAt *string `json:"expiresAt"` + MaxLevel *int `json:"maxLevel"` +} + +func (i *Item) Tick() { + if i.ExpiresAt != nil && !i.IsExpired && *i.ExpiresAt != "" { + expire_time, _ := time.Parse(time.RFC3339, *i.ExpiresAt) + if expire_time.Sub(time.Now()) < 0 { + i.IsExpired = true + } + } + if i.Cooldown != nil && *i.Cooldown > 0 { + (*i.Cooldown)-- + } +} + +type Section struct { + Name string `json:"section"` + IsAvailable bool `json:"isAvailable"` +} + +type DailyCombo struct { + UpgradeIDs []string `json:"upgradeIds"` + BonusCoins int `json:"bonusCoins"` + IsClaimed bool `json:"isClaimed"` + RemainingSeconds int `json:"remainSeconds"` +} + +type Upgrades map[string]*Item + +type Response struct { + UpgradesArray []*Item `json:"upgradesForBuy"` + Upgrades Upgrades `json:"-"` + Sections []Section `json:"sections"` + DailyCombo DailyCombo `json:"dailyCombo"` +} + +func (i *Item) PriceByLevel(level int) int { + return 0 +} + +func (i *Item) ProfitDeltaByLevel(level int) int { + return 0 +} + +func (i *Item) CooldownByLevel(level int) int { + return 0 +} + +// def profit_delta_by_level(self, level: int) -> int: +// return round(self.profit_per_hour_delta * 1.07 ** level) +// +// def price_by_level(self, level: int) -> int: +// return round(self.price * 1.05 ** ((level + 3) * level / 2)) +// +// def cooldown_by_level(self, level: int) -> int: +// return self.cooldown_seconds * 2 ** level if self.cooldown_seconds else 0 diff --git a/internal/entity/user/user.go b/internal/entity/user/user.go new file mode 100644 index 0000000..61ddd54 --- /dev/null +++ b/internal/entity/user/user.go @@ -0,0 +1,64 @@ +package user + +type Boost struct { + ID string `json:"id"` + Level int `json:"level"` + LastUpgradeTime int `json:"lastUpgradeAt"` +} + +type Boosts struct { + BoostMaxTaps Boost `json:"boostMaxTaps"` + BoostEarnPerTap Boost `json:"boostEarnPerTap"` + BoostFullAvailableTaps Boost `json:"boostFullAvailableTaps"` +} + +type Upgrade struct { + ID string `json:"id"` + Level int `json:"level"` + LastUpgradeAt int `json:"lastUpgradeAt"` +} + +type StreakDays struct { + ID string `json:"id"` + CompletedAt string `json:"completedAt"` + Days int `json:"days"` +} + +type Tasks struct { + StreakDays StreakDays `json:"streak_days"` +} + +type ClickerUser struct { + ID string `json:"id"` + TotalCoins float32 `json:"totalCoins"` + Balance float32 `json:"balanceCoins"` + Level int `json:"level"` + AvailableTaps int `json:"availableTaps"` + Boosts Boosts `json:"boosts"` + Tasks Tasks `json:"tasks"` + ReferralsCount int `json:"referralsCount"` + MaxTaps int `json:"maxTaps"` + EarnPerTap int `json:"earnPerTap"` + PassivePerSecond float32 `json:"earnPassivePerSec"` + PassivePerHour float32 `json:"earnPassivePerHour"` + RecoverPerSecond int `json:"tapsRecoverPerSec"` + CreatedAt string `json:"createdAt"` + BalanceTickets int `json:"balanceTickets"` + TotalKeys int `json:"totalKeys"` + BalanceKeys int `json:"balanceKeys"` +} + +func (u *ClickerUser) Tick() { + if u.AvailableTaps+u.RecoverPerSecond < u.MaxTaps { + u.AvailableTaps += u.RecoverPerSecond + } else { + u.AvailableTaps = u.MaxTaps + } + + u.Balance += u.PassivePerSecond + u.TotalCoins += u.PassivePerSecond +} + +type Response struct { + ClickerUser `json:"clickerUser"` +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..77ab888 --- /dev/null +++ b/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + _ "sync" + "time" + _ "time" + + "github.com/wzrayyy/tappin/internal/clicker" +) + +func die_if(err error) { + if err != nil { + panic(err) + } +} + +type account struct { + Name string + Phone string + AuthKey string + UserID int +} + +func main() { + data, err := os.ReadFile("./accounts.json") + die_if(err) + + var accounts []account + json.Unmarshal(data, &accounts) + account := accounts[0] + + c, err := clicker.NewClicker(account.AuthKey, account.UserID, clicker.Config{ + TapsPerSecond: 0, + TapInterval: 2, + UpdateFrequency: 3, + }) + die_if(err) + fmt.Println("a") + + go c.Start() + + time.Sleep(3 * time.Second) + + c.Config.TapInterval = 1 + time.Sleep(10 * time.Second) + + err = c.Stop() + + die_if(err) +}