Giter VIP home page Giter VIP logo

codingame's Introduction

codingame's People

Contributors

darthwalsh avatar devhypercoder avatar github-actions[bot] avatar liju09 avatar takos22 avatar yashovardhansingh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

codingame's Issues

[FEAT] Use ID of the logged in user for follower endpoints

Related problem

Only the followers and follows of the logged in CodinGamer can be accessed with CodinGamer.get_followers and CodinGamer.get_followed.

Wanted solution

Being able to access anyone's followers and follows, the 2nd parameter to the API in HTTPClient.get_codingamer_followers and HTTPClient.get_codingamer_following need to be updated to self.state.codingamer.id

    def get_codingamer_followers(self, id: int) -> typing.List[Follower]:
        # TODO fix this to use [id, logged_in.id, None]
        return self.request("CodinGamer", "findFollowers", [id, id, None])
    def get_codingamer_following(self, id: int) -> typing.List[Following]:
        # TODO fix this to use [id, logged_in.id]
        return self.request("CodinGamer", "findFollowing", [id, id])

Considered alternatives

Using CodinGamer.get_followers_ids and CodinGamer.get_followed_ids, but that would be slower as it requires way more requests to the API.

Additional context

None

[FEAT] Use ID of the logged in user for following endpoints

Duplicate of #8

Generated automatically

fix this to use [id, logged_in.id]

