Events

The following section provides an overview of the Events Client, providing an easy introduction with a wealth of examples.

What are they?

The Clash of Clans API, currently, provides no reasonable way to know when for example, someone upgrades a troop, or attacks in war, without making repeated calls and comparing objects over time.

The coc.py events client does exactly that: making requests to the API every X seconds, comparing the results internally and dispatching relevant “events”.

Functions are called, known as “callbacks” when these events occur, and are named as such. You must register the functions you wish to be called, in addition to telling the library which clan and player tags you wish to “track”.

Getting Started

Before receiving events for your clan/players, you must first register the clans and players you wish to “watch”, as well as registering callback functions that will be called when events are found.

Events Client

When using events, you must use the dedicated EventsClient class. This class extends the Client class, and allows all of the same operations, as well as more specific to events.

You just create a EventsClient instead of Client before you login.

Decorators

Decorators are a simple, easy way to interact with coc.py events. They are grouped into 4 categories:

  • coc.PlayerEvents: Events for players.

  • coc.ClanEvents: Events for clans.

  • coc.WarEvents: Events for wars.

  • coc.ClientEvents: Events for miscellaneous client events.

A simple example of how decorators are intended to work is below:

import coc

@coc.PlayerEvents.donations(tags=["#282L8GLJ"])
async def my_function(old_player, new_player):
    new_donations = new_player.donations - old_player.donations
    print(f"{new_player} just donated {new_donations} troops.")

Each event group corresponds to a specific model, and client method.

Decorator

Model

Method

coc.PlayerEvents

Player

Client.get_player()

coc.ClanEvents

Clan

Client.get_clan()

coc.WarEvents

ClanWar

Client.get_current_war()

Events are dynamically created. This means that you can have an event for any attribute of the decorator’s corresponding model. For example, if you use @coc.PlayerEvents, you can have an event for a player name change, level change, donations change.

The format is simple. You use the name of the attribute as the attribute you access from the decorator.

For Example,

To get an event for a player’s donations, ie. when the Player.donations changes, you would do:

@coc.PlayerEvents.donations()

To get an event for when a clan’s level, ie when the Clan.level changes, you would do:

@coc.ClanEvents.level()

To get an event for when a war’s state, ie ClanWar.state changes, you would do:

@coc.WarEvents.state()

To get an event for when a new war start, ie ClanWar.preparation_start_time changes, you would do:

@coc.WarEvents.new_war()

Note

The callback function of WarEvents.new_war() has only one parameter, the new ClanWar.

Note

The callback function of PlayerEvents.active_equipment_change() has four parameters, the old Player, the new Player, the Hero and the newly equipped Equipment.

The pattern is simple, and holds true for all attributes.

Some more examples:

@client.event
@coc.PlayerEvents.trophies()  # an event that is run for every player, when their `.trophies` attribute changes.
async def foo(old_player, new_player):
    assert old_player.trophies != new_player.trophies

@client.event
@coc.WarEvents.state()  # an event that is run when a war `.state` has changed
async def foo(old_war, new_war):
    assert old_war.state != new_war.state

@client.event
@coc.ClanEvents.public_war_log()  # an event that is run when a clan's `.public_war_log` attribute has changed.
async def foo(old_clan, new_clan):
    assert old_clan.public_war_log != new_clan.public_war_log

@client.event
@coc.ClanEvents.member_donations()  # an event that is run for every clan member when their `.donations` have changed.
async def foo(old_member, new_member):
    assert old_member.donations != new_member.donations

@client.event
@coc.PlayerEvents.clan_level()  # an event that is called when a player's clan's level has changed.
async def foo(old_player, new_player):
    assert old_player.clan.level != new_player.clan.level

You can also stack decorators to get multiple events reported to one callback:

@client.event
@coc.ClanEvents.public_war_log()
@coc.ClanEvents.description()
@coc.ClanEvents.level()
async def foo(old_clan, new_clan):
    if old_clan.level != new_clan.level:
        ...
    elif old_clan.description != new_clan.description:
        ...

Callbacks

Callbacks are the functions that are called when your event “happens”.

For Example:

@client.event
@coc.PlayerEvents.name()
async def my_function(old_player, new_player):  # <-- this is the line I'm talking about
    ...

A few points when dealing with callbacks:

  1. They must be an async function, in other words they must start with async def.

  2. They must have 2, and only 2 parameters: the old object, and the new object.

  3. There is no naming convention, that is, you can call it whatever you want.

Elaborating on point 2, the “old” object is the one before the event/change, and the “new” object is the one after the event/change. It’s often helpful to name them like so.

If your event is @coc.PlayerEvents.name(), you can expect the names of the old and new players to be different, for example:

@client.event
@coc.PlayerEvents.name()
async def foo(old_player, new_player):
    assert old_player.name != new_player.name  # True

For @coc.ClanEvents.member_x events, the first parameter should be the old member, and the second parameter the new member. You can access the member’s clan object via member.clan.

For Example:

