Parcourir la source

Fix sending embed messages to correct servers

Lucas Villeneuve il y a 5 ans
Parent
commit
760f6af218
2 fichiers modifiés avec 227 ajouts et 115 suppressions
  1. 140 114
      src/anilist.py
  2. 87 1
      src/utils.py

+ 140 - 114
src/anilist.py

@@ -2,27 +2,14 @@ import requests
 import time
 import datetime
 from enum import Enum
-from typing import List
+from typing import Dict, List
 
 import globals
 import myanimebot
 import utils
 
-ANILIST_GRAPHQL_URL = 'https://graphql.anilist.co'
-
-class MediaType(Enum):
-    ANIME="ANIME"
-    MANGA="MANGA"
-
-    @staticmethod
-    def from_str(label: str):
-        if label.upper() in ('ANIME', 'ANIME_LIST'):
-            return MediaType.ANIME
-        elif label.upper() in ('MANGA', 'MANGA_LIST'):
-            return MediaType.MANGA
-        else:
-            raise NotImplementedError('Error: Cannot convert "{}" to a MediaType'.format(label))
 
+ANILIST_GRAPHQL_URL = 'https://graphql.anilist.co'
 
 class MediaListStatus(Enum):
     CURRENT=0
@@ -52,6 +39,75 @@ class MediaListStatus(Enum):
             raise NotImplementedError('Error: Cannot convert "{}" to a MediaListStatus'.format(label))
     
 
+def get_media_name(activity):
+    ''' Returns the media name in english if possible '''
+
+    english_name = activity["media"]["title"]["english"]
+    if english_name is not None:
+        return english_name
+
+    romaji_name = activity["media"]["title"]["romaji"]
+    if romaji_name is not None:
+        return romaji_name
+
+    native_name = activity["media"]["title"]["native"]
+    if native_name is not None:
+        return native_name
+
+    return ''
+
+
+def get_progress(activity):
+    progress = activity["progress"]
+    if progress is None:
+        return '?'
+    return progress
+
+
+def build_status_string(activity):
+    status_str = activity["status"].capitalize()
+    status = MediaListStatus.from_str(status_str)
+    progress = get_progress(activity)
+    episodes = ''
+    media_label = ''
+    media_type = utils.MediaType.from_str(activity["type"])
+
+    # TODO Manage Completed/Dropped/Planned episodes/chapters count
+    if status == MediaListStatus.CURRENT \
+       or status == MediaListStatus.REPEATING:
+        if media_type == utils.MediaType.ANIME:
+            episodes = activity["media"]["episodes"]
+            if episodes is None:
+                episodes = '?'
+            media_label = 'episodes'
+        elif media_type == utils.MediaType.MANGA:
+            episodes = activity["media"]["chapters"]
+            if episodes is None:
+                episodes = '?'
+            media_label = 'chapters'
+        return '{} | {} of {} {}'.format(status_str, progress, episodes, media_label)
+
+    else:
+        return '{}'.format(status_str)
+
+
+def build_feed_from_activity(activity, user : utils.User):
+    if activity is None: return None
+
+    media = utils.Media(name=get_media_name(activity),
+                        url=activity["media"]["siteUrl"],
+                        episodes=utils.Media.get_number_episodes(activity),
+                        image=activity["media"]["coverImage"]["large"],
+                        type=utils.MediaType.from_str(activity["media"]["type"]))
+    feed = utils.Feed(service=utils.Service.ANILIST,
+                        date_publication=datetime.datetime.fromtimestamp(activity["createdAt"], globals.timezone),
+                        user=user,
+                        status=build_status_string(activity),
+                        description=activity["status"],
+                        media=media)
+    return feed
+ 
+
 def get_anilist_userId_from_name(user_name : str) -> int:
     """ Searches an AniList user by its name and returns its ID """
 
@@ -79,7 +135,7 @@ def get_anilist_userId_from_name(user_name : str) -> int:
     return None
 
 
-def get_latest_users_activities(users_id, page, perPage = 5):
+def get_latest_users_activities(users : List[utils.User], page: int, perPage = 5) -> List[utils.Feed]:
     """ Get latest users' activities """
 
     query = '''query ($userIds: [Int], $page: Int, $perPage: Int) {
@@ -104,6 +160,7 @@ def get_latest_users_activities(users_id, page, perPage = 5):
                         siteUrl
                         episodes
                         chapters
+                        type
                         title {
                             romaji
                             english
@@ -119,15 +176,33 @@ def get_latest_users_activities(users_id, page, perPage = 5):
     }'''
 
     variables = {
-        "userIds": users_id,
+        "userIds": [user.service_id for user in users],
         "perPage": perPage,
         "page": page
     }
 
     try:
