/bcbansapi.py
https://bitbucket.org/Lavode/bcbans-api · Python · 289 lines · 220 code · 23 blank · 46 comment · 21 complexity · 81c75ac9064e7e07ce3b3b129a7c52db MD5 · raw file
- # Copyright (C) 2012 Michael Senn "Morrolan"
- #
- # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
- # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
- # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
- # persons to whom the Software is furnished to do so, subject to the following conditions:
- # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
- # Software.
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
- # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
- # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
- # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- # API documentation: http://wiki.ecs-server.net/display/BCBANS/API
- # Better API documentation: http://www.bcbans.com/api
- # API implementation by plugin: https://bitbucket.org/aetaric/bcbansplugin/src/275a3fb9d4ed/src/me/jabjabjab/plugins/bcbans/API.java
- # IMPORTANT: Those three conflict. Search for: 'TODO: API:Conflict'
- from urllib.request import urlopen
- from urllib.parse import urlencode
- from urllib.error import URLError, HTTPError
- import socket
- import json
- import logging
- import re
- # Todo: API is there a player callback function?
- # I find it strange that there is no player callback function. Is that done via the query? If so: Supplying an IP there is broken.
- # Update: 09.09.12 according to aetaric on IRC there is none, but it does use the IP supplied via the ban function to do alt detection.
- # Most likely also uses the one supplied via query, if it wouldn't break.
- # Update 11.09.2012 api docs on http://www.bcbans.com/api claim there is one, but it 404s. Suppose not implemented yet.
- # Todo: API add support for different locale.
- # Todo: Docstrings
- class APIManager(object):
- _APIPOSTURL = "http://www.bcbans.com/api/{0}.{1}"
- _VALID_BAN_TYPES = {
- "local": 0,
- "global": 1
- }
- REGEX_APIKEY = "[0-9|A-F|a-f]{40]"
- def __init__(self, apikey=None, api_post_url=None):
- self._apikey = apikey
- # If an alternative API URL was supplied we'll use that one, else we'll stick with the default one.
- if api_post_url is not None:
- self._APIPOSTURL = api_post_url
- else:
- self._APIPOSTURL = APIManager._APIPOSTURL
- # Sets the timeout to something reasonable.
- socket.setdefaulttimeout(10)
- # Compiling the RegEx
- self._c_regex_apikey = re.compile(APIManager.REGEX_APIKEY)
- def _get_api_key(self):
- return self._apikey
- def _set_api_key(self, value):
- if not self._c_regex_apikey.match(str(value)):
- logging.warning("API key might be invalid {}".format(value))
- self._apikey = str(value)
- apikey = property(_get_api_key, _set_api_key)
- def add_player_ban(self, issuer, player, reason, type=0, ip=None):
- """Bans the specified player.
- Parameters:
- issuer -- The admin who issued the ban.
- player -- The player whom to ban.
- reason -- The ban reason.
- type=0 -- 0 or 'local' for a local ban, 1 or 'global' for a global ban.
- ip==None -- If set to anything other than none, it'll be used for alt detection. It will, however, not ban the IP.
- Returns:
- dict -- {"player_id": int, "server_id": int, "dispute_id": int/None, "issuer": str, "reason": str, "created_at": ISO-8601 string, "updated_at": ISO-8601 string, "global": bool, "id": 87}
- Exceptions:
- APIError -- Will be raised if the API call failed.
- """
- # IP Bans: When supplying an IP, the IP is used for alt detection, but it does NOT ban the IP.
- if type in APIManager._VALID_BAN_TYPES.values():
- pass
- elif type in APIManager._VALID_BAN_TYPES:
- type = APIManager._VALID_BAN_TYPES[type]
- else:
- raise ValueError("Invalid ban type supplied.")
- command = "ban_player"
- d = {"playername": str(player),
- "sender": str(issuer),
- "reason": str(reason)}
- if type == 1:
- d["global"] = type
- if ip is not None:
- d["ip"] = str(ip)
- result = self._do_api_post_request(command, d)
- if "error" in result:
- raise APIError(d, result, "Error occurred while trying to add a player ban.")
- return result
- def add_global_player_ban(self, issuer, player, reason, ip=None):
- """Globally bans the specified player.
- Parameters:
- issuer -- The admin who issued the ban.
- player -- The player whom to ban.
- reason -- The ban reason.
- ip==None -- If set to anything other than none, it'll be used for alt detection. It will, however, not ban the IP.
- Returns:
- dict -- {"player_id": int, "server_id": int, "dispute_id": int/None, "issuer": str, "reason": str, "created_at": ISO-8601 string, "updated_at": ISO-8601 string, "global": bool, "id": 87}
- Exceptions:
- APIError -- Will be raised if the API call failed.
- """
- return self.add_player_ban(issuer, player, reason, 1, ip)
- def add_local_player_ban(self, issuer, player, reason, ip=None):
- """Locally bans the specified player.
- Parameters:
- issuer -- The admin who issued the ban.
- player -- The player whom to ban.
- reason -- The ban reason.
- ip==None -- If set to anything other than none, it'll be used for alt detection. It will, however, not ban the IP.
- Returns:
- dict -- {"player_id": int, "server_id": int, "dispute_id": int/None, "issuer": str, "reason": str, "created_at": ISO-8601 string, "updated_at": ISO-8601 string, "global": bool, "id": 87}
- Exceptions:
- APIError -- Will be raised if the API call failed.
- """
- return self.add_player_ban(issuer, player, reason, 0, ip)
- def remove_player_ban(self, player):
- command = "unban_player"
- d = {"playername": player}
- result = self._do_api_post_request(command, d)
- if "error" in result:
- raise APIError(d, result, "Error occurred while trying to add a player ban.")
- if result["message"] == "Ban Removed":
- return True
- return False
- def get_player_bans(self, player, ip=None):
- command = "query"
- d = {"playername": player}
- if ip is not None:
- d["ip"] = str(ip)
- result = self._do_api_post_request(command, d)
- if "error" in result:
- raise APIError(d, result, "Error occurred while trying to query for a player's bans.")
- return result
- def get_player_ban(self, player, ip=None):
- command = "check"
- d = {"playername": player}
- if ip is not None:
- d["ip"] = str(ip)
- result = self._do_api_post_request(command, d)
- if "error" in result:
- raise APIError(d, result, "Error occurred while trying to check for a player's bans.")
- return result
- def get_server_info(self, id):
- command = "server_lookup"
- d = {"server_id": id}
- result = self._do_api_post_request(command, d)
- if "error" in result:
- raise APIError(d, result, "Error occurred while trying to retrieve server information.")
- return result
- def get_site_statistics(self):
- command = "site/stats"
- result = self._do_api_get_request(command)
- if "error" in result:
- raise APIError(None, result, "Error occurred while trying to retrieve site statistics.")
- def _do_api_post_request(self, command, data, type="json"):
- if self._apikey is None:
- logging.exception("Can not issue API request without an API key! Aborting now.")
- return
- result_json = None
- data["apikey"] = self._apikey
- url = self._APIPOSTURL.format(command, type)
- post_data_encoded = urlencode(data).encode("utf-8")
- try:
- response = urlopen(url, post_data_encoded)
- result_json = response.read().decode("utf-8")
- result = json.loads(result_json)
- except HTTPError as http_ex:
- logging.exception("HTTPError occurred while querying the API.", http_ex.errno, http_ex.reason, sep="\n")
- raise APIError(data, result_json, "HTTPError occurred while querying the API")
- except URLError as url_ex:
- logging.exception("URLError occurred while querying the API.", url_ex.reason, sep="\n")
- raise APIError(data, result_json, "Error occurred while querying the API")
- except UnicodeDecodeError as uni_ex:
- logging.exception("UnicodeDecodeError occurred while decoding the API's response.", uni_ex.reason, sep="\n")
- raise APIError(data, result_json, "Error occurred while decoding the API response")
- except ValueError as val_ex:
- logging.exception("ValueError occurred while parsing the JSON.", val_ex.args, sep="\n")
- raise APIError(data, result_json, "Error occurred while parsing the JSON.")
- return result
- def _do_api_get_request(self, command, type="json"):
- if self._apikey is None:
- logging.exception("Can not issue API request without an API key! Aborting now.")
- return
- result_json = None
- url = self._APIPOSTURL.format(command, type)
- try:
- response = urlopen(url)
- result_json = response.read().decode("utf-8")
- result = json.loads(result_json)
- except HTTPError as http_ex:
- logging.exception("HTTPError occurred while querying the API.", http_ex.errno, http_ex.reason, sep="\n")
- raise APIError(None, result_json, "HTTPError occurred while querying the API")
- except URLError as url_ex:
- logging.exception("URLError occurred while querying the API.", url_ex.reason, sep="\n")
- raise APIError(None, result_json, "Error occurred while querying the API")
- except UnicodeDecodeError as uni_ex:
- logging.exception("UnicodeDecodeError occurred while decoding the API's response.", uni_ex.reason, sep="\n")
- raise APIError(None, result_json, "Error occurred while decoding the API response")
- except ValueError as val_ex:
- logging.exception("ValueError occurred while parsing the JSON.", val_ex.args, sep="\n")
- raise APIError(None, result_json, "Error occurred while parsing the JSON.")
- return result
- class APIError(Exception):
- def __init__(self, query, result, description=None, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.query = query
- self.query["apikey"] = "***redacted***"
- self.result = result
- self.description = description