Source code for modio.game

"""Games are the umbrella entities under which all mods are stored."""
import json
from typing import List, Literal, Optional

from .mod import Mod
from .entities import Event, Image, Message, GameStats, ModStats, GamePlatform, TagOption, User
from .objects import Filter, NewMod, Pagination, Returned
from .utils import _convert_date, find
from .enums import APIAccess, Community, Curation, MaturityOptions, Presentation, Revenue, Status, Submission
from .mixins import OwnerMixin, ReportMixin


[docs]class Game(ReportMixin, OwnerMixin): """Represents an instance of a Game. Do not create manually. Attributes ----------- id : int ID of the game. Filter attribute. status : Status Status of the game. (see `status and visibility <https://docs.mod.io/#status-amp-visibility>`_ for details) Filter attribute. submitter : Optional[User] Instance of the modio user who submitted the game. Filter attribute. date : datetime.datetime UNIX timestamp of the date the game was registered. Filter attribute. updated : datetime.datetime UNIX timestamp of the date the game was last updated. Filter attribute. live : datetime.datetime UNIX timestamp of the date the game went live. Filter attribute. presentation : Presentation Presentation style used on the mod.io website. Filter attribute. submission : Submission Submission process modders must follow. Filter attribute. curation : Curation Curation process used to approve mods. Filter attribute. community : Community Community features enabled on the mod.io website. Filter attribute. revenue : Revenue Revenue capabilities mods can enable. Filter attribute. api : APIAccess Level of API access allowed by this game. Filter attribute. maturity_options : MaturityOptions Switch to allow developers to select if they flag their mods as containing mature content. Filter attribute. ugc : str Word used to describe user-generated content (mods, items, addons etc). Filter attribute. icon : Image The game icon logo : Image The game logo header : Image The game header name : str Name of the game. Filter attribute. name_id : str sub_domain name for the game (https://name_id.mod.io). Filter attribute. summary : str Summary of the game. Filter attribute. instructions : str Instructions on uploading mods for this game, only applicable if :attr:`submission` equals 0 instructions_url : str Link to a mod.io guide, your modding wiki or a page where modders can learn how to make and submit mods to your games profile. Filter attribute. profile : str URL to the game's mod.io page. tag_options : List[TagOption] List of tags from which mods can pick stats : Optional[GameStats] The game stats other_urls : Dict[str, str] A dictionnary of labels and urls for the game platforms : List[GamePlatform] Platforms this games supports """ _resource_type = "games" def __init__(self, **attrs): self.id = attrs.pop("id") self.status = Status(attrs.pop("status")) self.date = _convert_date(attrs.pop("date_added")) self.updated = _convert_date(attrs.pop("date_updated")) self.live = _convert_date(attrs.pop("date_live")) self.presentation = Presentation(attrs.pop("presentation_option")) self.submission = Submission(attrs.pop("submission_option")) self.curation = Curation(attrs.pop("curation_option")) self.community = Community(attrs.pop("community_options")) self.revenue = Revenue(attrs.pop("revenue_options")) self.api = APIAccess( attrs.pop( "api_access_options", ) ) self.ugc = attrs.pop("ugc_name") self.icon = Image(**attrs.pop("icon", None)) self.logo = Image(**attrs.pop("logo", None)) self.header = Image(**attrs.pop("header", None)) self.homepage = attrs.pop("homepage", None) self.name = attrs.pop("name") self.name_id = attrs.pop("name_id") self.summary = attrs.pop("summary") self.instructions = attrs.pop("instructions", None) self.instructions_url = attrs.pop("instructions_url", None) self.profile = attrs.pop("profile_url") self.tag_options = [TagOption(**tag) for tag in attrs.pop("tag_options", [])] self.maturity_options = MaturityOptions(attrs.pop("maturity_options")) self.connection = attrs.pop("connection") self.submitter = None self.stats = None # self.theme = Theme(**attrs.pop("theme")) self.other_urls = {value["label"]: value["url"] for value in attrs.pop("other_urls")} self.platforms = [GamePlatform(**platform) for platform in attrs.pop("platforms")] _submitter = attrs.pop("submitted_by", {}) if _submitter: self.submitter = User(connection=self.connection, **_submitter) _stats = attrs.pop("stats", {}) if _stats: self.stats = GameStats(**_stats) def __repr__(self): return f"<Game id={self.id} name={self.name}>"
[docs] def get_mod(self, mod_id: int) -> Mod: """Queries the mod.io API for the given mod ID and if found returns it as a Mod instance. If not found raises NotFound. |coro| Parameters ----------- mod_id : int The ID of the mod to query the API for Raises ------- NotFound A mod with the supplied id was not found. Returns -------- :class: `Mod` The mod with the given ID """ mod_json = self.connection.get_request(f"/games/{self.id}/mods/{mod_id}") return Mod(connection=self.connection, **mod_json)
[docs] async def async_get_mod(self, mod_id: int) -> Mod: mod_json = await self.connection.async_get_request(f"/games/{self.id}/mods/{mod_id}") return Mod(connection=self.connection, **mod_json)
[docs] def get_mods(self, *, filters: Filter = None) -> Returned[Mod]: """Gets all the mods available for the game. Returns a named tuple with parameters results and pagination. |filterable| |coro| Parameters ----------- filters : Optional[Filter] A instance of Filter to be used for filtering, paginating and sorting results Returns -------- Returned[List[Mod], Pagination] The results and pagination tuple from this request """ mod_json = self.connection.get_request(f"/games/{self.id}/mods", filters=filters) return Returned( [Mod(connection=self.connection, **mod) for mod in mod_json["data"]], Pagination(**mod_json) )
[docs] async def async_get_mods(self, *, filters: Filter = None) -> Returned[Mod]: mod_json = await self.connection.async_get_request(f"/games/{self.id}/mods", filters=filters) return Returned( [Mod(connection=self.connection, **mod) for mod in mod_json["data"]], Pagination(**mod_json) )
[docs] def get_mod_events(self, *, filters: Filter = None) -> Returned[Event]: """Gets all the mod events available for this game sorted by latest event first. |filterable| |coro| Parameters ----------- filters : Optional[Filter] A instance of Filter to be used for filtering, paginating and sorting results Returns -------- Returned[List[Event], Pagination] The results and pagination tuple from this request """ event_json = self.connection.get_request(f"/games/{self.id}/mods/events", filters=filters) return Returned([Event(**event) for event in event_json["data"]], Pagination(**event_json))
[docs] async def async_get_mod_events(self, *, filters: Filter = None) -> Returned[Event]: event_json = await self.connection.async_get_request(f"/games/{self.id}/mods/events", filters=filters) return Returned([Event(**event) for event in event_json["data"]], Pagination(**event_json))
[docs] def get_tag_options(self, *, filters: Filter = None): """Gets all the game tags available for this game. Updates the tag_option attribute. |filterable| |coro| Parameters ----------- filters : Optional[Filter] A instance of Filter to be used for filtering, paginating and sorting results Returns -------- Returned[List[TagOption], Pagination] The results and pagination tuple from this request """ tag_json = self.connection.get_request(f"/games/{self.id}/tags", filters=filters) self.tag_options = tags = [TagOption(**tag_option) for tag_option in tag_json["data"]] return Returned(tags, Pagination(**tag_json))
[docs] async def async_get_tag_options(self, *, filters: Filter = None): tag_json = await self.connection.async_get_request(f"/games/{self.id}/tags", filters=filters) self.tag_options = tags = [TagOption(**tag_option) for tag_option in tag_json["data"]] return Returned(tags, Pagination(**tag_json))
[docs] def get_stats(self, *, filters: Filter = None): """Get the stats for the game. |filterable| |coro| Parameters ----------- filter : Optional[Filter] A instance of Filter to be used for filtering, paginating and sorting results Returns -------- GameStats The stats for the game. """ stats_json = self.connection.get_request(f"/games/{self.id}/stats", filters=filters) return GameStats(**stats_json)
[docs] async def async_get_stats(self, *, filters: Filter = None): stats_json = await self.connection.async_get_request(f"/games/{self.id}/stats", filters=filters) return GameStats(**stats_json)
[docs] def get_mods_stats(self, *, filters: Filter = None): """Gets the stat for all the mods of this game. |filterable| |coro| Parameters ----------- filter : Optional[Filter] A instance of Filter to be used for filtering, paginating and sorting results Returns -------- Returned[List[ModStats], Pagination] The results and pagination tuple from this request """ stats_json = self.connection.get_request(f"/games/{self.id}/mods/stats", filters=filters) return Returned([ModStats(**stats) for stats in stats_json["data"]], Pagination(**stats_json))
[docs] async def async_get_mods_stats(self, *, filters: Filter = None): stats_json = await self.connection.async_get_request(f"/games/{self.id}/mods/stats", filters=filters) return Returned([ModStats(**stats) for stats in stats_json["data"]], Pagination(**stats_json))
[docs] def add_mod(self, mod: NewMod) -> Mod: """Add a mod to this game. |coro| Parameters ----------- mod : NewMod The mod to be submitted Raises ------- ValueError One of the requirements for a parameter has not been met. Returns -------- Mod The newly created mod """ mod_d = mod.__dict__.copy() tags = list(mod_d.pop("tags")) for index, tag in enumerate(tags): mod_d[f"tags[{index}]"] = tag with open(mod_d.pop("logo"), "rb") as f: mod_json = self.connection.post_request( f"/games/{self.id}/mods", h_type=1, data=mod_d, files={"logo": f} ) return Mod(connection=self.connection, **mod_json)
[docs] async def async_add_mod(self, mod: NewMod) -> Mod: mod_d = mod.__dict__.copy() tags = list(mod_d.pop("tags")) for index, tag in enumerate(tags): mod_d[f"tags[{index}]"] = tag with open(mod_d.pop("logo"), "rb") as f: mod_json = await self.connection.async_post_request( f"/games/{self.id}/mods", h_type=1, data=mod_d, files={"logo": f} ) return Mod(connection=self.connection, **mod_json)
[docs] def add_media(self, *, logo: str = None, icon: str = None, header: str = None): """Upload new media to to the game. This function can take between 1 to 3 arguments depending on what media you desire to upload/update. |coro| Parameters ----------- logo : Optional[str] Path to the file that you desire to be the game's logo. Dimensions must be at least 640x360 and we recommended you supply a high resolution image with a 16 / 9 ratio. mod.io will use this logo to create three thumbnails with the dimensions of 320x180, 640x360 and 1280x720. icon : Optional[str] Path to the file that you desire to be the game's icon. Must be gif, jpg or png format and cannot exceed 1MB in filesize. Dimensions must be at least 64x64 and a transparent png that works on a colorful background is recommended. mod.io will use this icon to create three thumbnails with the dimensions of 64x64, 128x128 and 256x256. header : Optional[str] Path to the file that you desire to be the game's header. Must be gif, jpg or png format and cannot exceed 256KB in filesize. Dimensions of 400x100 and a light transparent png that works on a dark background is recommended. Returns -------- Message A message containing the result of the query if successful. """ media = {} if logo: media["logo"] = open(logo, "rb") if icon: media["icon"] = open(icon, "rb") if header: media["header"] = open(header, "rb") try: message = self.connection.post_request(f"/games/{self.id}/media", h_type=1, files=media) finally: for image in media.values(): image.close() return Message(**message)
[docs] async def async_add_media(self, *, logo: str = None, icon: str = None, header: str = None): media = {} if logo: media["logo"] = open(logo, "rb") if icon: media["icon"] = open(icon, "rb") if header: media["header"] = open(header, "rb") try: message = await self.connection.async_post_request( f"/games/{self.id}/media", h_type=1, files=media ) finally: for image in media.values(): image.close() return Message(**message)
[docs] def add_tag_options( self, name: str, *, tags: Optional[List[str]] = None, hidden: Optional[bool] = False, locked: Optional[bool] = False, tag_type: Optional[Literal["dropdown", "checkboxes"]] = "dropdown", ): """Add tags which mods can apply to their profiles. If the tag names already exists, settings such as hidden or type will be overwritten to the values provided and all the tags will be added to the group. |coro| Parameters ----------- name : str Name of the tag group type : Optional[Literal['dropdown', 'checkboxes']] Defaults to dropdown dropdown : Mods can select only one tag from this group, dropdown menu shown on site profile. checkboxes : Mods can select multiple tags from this group, checkboxes shown on site profile. hidden : Optional[bool] Whether or not this group of tags should be hidden from users and mod devs. Defaults to False locked : Optional[bool] Whether or not mods can assign from this group of tag to themselves. If locked only game admins will be able to assign the tag. Defaults to False. tags : Optional[List[str]] Array of tags that mod creators can apply to their mod """ if tags is None: tags = [] tags = {f"tags[{index}]": tag for index, tag in enumerate(tags)} tags = { "name": name, "type": tag_type, "hidden": json.dumps(hidden), "locked": json.dumps(locked), **tags, } message = self.connection.post_request(f"/games/{self.id}/tags", data=tags) tag_option = find(self.tag_options, name=name) if not tag_option: self.tag_options.append(TagOption(**tags)) else: tag_option.type = tag_type tag_option.hidden = hidden tag_option.tags.extend(tags) return Message(**message)
[docs] async def async_add_tag_options( self, name: str, *, tags: Optional[List[str]] = None, hidden: Optional[bool] = False, locked: Optional[bool] = False, tag_type: Optional[Literal["dropdown", "checkboxes"]] = "dropdown", ): if tags is None: tags = [] tags = {f"tags[{index}]": tag for index, tag in enumerate(tags)} tags = { "name": name, "type": tag_type, "hidden": json.dumps(hidden), "locked": json.dumps(locked), **tags, } message = await self.connection.async_post_request(f"/games/{self.id}/tags", data=tags) tag_option = find(self.tag_options, name=name) if not tag_option: self.tag_options.append(TagOption(**tags)) else: tag_option.type = tag_type tag_option.hidden = hidden tag_option.tags.extend(tags) return Message(**message)
[docs] def delete_tag_options(self, name: str, *, tags: Optional[List[str]] = None) -> bool: """Delete one or more tags from a tag option. |coro| Parameters ----------- name : str Name of the group from which you wish to delete from tags : Optional[List[str]] Optional. Tags to delete from group. If left blank the entire group will be deleted Returns -------- bool Returns True if the tags were sucessfully removed, False if the requests was sucessful but the tags was not removed (if the tag wasn't part of the option.) """ data = {f"tags[{index}]": tag for index, tag in enumerate(tags)} if tags else {"tags[]": ""} data["name"] = name resp = self.connection.delete_request(f"/games/{self.id}/tags", data=data) option = find(self.tag_options, name=name) if tags: option.tags = [tag for tag in option.tags if tag not in tags] else: self.tag_options.remove(option) return not isinstance(resp, dict)
[docs] async def async_delete_tag_options(self, name: str, *, tags: Optional[List[str]] = None) -> bool: data = {f"tags[{index}]": tag for index, tag in enumerate(tags)} if tags else {"tags[]": ""} data["name"] = name resp = await self.connection.async_delete_request(f"/games/{self.id}/tags", data=data) option = find(self.tag_options, name=name) if tags: option.tags = [tag for tag in option.tags if tag not in tags] else: self.tag_options.remove(option) return not isinstance(resp, dict)