bot.go 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. package main
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "github.com/jasonlvhit/gocron"
  7. "github.com/lamperi/e4bot/spotify"
  8. "io"
  9. "log"
  10. "os"
  11. "regexp"
  12. "strconv"
  13. "strings"
  14. "time"
  15. )
  16. var songs SongPriorityQueue
  17. var credentials struct {
  18. APIURL string
  19. UserName string
  20. Password string
  21. SpotifyClientID string
  22. SpotifyClientSecret string
  23. ListenAddr 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(updateTitle, updateSection, author, song string) (bool, error) {
  44. wiki := CreateWikiClient(credentials.APIURL, credentials.UserName, credentials.Password)
  45. sections, err := wiki.GetWikiPageSections(updateTitle)
  46. if err != nil {
  47. return false, err
  48. }
  49. for _, section := range sections {
  50. if updateSection == section.title {
  51. wikiText, err := wiki.GetWikiPageSectionText(updateTitle, section.index)
  52. if err != nil {
  53. return false, err
  54. }
  55. changedWikiText := appendSong(wikiText, author, song)
  56. if false {
  57. // Stub
  58. fmt.Println("Pretend to update wiki text to ", updateTitle, section.index, changedWikiText,
  59. fmt.Sprintf("Added %s song for %s", updateSection, author))
  60. return true, nil
  61. }
  62. return wiki.EditWikiPageSection(updateTitle, section.index, changedWikiText,
  63. fmt.Sprintf("Added %s song for %s", updateSection, author))
  64. }
  65. }
  66. return false, errors.New("Could not find matching section")
  67. }
  68. func songSynced(userId, roundId int) error {
  69. query := `UPDATE public.entry SET synced = true WHERE user_id = $1 AND round_id = $2`
  70. res, err := getDb().Exec(query, userId, roundId)
  71. if err != nil {
  72. return err
  73. }
  74. affected, err := res.RowsAffected()
  75. if err != nil {
  76. return err
  77. }
  78. if affected != 1 {
  79. return errors.New("Unknown entry ID")
  80. }
  81. return nil
  82. }
  83. func submitSong() {
  84. query := `
  85. SELECT e.user_id, e.round_id, e.artist, e.title, e.spotify_url, p.article, u.username, r.section
  86. FROM public.entry e
  87. JOIN public."user" u ON u.id = e.user_id
  88. JOIN public.round r ON r.id = e.round_id
  89. JOIN public.panel p ON p.id = r.panel_id
  90. WHERE r.start < current_timestamp AND e.synced = false`
  91. rows, err := getDb().Query(query)
  92. if err != nil {
  93. log.Println("Error while reading songs from database:", err)
  94. return
  95. }
  96. defer rows.Close()
  97. for rows.Next() {
  98. var (
  99. userId, roundId int
  100. artist, title, spotifyURL, article, username, section string
  101. )
  102. err := rows.Scan(&userId, &roundId, &artist, &title, &spotifyURL, &article, &username, &section)
  103. if err != nil {
  104. log.Println("Error while scanning row:", err)
  105. continue
  106. }
  107. song := songWikiText(spotifyURL, artist, title)
  108. fmt.Println("Time has passed for " + song)
  109. success, err := addSong(article, section, username, song)
  110. if err != nil {
  111. log.Println("Error while adding song:", err)
  112. }
  113. if success {
  114. err = songSynced(userId, roundId)
  115. if err != nil {
  116. fmt.Println("Error received:", err)
  117. }
  118. }
  119. }
  120. err = rows.Err()
  121. if err != nil {
  122. log.Println("Error after reading cursor:", err)
  123. return
  124. }
  125. }
  126. func isCurrentAuthor(line, author string) bool {
  127. authorIndex := strings.Index(line, author)
  128. endIndex := strings.Index(line, "-->")
  129. return authorIndex != -1 && authorIndex < endIndex
  130. }
  131. func parseScore(line string) string {
  132. parts := strings.Split(line, "-->")
  133. if len(parts) < 2 {
  134. return ""
  135. }
  136. score := strings.TrimRight(strings.Trim(parts[1], " \t\n"), "p")
  137. if score == "" {
  138. return score
  139. }
  140. number, _ := regexp.Compile("^\\d([.,]\\d)?$")
  141. if number.MatchString(score) {
  142. return strings.Replace(score, ",", ".", 1)
  143. }
  144. numberHalf, _ := regexp.Compile("^\\d½$")
  145. if numberHalf.MatchString(score) {
  146. return fmt.Sprintf("%c.5", score[0])
  147. }
  148. stars, _ := regexp.Compile("^\\*+$")
  149. if stars.MatchString(score) {
  150. return fmt.Sprintf("%d", len(score))
  151. }
  152. imageStars, _ := regexp.Compile("^(\\[\\[Image:[01]\\.png\\]\\]){5}$")
  153. if imageStars.MatchString(score) {
  154. return fmt.Sprintf("%d", strings.Count(score, "1"))
  155. }
  156. quarterScore, _ := regexp.Compile("^\\d-\\d½?$")
  157. if quarterScore.MatchString(score) {
  158. return fmt.Sprintf("%c.25", score[0])
  159. }
  160. thirdQuarterScore, _ := regexp.Compile("^\\d½?-\\d$")
  161. if thirdQuarterScore.MatchString(score) {
  162. return fmt.Sprintf("%c.75", score[0])
  163. }
  164. fmt.Printf("Could not match '%s'\n", score)
  165. return ""
  166. }
  167. func appendAverages(wikiText string) string {
  168. const AUTHOR_MARK = "<!-- Lisääjä -->"
  169. const SONG_MARK = "<!-- Kappale -->"
  170. const AVERAGE_MARK = "<!-- KA -->"
  171. lines := strings.Split(wikiText, "\n")
  172. isScore := false
  173. scores := make([]string, 0)
  174. count := 0
  175. currentAuthor := ""
  176. changedLines := make([]string, 0, len(lines))
  177. for _, line := range lines {
  178. if strings.Index(line, AUTHOR_MARK) != -1 {
  179. currentAuthor = strings.Trim(strings.Split(line, AUTHOR_MARK)[1], " \t")
  180. } else if strings.Index(line, SONG_MARK) != -1 {
  181. isScore = true
  182. scores = make([]string, 0)
  183. count = 0
  184. } else if isScore && strings.Index(line, AVERAGE_MARK) == -1 {
  185. if !isCurrentAuthor(line, currentAuthor) {
  186. score := parseScore(line)
  187. if score != "" {
  188. scores = append(scores, score)
  189. count += 1
  190. } else {
  191. scores = append(scores, "0")
  192. }
  193. }
  194. }
  195. if strings.Index(line, AVERAGE_MARK) != -1 && count > 2 {
  196. expression := fmt.Sprintf("'''{{#expr:(%s)/%d round 2}}'''", strings.Join(scores, "+"), count)
  197. newLine := "| " + AVERAGE_MARK + " " + expression
  198. changedLines = append(changedLines, newLine)
  199. if newLine != line {
  200. fmt.Printf("Difference for %s\n%s\n%s\n", currentAuthor, newLine, line)
  201. }
  202. } else {
  203. changedLines = append(changedLines, line)
  204. }
  205. }
  206. return strings.Join(changedLines, "\n")
  207. }
  208. func fixAverages(title string) error {
  209. wiki := CreateWikiClient(credentials.APIURL, credentials.UserName, credentials.Password)
  210. sections, err := wiki.GetWikiPageSections(title)
  211. if err != nil {
  212. return err
  213. }
  214. _, currentWeek := time.Now().ISOWeek()
  215. numberReg, _ := regexp.Compile("\\d+")
  216. for _, section := range sections {
  217. weekStr := numberReg.FindString(section.title)
  218. if weekStr != "" {
  219. weekNumber, _ := strconv.Atoi(weekStr)
  220. if weekNumber < currentWeek-1 {
  221. continue
  222. }
  223. if weekNumber > currentWeek {
  224. break
  225. }
  226. wikiText, err := wiki.GetWikiPageSectionText(title, section.index)
  227. if err != nil {
  228. return err
  229. }
  230. changedWikiText := appendAverages(wikiText)
  231. if changedWikiText != wikiText {
  232. //fmt.Println(wikiText)
  233. //fmt.Println(changedWikiText)
  234. _, err := wiki.EditWikiPageSection(title, section.index, changedWikiText,
  235. fmt.Sprintf("Calculate averages for week %d", weekNumber))
  236. if err != nil {
  237. return err
  238. }
  239. }
  240. }
  241. }
  242. return nil
  243. }
  244. func fixAveragesTask() {
  245. err := fixAverages("Levyraati 2018")
  246. if err != nil {
  247. fmt.Println("Error while calculating averages:", err)
  248. }
  249. }
  250. func initCreds() error {
  251. f, err := os.Open("credentials.json")
  252. if err != nil {
  253. return err
  254. }
  255. defer f.Close()
  256. if err != nil {
  257. log.Fatal(err)
  258. return err
  259. }
  260. dec := json.NewDecoder(f)
  261. for {
  262. if err := dec.Decode(&credentials); err == io.EOF {
  263. break
  264. } else if err != nil {
  265. log.Fatal(err)
  266. }
  267. }
  268. return nil
  269. }
  270. func songWikiText(url string, artist string, title string) string {
  271. return "[" + url + " " + artist + " - " + title + "]"
  272. }
  273. func main() {
  274. err := initCreds()
  275. if err != nil {
  276. panic(err)
  277. }
  278. spotifyClient := spotify.NewClient(credentials.SpotifyClientID, credentials.SpotifyClientSecret)
  279. go func() {
  280. webStart(credentials.ListenAddr, spotifyClient)
  281. }()
  282. gocron.Every(1).Hour().Do(fixAveragesTask)
  283. gocron.Every(1).Second().Do(submitSong)
  284. <-gocron.Start()
  285. }