bot.go 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. package main
  2. import (
  3. "container/heap"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "github.com/jasonlvhit/gocron"
  8. "github.com/lamperi/e4bot/spotify"
  9. "io"
  10. "log"
  11. "os"
  12. "regexp"
  13. "strconv"
  14. "strings"
  15. "time"
  16. )
  17. var songs SongPriorityQueue
  18. var credentials struct {
  19. APIURL string
  20. UserName string
  21. Password string
  22. SpotifyClientID string
  23. SpotifyClientSecret string
  24. }
  25. func appendSong(wikiText string, author string, newSong string) string {
  26. const AUTHOR_MARK = "<!-- Lisääjä -->"
  27. const SONG_MARK = "<!-- Kappale -->"
  28. lines := strings.Split(wikiText, "\n")
  29. authorPrevIndex := -2
  30. changedLines := make([]string, 0, len(lines))
  31. for index, line := range lines {
  32. if strings.Index(line, AUTHOR_MARK) != -1 && strings.Index(line, author) != -1 {
  33. authorPrevIndex = index
  34. }
  35. if authorPrevIndex == (index-1) && strings.Index(line, SONG_MARK) != -1 {
  36. changedLines = append(changedLines, "| "+SONG_MARK+" "+newSong)
  37. } else {
  38. changedLines = append(changedLines, line)
  39. }
  40. }
  41. return strings.Join(changedLines, "\n")
  42. }
  43. func addSong(title string, week int, author string, song string) (bool, error) {
  44. wiki := CreateWikiClient(credentials.APIURL, credentials.UserName, credentials.Password)
  45. sections, err := wiki.GetWikiPageSections(title)
  46. if err != nil {
  47. return false, err
  48. }
  49. numberReg, _ := regexp.Compile("\\d+")
  50. for _, section := range sections {
  51. weekStr := numberReg.FindString(section.title)
  52. if weekStr != "" {
  53. weekNumber, _ := strconv.Atoi(weekStr)
  54. if weekNumber == week {
  55. wikiText, err := wiki.GetWikiPageSectionText(title, section.index)
  56. if err != nil {
  57. return false, err
  58. }
  59. changedWikiText := appendSong(wikiText, author, song)
  60. return wiki.EditWikiPageSection(title, section.index, changedWikiText,
  61. fmt.Sprintf("Added week %d song for %s", week, author))
  62. }
  63. }
  64. }
  65. return false, errors.New("Could not find matching section")
  66. }
  67. func songSynced(syncedWeek int) error {
  68. v, err := loadDb()
  69. if err != nil {
  70. return err
  71. }
  72. synced := false
  73. for _, song := range v.Songs {
  74. if song.Week == syncedWeek {
  75. song.Sync = true
  76. synced = true
  77. err := saveDb(v)
  78. if err != nil {
  79. return err
  80. }
  81. }
  82. }
  83. if !synced {
  84. return errors.New("No week matched from JSON for synced song")
  85. }
  86. return errors.New("Week not found")
  87. }
  88. func getSongs() (SongPriorityQueue, error) {
  89. dbSongs, err := loadDb()
  90. if err != nil {
  91. return nil, err
  92. }
  93. songs := make(SongPriorityQueue, len(dbSongs.Songs))
  94. for index, songObj := range dbSongs.Songs {
  95. songs[index] = &Song{
  96. time: targetTime(songObj),
  97. song: songEntryWikiText(songObj),
  98. week: songObj.Week,
  99. sync: songObj.Sync,
  100. index: index,
  101. }
  102. }
  103. heap.Init(&songs)
  104. for len(songs) > 0 && songs[0].sync {
  105. heap.Pop(&songs)
  106. }
  107. return songs, nil
  108. }
  109. func submitSong() {
  110. now := time.Now()
  111. if len(songs) > 0 && songs[0].time.Before(now) {
  112. fmt.Println("Time has passed for " + songs[0].song)
  113. success, err := addSong("Levyraati 2018", songs[0].week, "Lamperi", songs[0].song)
  114. if err != nil {
  115. log.Println("Error while adding song:", err)
  116. }
  117. if success {
  118. err := songSynced(songs[0].week)
  119. if err == nil {
  120. heap.Pop(&songs)
  121. } else {
  122. fmt.Println("Error received:", err)
  123. }
  124. }
  125. }
  126. }
  127. func isCurrentAuthor(line, author string) bool {
  128. authorIndex := strings.Index(line, author)
  129. endIndex := strings.Index(line, "-->")
  130. return authorIndex != -1 && authorIndex < endIndex
  131. }
  132. func parseScore(line string) string {
  133. parts := strings.Split(line, "-->")
  134. if len(parts) < 2 {
  135. return ""
  136. }
  137. score := strings.TrimRight(strings.Trim(parts[1], " \t\n"), "p")
  138. if score == "" {
  139. return score
  140. }
  141. number, _ := regexp.Compile("^\\d([.,]\\d)?$")
  142. if number.MatchString(score) {
  143. return strings.Replace(score, ",", ".", 1)
  144. }
  145. numberHalf, _ := regexp.Compile("^\\d½$")
  146. if numberHalf.MatchString(score) {
  147. return fmt.Sprintf("%c.5", score[0])
  148. }
  149. stars, _ := regexp.Compile("^\\*+$")
  150. if stars.MatchString(score) {
  151. return fmt.Sprintf("%d", len(score))
  152. }
  153. imageStars, _ := regexp.Compile("^(\\[\\[Image:[01]\\.png\\]\\]){5}$")
  154. if imageStars.MatchString(score) {
  155. return fmt.Sprintf("%d", strings.Count(score, "1"))
  156. }
  157. quarterScore, _ := regexp.Compile("^\\d-\\d½?$")
  158. if quarterScore.MatchString(score) {
  159. return fmt.Sprintf("%c.25", score[0])
  160. }
  161. thirdQuarterScore, _ := regexp.Compile("^\\d½?-\\d$")
  162. if thirdQuarterScore.MatchString(score) {
  163. return fmt.Sprintf("%c.75", score[0])
  164. }
  165. fmt.Printf("Could not match '%s'\n", score)
  166. return ""
  167. }
  168. func appendAverages(wikiText string) string {
  169. const AUTHOR_MARK = "<!-- Lisääjä -->"
  170. const SONG_MARK = "<!-- Kappale -->"
  171. const AVERAGE_MARK = "<!-- KA -->"
  172. lines := strings.Split(wikiText, "\n")
  173. isScore := false
  174. scores := make([]string, 0)
  175. count := 0
  176. currentAuthor := ""
  177. changedLines := make([]string, 0, len(lines))
  178. for _, line := range lines {
  179. if strings.Index(line, AUTHOR_MARK) != -1 {
  180. currentAuthor = strings.Trim(strings.Split(line, AUTHOR_MARK)[1], " \t")
  181. } else if strings.Index(line, SONG_MARK) != -1 {
  182. isScore = true
  183. scores = make([]string, 0)
  184. count = 0
  185. } else if isScore && strings.Index(line, AVERAGE_MARK) == -1 {
  186. if !isCurrentAuthor(line, currentAuthor) {
  187. score := parseScore(line)
  188. if score != "" {
  189. scores = append(scores, score)
  190. count += 1
  191. } else {
  192. scores = append(scores, "0")
  193. }
  194. }
  195. }
  196. if strings.Index(line, AVERAGE_MARK) != -1 {
  197. expression := fmt.Sprintf("'''{{#expr:(%s)/%d round 2}}'''", strings.Join(scores, "+"), count)
  198. newLine := "| " + AVERAGE_MARK + " " + expression
  199. changedLines = append(changedLines, newLine)
  200. if newLine != line {
  201. fmt.Printf("Difference for %s\n%s\n%s\n", currentAuthor, newLine, line)
  202. }
  203. } else {
  204. changedLines = append(changedLines, line)
  205. }
  206. }
  207. return strings.Join(changedLines, "\n")
  208. }
  209. func fixAverages(title string) error {
  210. wiki := CreateWikiClient(credentials.APIURL, credentials.UserName, credentials.Password)
  211. sections, err := wiki.GetWikiPageSections(title)
  212. if err != nil {
  213. return err
  214. }
  215. currentWeek := 2
  216. numberReg, _ := regexp.Compile("\\d+")
  217. for _, section := range sections {
  218. weekStr := numberReg.FindString(section.title)
  219. if weekStr != "" {
  220. weekNumber, _ := strconv.Atoi(weekStr)
  221. if weekNumber > currentWeek {
  222. break
  223. }
  224. wikiText, err := wiki.GetWikiPageSectionText(title, section.index)
  225. if err != nil {
  226. return err
  227. }
  228. changedWikiText := appendAverages(wikiText)
  229. if changedWikiText != wikiText {
  230. //fmt.Println(wikiText)
  231. //fmt.Println(changedWikiText)
  232. _, err := wiki.EditWikiPageSection(title, section.index, changedWikiText,
  233. fmt.Sprintf("Calculate averages for week %d", weekNumber))
  234. if err != nil {
  235. return err
  236. }
  237. }
  238. }
  239. }
  240. return nil
  241. }
  242. func fixAveragesTask() {
  243. err := fixAverages("Levyraati 2018")
  244. if err != nil {
  245. fmt.Println("Error while calculating averages:", err)
  246. }
  247. }
  248. func initCreds() error {
  249. f, err := os.Open("credentials.json")
  250. if err != nil {
  251. return err
  252. }
  253. defer f.Close()
  254. if err != nil {
  255. log.Fatal(err)
  256. return err
  257. }
  258. dec := json.NewDecoder(f)
  259. for {
  260. if err := dec.Decode(&credentials); err == io.EOF {
  261. break
  262. } else if err != nil {
  263. log.Fatal(err)
  264. }
  265. }
  266. return nil
  267. }
  268. func targetTime(entry *SongEntry) time.Time {
  269. yearStart, _ := time.Parse(time.RFC3339, "2018-01-01T00:00:00+02:00")
  270. target := yearStart.AddDate(0, 0, (entry.Week-1)*7)
  271. return target
  272. }
  273. func songEntryWikiText(entry *SongEntry) string {
  274. return songWikiText(entry.URL, entry.Artist, entry.Title)
  275. }
  276. func songWikiText(url string, artist string, title string) string {
  277. return "[" + url + " " + artist + " - " + title + "]"
  278. }
  279. func main() {
  280. err := initCreds()
  281. if err != nil {
  282. panic(err)
  283. }
  284. songs, err = getSongs()
  285. if err != nil {
  286. panic(err)
  287. }
  288. modifiedSongChan := make(chan *SongEntry)
  289. spotifyClient := spotify.NewClient(credentials.SpotifyClientID, credentials.SpotifyClientSecret)
  290. go func() {
  291. webStart(modifiedSongChan, spotifyClient)
  292. }()
  293. go func() {
  294. for {
  295. newSong := <-modifiedSongChan
  296. matched := false
  297. for _, song := range songs {
  298. if song.week == newSong.Week {
  299. song.song = songEntryWikiText(newSong)
  300. song.sync = newSong.Sync
  301. matched = true
  302. log.Printf("Updated song for week %d, artist: %s, title: %s, URL: %s, time: %v",
  303. newSong.Week, newSong.Artist, newSong.Title, newSong.URL, song.time)
  304. }
  305. }
  306. if !matched {
  307. song := &Song{
  308. time: targetTime(newSong),
  309. song: songEntryWikiText(newSong),
  310. week: newSong.Week,
  311. sync: newSong.Sync,
  312. index: len(songs),
  313. }
  314. heap.Push(&songs, song)
  315. log.Printf("Added song for week %d, artist: %s, title: %s, URL: %s, time: %v",
  316. newSong.Week, newSong.Artist, newSong.Title, newSong.URL, song.time)
  317. }
  318. }
  319. }()
  320. gocron.Every(1).Hour().Do(fixAveragesTask)
  321. gocron.Every(1).Second().Do(submitSong)
  322. <-gocron.Start()
  323. }