Skip to content

Commit

Permalink
Can now load up history of media cache and now storing photos of medi…
Browse files Browse the repository at this point in the history
…a objects
  • Loading branch information
MujyKun committed Jul 20, 2021
1 parent 7650db0 commit 8ba0d6e
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 69 deletions.
5 changes: 3 additions & 2 deletions Weverse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

from .error import InvalidToken, PageNotFound, BeingRateLimited
from .objects import create_tab_objects, create_community_objects, create_comment_objects, create_notification_objects,\
create_media_object, create_post_objects, create_artist_objects, create_photo_objects
create_media_object, create_post_objects, create_artist_objects, create_photo_objects, \
iterate_community_media_categories
from .weverseclient import WeverseClient
from .weversesync import WeverseClientSync
from .weverseasync import WeverseClientAsync

__title__ = 'Weverse'
__author__ = 'MujyKun'
__license__ = 'MIT'
__version__ = '1.0.4'
__version__ = '1.0.5'
2 changes: 1 addition & 1 deletion Weverse/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from .artist import Artist
from .comment import Comment
from .community import Community
from .media import Media
from .notification import Notification
from .photo import Photo
from .tab import Tab
from .video import Video
from .post import Post
from .media import Media
9 changes: 9 additions & 0 deletions Weverse/models/media.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
from typing import List

from . import Photo


class Media:
r"""A Media object that represents a Weverse Media Post.
Expand Down Expand Up @@ -48,6 +53,8 @@ class Media:
The video link supplied under the media post.
youtube_id: str
The youtube video ID.
photos: List[:ref:`Photo`]
A list of photos under the media post.
"""
def __init__(self, **kwargs):
self.id = kwargs.get('id')
Expand All @@ -59,4 +66,6 @@ def __init__(self, **kwargs):
self.level = kwargs.get('level')
self.video_link = kwargs.get('extVideoPath')
self.youtube_id = kwargs.get('youtubeId')
self.photos: List[Photo] = kwargs.get('photo_objects') or []


3 changes: 3 additions & 0 deletions Weverse/models/photo.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class Photo:
-----------
id: int
The ID of the photo.
media_id: Optional[int]
The media ID of the photo (if there is one).
content_index: int
Index the photo is in from a bundle of photos.
thumbnail_img_url: str
Expand All @@ -52,6 +54,7 @@ class Photo:
"""
def __init__(self, **kwargs):
self.id = kwargs.get('photo_id')
self.media_id = kwargs.get('media_id')
self.content_index = kwargs.get('content_index')
self.thumbnail_img_url = kwargs.get('thumbnail_img_url')
self.thumbnail_img_width = kwargs.get('thumbnail_img_width')
Expand Down
36 changes: 35 additions & 1 deletion Weverse/objects.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Optional, List

from .models import Community, Artist, Tab, Notification, Post, Photo, Comment, Media, Video


Expand Down Expand Up @@ -199,6 +201,7 @@ def create_photo_objects(current_photos: list) -> list:
for photo in current_photos:
kwargs = {
'photo_id': photo.get('id'),
'media_id': photo.get('mediaId'),
'content_index': photo.get('contentIndex'),
'thumbnail_img_url': photo.get('thumbnailImgUrl'),
'thumbnail_img_width': photo.get('thumbnailImgWidth'),
Expand Down Expand Up @@ -239,10 +242,41 @@ def create_comment_objects(current_comments: list) -> list:
return comments


def create_media_object(media_info: dict) -> Media:
def create_media_object(media_info: dict, ignore_photos=False) -> Media:
"""Creates and returns a media object
:param media_info: media information from endpoint.
:param ignore_photos: Whether to ignore the photos that belong in the media object. (Other methods can
create it themselves.)
:returns: :ref:`Media`
"""
if media_info.get("type") == "PHOTO" and not ignore_photos:
photos = media_info.get("photos")

if photos:
media_info["photo_objects"] = create_photo_objects(photos)

return Media(**media_info)


def iterate_community_media_categories(all_media_categories: dict) -> [List[Media], List[dict]]:
"""Iterates through community media categories, creates Media posts and returns a list of them.
:param all_media_categories: A dict containing media posts that are filtered by category.
:returns: [List[:ref:`Media`], List[:ref:`dict`]] A list of Video Media objects and a list of dicts containing
photo media objects to later make own calls on to retrieve photos.
"""
photo_media_dicts = []
video_media_objects = []
media_category = all_media_categories.get("mediasByCategory")
for media_dicts in media_category:
medias_list = media_dicts.get("medias")
# media_category_info = media_dicts.get("mediaCategory")
for media in medias_list:
# The information we are receiving from the endpoint does not give us any information about the photos,
# therefore, we need our caller method to take care of loading the media photos for us.
if media.get("type") == "PHOTO":
photo_media_dicts.append(media)
else:
video_media_objects.append(create_media_object(media))
return [video_media_objects, photo_media_dicts]
64 changes: 44 additions & 20 deletions Weverse/weverseasync.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from asyncio import get_event_loop
from .models import Community, Post as w_Post, Notification
from . import WeverseClient, create_post_objects, create_community_objects, create_notification_objects, \
create_comment_objects, create_media_object
create_comment_objects, create_media_object, iterate_community_media_categories


class WeverseClientAsync(WeverseClient):
Expand All @@ -29,7 +29,7 @@ def __init__(self, loop=get_event_loop(), **kwargs):
self.loop = loop
super().__init__(**kwargs)

async def start(self, create_old_posts=True, create_notifications=True):
async def start(self, create_old_posts=True, create_notifications=True, create_media=False):
"""Creates internal cache.
This is the main process that should be run.
Expand All @@ -38,6 +38,7 @@ async def start(self, create_old_posts=True, create_notifications=True):
:parameter create_old_posts: (:class:`bool`) Whether to create cache for old posts.
:parameter create_notifications: (:class:`bool`) Whether to create/update cache for old notifications.
:parameter create_media: (:class:`bool`) Whether to create/update cache for old media.
:raises: :class:`Weverse.error.InvalidToken`
:raises: :class:`Weverse.error.BeingRateLimited`
Expand All @@ -56,22 +57,46 @@ async def start(self, create_old_posts=True, create_notifications=True):
if create_notifications:
await self.get_user_notifications()

