Hide keyboard shortcuts

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> 

3 

4This file is part of otaku-info-bot. 

5 

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. 

10 

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. 

15 

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""" 

19 

20import json 

21import requests 

22from typing import List, Dict, Any, Optional 

23 

24 

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"] 

71 

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"] 

76 

77 return entries 

78 

79 

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 

102 

103 data = resp["data"]["Page"]["activities"] 

104 

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)) 

111 

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] 

116 

117 return best_guess 

118 

119 

120class GraphQlClient: 

121 """ 

122 A simple API wrapper for GraphQL APIs 

123 """ 

124 

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 

131 

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 = {} 

145 

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)