mg #
mg is a free and open source library for accessing the official MyAnimeList V2 API using Go. All endpoints are supported except forums as of now.
If you find any bugs, please write to me at vidhukant@vidhukant.com
License #
Licenced under GNU General Public Licence V3
GNU GPL License: link
Copyright (c) 2022-2023 Vidhu Kant Sharma
Documentation #
for v1.0.1+
Documentation might be incomplete or buggy. If you want to submit any changes, or need any help, please feel free to contact me at the given E-mail address.
Initializing the mg client #
There are 2 ways to authenticate with mg:
-
ClientAuth
With ClientAuth, you just need to use your MyAnimeList Client ID to access the read-only resources. You cannot do actions that require logging in. To use ClientAuth, set
client.ClientAuthOnly
totrue
. -
MainAuth
With MainAuth, you can perform actions like getting recommendations, or editing an anime/manga list. For this, you need to generate a Bearer token; for which a Client ID is still needed.
ClientAuthOnly
must be set tofalse
to use MainAuth.
Check out Authentication for more details.
1package main
2
3import (
4 "vidhukant.com/mg"
5)
6
7func main() {
8 var client mg.Client
9
10 // either this
11 client.MainAuth = "Bearer <your token here>"
12
13 // or this
14 client.ClientAuth = "<your client id here>"
15 client.ClientAuthOnly = true
16
17 // ...
18}
Anime #
Searching for an anime #
MainAuth
or ClientAuth
1// ...
2
3var animes []mg.Anime
4
5params := mg.SearchParamsNew()
6params.SearchString = "mushishi"
7
8// optional; these are the default values
9params.Limit = 100 // max = 100
10params.Offset = 0
11params.NSFW = false
12params.Fields = []string{}
13
14err := client.SearchAnime(&animes, params)
15if err != nil {
16 fmt.Println(err.Error())
17}
18
19for _, anime := range animes {
20 fmt.Println(anime.Title)
21}
22
23// ...
Getting an anime’s information #
MainAuth
or ClientAuth
searching can be a bit inaccurate, if you know the anime’s ID (obtain it with SearchAnime!) and want to get detailed information, use this.
1// ...
2
3var anime mg.Anime
4
5err := client.GetAnimeById(&anime, /*anime id*/ 457, /*fields*/ []string{})
6if err != nil {
7 fmt.Println(err.Error())
8}
9
10// ...
Getting anime ranking list #
MainAuth
or ClientAuth
Accepted ranking types:
Ranking Type | Description |
---|---|
mg.RankingTypeAll | Top Anime Series |
mg.RankingTypeByPopularity | Top Anime by Popularity |
mg.RankingTypeFavorite | Top Favorited Anime |
mg.RankingTypeAiring | Top Airing Anime |
mg.RankingTypeUpcoming | Top Upcoming Anime |
mg.RankingTypeTV | Top Anime TV Series |
mg.RankingTypeOVA | Top Anime OVA Series |
mg.RankingTypeMovie | Top Anime Movies |
mg.RankingTypeSpecial | Top Anime Specials |
Getting seasonal animes #
MainAuth
or ClientAuth
Accepted seasons:
mg.SeasonWinter
mg.SeasonSpring
mg.SeasonSummer
mg.SeasonFall
Accepted sorts:
mg.SeasonSortByAnimeScore
(descending)mg.SeasonSortByNumListUsers
(descending)
1// ...
2
3var seasonals []mg.Anime
4
5params := mg.SeasonalParamsNew()
6
7params.Year = "2023" // required
8params.Season = mg.SeasonFall // required
9
10// optional; these are the default values
11params.Sort = mg.SeasonSortByAnimeScore
12params.Limit = 100 // max = 500
13params.Offset = 0
14params.NSFW = false
15params.Fields = []string{}
16
17err := client.GetSeasonalAnime(&seasonals, params)
18if err != nil {
19 fmt.Println(err.Error())
20}
21
22// ...
Getting suggested animes #
MainAuth
only
1// ...
2
3var suggestedAnimes []mg.Anime
4
5params := mg.SuggestedParamsNew()
6
7// optional; these are the default values
8params.Limit = 100 // max = 100
9params.Offset = 0
10params.NSFW = false
11params.Fields = []string{}
12
13err := client.GetSeasonalAnime(&suggestedAnimes, params)
14if err != nil {
15 fmt.Println(err.Error())
16}
17
18// ...
Get any user’s anime list #
MainAuth
or ClientAuth
This returns a boolean telling us if there is a next page on this user’s list. Simply increment the offset by the limit and create a new request in order to get the next page.
In order to get the complete list, repeat the process until nextPageExists
becomes false.
Accepted statuses:
mg.ListStatusAll
mg.ListStatusWatching
mg.ListStatusCompleted
mg.ListStatusOnHold
mg.ListStatusDropped
mg.ListStatusPTW
Accepted list sorts:
mg.SortByListScore
(descending)mg.SortByListUpdatedAt
(descending)mg.SortByAnimeTitle
(ascending)mg.SortByAnimeStartDate
(descending)mg.SortByAnimeId
(ascending) (beta according to MAL)
1// ...
2
3var animes []mg.Anime
4
5params := mg.ListParamsNew()
6
7// optional; these are the default values
8params.Username = "@me" // can speciy any public account
9params.Status = mg.ListStatusAll
10params.Sort = mg.SortByListScore
11params.Limit = 100 // max = 1000
12params.Offset = 0
13params.NSFW = false
14params.Fields = []string{}
15
16nextPageExists, err := client.GetAnimeList(&animes, params)
17if err != nil {
18 fmt.Println(err.Error())
19}
20
21// ...
Updating an anime #
MainAuth
only
Updates an anime in the the currently logged in user’s anime list. Adds the anime if it’s not in the list.
Accepted statuses:
mg.ListStatusWatching
mg.ListStatusCompleted
mg.ListStatusOnHold
mg.ListStatusDropped
mg.ListStatusPTW
Score range: 0
- 10
Priority range: 0
- 2
Priority | Description |
---|---|
0 | Low |
1 | Medium |
2 | High |
RewatchValue range: 0
- 5
RewatchValue | Description |
---|---|
0 | None |
1 | Very Low |
2 | Low |
3 | Medium |
4 | High |
5 | Very High |
1// ...
2
3var res AnimeUpdateResponse
4
5// can omit any field but keep at least 1!
6params := map[string]interface{} {
7 mg.Status: mg.ListStatusCompleted,
8 mg.IsRewatching: false,
9 mg.Score: 10,
10 mg.EpisodesWatched: 26,
11 mg.Priority: 2,
12 mg.TimesRewatched: 2,
13 mg.RewatchValue: 5,
14 mg.Tags: "tags",
15 mg.Comments: "comments",
16}
17
18err := client.UpdateAnime(&res, /*anime id*/ 457, params)
19if err != nil {
20 fmt.Println(err.Error())
21}
22
23// ...
Deleting an anime from list #
MainAuth
only
Deletes the anime from currently logged in user’s anime list. Returns an error if anime is already not in the list.
1// ...
2
3err := client.DeleteAnime(/*anime id*/ 457)
4if err != nil {
5 fmt.Println(err.Error())
6}
7
8// ...
Manga #
Searching for a manga #
MainAuth
or ClientAuth
also works for light novels, etc
1// ...
2
3var mangas []mg.Manga
4
5carams := mg.SearchParamsNew()
6carams.SearchString = "empty box and zeroth maria"
7
8// optional; these are the default values
9params.Limit = 100
10params.Offset = 0
11params.NSFW = false
12params.Fields = []string{}
13
14err := client.SearchManga(&mangas, params)
15if err != nil {
16 fmt.Println(err.Error())
17}
18
19for _, manga range mangas {
20 fmt.Println(manga.Title)
21}
22
23// ...
Getting a manga’s information #
MainAuth
or ClientAuth
searching can be a bit inaccurate, if you know the manga’s ID (obtain it with SearchManga!) and want to get detailed information, use this.
1// ...
2
3var manga mg.Manga
4
5err := client.GetMangaById(&manga, /*manga id*/ 55215, /*fields*/ []string{})
6if err != nil {
7 fmt.Println(err.Error())
8}
9
10// ...
Getting manga ranking list #
MainAuth
or ClientAuth
Accepted ranking types:
RankingType | Description |
---|---|
mg.RankingTypeAll | All |
mg.RankingTypeByPopularity | Most Popular |
mg.RankingTypeFavorite | Most Favorited |
mg.RankingTypeManga | Top Manga |
mg.RankingTypeNovel | Top Novels |
mg.RankingTypeOneShot | Top One-shots |
mg.RankingTypeDoujin | Top Doujinshi |
mg.RankingTypeManhwa | Top Manhwa |
mg.RankingTypeManhua | Top Manhua |
RankedManga is just the Manga struct with an extra field RankNum int
1// ...
2
3var rankingList []mg.RankedManga
4
5params := mg.RankingParamsNew()
6
7// optional; these are the default values
8params.RankingType = mg.RankingTypeAll
9params.Limit = 100 // max = 500
10params.Offset = 0
11params.NSFW = false
12params.Fields = []string{}
13
14err := client.GetAnimeRanking(&rankingList, params)
15if err != nil {
16 fmt.Println(err.Error())
17}
18
19// ...
Get any user’s manga list #
MainAuth
or ClientAuth
This returns a boolean telling us if there is a next page on this user’s list. Simply increment the offset by the limit and create a new request in order to get the next page.
In order to get the complete list, repeat the process until nextPageExists
becomes false.
Accepted statuses:
mg.ListStatusAll
mg.ListStatusReading
mg.ListStatusCompleted
mg.ListStatusOnHold
mg.ListStatusDropped
mg.ListStatusPTR
Accepted list sorts:
mg.SortByListScore
(descending)mg.SortByListUpdatedAt
(descending)mg.SortByMangaTitle
(ascending)mg.SortByMangaStartDate
(descending)mg.SortByMangaId
(ascending) (beta according to MAL)
1// ...
2
3var mangas []mg.Manga
4
5params := mg.ListParamsNew()
6
7// optional; these are the default values
8params.Username = "@me" // can speciy any public account
9params.Status = mg.ListStatusAll
10params.Sort = mg.SortByListScore
11params.Limit = 100 // max = 1000
12params.Offset = 0
13params.NSFW = false
14params.Fields = []string{}
15
16nextPageExists, err := client.GetMangaList(&mangas, params)
17if err != nil {
18 fmt.Println(err.Error())
19}
20
21// ...
Updating a manga #
MainAuth
only
Updates a manga in the the currently logged in user’s manga list. Adds the manga if it’s not in the list.
Accepted statuses:
mg.ListStatusReading
mg.ListStatusCompleted
mg.ListStatusOnHold
mg.ListStatusDropped
mg.ListStatusPTR
Score range: 0
- 10
Priority range: 0
- 2
Priority | Description |
---|---|
0 | Low |
1 | Medium |
2 | High |
RereadValue range: 0
- 5
RereadValue | Description |
---|---|
0 | None |
1 | Very Low |
2 | Low |
3 | Medium |
4 | High |
5 | Very High |
1// ...
2
3var res MangaUpdateResponse
4
5// can omit any field but keep at least 1!
6params := map[string]interface{} {
7 mg.Status: mg.ListStatusReading,
8 mg.IsRereading: false,
9 mg.Score: 10,
10 mg.VolumesRead: 2,
11 mg.ChaptersRead: 20,
12 mg.Priority: 2,
13 mg.TimesReread: 0,
14 mg.RereadValue: 5,
15 mg.Tags: "tags",
16 mg.Comments: "comments",
17}
18
19err := client.UpdateManga(&res, /*manga id*/ 55215, params)
20if err != nil {
21 fmt.Println(err.Error())
22}
23
24// ...
Deleting a manga from list #
MainAuth
only
Deletes the manga from currently logged in user’s manga list. Returns an error if manga is already not in the list.
1// ...
2
3err := client.DeleteManga(/*manga id*/ 55215)
4if err != nil {
5 fmt.Println(err.Error())
6}
7
8// ...
User #
Get logged in user’s details #
MainAuth
only
MyAnimeList’s official API doesn’t support getting another user’s details. Only the currently logged in user.
1// ...
2
3var user mg.User
4
5// to get user's list statistics or not
6getStatistics := true
7
8err := client.GetSelfInfo(&user, getStatistics)
9if err != nil {
10 fmt.Println(err.Error())
11}
12
13// ...
Authentication #
With maltoken #
You can use maltoken, which provides an easy way to automate this whole process. Here’s an example:
Go to https://myanimelist.net/apiconfig to generate your very own Client ID.
- Enter all the required details, for the app’s homepage you can use the URL for the repository
- For the App Redirect URL, use
localhost:PORT
(or your domain if you want to run mg on a server)
Then use this code to authenticate:
replace PORT with the port you set as the App Redirect URL while creating a Client ID
1package main
2
3import (
4 "fmt"
5 mt "vidhukant.com/maltoken"
6)
7
8func main() {
9 // optionally change the HTML that is shown in the browser
10 mt.SuccessHTML = "<p>Yee haw</p>"
11 mt.BadRequestHTML = "<p>Y u do this</p>"
12
13 // The %s is optional
14 mt.ErrHTML = `
15 <style>
16 body {
17 color: red;
18 }
19 </style>
20 <p>An error occoured: %s</p>
21 `
22
23 cid := "<your_client_id_here>"
24 challenge, link := mt.GetChallengeLink(cid)
25
26 fmt.Println("Open this link in your browser: ", link)
27
28 res, err := mt.Listen(cid, challenge, PORT)
29 if err != nil {
30 fmt.Println(err.Error())
31 }
32
33 fmt.Println("API Response:", res)
34}
With maltoken-cli #
Install maltoken-cli:
1go install vidhukant.com/maltoken-cli@latest
Then run like this (flags are optional):
1maltoken-cli --launch --client-id "<your_client_id_here>" --port 8000
Run maltoken-cli -h
for more info.
Manually/other options #
Otherwise, check out this guide to know more about how this whole process works.