# TODO fix this to use [id, logged_in.id]

    ):
        ...  # pragma: no cover

    def get_file_url(self, id: int, format: str = None) -> str:
        url = f"https://static.codingame.com/servlet/fileservlet?id={id}"
        if format:
            url += f"&format={format}"
        return url

    # Search

    def search(self, query: str):
        return self.request("Search", "search", [query, "en", None])

    # ProgrammingLanguage

    def get_language_ids(self) -> typing.List[str]:
        return self.request("ProgrammingLanguage", "findAllIds")

    # CodinGamer

    def login(self, email: str, password: str):
        return self.request(
            "CodinGamer", "loginSiteV2", [email, password, True]
        )

    def get_codingamer_from_handle(self, handle: str) -> PointsStatsFromHandle:
        return self.request(
            "CodinGamer", "findCodingamePointsStatsByHandle", [handle]
        )

    def get_codingamer_from_id(self, id: int) -> CodinGamerFromID:
        return self.request(
            "CodinGamer", "findCodinGamerPublicInformations", [id]
        )

    def get_codingamer_followers(self, id: int) -> typing.List[Follower]:
        # TODO fix this to use [id, logged_in.id, None]
        return self.request("CodinGamer", "findFollowers", [id, id, None])

    def get_codingamer_follower_ids(self, id: int) -> typing.List[int]:
        return self.request("CodinGamer", "findFollowerIds", [id])

    def get_codingamer_following(self, id: int) -> typing.List[Following]:
        # TODO fix this to use [id, logged_in.id]
        return self.request("CodinGamer", "findFollowing", [id, id])

    def get_codingamer_following_ids(self, id: int) -> typing.List[int]:
        return self.request("CodinGamer", "findFollowingIds", [id])

    # ClashOfCode/

    def get_codingamer_clash_of_code_rank(self, id: int) -> int:
        return self.request("ClashOfCode", "getClashRankByCodinGamerId", [id])

    def get_clash_of_code_from_handle(self, handle: str) -> ClashOfCode:
        return self.request("ClashOfCode", "findClashByHandle", [handle])

    def get_pending_clash_of_code(self) -> ClashOfCode:
        return self.request("ClashOfCode", "findPendingClashes")

    # Notification

    def get_unread_notifications(self, id: int) -> typing.List[Notification]:
        return self.request("Notification", "findUnreadNotifications", [id])

    def get_unseen_notifications(self, id: int) -> typing.List[Notification]:
        return self.request("Notification", "findUnseenNotifications", [id])

    def get_last_read_notifications(self, id: int) -> typing.List[Notification]:
        return self.request(
            "Notification", "findLastReadNotifications", [id, None]
        )

    # Leaderboards

    def get_global_leaderboard(
        self,

23aaa6ecba8d489eb5cda32f2848dd0a2601700e

[FEAT] Make Client useable for any authenticated POST request

Is your feature request related to a problem? Please describe.
I made a small app that downloads all my CodinGame solutions. I need to make authenticated API requests to different URLs e.g. https://www.codingame.com/services/Puzzle/findAllMinimalProgress, /Solution/findSolution, etc.

I've now blocked with the login problem #5 and at the same time I was guessing about some solution you were building it! (wow!)

Describe the solution you'd like
Add codingame.Client.request(service: str, func: str, json: list = []) to the public API for both the sync and async clients.

Describe alternatives you've considered

  • I think my app could invoke client.request() but as it's undocumented it seems to be an internal API?
  • I could add cookie session in my own repo, but I'd rather rely on some dedicated client to handle the logic.
  • It would be awesome if this client natively supported the various CodinGame APIs my puzzle solution download app needed, but that feels like a big request. Instead, having the generic .request() escape hatch enables the library to be used for any new APIs that CodinGame releases!

Additional context
I'm impressed by this project! Nice!

[BUG] Datetime format change

Description

An error related to date format occur when trying to get the pending clash of code (with codingame.Client().get_pending_clash_of_code())

To Reproduce

Just call codingame.Client().get_pending_clash_of_code() and you will get this error:

  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/codingame/client/sync.py", line 128, in get_pending_clash_of_code
    return ClashOfCode(self._state, data[0])  # pragma: no cover
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/codingame/clash_of_code.py", line 130, in __init__
    self.creation_time = to_datetime(data["creationTime"])
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/codingame/utils.py", line 102, in to_datetime
    return datetime.strptime(data, DT_FORMAT)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/_strptime.py", line 568, in _strptime_datetime
    tt, fraction, gmtoff_fraction = _strptime(data_string, format)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/_strptime.py", line 349, in _strptime
    raise ValueError("time data %r does not match format %r" %
ValueError: time data 'Feb 10, 2023, 11:42:01 PM' does not match format '%b %d, %Y %I:%M:%S %p'

Environment information:

  • OS: macOS Sierra 10.12.6
  • Python version: 3.9.7
  • codingame module version: 1.4.0

Suggestion

I just added a comma after the %Y in DT_FORMAT from the codingame/utils.py file, line 95, in order to match the data, like this: DT_FORMAT = "%b %d, %Y, %I:%M:%S %p".
It solved the problem for me.

[DUPLICATE] implement state.current_language

implement state.current_language

def str(self) -> str:

return self[state.current_language]

# TODO implement state.current_language

import re
import typing
from datetime import datetime

from ..abc import Mapping
from ..types import notification as types
from ..utils import to_datetime
from .enums import (
    CommentType,
    ContributionModeratedActionType,
    ContributionType,
    NotificationType,
)

if typing.TYPE_CHECKING:
    from ..state import ConnectionState

__all__ = (
    "LanguageMapping",
    "NotificationData",
    "AchievementUnlockedData",
    "LeagueData",
    "NewBlogData",
    "ClashInviteData",
    "ClashOverData",
    "Contribution",
    "PuzzleSolution",
    "NewCommentData",
    "ContributionData",
    "FeatureData",
    "NewHintData",
    "ContributionModeratedData",
    "NewPuzzleData",
    "PuzzleOfTheWeekData",
    "QuestCompletedData",
    "FriendRegisteredData",
    "NewLevelData",
    "GenericData",
    "CustomData",
    "CareerCandidateData",
    "TestFinishedData",
    "JobAcceptedData",
    "JobExpiredData",
    "NewWorkBlogData",
    "OfferApplyData",
)


class LanguageMapping(Mapping):
    """Mapping to store text with multiple languages.

    This class has the same interface as :class:`dict` for backwards
    compatibility.

    Attributes
    -----------
        en: :class:`str`
            The text in english.

        fr: :class:`str`
            The text in french.
    """

    en: str
    fr: str

    __slots__ = ("en", "fr")

    def __init__(
        self, state: "ConnectionState", mapping: types.LanguageMapping
    ):
        self.en = mapping["en"]
        self.fr = mapping["fr"]

        super().__init__(state, mapping)

    # TODO implement state.current_language
    # def __str__(self) -> str:
    #     return self[state.current_language]


class NotificationData(Mapping):
    """Base class for the notification data classes.

    This class has the same interface as :class:`dict` for backwards
    compatibility.

    Attributes
    -----------
        _raw: :class:`dict`
            Raw data of the :class:`Notification`, useful when one of the values
            isn't an attribute.
    """

    _raw: dict

    def __init__(self, state: "ConnectionState", data: types.NotificationData):
        super().__init__(state, data)

    @classmethod
    def from_type(
        cls,
        type: NotificationType,
        state: "ConnectionState",
        data: types.NotificationData,
    ) -> typing.Optional["NotificationData"]:
        """Create the correct :class:`NotificationData` subclass according to
        the :class:`notification type <NotificationType>`.

        Parameters
        ----------
            type : :class:`NotificationType`
                The notification type.

            data : :class:`dict`
                The notification data.

        Returns
        -------
            :class:`NotificationData`
                The parsed data of the notifcation.
        """

        NT = NotificationType
        type_to_obj = {
            NT.achievement_unlocked: AchievementUnlockedData,
            NT.new_league: LeagueData,
            NT.eligible_for_next_league: LeagueData,
            NT.promoted_league: LeagueData,
            NT.new_league_opened: LeagueData,
            NT.new_blog: NewBlogData,
            NT.clash_invite: ClashInviteData,
            NT.clash_over: ClashOverData,
            NT.new_comment: NewCommentData,
            NT.new_comment_response: NewCommentData,
            NT.contribution_received: ContributionData,
            NT.contribution_accepted: ContributionData,
            NT.contribution_refused: ContributionData,
            NT.contribution_clash_mode_removed: ContributionData,
            NT.feature: FeatureData,
            NT.new_hint: NewHintData,
            NT.contribution_moderated: ContributionModeratedData,
            NT.new_puzzle: NewPuzzleData,
            NT.puzzle_of_the_week: PuzzleOfTheWeekData,
            NT.quest_completed: QuestCompletedData,
            NT.friend_registered: FriendRegisteredData,
            NT.new_level: NewLevelData,
            NT.info_generic: GenericData,
            NT.warning_generic: GenericData,
            NT.important_generic: GenericData,
            NT.custom: CustomData,
            NT.career_new_candidate: CareerCandidateData,
            NT.career_update_candidate: CareerCandidateData,
            NT.test_finished: TestFinishedData,
            NT.job_accepted: JobAcceptedData,
            NT.job_expired: JobExpiredData,
            NT.new_work_blog: NewWorkBlogData,
            NT.offer_apply: OfferApplyData,
        }
        return (
            type_to_obj.get(type, NotificationData)(state, data)
            if data
            else None
        )


# achievement


class AchievementUnlockedData(NotificationData):
    """Data of a :attr:`NotificationType.achievement_unlocked` notification."""

    id: str
    label: LanguageMapping
    points: int
    level: str
    completion_time: datetime
    image_url: str

    __slots__ = (
        "id",
        "label",
        "points",
        "level",
        "completion_time",
        "image_url",
    )

    def __init__(
        self, state: "ConnectionState", data: types.AchievementUnlockedData
    ):
        self.id = data["id"]
        self.label = LanguageMapping(state, data["label"])
        self.points = data["points"]
        self.level = data["level"]
        self.completion_time = to_datetime(data["completionTime"])
        self.image_url = state.http.get_file_url(
            data["imageId"], format="notification_picture"
        )

        super().__init__(state, data)


# arena and new-league-opened


class LeagueData(NotificationData):
    """Data of :attr:`NotificationType.new_league`,
    :attr:`NotificationType.eligible_for_next_league`,
    :attr:`NotificationType.promoted_league` and
    :attr:`NotificationType.new_league_opened` notifications."""

    title_label: LanguageMapping
    division_index: int
    division_count: int
    division_offset: int
    threshold_index: int
    thumbnail_url: str
    test_session_handle: str

    __slots__ = (
        "title_label",
        "division_index",
        "division_count",
        "division_offset",
        "threshold_index",
        "thumbnail_url",
        "test_session_handle",
    )

    def __init__(self, state: "ConnectionState", data: types.LeagueData):
        self.title_label = LanguageMapping(state, data["titleLabel"])
        self.division_index = data["divisionIndex"]
        self.division_count = data["divisionCount"]
        self.division_offset = data["divisionOffset"]
        self.threshold_index = data["thresholdIndex"]
        self.thumbnail_url = state.http.get_file_url(
            data["thumbnailBinaryId"], format="notification_picture"
        )
        self.test_session_handle = data["testSessionHandle"]

        super().__init__(state, data)


# blog


class NewBlogData(NotificationData):
    """Data of a :attr:`NotificationType.new_blog` notification."""

    title: LanguageMapping
    url: LanguageMapping

    __slots__ = (
        "title",
        "url",
    )

    def __init__(self, state: "ConnectionState", data: types.NewBlogData):
        self.title = LanguageMapping(state, data["title"])
        self.url = LanguageMapping(state, data["url"])

        super().__init__(state, data)


# clash


class ClashInviteData(NotificationData):
    """Data of a :attr:`NotificationType.clash_invite` notification."""

    handle: str

    __slots__ = ("handle",)

    def __init__(self, state: "ConnectionState", data: types.ClashInviteData):
        self.handle = data["handle"]

        super().__init__(state, data)


class ClashOverData(NotificationData):
    """Data of a :attr:`NotificationType.clash_over` notification."""

    handle: str
    rank: int
    player_count: int

    __slots__ = (
        "handle",
        "rank",
        "player_count",
    )

    def __init__(self, state: "ConnectionState", data: types.ClashOverData):
        self.handle = data["handle"]
        self.rank = data["rank"]
        self.player_count = data["playerCount"]

        super().__init__(state, data)


# comment


class Contribution(Mapping):
    """Data about a contribution.

    This class has the same interface as :class:`dict` for backwards
    compatibility.
    """

    handle: str
    title: typing.Optional[str]
    type: typing.Optional[ContributionType]

    __slots__ = ("handle", "title", "type")

    def __init__(self, state: "ConnectionState", data: types.ContributionData):
        self.handle = data["handle"]
        self.title = data.get("title")
        self.type = ContributionType(data["type"]) if "type" in data else None

        super().__init__(state, data)


class PuzzleSolution(Mapping):
    """Data about a puzzle solution.

    This class has the same interface as :class:`dict` for backwards
    compatibility.
    """

    puzzle_id: str
    puzzle_url: typing.Optional[str]
    test_session_submission_id: int

    __slots__ = ("puzzle_id", "puzzle_url", "test_session_submission_id")

    def __init__(
        self, state: "ConnectionState", data: types.PuzzleSolutionData
    ):
        self.puzzle_id = data["puzzleId"]
        self.puzzle_url = (
            (state.http.BASE_URL + data["puzzleDetailsPageUrl"])
            if "puzzleDetailsPageUrl" in data
            else None
        )
        self.test_session_submission_id = data["testSessionSubmissionId"]

        super().__init__(state, data)


class NewCommentData(NotificationData):
    """Data of a :attr:`NotificationType.new_comment` and
    :attr:`NotificationType.new_comment_response` notifications."""

    type: LanguageMapping
    comment_type: typing.Optional[CommentType]
    type_data: typing.Union[Contribution, PuzzleSolution, None]
    url: typing.Optional[str]

    __slots__ = ("type", "comment_type", "type_data", "url")

    def __init__(self, state: "ConnectionState", data: types.NewCommentData):
        self.type = LanguageMapping(state, data["type"])
        self.comment_type = (
            CommentType(data["commentType"]) if "commentType" in data else None
        )
        self.type_data = None
        if "typeData" in data:
            self.type_data = (
                Contribution(state, data["typeData"])
                if self.comment_type == CommentType.contribution
                else PuzzleSolution(state, data["typeData"])
            )
        self.url = (
            (state.http.BASE_URL + data["url"]) if "url" in data else None
        )

        super().__init__(state, data)


# contribution


class ContributionData(Mapping):
    """Data of :attr:`NotificationType.contribution_received`,
    :attr:`NotificationType.contribution_accepted`,
    :attr:`NotificationType.contribution_refused` and
    :attr:`NotificationType.contribution_clash_mode_removed` notifications."""

    handle: str
    title: typing.Optional[str]
    type: typing.Optional[ContributionType]

    __slots__ = ("handle", "title", "type")

    def __init__(self, state: "ConnectionState", data: types.ContributionData):
        self.handle = data["handle"]
        self.title = data.get("title")
        self.type = ContributionType(data["type"]) if "type" in data else None

        super().__init__(state, data)


# feature


class FeatureData(NotificationData):
    """Data of a :attr:`NotificationType.feature` notification."""

    title: typing.Optional[LanguageMapping]
    description: LanguageMapping
    image_url: str
    url: str

    __slots__ = (
        "title",
        "description",
        "image_url",
        "url",
    )

    def __init__(self, state: "ConnectionState", data: types.FeatureData):
        self.title = (
            LanguageMapping(state, data["title"]) if "title" in data else None
        )
        self.description = LanguageMapping(state, data["description"])
        self.image_url = data["image-instant"]
        self.url = (
            data["url"]
            if re.match(r"https?://", data["url"])
            else (state.http.BASE_URL + data["url"])
        )

        super().__init__(state, data)


# hints


class NewHintData(NotificationData):
    """Data of a :attr:`NotificationType.new_hint` notification."""

    puzzle_title: LanguageMapping
    thumbnail_url: str
    test_session_handle: str

    __slots__ = (
        "puzzle_title",
        "thumbnail_url",
        "test_session_handle",
    )

    def __init__(self, state: "ConnectionState", data: types.NewHintData):
        self.puzzle_title = LanguageMapping(state, data["puzzleTitle"])
        self.thumbnail_url = state.http.get_file_url(
            data["thumbnailBinaryId"], format="notification_picture"
        )
        self.test_session_handle = data["testSessionHandle"]

        super().__init__(state, data)


# moderation


class ContributionModeratedData(NotificationData):
    """Data of a :attr:`NotificationType.contribution_moderated`
    notification."""

    action_type: ContributionModeratedActionType
    contribution: Contribution

    __slots__ = (
        "action_type",
        "contribution",
    )

    def __init__(
        self, state: "ConnectionState", data: types.ContributionModeratedData
    ):
        self.action_type = ContributionModeratedActionType(data["actionType"])
        self.contribution = Contribution(state, data["contribution"])

        super().__init__(state, data)


# puzzle


class NewPuzzleData(NotificationData):
    """Data of a :attr:`NotificationType.new_puzzle` notification."""

    level: LanguageMapping
    name: LanguageMapping
    image_url: str
    puzzle_id: int

    __slots__ = (
        "level",
        "name",
        "image_url",
        "puzzle_id",
    )

    def __init__(self, state: "ConnectionState", data: types.NewPuzzleData):
        self.level = LanguageMapping(state, data["level"])
        self.name = LanguageMapping(state, data["name"])
        self.image_url = data["image"]
        self.puzzle_id = data["puzzleId"]

        super().__init__(state, data)


class PuzzleOfTheWeekData(NotificationData):
    """Data of a :attr:`NotificationType.puzzle_of_the_week` notification."""

    puzzle_id: int
    puzzle_level: str
    puzzle_pretty_id: str
    puzzle_name: LanguageMapping
    puzzle_image_url: str
    contributor_pseudo: str
    contributor_avatar_url: typing.Optional[str]

    __slots__ = (
        "puzzle_id",
        "puzzle_level",
        "puzzle_pretty_id",
        "puzzle_name",
        "puzzle_image_url",
        "contributor_pseudo",
        "contributor_avatar_url",
    )

    def __init__(
        self, state: "ConnectionState", data: types.PuzzleOfTheWeekData
    ):
        self.puzzle_id = data["puzzleId"]
        self.puzzle_level = data["puzzleLevel"]
        self.puzzle_pretty_id = data["puzzlePrettyId"]
        self.puzzle_name = LanguageMapping(state, data["puzzleName"])
        self.puzzle_image_url = state.http.get_file_url(
            data["puzzleOfTheWeekImageId"], format="notification_picture"
        )
        self.contributor_pseudo = data["contributorNickname"]
        self.contributor_avatar_url = (
            state.http.get_file_url(
                data["contributorAvatarId"], format="notification_picture"
            )
            if "contributorAvatarId" in data
            else None
        )

        super().__init__(state, data)


# quest


class QuestCompletedData(NotificationData):
    """Data of a :attr:`NotificationType.quest_completed` notification."""

    id: int
    label: LanguageMapping

    __slots__ = (
        "id",
        "label",
    )

    def __init__(
        self, state: "ConnectionState", data: types.QuestCompletedData
    ):
        self.id = data["questId"]
        self.label = LanguageMapping(state, data["label"])

        super().__init__(state, data)


# social


class FriendRegisteredData(NotificationData):
    """Data of a :attr:`NotificationType.friend_registered` notification."""

    name: str

    __slots__ = ("name",)

    def __init__(
        self, state: "ConnectionState", data: types.FriendRegisteredData
    ):
        self.name = data["name"]

        super().__init__(state, data)


# xp


class NewLevelData(NotificationData):
    """Data of a :attr:`NotificationType.new_level` notification."""

    level: int
    reward: typing.Optional[LanguageMapping]
    trigger_career_popup: typing.Optional[bool]

    __slots__ = (
        "level",
        "reward",
        "trigger_career_popup",
    )

    def __init__(self, state: "ConnectionState", data: types.NewLevelData):
        self.level = data["level"]
        self.reward = (
            LanguageMapping(state, data["reward"]) if "reward" in data else None
        )
        self.trigger_career_popup = data.get("triggerCareerPopup")

        super().__init__(state, data)


# generic


class GenericData(NotificationData):
    """Data of a :attr:`NotificationType.info_generic`,
    :attr:`NotificationType.warning_generic` and
    :attr:`NotificationType.important_generic` notifications."""

    title: LanguageMapping
    description: LanguageMapping
    image_url: str
    url: str

    __slots__ = (
        "title",
        "description",
        "image_url",
        "url",
    )

    def __init__(self, state: "ConnectionState", data: types.GenericData):
        self.description = LanguageMapping(state, data["description"])
        self.url = data["url"]

        super().__init__(state, data)


# custom


class CustomData(NotificationData):
    """Data of a :attr:`NotificationType.custom` notification."""

    title: LanguageMapping
    description: LanguageMapping
    image_url: str
    url: str

    __slots__ = (
        "title",
        "description",
        "image_url",
        "url",
    )

    def __init__(self, state: "ConnectionState", data: types.CustomData):
        self.title = LanguageMapping(state, data["title"])
        self.description = LanguageMapping(state, data["description"])
        self.image_url = state.http.STATIC_URL + data["image"]
        self.url = (
            (state.http.BASE_URL + data["url"])
            if data["url"].startswith("/")
            else data["url"]
        )

        super().__init__(state, data)


# other


class CareerCandidateData(NotificationData):
    """Data of a :attr:`NotificationType.career_new_candidate` and
    :attr:`NotificationType.career_update_candidate` notifications."""

    handle: str
    username: typing.Optional[str]
    country: str
    region: str
    avatar_url: typing.Optional[str]

    __slots__ = (
        "handle",
        "username",
        "country",
        "region",
        "avatar_url",
    )

    def __init__(
        self, state: "ConnectionState", data: types.CareerCandidateData
    ):
        self.handle = data["handle"]
        self.username = data.get("username")
        self.country = data["country"]
        self.region = data["region"]
        self.avatar_url = (
            state.http.get_file_url(
                data["avatar"], format="notification_picture"
            )
            if "avatar" in data
            else None
        )

        super().__init__(state, data)


# no category


class TestFinishedData(NotificationData):
    """Data of a :attr:`NotificationType.test_finished` notification."""

    campaign_id: int
    candidate_id: int
    candidate_name: typing.Optional[str]
    candidate_email: str

    __slots__ = (
        "campaign_id",
        "candidate_id",
        "candidate_name",
        "candidate_email",
    )

    def __init__(self, state: "ConnectionState", data: types.TestFinishedData):
        self.campaign_id = data["campaignId"]
        self.candidate_id = data["candidateId"]
        self.candidate_name = data.get("candidateName")
        self.candidate_email = data["candidateEmail"]

        super().__init__(state, data)


class JobAcceptedData(NotificationData):
    """Data of a :attr:`NotificationType.job_accepted` notification."""

    job_name: typing.Optional[str]
    job_offer_location: str
    challenge_id: typing.Optional[int]

    __slots__ = (
        "job_name",
        "job_offer_location",
        "challenge_id",
    )

    def __init__(self, state: "ConnectionState", data: types.JobAcceptedData):
        self.job_name = data.get("jobName")
        self.job_offer_location = data["jobOfferLocation"]
        self.challenge_id = data.get("challengeId")

        super().__init__(state, data)


class JobExpiredData(NotificationData):
    """Data of a :attr:`NotificationType.job_expired` notification."""

    job_name: typing.Optional[str]

    __slots__ = ("job_name",)

    def __init__(self, state: "ConnectionState", data: types.JobExpiredData):
        self.job_name = data.get("jobName")

        super().__init__(state, data)


class NewWorkBlogData(NotificationData):
    """Data of a :attr:`NotificationType.new_work_blog` notification."""

    title: LanguageMapping
    url: LanguageMapping

    __slots__ = (
        "title",
        "url",
    )

    def __init__(self, state: "ConnectionState", data: types.NewWorkBlogData):
        self.title = LanguageMapping(state, data["title"])
        self.url = LanguageMapping(
            state,
            {
                lang: state.http.BASE_URL + path
                for lang, path in data["url"].items()
            },
        )

        super().__init__(state, data)


class OfferApplyData(NotificationData):
    """Data of a :attr:`NotificationType.offer_apply` notification."""

    candidate_name: str
    job_name: typing.Optional[str]
    job_offer_location: str
    challenge_id: typing.Optional[int]
    job_offer_id: typing.Optional[int]
    job_offer_applicant_id: typing.Optional[int]

    __slots__ = (
        "candidate_name",
        "job_name",
        "job_offer_location",
        "challenge_id",
        "job_offer_id",
        "job_offer_applicant_id",
    )

    def __init__(self, state: "ConnectionState", data: types.OfferApplyData):
        self.candidate_name = data["candidateName"]
        self.job_name = data.get("jobName")
        self.job_offer_location = data["jobOfferLocation"]
        self.challenge_id = data.get("challengeId")
        self.job_offer_id = data.get("jobOfferId")
        self.job_offer_applicant_id = data.get("jobOfferApplicantId")

        super().__init__(state, data)

3e717771f317d9ca1ad8ad0078d2a34a832f2ad2

[BUG] Client.get_challenge_leaderboard not found raises the wrong error

Describe the bug

When using Client.get_challenge_leaderboard with an invalid challenge ID, it raises a KeyError instead of a codingame.ChallengeNotFound.

To Reproduce

Steps to reproduce the behavior:

  1. Execute
import codingame

client = codingame.Client()
client.get_challenge_leaderboard("nonexistent")
  1. See the KeyError in the terminal instead of codingame.ChallengeNotFound.

Expected behavior

When trying to get an nonexistent challenge leaderboard, Client.get_challenge_leaderboard should raise a codingame.ChallengeNotFound error.

Suggested solution

When giving a nonexistent challenge ID, the HTTPError data has a code field that is set to NOT_FOUND. A check could be implemented on that field instead of the id field that doesn't exist anymore.

Screenshots

image

Environment information:

  • OS: Linux Ubuntu 22.04.2
  • Python version: 3.10.10
  • codingame module version: 1.4.1

Additional context

See the failed GitHub action here

[BUG] KeyError when running .get_clash_of_code

Describe the bug

There is a KeyError when running Client.get_clash_of_code

  File "../codingame/client/async_.py", line 127, in get_clash_of_code
    return ClashOfCode(self._state, data)
  File "../codingame/clash_of_code.py", line 120, in __init__
    self.public = data["publicClash"]
KeyError: 'publicClash'

Expected behavior

It was supposed to return information about the provided clash of code

Environment information:

  • OS: MacOS Sonoma 14.2.1
  • Python version: 3.11.2
  • codingame module version: 1.4.2

[FEAT] Add ConnectionState.current_language

Related problem

When dealing with endpoint that give a title/description in both French and English, displaying both is a bit bulky, so having the ConnectionState know its current language would help displaying only the message in the correct language.

Wanted solution

Adding a ConnectionState.current_language attribute/property to display texts in the right language.

Considered alternatives

None

Additional context

None

[BUG] Login service not found

Describe the bug

When logging in to CodinGame API, with Client.login(email, password), a LoginError is raised saying Service not found: codingamer.loginsitev2(3). This means that the endpoint in the API no longer exists, so the login endpoint was changed.

To Reproduce

Steps to reproduce the behavior:

  1. Copy this code to a file (you can replace email and password with your own but that doesn't change anything to the error):
    import codingame
    client = codingame.Client()
    client.login("[email protected]", "password")
  2. Execute the previously copied code.
  3. See error: codingame.exceptions.LoginError: Service not found: codingamer.loginsitev2(3)

Expected behavior

Login works and doesn't raise an error.

Environment information:

  • OS: Windows 10 (but works on any OS)
  • Python version: 3.7.6 (but works on any version)
  • codingame module version: 1.0.1 (but works on any version)

Additional context

The login endpoint was moved to CodinGamer/loginSite and now needs a Captcha or a valid session.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.