Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""LICENSE
2Copyright 2019 Hermann Krumrey <hermann@krumreyh.com>
4This file is part of otaku-info-bot.
6otaku-info-bot is free software: you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published by
8the Free Software Foundation, either version 3 of the License, or
9(at your option) any later version.
11otaku-info-bot is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
16You should have received a copy of the GNU General Public License
17along with otaku-info-bot. If not, see <http://www.gnu.org/licenses/>.
18LICENSE"""
20import json
21import requests
22from typing import List, Dict, Any, Optional
25def load_anilist(
26 username: str,
27 media_type: str,
28 list_name: Optional[str] = None
29) -> List[Dict[str, Any]]:
30 """
31 Loads the anilist for a user
32 :param username: The username
33 :param media_type: The media type, either MANGA or ANIME
34 :param list_name: Optionalyy restrict to a specific list
35 :return: The anilist
36 """
37 graphql = GraphQlClient("https://graphql.anilist.co")
38 query = """
39 query ($username: String, $media_type: MediaType) {
40 MediaListCollection(userName: $username, type: $media_type) {
41 lists {
42 name
43 entries {
44 progress
45 score
46 media {
47 id
48 chapters
49 episodes
50 status
51 title {
52 english
53 romaji
54 }
55 nextAiringEpisode {
56 episode
57 }
58 }
59 }
60 }
61 }
62 }
63 """
64 resp = graphql.query(query, {
65 "username": username,
66 "media_type": media_type.upper()
67 })
68 if resp is None:
69 return []
70 user_lists = resp["data"]["MediaListCollection"]["lists"]
72 entries = [] # type: List[Dict[str, Any]]
73 for _list in user_lists:
74 if list_name is None or _list["name"] == list_name:
75 entries += _list["entries"]
77 return entries
80def guess_latest_manga_chapter(anilist_id: int) -> Optional[int]:
81 """
82 Guesses the latest chapter number based on anilist user activity
83 :param anilist_id: The anilist ID to check
84 :return: The latest chapter number
85 """
86 graphql = GraphQlClient("https://graphql.anilist.co")
87 query = """
88 query ($id: Int) {
89 Page(page: 1) {
90 activities(mediaId: $id, sort: ID_DESC) {
91 ... on ListActivity {
92 progress
93 userId
94 }
95 }
96 }
97 }
98 """
99 resp = graphql.query(query, {"id": anilist_id})
100 if resp is None:
101 return None
103 data = resp["data"]["Page"]["activities"]
105 progresses = []
106 for entry in data:
107 progress = entry["progress"]
108 if progress is not None:
109 progress = entry["progress"].split(" - ")[-1]
110 progresses.append(int(progress))
112 progresses = progresses[0:20]
113 progresses.sort(key=lambda x: progresses.count(x), reverse=True)
114 progresses = sorted(progresses, key=progresses.count, reverse=True)
115 best_guess = progresses[0]
117 return best_guess
120class GraphQlClient:
121 """
122 A simple API wrapper for GraphQL APIs
123 """
125 def __init__(self, api_url: str):
126 """
127 Initializes the GraphQL API wrapper
128 :param api_url: The API endpoint URL
129 """
130 self.api_url = api_url
132 def query(
133 self,
134 query_string: str,
135 variables: Optional[Dict[str, Any]]
136 ) -> Optional[Dict[str, Any]]:
137 """
138 Executes a GraphQL query
139 :param query_string: The query string to use
140 :param variables: The variables to send
141 :return: The response JSON, or None if an error occurred.
142 """
143 if variables is None:
144 variables = {}
146 resp = requests.post(self.api_url, json={
147 "query": query_string,
148 "variables": variables
149 })
150 if not resp.status_code < 300:
151 return None
152 else:
153 return json.loads(resp.text)