]> git.lizzy.rs Git - go-anidb.git/blob - http/anime.go
8c609bd67e4fd0fa11c7bd9853953a20fb1c1741
[go-anidb.git] / http / anime.go
1 // Low-level wrapper around the AniDB HTTP API.
2 // Only implements the 'anime' and 'categorylist' requests.
3 //
4 // This wrapper does not implement caching. The API requires
5 // aggressive caching.
6 //
7 // http://wiki.anidb.info/w/HTTP_API_Definition
8 package httpapi
9
10 import (
11         "encoding/xml"
12         "fmt"
13         "log"
14         "net/http"
15         "net/url"
16         "strings"
17 )
18
19 const (
20         AniDBImageBaseURL = "http://img7.anidb.net/pics/anime/" // Base URL for the various Pictures in the response
21
22         DateFormat = "2006-01-02" // Use to convert the various YYYY-MM-DD timestamps to a time.Time.
23
24         // Base URLs for the various resources.
25         // Meant for use with fmt.Sprintf.
26         ANNFormat          = "http://www.animenewsnetwork.com/encyclopedia/anime.php?id=%v" // Type 1
27         MyAnimeListFormat  = "http://myanimelist.net/anime/%v"                              // Type 2
28         AnimeNfoFormat     = "http://www.animenfo.com/animetitle,%v,%v,a.html"              // Type 3
29         _                                                                                   // Type 4
30         _                                                                                   // Type 5
31         WikiEnglishFormat  = "http://en.wikipedia.org/wiki/%v"                              // Type 6
32         WikiJapaneseFormat = "http://ja.wikipedia.org/wiki/%v"                              // Type 7
33         SyoboiFormat       = "http://cal.syoboi.jp/tid/%v/time"                             // Type 8
34         AllCinemaFormat    = "http://www.allcinema.net/prog/show_c.php?num_c=%v"            // Type 9
35         AnisonFormat       = "http://anison.info/data/program/%v.html"                      // Type 10
36         VNDBFormat         = "http://vndb.org/v%v"                                          // Type 14
37         MaruMeganeFormat   = "http://www.anime.marumegane.com/%v.html"                      // Type 15
38 )
39
40 const (
41         aniDBHTTPAPIBaseURL = "http://api.anidb.net:9001/httpapi"
42         aniDBProtoVer       = 1
43         clientStr           = "goanidbhttp"
44         clientVer           = 1
45 )
46
47 // Requests information about the given Anime ID.
48 func GetAnime(AID int) (a Anime, err error) {
49         if res, err := doRequest("anime", reqMap{"aid": AID}); err != nil {
50                 return a, err
51         } else {
52                 dec := xml.NewDecoder(res.Body)
53                 err = dec.Decode(&a)
54                 res.Body.Close()
55
56                 a.Error = strings.TrimSpace(a.Error)
57
58                 title := ""
59                 for _, t := range a.Titles {
60                         if t.Type == "main" {
61                                 title = t.Title
62                                 break
63                         }
64                 }
65
66                 for _, r := range a.Resources {
67                         switch r.Type {
68                         case 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 14, 15:
69                                 // documentation knows about these
70                         default:
71                                 log.Printf("Anime %d (%s) has unknown resource type %d", a.ID, title, r.Type)
72                                 log.Printf("Type %d external entities: %#v", r.Type, r.ExternalEntity)
73                         }
74                 }
75
76                 return a, err
77         }
78 }
79
80 type reqMap map[string]interface{}
81
82 func doRequest(request string, reqMap reqMap) (*http.Response, error) {
83         v := url.Values{}
84         v.Set("protover", fmt.Sprint(aniDBProtoVer))
85         v.Set("client", clientStr)
86         v.Set("clientver", fmt.Sprint(clientVer))
87         v.Set("request", request)
88
89         for k, val := range reqMap {
90                 v.Add(k, fmt.Sprint(val))
91         }
92
93         u, _ := url.Parse(aniDBHTTPAPIBaseURL)
94         u.RawQuery = v.Encode()
95         log.Println(u.String())
96         return http.Get(u.String())
97 }
98
99 // Title with language and type identifier.
100 //
101 // Title with Lang = ja, Type = official is the official Kanji title.
102 //
103 // Title with Lang = x-jat, Type = main is the romanized version, also known in other APIs as the Primary Title.
104 type AnimeTitle struct {
105         Lang  string `xml:"lang,attr"` // Language in ISO-ish format
106         Type  string `xml:"type,attr"` // "official", "short", etc
107         Title string `xml:",chardata"`
108 }
109
110 type RelatedAnime struct {
111         ID    int    `xml:"id,attr"`   // AID of the related anime
112         Type  string `xml:"type,attr"` // "prequel", "sequel", etc
113         Title string `xml:",chardata"` // Primary title of the related anime
114 }
115
116 type SimilarAnime struct {
117         ID       int    `xml:"id,attr"`       // AID of the similar anime
118         Approval int    `xml:"approval,attr"` // How many users have approved of this connection
119         Total    int    `xml:"total,attr"`    // Total of votes in this connection
120         Title    string `xml:",chardata"`     // Primary title of the recommended anime
121 }
122
123 type Recommendation struct {
124         Type string `xml:"type,attr"` // "Recommended", "Must See", etc
125         ID   int    `xml:"uid,attr"`  // User ID of the recommending user
126         Text string `xml:",chardata"` // Text of the user's recommendation
127 }
128
129 type Creator struct {
130         ID   int    `xml:"id,attr"` // Creator ID
131         Type string `xml:"type,attr"`
132         Name string `xml:",chardata"` // Always romaji
133 }
134
135 // Separate from regular Rating because the XML structure is different.
136 type AnimeRating struct {
137         Count  int     `xml:"count,attr"` // Amount of votes/reviews
138         Rating float32 `xml:",chardata"`  // Average
139 }
140
141 type AnimeRatings struct {
142         Permanent AnimeRating `xml:"permanent"` // Votes from people who watched everything
143         Temporary AnimeRating `xml:"temporary"` // Votes from people who are still watching it
144         Review    AnimeRating `xml:"review"`    // Votes from reviews
145 }
146
147 type Category struct {
148         ID       int  `xml:"id,attr"`       // Category ID
149         ParentID int  `xml:"parentid,attr"` // ID of the parent category
150         R18      bool `xml:"hentai,attr"`   // Whether the category represents porn works or not
151         Weight   int  `xml:"weight,attr"`   // Weight of the category for this anime
152
153         Name        string `xml:"name"`        // Category name
154         Description string `xml:"description"` // Category description
155 }
156
157 type ExternalEntity struct {
158         Identifiers []string `xml:"identifier"`
159         URL         []string `xml:"url"` // Used for types 5 and 6
160 }
161
162 // Completely undocumented.
163 // Most entries just have one or two numbers as Identifiers.
164 //
165 // Empiric documentation:
166 //
167 // Type 1 is the ANN id.
168 //
169 // Type 2 is the MyAnimeList ID.
170 //
171 // Type 3 is the AnimeNfo ID tuple.
172 //
173 // Type 4 is the official japanese webpage.
174 //
175 // Type 5 is the official english webpage.
176 //
177 // Type 6 is the english wikipedia page name.
178 //
179 // Type 7 is the japanese wikipedia page name.
180 //
181 // Type 8 is the cal.syoboi.jp schedule ID.
182 //
183 // Type 9 is the AllCinema ID.
184 //
185 // Type 10 is the anison.info ID.
186 //
187 // Type 14 is the VNDB ID.
188 //
189 // Type 15 is the MaruMegane ID.
190 type Resource struct {
191         Type           int              `xml:"type,attr"`
192         ExternalEntity []ExternalEntity `xml:"externalentity"`
193 }
194
195 type Tag struct {
196         ID            int    `xml:"id,attr"`            // Tag ID
197         Approval      int    `xml:"approval,attr"`      // How many users have approved of the tag
198         Spoiler       bool   `xml:"localspoiler,attr"`  // undocumented
199         GlobalSpoiler bool   `xml:"globalspoiler,attr"` // undocumented
200         Updated       string `xml:"update,attr"`        // YYYY-MM-DD
201
202         Name  string `xml:"name"`  // Tag name
203         Count int    `xml:"count"` // undocumented
204 }
205
206 type Seiyuu struct {
207         ID      int    `xml:"id,attr"`      // Creator ID
208         Name    string `xml:",chardata"`    // Always romaji
209         Picture string `xml:"picture,attr"` // Picture basename; combine with AniDBImageBaseURL for full URL
210 }
211
212 type Character struct {
213         ID      int    `xml:"id,attr"`     // Character ID
214         Type    string `xml:"type,attr"`   // "main character in", "secondary cast in", "appears in"
215         Updated string `xml:"update,attr"` // YYYY-MM-DD
216
217         Rating        Rating `xml:"rating"`
218         Name          string `xml:"name"`   // Always romaji
219         Gender        string `xml:"gender"` // "male", "female", "unknown", sometimes blank
220         Description   string `xml:"description"`
221         CharacterType string `xml:"charactertype"` // "Character", "Organization", "Vessel", etc
222         Episodes      string `xml:"episodes"`      // List of episodes where character appears
223         Picture       string `xml:"picture"`       // Picture basename; combine with AniDBImageBaseURL for full URL
224
225         Seiyuu *Seiyuu `xml:"seiyuu"` // The voice actor, if present
226 }
227
228 type Characters []Character // Implements sort.Interface; groups by Type and sorts by Name
229
230 type EpisodeTitle struct {
231         Lang  string `xml:"lang,attr"`
232         Title string `xml:",chardata"`
233 }
234
235 type Rating struct {
236         Votes  int     `xml:"votes,attr"`
237         Rating float32 `xml:",chardata"`
238 }
239
240 type EpNo struct {
241         Type int    `xml:"type,attr"` // 1 for regular episodes, 2 for specials, etc
242         EpNo string `xml:",chardata"` // Not necessarily a plain integer; may be prefixed by a single letter indicating the Type
243 }
244
245 type Episode struct {
246         ID      int    `xml:"id,attr"`     // Episode ID
247         Updated string `xml:"update,attr"` // YYYY-MM-DD
248
249         EpNo    EpNo           `xml:"epno"`
250         Length  int            `xml:"length"`  // Length in minutes (rounding method undocumented)
251         AirDate string         `xml:"airdate"` // YYYY-MM-DD
252         Rating  Rating         `xml:"rating"`
253         Titles  []EpisodeTitle `xml:"title"`
254 }
255
256 type Episodes []Episode // Implements sort.Interface; groups by EpNo.Type, orders by the integer portion of EpNo.EpNo
257
258 type Anime struct {
259         Error string `xml:",chardata"` // API request encountered an error if this is not ""
260
261         ID  int  `xml:"id,attr"`         // AID of the anime
262         R18 bool `xml:"restricted,attr"` // Whether the anime is considered porn
263
264         Type         string `xml:"type"`         // "TV Series", "Movie", "OVA", etc
265         EpisodeCount int    `xml:"episodecount"` // Unreliable, has a set value even when the total number is unknown
266         StartDate    string `xml:"startdate"`    // YYYY-MM-DD
267         EndDate      string `xml:"enddate"`      // YYYY-MM-DD
268
269         Titles       []AnimeTitle   `xml:"titles>title"`
270         RelatedAnime []RelatedAnime `xml:"relatedanime>anime"`
271         SimilarAnime []SimilarAnime `xml:"similaranime>anime"`
272
273         Recommendations []Recommendation `xml:"recommendations>recommendation"`
274
275         URL string `xml:"url"` // Official URL
276
277         Creators []Creator `xml:"creators>name"`
278
279         Description string `xml:"description"`
280
281         Ratings AnimeRatings `xml:"ratings"`
282
283         Picture string `xml:"picture"` // Picture basename; combine with AniDBImageBaseURL for full URL
284
285         Categories []Category `xml:"categories>category"`  // Unsorted
286         Resources  []Resource `xml:"resources>resource"`   // undocumented
287         Tags       []Tag      `xml:"tags>tag"`             // Unsorted
288         Characters Characters `xml:"characters>character"` // Unsorted
289         Episodes   Episodes   `xml:"episodes>episode"`     // Unsorted
290 }