mirror of
https://github.com/Luzifer/runemetrics.git
synced 2024-12-21 19:51:21 +00:00
Initial version
This commit is contained in:
commit
4d1f10502a
6 changed files with 883 additions and 0 deletions
11
go.mod
Normal file
11
go.mod
Normal file
|
@ -0,0 +1,11 @@
|
|||
module github.com/Luzifer/runemetrics
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/Luzifer/rconfig/v2 v2.2.1
|
||||
github.com/gizak/termui/v3 v3.1.0
|
||||
github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
)
|
33
go.sum
Normal file
33
go.sum
Normal file
|
@ -0,0 +1,33 @@
|
|||
github.com/Luzifer/rconfig/v2 v2.2.1 h1:zcDdLQlnlzwcBJ8E0WFzOkQE1pCMn3EbX0dFYkeTczg=
|
||||
github.com/Luzifer/rconfig/v2 v2.2.1/go.mod h1:OKIX0/JRZrPJ/ZXXWklQEFXA6tBfWaljZbW37w+sqBw=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gizak/termui/v3 v3.1.0 h1:ZZmVDgwHl7gR7elfKf1xc4IudXZ5qqfDh4wExk4Iajc=
|
||||
github.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY=
|
||||
github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75 h1:f0n1xnMSmBLzVfsMMvriDyA75NB/oBgILX2GcHXIQzY=
|
||||
github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840=
|
||||
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/validator.v2 v2.0.0-20180514200540-135c24b11c19 h1:WB265cn5OpO+hK3pikC9hpP1zI/KTwmyMFKloW9eOVc=
|
||||
gopkg.in/validator.v2 v2.0.0-20180514200540-135c24b11c19/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
285
levels.go
Normal file
285
levels.go
Normal file
|
@ -0,0 +1,285 @@
|
|||
package main
|
||||
|
||||
var levels = map[int]int64{
|
||||
1: 0,
|
||||
2: 83,
|
||||
3: 174,
|
||||
4: 276,
|
||||
5: 388,
|
||||
6: 512,
|
||||
7: 650,
|
||||
8: 801,
|
||||
9: 969,
|
||||
10: 1154,
|
||||
11: 1358,
|
||||
12: 1584,
|
||||
13: 1833,
|
||||
14: 2107,
|
||||
15: 2411,
|
||||
16: 2746,
|
||||
17: 3115,
|
||||
18: 3523,
|
||||
19: 3973,
|
||||
20: 4470,
|
||||
21: 5018,
|
||||
22: 5624,
|
||||
23: 6291,
|
||||
24: 7028,
|
||||
25: 7842,
|
||||
26: 8740,
|
||||
27: 9730,
|
||||
28: 10824,
|
||||
29: 12031,
|
||||
30: 13363,
|
||||
31: 14833,
|
||||
32: 16456,
|
||||
33: 18247,
|
||||
34: 20224,
|
||||
35: 22406,
|
||||
36: 24815,
|
||||
37: 27473,
|
||||
38: 30408,
|
||||
39: 33648,
|
||||
40: 37224,
|
||||
41: 41171,
|
||||
42: 45529,
|
||||
43: 50339,
|
||||
44: 55649,
|
||||
45: 61512,
|
||||
46: 67983,
|
||||
47: 75127,
|
||||
48: 83014,
|
||||
49: 91721,
|
||||
50: 101333,
|
||||
51: 111945,
|
||||
52: 123660,
|
||||
53: 136594,
|
||||
54: 150872,
|
||||
55: 166636,
|
||||
56: 184040,
|
||||
57: 203254,
|
||||
58: 224466,
|
||||
59: 247886,
|
||||
60: 273742,
|
||||
61: 302288,
|
||||
62: 333804,
|
||||
63: 368599,
|
||||
64: 407015,
|
||||
65: 449428,
|
||||
66: 496254,
|
||||
67: 547953,
|
||||
68: 605032,
|
||||
69: 668051,
|
||||
70: 737627,
|
||||
71: 814445,
|
||||
72: 899257,
|
||||
73: 992895,
|
||||
74: 1096278,
|
||||
75: 1210421,
|
||||
76: 1336443,
|
||||
77: 1475581,
|
||||
78: 1629200,
|
||||
79: 1798808,
|
||||
80: 1986068,
|
||||
81: 2192818,
|
||||
82: 2421087,
|
||||
83: 2673114,
|
||||
84: 2951373,
|
||||
85: 3258594,
|
||||
86: 3597792,
|
||||
87: 3972294,
|
||||
88: 4385776,
|
||||
89: 4842295,
|
||||
90: 5346332,
|
||||
91: 5902831,
|
||||
92: 6517253,
|
||||
93: 7195629,
|
||||
94: 7944614,
|
||||
95: 8771558,
|
||||
96: 9684577,
|
||||
97: 10692629,
|
||||
98: 11805606,
|
||||
99: 13034431,
|
||||
100: 14391160,
|
||||
101: 15889109,
|
||||
102: 17542976,
|
||||
103: 19368992,
|
||||
104: 21385073,
|
||||
105: 23611006,
|
||||
106: 26068632,
|
||||
107: 28782069,
|
||||
108: 31777943,
|
||||
109: 35085654,
|
||||
110: 38737661,
|
||||
111: 42769801,
|
||||
112: 47221641,
|
||||
113: 52136869,
|
||||
114: 57563718,
|
||||
115: 63555443,
|
||||
116: 70170840,
|
||||
117: 77474828,
|
||||
118: 85539082,
|
||||
119: 94442737,
|
||||
120: 104273167,
|
||||
121: 115126838,
|
||||
122: 127110260,
|
||||
123: 140341028,
|
||||
124: 154948977,
|
||||
125: 171077457,
|
||||
126: 188884740,
|
||||
127: 2e8,
|
||||
}
|
||||
|
||||
var masterLevels = map[int]int64{
|
||||
1: 0,
|
||||
2: 830,
|
||||
3: 1861,
|
||||
4: 2902,
|
||||
5: 3980,
|
||||
6: 5126,
|
||||
7: 6380,
|
||||
8: 7787,
|
||||
9: 9400,
|
||||
10: 11275,
|
||||
11: 13605,
|
||||
12: 16372,
|
||||
13: 19656,
|
||||
14: 23546,
|
||||
15: 28134,
|
||||
16: 33520,
|
||||
17: 39809,
|
||||
18: 47109,
|
||||
19: 55535,
|
||||
20: 65209,
|
||||
21: 77190,
|
||||
22: 90811,
|
||||
23: 106221,
|
||||
24: 123573,
|
||||
25: 143025,
|
||||
26: 164742,
|
||||
27: 188893,
|
||||
28: 215651,
|
||||
29: 245196,
|
||||
30: 277713,
|
||||
31: 316311,
|
||||
32: 358547,
|
||||
33: 404634,
|
||||
34: 454796,
|
||||
35: 509259,
|
||||
36: 568254,
|
||||
37: 632019,
|
||||
38: 700797,
|
||||
39: 774834,
|
||||
40: 854383,
|
||||
41: 946227,
|
||||
42: 1044569,
|
||||
43: 1149696,
|
||||
44: 1261903,
|
||||
45: 1381488,
|
||||
46: 1508756,
|
||||
47: 1644015,
|
||||
48: 1787581,
|
||||
49: 1939773,
|
||||
50: 2100917,
|
||||
51: 2283490,
|
||||
52: 2476369,
|
||||
53: 2679917,
|
||||
54: 2894505,
|
||||
55: 3120508,
|
||||
56: 3358307,
|
||||
57: 3608290,
|
||||
58: 3870846,
|
||||
59: 4146374,
|
||||
60: 4435275,
|
||||
61: 4758122,
|
||||
62: 5096111,
|
||||
63: 5449685,
|
||||
64: 5819299,
|
||||
65: 6205407,
|
||||
66: 6608473,
|
||||
67: 7028964,
|
||||
68: 7467354,
|
||||
69: 7924122,
|
||||
70: 8399751,
|
||||
71: 8925664,
|
||||
72: 9472665,
|
||||
73: 10041285,
|
||||
74: 10632061,
|
||||
75: 11245538,
|
||||
76: 11882262,
|
||||
77: 12542789,
|
||||
78: 13227679,
|
||||
79: 13937496,
|
||||
80: 14672812,
|
||||
81: 15478994,
|
||||
82: 16313404,
|
||||
83: 17176661,
|
||||
84: 18069395,
|
||||
85: 18992239,
|
||||
86: 19945833,
|
||||
87: 20930821,
|
||||
88: 21947856,
|
||||
89: 22997593,
|
||||
90: 24080695,
|
||||
91: 25259906,
|
||||
92: 26475754,
|
||||
93: 27728955,
|
||||
94: 29020233,
|
||||
95: 30350318,
|
||||
96: 31719944,
|
||||
97: 33129852,
|
||||
98: 34580790,
|
||||
99: 36073511,
|
||||
100: 37608773,
|
||||
101: 39270442,
|
||||
102: 40978509,
|
||||
103: 42733789,
|
||||
104: 44537107,
|
||||
105: 46389292,
|
||||
106: 48291180,
|
||||
107: 50243611,
|
||||
108: 52247435,
|
||||
109: 54303504,
|
||||
110: 56412678,
|
||||
111: 58575824,
|
||||
112: 60793812,
|
||||
113: 63067521,
|
||||
114: 65397835,
|
||||
115: 67785643,
|
||||
116: 70231841,
|
||||
117: 72737330,
|
||||
118: 75303019,
|
||||
119: 77929820,
|
||||
120: 80618654,
|
||||
121: 83370445,
|
||||
122: 86186124,
|
||||
123: 89066630,
|
||||
124: 92012904,
|
||||
125: 95025896,
|
||||
126: 98106559,
|
||||
127: 101255855,
|
||||
128: 104474750,
|
||||
129: 107764216,
|
||||
130: 111125230,
|
||||
131: 114558777,
|
||||
132: 118065845,
|
||||
133: 121647430,
|
||||
134: 125304532,
|
||||
135: 129038159,
|
||||
136: 132849323,
|
||||
137: 136739041,
|
||||
138: 140708338,
|
||||
139: 144758242,
|
||||
140: 148889790,
|
||||
141: 153104021,
|
||||
142: 157401983,
|
||||
143: 161784728,
|
||||
144: 166253312,
|
||||
145: 170808801,
|
||||
146: 175452262,
|
||||
147: 180184770,
|
||||
148: 185007406,
|
||||
149: 189921255,
|
||||
150: 194927409,
|
||||
151: 2e8,
|
||||
}
|
215
main.go
Normal file
215
main.go
Normal file
|
@ -0,0 +1,215 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
ui "github.com/gizak/termui/v3"
|
||||
"github.com/gizak/termui/v3/widgets"
|
||||
"github.com/gorhill/cronexpr"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/Luzifer/rconfig/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
updateKeyTotalXP = "total_xp"
|
||||
updateKeyGeneral = "general"
|
||||
updateKeyFeed = "feed"
|
||||
)
|
||||
|
||||
var (
|
||||
cfg = struct {
|
||||
MarkerTime time.Duration `flag:"marker-time" default:"30m" description:"How long to highlight new entries"`
|
||||
Update string `flag:"update" default:"* * * * *" description:"When to fetch metrics (cron syntax)"`
|
||||
LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"`
|
||||
VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"`
|
||||
}{}
|
||||
|
||||
lastUpdate = map[string]time.Time{}
|
||||
|
||||
version = "dev"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := rconfig.ParseAndValidate(&cfg); err != nil {
|
||||
log.Fatalf("Unable to parse commandline options: %s", err)
|
||||
}
|
||||
|
||||
if cfg.VersionAndExit {
|
||||
fmt.Printf("git-changerelease %s\n", version)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if l, err := log.ParseLevel(cfg.LogLevel); err != nil {
|
||||
log.WithError(err).Fatal("Unable to parse log level")
|
||||
} else {
|
||||
log.SetLevel(l)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
|
||||
if len(rconfig.Args()) != 2 {
|
||||
log.Fatal("Usage: runemetrics <player>")
|
||||
}
|
||||
|
||||
if playerInfoCache, err = loadPlayerInfoCache(); err != nil {
|
||||
log.WithError(err).Fatal("Unable to load cache")
|
||||
}
|
||||
|
||||
if err = ui.Init(); err != nil {
|
||||
log.WithError(err).Fatal("Unable to initialize termui")
|
||||
}
|
||||
defer ui.Close()
|
||||
|
||||
var (
|
||||
cron = cronexpr.MustParse(cfg.Update)
|
||||
player = rconfig.Args()[1]
|
||||
updateTicker = time.NewTimer(0)
|
||||
)
|
||||
|
||||
updateUI(player)
|
||||
|
||||
for {
|
||||
select {
|
||||
|
||||
case evt := <-ui.PollEvents():
|
||||
switch evt.ID {
|
||||
|
||||
case "q", "<C-c>":
|
||||
return
|
||||
|
||||
case "<C-r>":
|
||||
updateTicker.Reset(0)
|
||||
|
||||
case "<Resize>":
|
||||
ui.Clear()
|
||||
updateUI(player)
|
||||
|
||||
}
|
||||
|
||||
case <-updateTicker.C:
|
||||
if err := updateUI(player); err != nil {
|
||||
log.WithError(err).Error("Unable to update metrics")
|
||||
return
|
||||
}
|
||||
updateTicker.Reset(time.Until(cron.Next(time.Now())))
|
||||
|
||||
if err := playerInfoCache.storeCache(); err != nil {
|
||||
log.WithError(err).Error("Unable to write cache")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateUI(player string) error {
|
||||
termWidth, termHeight := ui.TerminalDimensions()
|
||||
|
||||
playerData, err := getPlayerInfo(player, 20)
|
||||
|
||||
// Status-bar
|
||||
status := widgets.NewParagraph()
|
||||
status.Title = "Status"
|
||||
status.Text = fmt.Sprintf("Last Refresh: %s | XP Change: %s | Feed Change: %s",
|
||||
lastUpdate[updateKeyGeneral].Format("15:04:05"),
|
||||
lastUpdate[updateKeyTotalXP].Format("15:04:05"),
|
||||
lastUpdate[updateKeyFeed].Format("15:04:05"),
|
||||
)
|
||||
status.SetRect(0, termHeight-3, termWidth, termHeight)
|
||||
defer ui.Render(status)
|
||||
|
||||
if err != nil {
|
||||
status.Text = fmt.Sprintf("Unable to get player info: %s", err.Error())
|
||||
status.BorderStyle.Fg = ui.ColorRed
|
||||
return nil
|
||||
}
|
||||
|
||||
// Header
|
||||
hdrText := widgets.NewParagraph()
|
||||
hdrText.Title = "Player"
|
||||
hdrText.Text = playerData.Name
|
||||
hdrText.SetRect(0, 0, termWidth, 3)
|
||||
ui.Render(hdrText)
|
||||
|
||||
// General stats
|
||||
combatLevel := widgets.NewParagraph()
|
||||
combatLevel.Title = "Combat Level"
|
||||
combatLevel.Text = strconv.Itoa(playerData.CombatLevel)
|
||||
|
||||
totalXP := widgets.NewParagraph()
|
||||
totalXP.Title = "Total XP"
|
||||
totalXP.Text = strconv.FormatInt(playerData.TotalXP, 10)
|
||||
|
||||
totalLevel := widgets.NewParagraph()
|
||||
totalLevel.Title = "Total Level"
|
||||
totalLevel.Text = strconv.FormatInt(playerData.TotalSkill, 10)
|
||||
|
||||
rank := widgets.NewParagraph()
|
||||
rank.Title = "Rank"
|
||||
rank.Text = strconv.FormatInt(playerData.NumericRank(), 10)
|
||||
|
||||
statsGrid := ui.NewGrid()
|
||||
statsGrid.SetRect(0, 3, termWidth, 6)
|
||||
statsGrid.Set(
|
||||
ui.NewRow(1.0,
|
||||
ui.NewCol(1.0/4, combatLevel),
|
||||
ui.NewCol(1.0/4, totalXP),
|
||||
ui.NewCol(1.0/4, totalLevel),
|
||||
ui.NewCol(1.0/4, rank),
|
||||
),
|
||||
)
|
||||
ui.Render(statsGrid)
|
||||
|
||||
// Levels
|
||||
levelTable := widgets.NewTable()
|
||||
levelTable.Title = "Levels"
|
||||
levelTable.TextAlignment = ui.AlignRight
|
||||
levelTable.RowStyles[0] = ui.Style{Fg: ui.ColorWhite, Modifier: ui.ModifierBold}
|
||||
levelTable.SetRect(0, 6, termWidth, 6+2+len(playerData.SkillValues)+1)
|
||||
levelTable.RowSeparator = false
|
||||
levelTable.Rows = [][]string{{"Skill", "Level", "Level %", "XP", "XP to next Level"}}
|
||||
for i, s := range playerData.SkillValues {
|
||||
levelTable.Rows = append(levelTable.Rows, []string{
|
||||
s.ID.String(),
|
||||
strconv.Itoa(s.Level),
|
||||
strconv.FormatFloat(s.ID.Info().LevelPercentage(s.Level, s.XP/10), 'f', 1, 64),
|
||||
strconv.FormatInt(s.XP/10, 10),
|
||||
strconv.FormatInt(s.ID.Info().XPToNextLevel(s.Level, s.XP/10), 10),
|
||||
})
|
||||
|
||||
if time.Since(s.Updated) < cfg.MarkerTime {
|
||||
levelTable.RowStyles[i+1] = ui.Style{Fg: ui.ColorGreen}
|
||||
}
|
||||
}
|
||||
ui.Render(levelTable)
|
||||
|
||||
// Latest events
|
||||
events := widgets.NewTable()
|
||||
events.Title = "Event Log"
|
||||
events.RowSeparator = false
|
||||
events.ColumnWidths = []int{12, termWidth - 3 - 12}
|
||||
events.SetRect(0, 6+2+len(playerData.SkillValues)+1, termWidth, termHeight-3)
|
||||
for i, logEntry := range playerData.Activities {
|
||||
date, _ := logEntry.GetParsedDate()
|
||||
events.Rows = append(
|
||||
events.Rows,
|
||||
[]string{
|
||||
date.Local().Format("01/02 15:04"),
|
||||
strings.Replace(logEntry.Details, " ", " ", -1),
|
||||
},
|
||||
)
|
||||
|
||||
if time.Since(date) < cfg.MarkerTime {
|
||||
events.RowStyles[i] = ui.Style{Fg: ui.ColorGreen}
|
||||
}
|
||||
}
|
||||
ui.Render(events)
|
||||
|
||||
return nil
|
||||
}
|
172
metrics.go
Normal file
172
metrics.go
Normal file
|
@ -0,0 +1,172 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
knownTotalXP int64
|
||||
knownFeed time.Time
|
||||
|
||||
playerInfoCache *playerInfo
|
||||
)
|
||||
|
||||
type activity struct {
|
||||
Date string `json:"date"`
|
||||
Details string `json:"details"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
func (a activity) GetParsedDate() (time.Time, error) {
|
||||
loc, err := time.LoadLocation("Europe/London")
|
||||
if err != nil {
|
||||
return time.Time{}, errors.Wrap(err, "Unable to load London time information")
|
||||
}
|
||||
|
||||
return time.ParseInLocation("02-Jan-2006 15:04", a.Date, loc)
|
||||
}
|
||||
|
||||
type skill struct {
|
||||
ID skillID `json:"id"`
|
||||
Level int `json:"level"`
|
||||
Rank int64 `json:"rank"`
|
||||
XP int64 `json:"xp"`
|
||||
|
||||
Updated time.Time
|
||||
}
|
||||
|
||||
type playerInfo struct {
|
||||
Activities []activity `json:"activities"`
|
||||
CombatLevel int `json:"combatlevel"`
|
||||
LoggedIn bool `json:"loggedIn,string"`
|
||||
Magic int64 `json:"magic"`
|
||||
Melee int64 `json:"melee"`
|
||||
Name string `json:"name"`
|
||||
QuestsComplete int `json:"questscomplete"`
|
||||
QuestsNotStarted int `json:"questsnotstarted"`
|
||||
QuestsStarted int `json:"questsstarted"`
|
||||
Ranged int64 `json:"ranged"`
|
||||
Rank string `json:"rank"`
|
||||
SkillValues []skill `json:"skillvalues"`
|
||||
TotalSkill int64 `json:"totalskill"`
|
||||
TotalXP int64 `json:"totalxp"`
|
||||
}
|
||||
|
||||
func (p playerInfo) NumericRank() int64 {
|
||||
v, _ := strconv.ParseInt(strings.Replace(p.Rank, ",", "", -1), 10, 64)
|
||||
return v
|
||||
}
|
||||
|
||||
func (p playerInfo) GetSkill(s skillID) skill {
|
||||
for _, sk := range p.SkillValues {
|
||||
if sk.ID == s {
|
||||
return sk
|
||||
}
|
||||
}
|
||||
return skill{}
|
||||
}
|
||||
|
||||
func (p playerInfo) storeCache() error {
|
||||
cacheDir, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Unable to retrieve user cache dir")
|
||||
}
|
||||
|
||||
cacheDir = path.Join(cacheDir, "luzifer", "runemetrics")
|
||||
if err = os.MkdirAll(cacheDir, 0755); err != nil {
|
||||
return errors.Wrap(err, "Unable to create cache dir")
|
||||
}
|
||||
|
||||
cacheFile := path.Join(cacheDir, "metrics.json")
|
||||
f, err := os.Create(cacheFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Unable to create cache file")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return errors.Wrap(json.NewEncoder(f).Encode(p), "Unable to marshal into cache file")
|
||||
}
|
||||
|
||||
func getPlayerInfo(name string, activities int) (*playerInfo, error) {
|
||||
if name == "" {
|
||||
return nil, errors.New("Player name must not be empty")
|
||||
}
|
||||
|
||||
params := url.Values{
|
||||
"user": []string{name},
|
||||
"activities": []string{strconv.Itoa(activities)},
|
||||
}
|
||||
uri := "https://apps.runescape.com/runemetrics/profile/profile?" + params.Encode()
|
||||
|
||||
resp, err := http.Get(uri)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Unable to query profile data")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
out := &playerInfo{}
|
||||
if err = json.NewDecoder(resp.Body).Decode(out); err != nil {
|
||||
return nil, errors.Wrap(err, "Unable to decode profile data")
|
||||
}
|
||||
|
||||
if playerInfoCache != nil {
|
||||
for i, nSk := range out.SkillValues {
|
||||
if oSk := playerInfoCache.GetSkill(nSk.ID); oSk.XP == nSk.XP {
|
||||
out.SkillValues[i].Updated = oSk.Updated
|
||||
continue
|
||||
}
|
||||
|
||||
out.SkillValues[i].Updated = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
if knownTotalXP != out.TotalXP {
|
||||
knownTotalXP = out.TotalXP
|
||||
lastUpdate[updateKeyTotalXP] = time.Now()
|
||||
}
|
||||
|
||||
if d, _ := out.Activities[0].GetParsedDate(); !d.Equal(knownFeed) {
|
||||
knownFeed = d
|
||||
lastUpdate[updateKeyFeed] = time.Now()
|
||||
}
|
||||
|
||||
lastUpdate[updateKeyGeneral] = time.Now()
|
||||
|
||||
playerInfoCache = out
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func loadPlayerInfoCache() (*playerInfo, error) {
|
||||
cacheDir, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Unable to retrieve user cache dir")
|
||||
}
|
||||
|
||||
cacheFile := path.Join(cacheDir, "luzifer", "runemetrics", "metrics.json")
|
||||
if _, err := os.Stat(cacheFile); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// Empty cache
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.Wrap(err, "Unable to stat cache file")
|
||||
}
|
||||
|
||||
f, err := os.Open(cacheFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Unable to open cache file")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
p := &playerInfo{}
|
||||
return p, errors.Wrap(json.NewDecoder(f).Decode(p), "Unable to unmarshal cache file")
|
||||
}
|
167
skill.go
Normal file
167
skill.go
Normal file
|
@ -0,0 +1,167 @@
|
|||
package main
|
||||
|
||||
type skillInfo struct {
|
||||
id uint
|
||||
name string
|
||||
color string
|
||||
maxLevel int
|
||||
skillCurve string // ???
|
||||
}
|
||||
|
||||
func (s skillInfo) NextLevelXP(level int) int64 {
|
||||
levelTree := levels
|
||||
if s.skillCurve != "" {
|
||||
levelTree = masterLevels
|
||||
}
|
||||
|
||||
return levelTree[level+1]
|
||||
}
|
||||
|
||||
func (s skillInfo) LevelPercentage(level int, xp int64) float64 {
|
||||
var (
|
||||
xpCurr = float64(s.NextLevelXP(level - 1))
|
||||
xpNext = float64(s.NextLevelXP(level))
|
||||
)
|
||||
|
||||
return (float64(xp) - xpCurr) / (xpNext - xpCurr) * 100
|
||||
}
|
||||
|
||||
func (s skillInfo) XPToNextLevel(level int, xp int64) int64 {
|
||||
return s.NextLevelXP(level) - xp
|
||||
}
|
||||
|
||||
var skillList = []skillInfo{
|
||||
{
|
||||
id: 0,
|
||||
name: "Attack",
|
||||
color: "#981414",
|
||||
}, {
|
||||
id: 1,
|
||||
name: "Defence",
|
||||
color: "#147e98",
|
||||
}, {
|
||||
id: 2,
|
||||
name: "Strength",
|
||||
color: "#13b787",
|
||||
}, {
|
||||
id: 3,
|
||||
name: "Constitution",
|
||||
color: "#AACEDA",
|
||||
}, {
|
||||
id: 4,
|
||||
name: "Ranged",
|
||||
color: "#13b751",
|
||||
}, {
|
||||
id: 5,
|
||||
name: "Prayer",
|
||||
color: "#6dbff2",
|
||||
}, {
|
||||
id: 6,
|
||||
name: "Magic",
|
||||
color: "#c3e3dc",
|
||||
}, {
|
||||
id: 7,
|
||||
name: "Cooking",
|
||||
color: "#553285",
|
||||
}, {
|
||||
id: 8,
|
||||
name: "Woodcutting",
|
||||
color: "#7e4f35",
|
||||
}, {
|
||||
id: 9,
|
||||
name: "Fletching",
|
||||
color: "#149893",
|
||||
}, {
|
||||
id: 10,
|
||||
name: "Fishing",
|
||||
color: "#3e70b9",
|
||||
}, {
|
||||
id: 11,
|
||||
name: "Firemaking",
|
||||
color: "#f75f28",
|
||||
}, {
|
||||
id: 12,
|
||||
name: "Crafting",
|
||||
color: "#b6952c",
|
||||
}, {
|
||||
id: 13,
|
||||
name: "Smithing",
|
||||
color: "#65887e",
|
||||
}, {
|
||||
id: 14,
|
||||
name: "Mining",
|
||||
color: "#56495e",
|
||||
}, {
|
||||
id: 15,
|
||||
name: "Herblore",
|
||||
color: "#12453a",
|
||||
}, {
|
||||
id: 16,
|
||||
name: "Agility",
|
||||
color: "#284A95",
|
||||
}, {
|
||||
id: 17,
|
||||
name: "Thieving",
|
||||
color: "#36175e",
|
||||
}, {
|
||||
id: 18,
|
||||
name: "Slayer",
|
||||
color: "#48412f",
|
||||
}, {
|
||||
id: 19,
|
||||
name: "Farming",
|
||||
color: "#1f7d54",
|
||||
}, {
|
||||
id: 20,
|
||||
name: "Runecrafting",
|
||||
color: "#d7eba3",
|
||||
}, {
|
||||
id: 21,
|
||||
name: "Hunter",
|
||||
color: "#c38b4e",
|
||||
}, {
|
||||
id: 22,
|
||||
name: "Construction",
|
||||
color: "#a8babc",
|
||||
}, {
|
||||
id: 23,
|
||||
name: "Summoning",
|
||||
color: "#DEA1B0",
|
||||
}, {
|
||||
id: 24,
|
||||
name: "Dungeoneering",
|
||||
color: "#723920",
|
||||
maxLevel: 120,
|
||||
}, {
|
||||
id: 25,
|
||||
name: "Divination",
|
||||
color: "#943fba",
|
||||
}, {
|
||||
id: 26,
|
||||
name: "Invention",
|
||||
color: "#f7b528",
|
||||
skillCurve: "master",
|
||||
},
|
||||
}
|
||||
|
||||
type skillID uint
|
||||
|
||||
func (s skillID) String() string {
|
||||
for _, se := range skillList {
|
||||
if se.id == uint(s) {
|
||||
return se.name
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s skillID) Info() skillInfo {
|
||||
for _, se := range skillList {
|
||||
if se.id == uint(s) {
|
||||
return se
|
||||
}
|
||||
}
|
||||
|
||||
return skillInfo{}
|
||||
}
|
Loading…
Reference in a new issue