+        # Execute GraphQL query
         response = requests.post(ANILIST_GRAPHQL_URL, json={'query': query, 'variables': variables})
         response.raise_for_status()
-        return response.json()["data"]["Page"]["activities"]
+        data = response.json()["data"]["Page"]["activities"]
+
+        # Create feeds from data
+        feeds = []
+        for activity in data:
+            # Check if activity is a ListActivity
+            if activity["__typename"] != 'ListActivity':
+                continue
+            
+            # Find corresponding user for this ListActivity
+            user = next((user for user in users if user.name == activity["user"]["name"]), None)
+            if user is None:
+                raise RuntimeError('Cannot find {} in our registered users'.format(activity["user"]["name"]))
+
+            # Add new builded feed
+            feeds.append(build_feed_from_activity(activity, user))
+        return feeds
+
     except requests.HTTPError as e:
         #TODO Correct error response
         print('ERROR WRONG RESPONSE CODE')
@@ -135,7 +210,7 @@ def get_latest_users_activities(users_id, page, perPage = 5):
         #TODO Correct error response
         print('UNKNOWN Error when trying to get the users\' activities :')
         print(e)
-    return None
+    return []
 
 
 def check_username_validity(username) -> bool:
@@ -163,7 +238,7 @@ def check_username_validity(username) -> bool:
         return False
 
 
-def get_latest_activity(users_id):
+def get_latest_activity(users : List[utils.User]):
     """ Get the latest users' activity """
 
     # TODO Will fail if last activity is not a ListActivity
