bookserie.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. package wikidata
  2. import (
  3. "notabug.org/apiote/amuse/datastructure"
  4. "notabug.org/apiote/amuse/network"
  5. "database/sql"
  6. "encoding/json"
  7. "net/http"
  8. "sort"
  9. "strings"
  10. "notabug.org/apiote/gott"
  11. )
  12. type Ordinals struct {
  13. Entities map[string]struct {
  14. Claims struct {
  15. P527 []struct {
  16. Mainsnak struct {
  17. Datavalue struct {
  18. Value struct {
  19. Id string
  20. }
  21. }
  22. }
  23. Qualifiers struct {
  24. P1545 []struct {
  25. Datavalue struct {
  26. Value string
  27. }
  28. }
  29. }
  30. }
  31. }
  32. }
  33. }
  34. type BookSeriePart struct {
  35. Cover string
  36. Ordinal string
  37. Title string
  38. Uri string
  39. }
  40. type BookSerie struct {
  41. Title string
  42. Cover string // note maybe first book’s cover?
  43. Description string
  44. Authors []string
  45. Genres []string
  46. Source []datastructure.Source
  47. Article string
  48. Parts map[string]BookSeriePart
  49. SortedParts []BookSeriePart
  50. }
  51. func (s BookSerie) GetArticle() string {
  52. return s.Article
  53. }
  54. func (s *BookSerie) SetDescription(description string) {
  55. s.Description = description
  56. }
  57. func queryBookSerie(args ...interface{}) (interface{}, error) {
  58. id := args[0].(*network.Request).Id
  59. tag := args[0].(*network.Request).Language[:2]
  60. result := args[1].(*Result)
  61. repo := result.Repo
  62. res, err := repo.Query(`SELECT ?title ?seriesLabel ?genreLabel ?authorLabel ?partLabel ?partTitle ?part ?article_title WHERE {
  63. ` + id + ` wdt:P50 ?author;
  64. wdt:P1476 ?title;
  65. wdt:P527 ?part.
  66. ?part wdt:P1476 ?partTitle.
  67. OPTIONAL {
  68. ` + id + ` wdt:P136 ?genre.
  69. }
  70. OPTIONAL {
  71. ?article schema:about ` + id + `;
  72. schema:inLanguage "` + tag + `";
  73. schema:name ?article_title;
  74. schema:isPartOf _:b18.
  75. _:b18 wikibase:wikiGroup "wikipedia".
  76. FILTER(!(CONTAINS(?article_title, ":")))
  77. }
  78. SERVICE wikibase:label { bd:serviceParam wikibase:language "` + tag + `", "en". }
  79. SERVICE wikibase:label {
  80. bd:serviceParam wikibase:language "` + tag + `", "en".
  81. ` + id + ` rdfs:label ?seriesLabel.
  82. }
  83. }`)
  84. result.Result = res
  85. return gott.Tuple(args), err
  86. }
  87. func parseBookSerie(args ...interface{}) interface{} {
  88. id := args[0].(*network.Request).Id
  89. results := args[1].(*Result)
  90. res := results.Result
  91. if res.Results.Bindings == nil || len(res.Results.Bindings) == 0 {
  92. results.Work = &BookSerie{}
  93. } else {
  94. authors := map[string]bool{}
  95. genres := map[string]bool{}
  96. result := res.Results.Bindings[0]
  97. title := result["bookLabel"].Value
  98. if title == strings.Replace(id, "wd:", "", 1) || title == "" {
  99. title = result["title"].Value
  100. }
  101. bookSerie := BookSerie{
  102. Title: title,
  103. Source: []datastructure.Source{},
  104. Authors: []string{},
  105. Genres: []string{},
  106. Article: result["article_title"].Value,
  107. Parts: map[string]BookSeriePart{},
  108. }
  109. for _, r := range res.Results.Bindings {
  110. authors[r["authorLabel"].Value] = true
  111. if r["genreLabel"].Value != "" {
  112. genres[r["genreLabel"].Value] = true
  113. }
  114. title := r["partLabel"].Value
  115. if title == strings.Replace(r["part"].Value, "http://www.wikidata.org/entity/", "", 1) || title == "" {
  116. title = r["partTitle"].Value
  117. }
  118. partId := strings.Replace(r["part"].Value, "http://www.wikidata.org/entity/", "", 1)
  119. bookSerie.Parts[partId] = BookSeriePart{
  120. Uri: strings.Replace(r["part"].Value, "http://www.wikidata.org/entity/", "/books/wd:", 1),
  121. Title: title,
  122. }
  123. }
  124. bookSerie.Source = append(bookSerie.Source, datastructure.Source{
  125. Url: "https://www.wikidata.org/wiki/" + strings.Replace(id, "wd:", "", 1),
  126. Name: "Wikidata",
  127. })
  128. bookSerie.Source = append(bookSerie.Source, datastructure.Source{
  129. Url: "https://inventaire.io/entity/" + id,
  130. Name: "Inventaire",
  131. })
  132. for k := range authors {
  133. bookSerie.Authors = append(bookSerie.Authors, k)
  134. }
  135. for k := range genres {
  136. bookSerie.Genres = append(bookSerie.Genres, k)
  137. }
  138. results.Work = &bookSerie
  139. }
  140. return gott.Tuple(args)
  141. }
  142. func createOrdinalsRequest(args ...interface{}) (interface{}, error) {
  143. request := args[0].(*network.Request)
  144. result := args[1].(*network.Result)
  145. id := strings.Replace(request.Id, "wd:", "", 1)
  146. client := &http.Client{}
  147. httpRequest, err := http.NewRequest("GET", "https://www.wikidata.org/wiki/Special:EntityData/"+id+".json", nil)
  148. result.Client = client
  149. result.Request = httpRequest
  150. return gott.Tuple(args), err
  151. }
  152. func unmarshalOrdinals(args ...interface{}) (interface{}, error) {
  153. result := args[1].(*network.Result)
  154. var ordinals Ordinals
  155. err := json.Unmarshal(result.Body, &ordinals)
  156. result.Result = ordinals
  157. return gott.Tuple(args), err
  158. }
  159. func sortOrdinals(args ...interface{}) (interface{}, error) {
  160. bookSerie := args[2].(*BookSerie)
  161. ordinals := args[1].(*network.Result).Result.(Ordinals).Entities
  162. sorted := []BookSeriePart{}
  163. for _, entity := range ordinals {
  164. for _, claim := range entity.Claims.P527 {
  165. id := claim.Mainsnak.Datavalue.Value.Id
  166. part := bookSerie.Parts[id]
  167. if len(claim.Qualifiers.P1545) > 0 {
  168. ordinal := claim.Qualifiers.P1545[0].Datavalue.Value
  169. part.Ordinal = ordinal
  170. } else {
  171. part.Ordinal = ""
  172. }
  173. sorted = append(sorted, part)
  174. }
  175. }
  176. sort.Slice(sorted, func(i, j int) bool {
  177. if sorted[i].Ordinal == "prologue" || sorted[j].Ordinal == "epilogue" {
  178. return true
  179. } else if sorted[i].Ordinal == "epilogue" || sorted[j].Ordinal == "prologue" {
  180. return false
  181. } else {
  182. return sorted[i].Ordinal < sorted[j].Ordinal
  183. }
  184. })
  185. bookSerie.SortedParts = sorted
  186. return gott.Tuple(args), nil
  187. }
  188. func GetBookSerie(id, language string, connection *sql.DB) (*BookSerie, error) {
  189. res, err := gott.
  190. NewResult(gott.Tuple{&network.Request{Id: id, Language: language, Connection: connection}, &Result{}}).
  191. Bind(createRepo).
  192. Bind(queryBookSerie).
  193. Map(parseBookSerie).
  194. Finish()
  195. if err != nil {
  196. return &BookSerie{}, err
  197. }
  198. return res.(gott.Tuple)[1].(*Result).Work.(*BookSerie), nil
  199. }
  200. func GetBookSerieOrdinals(id string, bookSerie *BookSerie, connection *sql.DB) (*BookSerie, error) {
  201. _, err := gott.
  202. NewResult(gott.Tuple{&network.Request{Id: id, Connection: connection}, &network.Result{}, bookSerie}).
  203. Bind(createOrdinalsRequest).
  204. Bind(network.DoRequest).
  205. Bind(network.HandleRequestError).
  206. Bind(network.ReadResponse).
  207. Bind(unmarshalOrdinals).
  208. Bind(sortOrdinals).
  209. // todo add is_read in parts
  210. Finish()
  211. if err != nil {
  212. return &BookSerie{}, err
  213. }
  214. return bookSerie, nil
  215. }