@client.event
@coc.ClanEvents.member_donations()
async def foo(old_member, new_member):
    assert old_member.donations != new_member.donations
    print("The clan is {}".format(new_member.clan.name))

Retry / Refresh Intervals

Unless you wish to only check for new events once every hour or 6 hours, or any time greater than the refresh time for objects in the API, it is suggested to omit the ``retry_interval`` parameter. The library will automatically determine when the next fresh object is available, and instead of sleeping for a predefined 60seconds between every loop, it will instead sleep until a fresh object is available from the API. This means some events could see an up to 50% reduction in latency between when the event happens in game and when coc.py reports it.

For example:

@client.event
@coc.ClanEvents.level()
async def foo(...): ...  # check as often as API updates the clan for an event

@client.event
@coc.ClanEvents.level(retry_interval=1800)  # check every 30 minutes for a new event.
async def foo(...): ...

Adding Clan and Player Tags

Tags can be added via the EventsClient.add_player_updates(), EventsClient.add_clan_updates() or EventsClient.add_war_updates(). Alternatively, they can be passed to the decorator function.

For example:

@client.event
@coc.PlayerEvents.name(tags=['#tag', '#tag2', '#tag3'])
async def foo(old_player, new_player): ...

# alternatively:

@client.event
@coc.PlayerEvents.name()
async def foo(old_player, new_player): ...

client.add_player_updates('#tag', '#tag2', '#tag3')

A few points to note:

  • Tags will be automatically corrected via the coc.correct_tag() function.

  • Every tag that is added to the client will be sent to every callback for that event group.

    This makes for a much simpler internal design.

For Example:

@client.event
@coc.PlayerEvents.exp_level("#tag1")
async def foo(...):
    # events will be received for #tag1, #tag2, #tag3 and #tag4.

@client.event
@coc.PlayerEvents.name("#tag2")
async def foo(...):
    # events will be received for #tag1, #tag2, #tag3 and #tag4.

@client.event
@coc.PlayerEvents.donations()
async def foo(...):
    # events will be received for #tag1, #tag2, #tag3 and #tag4.

client.add_player_updates("#tag3", "#tag4")

The inverse applies; you only need to register a tag with 1 decorator for it to apply to all events.

Removing Clan and Player Tags

v1.0 provides added functionality of removing player and clan tags from the list of tags being updated: EventsClient.remove_player_updates(), EventsClient.remove_clan_updates() and EventsClient.remove_war_updates().

The usage is intuitive, and identical to adding the tags:

client.remove_player_updates("#tag1", "#tag2", "#tag3")

tags = ["#tag1", "#tag2", "#tag3"]
client.remove_player_updates(*tags)

Custom Classes

For more information on custom class support, please see Custom Classes.

Client (coc.py) Events

coc.py has a few events that are unique to the library, and which provide some useful information. They are:

Event

Parameter(s)

Description

@coc.ClientEvents.maintenance_start()

None

Fired when API (and in-game) maintenance starts.

@coc.ClientEvents.maintenance_completion()

start_datetime

Fired when API (and in-game) maintenance ends.

@coc.ClientEvents.new_season_start()

None

Fired when a season starts. This goes by the assumption that the season resets at 5am UTC at every last monday of the month.

@coc.ClientEvents.raid_weekend_start()

None

Fired when a raid weekend starts.

@coc.ClientEvents.raid_weekend_end()

None

Fired when a raid weekend ends.

@coc.ClientEvents.clan_games_start()

None

Fired when clan games starts. This goes by the assumption that clan games start at 8am UTC at the 22nd of each month.

@coc.ClientEvents.clan_games_end()

None

Fired when clan games ends. This goes by the assumption that clan games end at 8am UTC at the 28th of each month.

@coc.ClientEvents.event_error()

exception

Fired when an event hits an unhandled exception

@coc.ClientEvents.clan_loop_start()

iteration_number

Fired when the clan loop starts an iteration

@coc.ClientEvents.clan_loop_finish()

iteration_number

Fired when the clan loop finishes an iteration

@coc.ClientEvents.player_loop_start()

iteration_number

Fired when the player loop starts an iteration

@coc.ClientEvents.player_loop_finish()

iteration_number

Fired when the player loop finishes an iteration

@coc.ClientEvents.war_loop_start()

iteration_number

Fired when the war loop starts an iteration

@coc.ClientEvents.war_loop_finish()

iteration_number

Fired when the war loop finishes an iteration

Parameters refer to the parameters of the callback function, for example:

@coc.ClientEvents.maintenance_start()
async def my_callback():
    print('Maintenance has started!')

@coc.ClientEvents.maintenance_completion()
async def second_callback(time_started):
    print('Maintenance has finished, started at' + str(time_started))

For maintenance_completion, the parameter is of type datetime.datetime. Iteration_number is an integer indicating how many times the client has run update loops so far. Exception is an exception class that can be passed into an exception log, for example

import logging

log = logging.getLogger()

@coc.ClientEvents.event_error()
async def callback(exception):
    log.error("events had an error!", exc_info=exception)