# load up posts
if create_old_posts:
for community in self.all_communities.values():
for community in self.all_communities.values():
# load up posts
if create_old_posts:
await self.create_posts(community)

# load up media
if create_media:
await self.create_media(community)

self.cache_loaded = True
except Exception as err:
raise err

async def create_media(self, community: Community):
"""Paginate through a community's media and add it to object cache.
:parameter community: :ref:`Community` the posts exist under.
"""
media_tab_url = f"{self._api_stream_url}{community.id}/{self._api_media_tab}"
async with self.web_session.get(media_tab_url, headers=self._headers) as resp:
if self.check_status(resp.status, media_tab_url):
data = await resp.json()
media_objects, photo_media_dicts = iterate_community_media_categories(data)

# This endpoint does NOT give us any information about the photos, therefore we must make
# a separate api call to retrieve proper photo information for the photo media.
for media in photo_media_dicts:
media_obj = await self.fetch_media(community.id, media.get("id"))
if media_obj:
media_objects.append(media_obj)

self._add_media_to_cache(media_objects)

async def create_communities(self):
"""Get and Create the communities the logged in user has access to.
This is a coroutine and must be awaited.
"""
async with self.web_session.get(self.api_communities_url, headers=self._headers) as resp:
if self.check_status(resp.status, self.api_communities_url):
async with self.web_session.get(self._api_communities_url, headers=self._headers) as resp:
if self.check_status(resp.status, self._api_communities_url):
data = await resp.json()
user_communities = data.get("communities")
self.all_communities = create_community_objects(user_communities)
Expand All @@ -82,7 +107,7 @@ async def create_community_artists_and_tabs(self):
This is a coroutine and must be awaited.
"""
for community in self.all_communities.values():
url = self.api_communities_url + str(community.id)
url = self._api_communities_url + str(community.id)
async with self.web_session.get(url, headers=self._headers) as resp:
if self.check_status(resp.status, url):
data = await resp.json()
Expand All @@ -100,7 +125,7 @@ async def create_posts(self, community: Community, next_page_id: int = None):
:parameter community: :ref:`Community` the posts exist under.
:parameter [OPTIONAL] next_page_id: Next Page ID (Weverse paginates posts).
"""
artist_tab_url = self.api_communities_url + str(community.id) + '/' + self.api_all_artist_posts_url
artist_tab_url = self._api_communities_url + str(community.id) + '/' + self._api_all_artist_posts_url
if next_page_id:
artist_tab_url = artist_tab_url + "?from=" + str(next_page_id)
async with self.web_session.get(artist_tab_url, headers=self._headers) as resp:
Expand Down Expand Up @@ -128,7 +153,7 @@ async def create_post(self, community: Community, post_id) -> w_Post:
:parameter community: :ref:`Community` the post was created under.
:parameter post_id: The id of the post we are needing to fetch.
"""
post_url = self.api_communities_url + str(community.id) + '/posts/' + str(post_id)
post_url = self._api_communities_url + str(community.id) + '/posts/' + str(post_id)
async with self.web_session.get(post_url, headers=self._headers) as resp:
if self.check_status(resp.status, post_url):
data = await resp.json()
Expand All @@ -143,8 +168,8 @@ async def get_user_notifications(self):
"""
self._old_notifications = self.user_notifications # important for keeping track of what is new.

