bot.go 8.8KB

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