bot.go 8.5KB

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