|
@@ -2,27 +2,14 @@ import requests
|
|
|
import time
|
|
import time
|
|
|
import datetime
|
|
import datetime
|
|
|
from enum import Enum
|
|
from enum import Enum
|
|
|
-from typing import List
|
|
|
|
|
|
|
+from typing import Dict, List
|
|
|
|
|
|
|
|
import globals
|
|
import globals
|
|
|
import myanimebot
|
|
import myanimebot
|
|
|
import utils
|
|
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):
|
|
class MediaListStatus(Enum):
|
|
|
CURRENT=0
|
|
CURRENT=0
|
|
@@ -52,6 +39,75 @@ class MediaListStatus(Enum):
|
|
|
raise NotImplementedError('Error: Cannot convert "{}" to a MediaListStatus'.format(label))
|
|
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:
|
|
def get_anilist_userId_from_name(user_name : str) -> int:
|
|
|
""" Searches an AniList user by its name and returns its ID """
|
|
""" 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
|
|
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 """
|
|
""" Get latest users' activities """
|
|
|
|
|
|
|
|
query = '''query ($userIds: [Int], $page: Int, $perPage: Int) {
|
|
query = '''query ($userIds: [Int], $page: Int, $perPage: Int) {
|
|
@@ -104,6 +160,7 @@ def get_latest_users_activities(users_id, page, perPage = 5):
|
|
|
siteUrl
|
|
siteUrl
|
|
|
episodes
|
|
episodes
|
|
|
chapters
|
|
chapters
|
|
|
|
|
+ type
|
|
|
title {
|
|
title {
|
|
|
romaji
|
|
romaji
|
|
|
english
|
|
english
|
|
@@ -119,15 +176,33 @@ def get_latest_users_activities(users_id, page, perPage = 5):
|
|
|
}'''
|
|
}'''
|
|
|
|
|
|
|
|
variables = {
|
|
variables = {
|
|
|
- "userIds": users_id,
|
|
|
|
|
|
|
+ "userIds": [user.service_id for user in users],
|
|
|
"perPage": perPage,
|
|
"perPage": perPage,
|
|
|
"page": page
|
|
"page": page
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
|
|
|
+ # Execute GraphQL query
|
|
|
response = requests.post(ANILIST_GRAPHQL_URL, json={'query': query, 'variables': variables})
|
|
response = requests.post(ANILIST_GRAPHQL_URL, json={'query': query, 'variables': variables})
|
|
|
response.raise_for_status()
|
|
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:
|
|
except requests.HTTPError as e:
|
|
|
#TODO Correct error response
|
|
#TODO Correct error response
|
|
|
print('ERROR WRONG RESPONSE CODE')
|
|
print('ERROR WRONG RESPONSE CODE')
|
|
@@ -135,7 +210,7 @@ def get_latest_users_activities(users_id, page, perPage = 5):
|
|
|
#TODO Correct error response
|
|
#TODO Correct error response
|
|
|
print('UNKNOWN Error when trying to get the users\' activities :')
|
|
print('UNKNOWN Error when trying to get the users\' activities :')
|
|
|
print(e)
|
|
print(e)
|
|
|
- return None
|
|
|
|
|
|
|
+ return []
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_username_validity(username) -> bool:
|
|
def check_username_validity(username) -> bool:
|
|
@@ -163,7 +238,7 @@ def check_username_validity(username) -> bool:
|
|
|
return False
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
-def get_latest_activity(users_id):
|
|
|
|
|
|
|
+def get_latest_activity(users : List[utils.User]):
|
|
|
""" Get the latest users' activity """
|
|
""" Get the latest users' activity """
|
|
|
|
|
|
|
|
# TODO Will fail if last activity is not a ListActivity
|
|
# TODO Will fail if last activity is not a ListActivity
|
|
@@ -179,7 +254,7 @@ def get_latest_activity(users_id):
|
|
|
}'''
|
|
}'''
|
|
|
|
|
|
|
|
variables = {
|
|
variables = {
|
|
|
- "userIds": users_id
|
|
|
|
|
|
|
+ "userIds": [user.service_id for user in users]
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
@@ -201,88 +276,42 @@ def get_users_db():
|
|
|
|
|
|
|
|
# TODO Make generic execute
|
|
# TODO Make generic execute
|
|
|
cursor = globals.conn.cursor(buffered=True, dictionary=True)
|
|
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 '''
|
|
''' Returns the id of the registered users using AniList '''
|
|
|
|
|
|
|
|
users_ids = []
|
|
users_ids = []
|
|
|
|
|
|
|
|
# Get users using AniList
|
|
# Get users using AniList
|
|
|
- users_data = get_users_db()
|
|
|
|
|
if users_data is not None:
|
|
if users_data is not None:
|
|
|
print("Users found: {}".format(users_data))
|
|
print("Users found: {}".format(users_data))
|
|
|
for user_data in users_data:
|
|
for user_data in users_data:
|
|
|
users_ids.append(get_anilist_userId_from_name(user_data[globals.DB_USER_NAME]))
|
|
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
|
|
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)
|
|
data_channels = utils.get_channels(server)
|
|
|
|
|
|
|
|
if data_channels is not None:
|
|
if data_channels is not None:
|
|
@@ -290,51 +319,48 @@ async def send_embed_to_channels(activity):
|
|
|
await myanimebot.send_embed_wrapper(None,
|
|
await myanimebot.send_embed_wrapper(None,
|
|
|
channel["channel"],
|
|
channel["channel"],
|
|
|
globals.client,
|
|
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 = 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.SERVICE_ANILIST))
|
|
|
globals.conn.commit()
|
|
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 """
|
|
""" Fetch and process all newest activities """
|
|
|
|
|
|
|
|
continue_fetching = True
|
|
continue_fetching = True
|
|
|
page_number = 1
|
|
page_number = 1
|
|
|
while continue_fetching:
|
|
while continue_fetching:
|
|
|
# Get activities
|
|
# Get activities
|
|
|
- activities = get_latest_users_activities(users_id, page_number)
|
|
|
|
|
|
|
+ activities = get_latest_users_activities(users, page_number)
|
|
|
|
|
|
|
|
# Processing them
|
|
# Processing them
|
|
|
for activity in activities:
|
|
for activity in activities:
|
|
|
- # Check if activity is a ListActivity
|
|
|
|
|
- if activity["__typename"] != 'ListActivity':
|
|
|
|
|
- continue
|
|
|
|
|
-
|
|
|
|
|
print(activity) # TODO Remove, DEBUG
|
|
print(activity) # TODO Remove, DEBUG
|
|
|
|
|
|
|
|
# Get time difference between now and activity creation date
|
|
# 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))
|
|
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
|
|
# 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
|
|
# 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
|
|
# FIXME If two or more feeds are published at the same time, this would skip them
|
|
|
continue_fetching = False
|
|
continue_fetching = False
|
|
|
break
|
|
break
|
|
@@ -375,14 +401,14 @@ async def check_new_activities():
|
|
|
print(last_activity_date)
|
|
print(last_activity_date)
|
|
|
|
|
|
|
|
# Get latest activity on AniList
|
|
# 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 latest_activity is not None:
|
|
|
|
|
|
|
|
# If the latest activity is more recent than the last we stored
|
|
# If the latest activity is more recent than the last we stored
|
|
|
if last_activity_date < latest_activity["createdAt"]:
|
|
if last_activity_date < latest_activity["createdAt"]:
|
|
|
print("Latest activity is more recent")
|
|
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
|
|
# [x] Convertir AniList ID en MAL ID
|