async with self.web_session.get(self.api_notifications_url, headers=self._headers) as resp:
if self.check_status(resp.status, self.api_notifications_url):
async with self.web_session.get(self._api_notifications_url, headers=self._headers) as resp:
if self.check_status(resp.status, self._api_notifications_url):
data = await resp.json()
self.user_notifications = create_notification_objects(data.get('notifications'))
for user_notification in self.user_notifications:
Expand All @@ -158,8 +183,8 @@ async def check_new_user_notifications(self) -> bool:
:returns: (:class:`bool`) Whether there is a new notification.
"""
async with self.web_session.get(self.api_new_notifications_url, headers=self._headers) as resp:
if self.check_status(resp.status, self.api_new_notifications_url):
async with self.web_session.get(self._api_new_notifications_url, headers=self._headers) as resp:
if self.check_status(resp.status, self._api_new_notifications_url):
data = await resp.json()
has_new = data.get('has_new')
if has_new:
Expand Down Expand Up @@ -206,7 +231,7 @@ async def translate(self, post_or_comment_id, is_post=False, is_comment=False, p
community_id = p_obj.artist.community_id
else:
return None
url = self.api_communities_url + str(community_id) + "/" + method_url + str(
url = self._api_communities_url + str(community_id) + "/" + method_url + str(
post_or_comment_id) + "/translate?languageCode=en"
async with self.web_session.get(url, headers=self._headers) as resp:
if self.check_status(resp.status, url):
Expand All @@ -222,7 +247,7 @@ async def fetch_artist_comments(self, community_id, post_id):
:parameter post_id: Post ID to fetch the artist comments of.
:returns: List[:ref:`Comment`]
"""
post_comments_url = self.api_communities_url + str(community_id) + '/posts/' + str(post_id) + "/comments/"
post_comments_url = self._api_communities_url + str(community_id) + '/posts/' + str(post_id) + "/comments/"
async with self.web_session.get(post_comments_url, headers=self._headers) as resp:
if self.check_status(resp.status, post_comments_url):
data = await resp.json()
Expand All @@ -237,7 +262,7 @@ async def fetch_comment_body(self, community_id, comment_id):
:parameter comment_id: The ID of the comment to fetch.
:returns: (:class:`str`) Body of the comment.
"""
comment_url = f"{self.api_communities_url}{str(community_id)}/comments/{comment_id}/"
comment_url = f"{self._api_communities_url}{str(community_id)}/comments/{comment_id}/"
async with self.web_session.get(comment_url, headers=self._headers) as resp:
if self.check_status(resp.status, comment_url):
data = await resp.json()
Expand All @@ -252,7 +277,7 @@ async def fetch_media(self, community_id, media_id):
:parameter media_id: The ID of the media to fetch.
:returns: :ref:`Media` or NoneType
"""
media_url = self.api_communities_url + str(community_id) + "/medias/" + str(media_id)
media_url = self._api_communities_url + str(community_id) + "/medias/" + str(media_id)
async with self.web_session.get(media_url, headers=self._headers) as resp:
if self.check_status(resp.status, media_url):
data = await resp.json()
Expand Down Expand Up @@ -302,7 +327,6 @@ async def __manage_notification_posts(self, notification: Notification):
elif notification_type == 'media':
media = await self.fetch_media(community.id, notification.contents_id)
if media:
self.new_media.insert(0, media)
self.all_media[media.id] = media
elif notification_type == 'announcement':
return # not keeping track of announcements in cache ATM
Expand All @@ -315,5 +339,5 @@ async def check_token_works(self) -> bool:
:returns: (:class:`bool`) True if the token works.
"""
async with self.web_session.get(url=self.user_endpoint, headers=self._headers) as resp:
async with self.web_session.get(url=self._user_endpoint, headers=self._headers) as resp:
return resp.status == 200
47 changes: 22 additions & 25 deletions Weverse/weverseclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,9 @@ class WeverseClient:
An aiohttp or requests client session.
user_notifications: list
Most recent notifications of the account connected.
api_url: str
URL to connect to the API
api_communities_url: str
Endpoint for communities
api_notifications_url: str
Endpoint for user notifications
api_new_notifications_url: str
Endpoint for checking new user notifications
api_all_artist_posts_url: str
Endpoint for getting the Artist Feed from a community
api_artist_to_fans: str
part of the endpoint (NOT FULL) for viewing the tofans posts.
api_all_communities_info_url: str
Endpoint for information about ALL communities and ALL idols.
cache_loaded: bool
Whether the Internal Weverse Cache is fully loaded.
This will change for a split moment when grabbing a new post.
new_media: list(Media)
We do not store ALL old media objects as cache, so only when there are new media, we store it
user_endpoint: str
User info endpoint.
all_posts: dict(Post)
Expand Down Expand Up @@ -80,18 +64,19 @@ def __init__(self, **kwargs):
self._old_notifications = []
self._headers = {'Authorization': f"Bearer {self._token}"}

self.api_url = "https://weversewebapi.weverse.io/wapi/v1/"
self.api_communities_url = self.api_url + "communities/" # endpoint for communities
self.api_notifications_url = self.api_url + "stream/notifications/" # endpoint for user notifications
self._api_url = "https://weversewebapi.weverse.io/wapi/v1/"
self._api_communities_url = self._api_url + "communities/" # endpoint for communities
self._api_notifications_url = self._api_url + "stream/notifications/" # endpoint for user notifications
# endpoint for checking new user notifications
self.api_new_notifications_url = self.api_notifications_url + "has-new/"
self.api_all_artist_posts_url = "posts/artistTab/" # Artist Feed from a community
self.api_artist_to_fans = "posts/tofans/"
self._api_new_notifications_url = self._api_notifications_url + "has-new/"
self._api_all_artist_posts_url = "posts/artistTab/" # Artist Feed from a community
self._api_stream_url = self._api_url + "/stream/community/" # followed by community id and mediaTab.
self._api_media_tab = "mediaTab/categorical?countByCategory=100000"
self._api_artist_to_fans = "posts/tofans/"
# endpoint for information about ALL communities and ALL idols.
self.api_all_communities_info_url = self.api_communities_url + "info/"
self._api_all_communities_info_url = self._api_communities_url + "info/"
self.cache_loaded = False
self.new_media = [] # We do not store ALL media objects as cache, so only when there are new media, we store it
self.user_endpoint = "https://weversewebapi.weverse.io/wapi/v1/users/me"
self._user_endpoint = "https://weversewebapi.weverse.io/wapi/v1/users/me"

self.all_posts = {}
self.all_artists = {}
Expand Down Expand Up @@ -239,6 +224,18 @@ def get_media_by_id(self, media_id) -> Optional[w_Media]:
"""
return self.all_media.get(media_id)

def _add_media_to_cache(self, media_objects: List[w_Media]):
"""
Will add media objects and it's photos to cache.
:param media_objects:
"""
for media in media_objects:
self.all_media[media.id] = media
if media.photos:
for photo in media.photos:
self.all_photos[photo.id] = photo

@staticmethod
def determine_notification_type(notification: Union[w_Notification, str]) -> str:
"""
Expand Down
Loading

0 comments on commit 8ba0d6e

Please sign in to comment.