@@ -179,7 +254,7 @@ def get_latest_activity(users_id):
     }'''
 
     variables = {
-        "userIds": users_id
+        "userIds": [user.service_id for user in users]
     }
 
     try:
@@ -201,88 +276,42 @@ def get_users_db():
 
 	# TODO Make generic execute
     cursor = globals.conn.cursor(buffered=True, dictionary=True)
-    cursor.execute("SELECT {}, servers FROM t_users WHERE service = %s".format(globals.DB_USER_NAME), [globals.SERVICE_ANILIST])
-    return cursor.fetchall()
+    cursor.execute("SELECT id, {}, servers FROM t_users WHERE service = %s".format(globals.DB_USER_NAME), [globals.SERVICE_ANILIST])
+    users_data = cursor.fetchall()
+    cursor.close()
+    return users_data
+
+
+def get_users() -> List[utils.User]:
+    users = []
+    users_data = get_users_db()
+    if users_data is not None:
+        for user_data in users_data:
+            users.append(utils.User(id=user_data["id"],
+                            service_id=get_anilist_userId_from_name(user_data[globals.DB_USER_NAME]),
+                            name=user_data[globals.DB_USER_NAME],
+                            servers=user_data["servers"].split(',')))
+    return users
 
 
-def get_users_id() -> List[int]:
+def get_users_id(users_data) -> List[int]:
     ''' Returns the id of the registered users using AniList '''
 
     users_ids = []
 
     # Get users using AniList
-    users_data = get_users_db()
     if users_data is not None:
         print("Users found: {}".format(users_data))
         for user_data in users_data:
             users_ids.append(get_anilist_userId_from_name(user_data[globals.DB_USER_NAME]))
+        # TODO Normalement pas besoin de recuperer les ids vu que je peux faire la recherche avec les noms
 
     return users_ids
 
 
-def get_media_name(activity):
-    ''' Returns the media name in english if possible '''
-
-    english_name = activity["media"]["title"]["english"]
-    if english_name is not None:
-        return english_name
-
-    romaji_name = activity["media"]["title"]["romaji"]
-    if romaji_name is not None:
-        return romaji_name
-
-    native_name = activity["media"]["title"]["native"]
-    if native_name is not None:
-        return native_name
-
-    return ''
-
-
-def get_progress(activity):
-    progress = activity["progress"]
-    if progress is None:
-        return '?'
-    return progress
-
-
-def build_status_string(activity):
-    status_str = activity["status"].capitalize()
-    status = MediaListStatus.from_str(status_str)
-    progress = get_progress(activity)
-    episodes = ''
-    media_label = ''
-    media_type = MediaType.from_str(activity["type"])
-
-    # TODO Manage Completed/Dropped/Planned episodes/chapters count
-    if status == MediaListStatus.CURRENT \
-       or status == MediaListStatus.REPEATING:
-        if media_type == MediaType.ANIME:
-            episodes = activity["media"]["episodes"]
-            if episodes is None:
-                episodes = '?'
-            media_label = 'episodes'
-        elif media_type == MediaType.MANGA:
-            episodes = activity["media"]["chapters"]
-            if episodes is None:
-                episodes = '?'
-            media_label = 'chapters'
-        return '{} | {} of {} {}'.format(status_str, progress, episodes, media_label)
-
-    else:
-        return '{}'.format(status_str)
-
-async def send_embed_to_channels(activity):
-
-    image = activity["media"]["coverImage"]["large"]
-    user_name = activity["user"]["name"]
-    media_name = get_media_name(activity)
-    media_url = activity["media"]["siteUrl"]
-    published_date = datetime.datetime.fromtimestamp(activity["createdAt"])
-    status_str = build_status_string(activity)
-
-    user_data = utils.get_user_data()
-    servers = user_data["servers"].split(",")
-    for server in servers:
+async def send_embed_to_channels(activity : utils.Feed):
+    # TODO Doc
+    for server in activity.user.servers:
         data_channels = utils.get_channels(server)
     
         if data_channels is not None:
@@ -290,51 +319,48 @@ async def send_embed_to_channels(activity):
                 await myanimebot.send_embed_wrapper(None,
                                                     channel["channel"],
                                                     globals.client,
-                                                    myanimebot.build_embed(user_name, media_name, media_url, status_str, published_date, image, utils.Service.ANILIST))
-
+                                                    myanimebot.build_embed(activity.user.name,
+                                                                            activity.media.name,
+                                                                            activity.media.url,
+                                                                            activity.status,
+                                                                            activity.date_publication,
+                                                                            activity.media.image,
+                                                                            activity.service))
 
-def insert_feed_db(activity):
-    user_name = activity["user"]["name"]
-    media_name = get_media_name(activity)
-    media_url = activity["media"]["siteUrl"]
-    published_date = datetime.datetime.fromtimestamp(activity["createdAt"]).isoformat()
-    status = activity["status"]
 
+def insert_feed_db(activity: utils.Feed):
     cursor = globals.conn.cursor(buffered=True)
-    cursor.execute("INSERT INTO t_feeds (published, title, url, user, found, type, service) VALUES (%s, %s, %s, %s, NOW(), %s, %s)",
-                    (published_date,
-                     media_name,
-                     media_url,
-                     user_name,
-                     status, # TODO Create enum to make it generic
+    cursor.execute("INSERT INTO t_feeds (published, title, url, user, found, type, service) VALUES (FROM_UNIXTIME(%s), %s, %s, %s, NOW(), %s, %s)",
+                    (activity.date_publication.timestamp(),
+                     activity.media.name,
+                     activity.media.url,
+                     activity.user.name,
+                     activity.description, # TODO Create enum to make it generic
                      globals.SERVICE_ANILIST))
     globals.conn.commit()
 
 
-async def process_new_activities(last_activity_date, users_id: List[int]):
+async def process_new_activities(last_activity_date, users : List[utils.User]):
     """ Fetch and process all newest activities """
     
     continue_fetching = True
     page_number = 1
     while continue_fetching:
         # Get activities
-        activities = get_latest_users_activities(users_id, page_number)
+        activities = get_latest_users_activities(users, page_number)
 
         # Processing them
         for activity in activities:
-            # Check if activity is a ListActivity
-            if activity["__typename"] != 'ListActivity':
-                continue
-
             print(activity) # TODO Remove, DEBUG
 
             # Get time difference between now and activity creation date
-            diffTime = datetime.datetime.now(globals.timezone) - datetime.datetime.fromtimestamp(activity["createdAt"], globals.timezone)
-
+            diffTime = datetime.datetime.now(globals.timezone) - activity.date_publication
             print("Time difference between feed and now = {}".format(diffTime))
+
             # If the activity is older than the last_activity_date, we processed all the newest activities
             # Also, if the time difference is bigger than the config's "secondMax", we can stop processing them
-            if activity["createdAt"] <= last_activity_date or diffTime.total_seconds() > globals.secondMax:
+            if activity.date_publication.timestamp() <= last_activity_date \
+                 or diffTime.total_seconds() > globals.secondMax:
                 # FIXME If two or more feeds are published at the same time, this would skip them
                 continue_fetching = False
                 break
@@ -375,14 +401,14 @@ async def check_new_activities():
     print(last_activity_date)
 
     # Get latest activity on AniList
-    users_id = get_users_id()
-    latest_activity = get_latest_activity(users_id)
+    users = get_users()
+    latest_activity = get_latest_activity(users)
     if latest_activity is not None:
 
         # If the latest activity is more recent than the last we stored
         if last_activity_date < latest_activity["createdAt"]:
             print("Latest activity is more recent")
-            await process_new_activities(last_activity_date, users_id)
+            await process_new_activities(last_activity_date, users)
 
 
 # [x] Convertir AniList ID en MAL ID

+ 87 - 1
src/utils.py

@@ -1,5 +1,7 @@
 import urllib.request
 import re
+import datetime
+from typing import List
 from enum import Enum
 
 from bs4 import BeautifulSoup
@@ -21,6 +23,78 @@ class Service(Enum):
 			raise NotImplementedError('Error: Cannot convert "{}" to a Service'.format(label))
 
 
+class MediaType(Enum):
+    ANIME="ANIME"
+    MANGA="MANGA"
+
+    @staticmethod
+    def from_str(label: str):
+        if label.upper() in ('ANIME', 'ANIME_LIST'):
+            return MediaType.ANIME
+        elif label.upper() in ('MANGA', 'MANGA_LIST'):
+            return MediaType.MANGA
+        else:
+            raise NotImplementedError('Error: Cannot convert "{}" to a MediaType'.format(label))
+
+
+class User():
+	data = None
+
+	def __init__(self,
+				  id			: int,
+				  service_id	: int,
+				  name			: str,
+				  servers		: List[int]):
+		self.id = id
+		self.service_id = service_id
+		self.name = name
+		self.servers = servers
+
+class Media():
+	def __init__(self,
+				 name		: str,
+				 url		: str,
+				 episodes	: str,
+				 image		: str,
+				 type		: MediaType):
+		self.name = name
+		self.url = url
+		self.episodes = episodes
+		self.image = image
+		self.type = type
+
+	@staticmethod
+	def get_number_episodes(activity):
+		media_type = MediaType.from_str(activity["type"])
+		episodes = '?'
+		if media_type == MediaType.ANIME:
+			episodes = activity["media"]["episodes"]
+		elif media_type == MediaType.MANGA:
+			episodes = activity["media"]["chapters"]
+		else:
+			raise NotImplementedError('Error: Unknown media type "{}"'.format(media_type))
+		if episodes is None:
+			episodes = '?'
+		return episodes
+
+
+class Feed():
+	def __init__(self,
+				 service 		: Service,
+				 date_publication : datetime.datetime,
+				 user 			: User,
+				 status			: str, # TODO Need to change
+				 description	: str, # TODO Need to change
+				 media 			: Media
+				 ):
+		self.service = service
+		self.date_publication = date_publication
+		self.user = user
+		self.status = status
+		self.media = media
+		self.description = description
+
+
 # Get thumbnail from an URL
 def getThumbnail(urlParam):
 	url = "/".join((urlParam).split("/")[:5])
@@ -99,10 +173,22 @@ def is_server_in_db(server_id) -> bool:
 def get_users():
 	''' Returns all registered users '''
     # Refresh database
-	globals.conn.commit()
+	# globals.conn.commit()
 
 	cursor = globals.conn.cursor(buffered=True, dictionary=True)
 	cursor.execute('SELECT {}, service, servers FROM t_users'.format(globals.DB_USER_NAME))
 	users = cursor.fetchall()
 	cursor.close()
 	return users
+
+
+def get_user(user_id):
+	''' Returns the user from an id '''
+    # Refresh database
+	# globals.conn.commit()
+
+	cursor = globals.conn.cursor(buffered=True, dictionary=True)
+	cursor.execute('SELECT {}, service, servers FROM t_users WHERE '.format(globals.DB_USER_NAME))
+	users = cursor.fetchall()
+	cursor.close()
+	return users