altvmasterlist.masterlist

  1#!/usr/bin/env python3
  2from altvmasterlist import exceptions as error
  3from altvmasterlist import enum as enum
  4from urllib.parse import quote
  5from dataclasses import dataclass, field
  6from typing import Optional
  7from io import StringIO
  8from re import compile
  9from enum import Enum
 10import requests
 11import logging
 12import sys
 13
 14
 15"""You can find the masterlist api docs here: https://docs.altv.mp/articles/master_list_api.html"""
 16logger = logging.getLogger(__name__)
 17session = requests.session()
 18
 19
 20def request(url: str, server: any = None) -> dict | None:
 21    """This is the common request function to fetch remote data.
 22
 23    Args:
 24        url (str): The Url to fetch.
 25        server (Server): An alt:V masterlist Server object.
 26
 27    Returns:
 28        None: When an error occurred. But exceptions will still be logged!
 29        json: As data
 30
 31    Raises:
 32        FetchError: there was an error while getting the data
 33    """
 34    # Use the User-Agent: AltPublicAgent, because some servers protect their CDN with
 35    # a simple User-Agent check e.g. https://luckyv.de did that before
 36    session.headers.clear()
 37
 38    # if we get a request for a server that is not using
 39    # a cdn then set the same headers as the alt:V client while connecting
 40    # otherwise set a user-agent and Content-Type for the api
 41    if server and "http://" in url and not server.useCdn:
 42        session.headers = enum.RequestHeaders(server).to_dict()
 43    else:
 44        session.headers = {
 45            "User-Agent": enum.Extra.user_agent.value,
 46            "Content-Type": "application/json; charset=utf-8",
 47        }
 48
 49    try:
 50        api_request = session.get(url, timeout=5)
 51
 52        if not api_request.ok:
 53            raise error.FetchError(f"The request returned an error. {url}")
 54        else:
 55            return api_request.json()
 56    except Exception as e:
 57        logger.error(e)
 58        return None
 59
 60
 61@dataclass
 62class Server:
 63    playersCount: int = field(default=0, metadata={"description": "Current player count"})
 64    maxPlayersCount: int = field(default=0, metadata={"description": "Player limit"})
 65    passworded: bool = field(default=False, metadata={"description": "Password protected"})
 66    port: int = field(default=0, metadata={"description": "Server game port"})
 67    language: str = field(default="en", metadata={"description": "Two letter country code"})
 68    useEarlyAuth: bool = field(default=False, metadata={"description": "Server is using early auth (https://docs.altv.mp/articles/earlyauth.html)"})
 69    earlyAuthUrl: str = field(default="", metadata={"description": "Early auth URL (usually a login screen)"})
 70    useCdn: bool = field(default=False, metadata={"description": "Server is using a CDN (https://docs.altv.mp/articles/cdn.html)"})
 71    cdnUrl: str = field(default="", metadata={"description": "CDN URL"})
 72    useVoiceChat: bool = field(default=False, metadata={"description": "Server is using the built-in voice chat (https://docs.altv.mp/articles/voice.html)"})
 73    version: str = field(default="", metadata={"description": "Server version"})
 74    branch: str = field(default="", metadata={"description": "Server branch (release, rc, dev)"})
 75    available: bool = field(default=False, metadata={"description": "Server is online"})
 76    banned: bool = field(default=False)
 77    name: str = field(default="", metadata={"description": "Server name"})
 78    publicId: str = field(default=None, metadata={"description": "The server ID."})
 79    vanityUrl: str = field(default="")
 80    website: str = field(default="", metadata={"description": "Server website"})
 81    gameMode: str = field(default="", metadata={"description": "Gamemode provided by the server"})
 82    description: str = field(default="", metadata={"description": "Description provided by the server"})
 83    tags: str = field(default="", metadata={"description": "Tags provided by the server"})
 84    lastTimeUpdate: str = field(default="", metadata={"description": "Time string with format 2024-02-12T16:22:24.195392493Z"})
 85    verified: bool = field(default=False, metadata={"description": "alt:V verified server"})
 86    promoted: bool = field(default=False, metadata={"description": "Promoted server"})
 87    visible: bool = field(default=False, metadata={"description": "Visible in server list"})
 88    hasCustomSkin: bool = field(default=False, metadata={"description": "Defines if the server has a custom launcher skin"})
 89    bannerUrl: str = field(default="")
 90    address: str = field(default="", metadata={"description": "Connection address for the client, can be URL + port or IP + port"})
 91    group: Optional[enum.Group] = field(default=None, metadata={"description": "Server group info"})
 92    masterlist_icon_url: Optional[str] = field(default=None, metadata={"description": "Server icon shown on masterlist"})
 93    masterlist_banner_url: Optional[str] = field(default=None, metadata={"description": "Banner shown when you click on the server in the masterlist"})
 94
 95    def __post_init__(self):
 96        self.fetch_data()
 97
 98    def fetch_data(self):
 99        try:
100            temp_data = request(enum.MasterlistUrls.specific_server.value.format(self.publicId))
101        except error.FetchError as e:
102            logger.error(f"there was an error fetching server data: {self.publicId} {e}")
103            return
104
105        if temp_data:
106            for key, value in temp_data.items():
107                if hasattr(self, key):
108                    setattr(self, key, value)
109            if "group" in temp_data and temp_data["group"]:
110                self.group = enum.Group(**temp_data["group"])
111
112    def __init__(self, server_id: str, no_fetch: bool = False) -> None:
113        self.publicId = server_id
114        if not no_fetch:
115            self.fetch_data()
116
117    def update(self) -> None:
118        """Update the server data using the api."""
119        self.__post_init__()
120
121    def get_max(self, time: str = "1d") -> dict | None:
122        """Maximum - Returns maximum data about the specified server (TIME = 1d, 7d, 31d)
123
124        Args:
125            time (str): The timerange of the data. Can be 1d, 7d, 31d.
126
127        Returns:
128            None: When an error occurs
129            dict: The maximum player data
130
131        Raises:
132            FetchError: there was an error while getting the data
133            NoPublicID: the server has no publicID
134        """
135        if not self.publicId:
136            logger.warning("server got no masterlist publicID")
137            raise error.NoPublicID(f"The server got no publicID")
138        else:
139            try:
140                tmp_data = request(enum.MasterlistUrls.specific_server_maximum.value.format(self.publicId, time))
141                return tmp_data
142            except error.FetchError as e:
143                logger.error(f"there was an error while getting max stats: {e}")
144                raise error.FetchError(f"there was an error while getting max stats: {e}")
145
146    def get_avg(
147        self, time: str = "1d", return_result: bool = False
148    ) -> dict | int | None:
149        """Averages - Returns averages data about the specified server (TIME = 1d, 7d, 31d)
150
151        Args:
152            time (str): The timerange of the data. Can be 1d, 7d, 31d.
153            return_result (bool): Define if you want the overall average.
154
155        Returns:
156            None: When an error occurs
157            dict: The maximum player data
158            int: Overall average of defined timerange
159
160        Raises:
161            FetchError: there was an error while getting the data
162            NoPublicID: the server has no publicID
163        """
164        if not self.publicId:
165            logger.warning("server got not masterlist publicID")
166            raise error.NoPublicID(f"The server got no publicID")
167        else:
168            average_data = request(
169                enum.MasterlistUrls.specific_server_average.value.format(self.publicId, time)
170            )
171
172            if not average_data:
173                raise error.FetchError(f"There was an error while fetching data for {self.publicId} {time} {return_result}")
174
175            if return_result:
176                players_all = 0
177                for entry in average_data:
178                    players_all = players_all + entry["c"]
179                result = players_all / len(average_data)
180                return round(result)
181            else:
182                return average_data
183
184    @property
185    def connect_json(self) -> dict | None:
186        """This function fetched the connect.json of an alt:V server.
187
188        Returns:
189            None: When an error occurred. But exceptions will still be logged!
190            dict: The connect.json
191
192        Raises:
193            FetchError: there was an error while getting the data
194        """
195        if not self.available or self.passworded:
196            raise error.FetchError(f"{self.publicId} is offline or password protected.")
197
198        if self.publicId:
199            if not self.useCdn:
200                # server is on masterlist but not using a cdn
201                cdn_request = request(f"http://{self.address}/connect.json", self)
202            else:
203                # server is on masterlist and using a cdn
204                match self.cdnUrl:
205                    case _ if ":80" in self.cdnUrl:
206                        cdn_request = request(
207                            f"http://{self.cdnUrl.replace(':80', '')}/connect.json", self
208                        )
209                    case _ if ":443" in self.cdnUrl:
210                        cdn_request = request(
211                            f"https://{self.cdnUrl.replace(':443', '')}/connect.json", self
212                        )
213                    case _:
214                        cdn_request = request(f"{self.cdnUrl}/connect.json", self)
215        else:
216            # server is not on masterlist
217            logger.info("getting connect.json by ip")
218            cdn_request = request(f"{self.address}:{self.port}/connect.json", self)
219
220        if cdn_request is None:
221            raise error.FetchError(f"There was an error while fetching connect.json for {self.publicId}")
222        else:
223            return cdn_request
224
225    @property
226    def permissions(self) -> enum.Permissions | None:
227        """This function returns the Permissions defined by the server. https://docs.altv.mp/articles/permissions.html
228
229        Returns:
230            None: When an error occurred. But exceptions will still be logged!
231            Permissions: The permissions of the server.
232
233        Raises:
234            FetchError: there was an error while getting the data
235        """
236
237        class Permission(Enum):
238            screen_capture = "Screen Capture"
239            webrtc = "WebRTC"
240            clipboard_access = "Clipboard Access"
241            optional = "optional-permissions"
242            required = "required-permissions"
243
244        try:
245            connect_json = self.connect_json
246        except error.FetchError as e:
247            logger.error(e)
248            raise error.FetchError(f"couln't get permissions {e} ")
249
250        optional = connect_json[Permission.optional.value]
251        required = connect_json[Permission.required.value]
252
253        permissions = enum.Permissions()
254
255        # Define a list of permission attributes to check
256        permission_keys = [
257            "screen_capture",
258            "webrtc",
259            "clipboard_access"
260        ]
261
262        # Assign values for optional permissions
263        for key in permission_keys:
264            if key in optional:
265                setattr(permissions.Optional, key, optional[key])
266            if key in required:
267                setattr(permissions.Required, key, required[key])
268
269        return permissions
270
271    def get_dtc_url(self, password=None) -> str | None:
272        """This function gets the direct connect protocol url of an alt:V Server.
273        (https://docs.altv.mp/articles/connectprotocol.html)
274
275        Args:
276            password (str): The password of the server.
277
278        Returns:
279            None: When an error occurred. But exceptions will still be logged!
280            str: The direct connect protocol url.
281        """
282        with StringIO() as dtc_url:
283            if self.useCdn:
284                tmp_url = quote(self.cdnUrl, safe='')
285                if "http" not in self.cdnUrl:
286                    dtc_url.write(f"altv://connect/http://{tmp_url}")
287                else:
288                    dtc_url.write(f"altv://connect/{tmp_url}")
289            else:
290                dtc_url.write(f"altv://connect/{quote(self.address, safe='')}")
291
292            if self.passworded and password is None:
293                logger.warning(
294                    "Your server is password protected but you did not supply a password for the Direct Connect Url."
295                )
296            if password is not None:
297                dtc_url.write(f"?password={quote(password, safe='')}")
298
299            return dtc_url.getvalue()
300
301
302def get_server_stats() -> dict | None:
303    """Statistics - Player Count across all servers & The amount of servers online
304
305    Returns:
306        None: When an error occurs
307        dict: The stats
308
309    Raises:
310        FetchError: there was an error while getting the data
311    """
312    try:
313        tmp_data = request(enum.MasterlistUrls.all_server_stats.value)
314        return tmp_data
315    except error.FetchError as e:
316        logger.error(f"error while getting server stats: {e}")
317        raise error.FetchError(f"error while getting server stats: {e}")
318
319
320def get_servers() -> list[Server] | None:
321    """Generates a list of all servers that are currently online.
322    Note that the server objects returned are not complete!
323
324    Returns:
325        None: When an error occurs
326        list: List object that contains all servers.
327
328    Raises:
329        FetchError: there was an error while getting the data
330    """
331    return_servers = []
332    try:
333        servers = request(enum.MasterlistUrls.all_servers.value)
334    except error.FetchError as e:
335        raise error.FetchError(f"failed to get servers: {e}")
336
337    server_attributes = [
338        "playersCount", "maxPlayersCount", "passworded", "language",
339        "useEarlyAuth", "earlyAuthUrl", "useCdn", "cdnUrl", "useVoiceChat",
340        "version", "branch", "available", "banned", "name", "publicId",
341        "vanityUrl", "website", "gameMode", "description", "tags",
342        "lastTimeUpdate", "verified", "promoted", "visible", "hasCustomSkin",
343        "bannerUrl", "address", "group", "masterlist_icon_url",
344        "masterlist_banner_url"
345    ]
346
347    for server in servers:
348        tmp_server = Server(server["publicId"], no_fetch=True)
349        for attr in server_attributes:
350            setattr(tmp_server, attr, server[attr])
351        return_servers.append(tmp_server)
352
353    return return_servers
354
355
356def validate_id(server_id: any) -> bool:
357    """Validate a server id
358
359    Args:
360        server_id (any): The id you want to check.
361
362    Returns:
363        bool: True = valid, False = invalid
364    """
365    regex = compile(r"^[\da-zA-Z]{7}$")
366    return isinstance(server_id, str) and regex.match(server_id) is not None
367
368
369if __name__ == "__main__":
370    print("This is a Module!")
371    sys.exit()
logger = <Logger altvmasterlist.masterlist (WARNING)>
session = <requests.sessions.Session object>
def request(url: str, server: <built-in function any> = None) -> dict | None:
21def request(url: str, server: any = None) -> dict | None:
22    """This is the common request function to fetch remote data.
23
24    Args:
25        url (str): The Url to fetch.
26        server (Server): An alt:V masterlist Server object.
27
28    Returns:
29        None: When an error occurred. But exceptions will still be logged!
30        json: As data
31
32    Raises:
33        FetchError: there was an error while getting the data
34    """
35    # Use the User-Agent: AltPublicAgent, because some servers protect their CDN with
36    # a simple User-Agent check e.g. https://luckyv.de did that before
37    session.headers.clear()
38
39    # if we get a request for a server that is not using
40    # a cdn then set the same headers as the alt:V client while connecting
41    # otherwise set a user-agent and Content-Type for the api
42    if server and "http://" in url and not server.useCdn:
43        session.headers = enum.RequestHeaders(server).to_dict()
44    else:
45        session.headers = {
46            "User-Agent": enum.Extra.user_agent.value,
47            "Content-Type": "application/json; charset=utf-8",
48        }
49
50    try:
51        api_request = session.get(url, timeout=5)
52
53        if not api_request.ok:
54            raise error.FetchError(f"The request returned an error. {url}")
55        else:
56            return api_request.json()
57    except Exception as e:
58        logger.error(e)
59        return None

This is the common request function to fetch remote data.

Args: url (str): The Url to fetch. server (Server): An alt:V masterlist Server object.

Returns: None: When an error occurred. But exceptions will still be logged! json: As data

Raises: FetchError: there was an error while getting the data

@dataclass
class Server:
 62@dataclass
 63class Server:
 64    playersCount: int = field(default=0, metadata={"description": "Current player count"})
 65    maxPlayersCount: int = field(default=0, metadata={"description": "Player limit"})
 66    passworded: bool = field(default=False, metadata={"description": "Password protected"})
 67    port: int = field(default=0, metadata={"description": "Server game port"})
 68    language: str = field(default="en", metadata={"description": "Two letter country code"})
 69    useEarlyAuth: bool = field(default=False, metadata={"description": "Server is using early auth (https://docs.altv.mp/articles/earlyauth.html)"})
 70    earlyAuthUrl: str = field(default="", metadata={"description": "Early auth URL (usually a login screen)"})
 71    useCdn: bool = field(default=False, metadata={"description": "Server is using a CDN (https://docs.altv.mp/articles/cdn.html)"})
 72    cdnUrl: str = field(default="", metadata={"description": "CDN URL"})
 73    useVoiceChat: bool = field(default=False, metadata={"description": "Server is using the built-in voice chat (https://docs.altv.mp/articles/voice.html)"})
 74    version: str = field(default="", metadata={"description": "Server version"})
 75    branch: str = field(default="", metadata={"description": "Server branch (release, rc, dev)"})
 76    available: bool = field(default=False, metadata={"description": "Server is online"})
 77    banned: bool = field(default=False)
 78    name: str = field(default="", metadata={"description": "Server name"})
 79    publicId: str = field(default=None, metadata={"description": "The server ID."})
 80    vanityUrl: str = field(default="")
 81    website: str = field(default="", metadata={"description": "Server website"})
 82    gameMode: str = field(default="", metadata={"description": "Gamemode provided by the server"})
 83    description: str = field(default="", metadata={"description": "Description provided by the server"})
 84    tags: str = field(default="", metadata={"description": "Tags provided by the server"})
 85    lastTimeUpdate: str = field(default="", metadata={"description": "Time string with format 2024-02-12T16:22:24.195392493Z"})
 86    verified: bool = field(default=False, metadata={"description": "alt:V verified server"})
 87    promoted: bool = field(default=False, metadata={"description": "Promoted server"})
 88    visible: bool = field(default=False, metadata={"description": "Visible in server list"})
 89    hasCustomSkin: bool = field(default=False, metadata={"description": "Defines if the server has a custom launcher skin"})
 90    bannerUrl: str = field(default="")
 91    address: str = field(default="", metadata={"description": "Connection address for the client, can be URL + port or IP + port"})
 92    group: Optional[enum.Group] = field(default=None, metadata={"description": "Server group info"})
 93    masterlist_icon_url: Optional[str] = field(default=None, metadata={"description": "Server icon shown on masterlist"})
 94    masterlist_banner_url: Optional[str] = field(default=None, metadata={"description": "Banner shown when you click on the server in the masterlist"})
 95
 96    def __post_init__(self):
 97        self.fetch_data()
 98
 99    def fetch_data(self):
100        try:
101            temp_data = request(enum.MasterlistUrls.specific_server.value.format(self.publicId))
102        except error.FetchError as e:
103            logger.error(f"there was an error fetching server data: {self.publicId} {e}")
104            return
105
106        if temp_data:
107            for key, value in temp_data.items():
108                if hasattr(self, key):
109                    setattr(self, key, value)
110            if "group" in temp_data and temp_data["group"]:
111                self.group = enum.Group(**temp_data["group"])
112
113    def __init__(self, server_id: str, no_fetch: bool = False) -> None:
114        self.publicId = server_id
115        if not no_fetch:
116            self.fetch_data()
117
118    def update(self) -> None:
119        """Update the server data using the api."""
120        self.__post_init__()
121
122    def get_max(self, time: str = "1d") -> dict | None:
123        """Maximum - Returns maximum data about the specified server (TIME = 1d, 7d, 31d)
124
125        Args:
126            time (str): The timerange of the data. Can be 1d, 7d, 31d.
127
128        Returns:
129            None: When an error occurs
130            dict: The maximum player data
131
132        Raises:
133            FetchError: there was an error while getting the data
134            NoPublicID: the server has no publicID
135        """
136        if not self.publicId:
137            logger.warning("server got no masterlist publicID")
138            raise error.NoPublicID(f"The server got no publicID")
139        else:
140            try:
141                tmp_data = request(enum.MasterlistUrls.specific_server_maximum.value.format(self.publicId, time))
142                return tmp_data
143            except error.FetchError as e:
144                logger.error(f"there was an error while getting max stats: {e}")
145                raise error.FetchError(f"there was an error while getting max stats: {e}")
146
147    def get_avg(
148        self, time: str = "1d", return_result: bool = False
149    ) -> dict | int | None:
150        """Averages - Returns averages data about the specified server (TIME = 1d, 7d, 31d)
151
152        Args:
153            time (str): The timerange of the data. Can be 1d, 7d, 31d.
154            return_result (bool): Define if you want the overall average.
155
156        Returns:
157            None: When an error occurs
158            dict: The maximum player data
159            int: Overall average of defined timerange
160
161        Raises:
162            FetchError: there was an error while getting the data
163            NoPublicID: the server has no publicID
164        """
165        if not self.publicId:
166            logger.warning("server got not masterlist publicID")
167            raise error.NoPublicID(f"The server got no publicID")
168        else:
169            average_data = request(
170                enum.MasterlistUrls.specific_server_average.value.format(self.publicId, time)
171            )
172
173            if not average_data:
174                raise error.FetchError(f"There was an error while fetching data for {self.publicId} {time} {return_result}")
175
176            if return_result:
177                players_all = 0
178                for entry in average_data:
179                    players_all = players_all + entry["c"]
180                result = players_all / len(average_data)
181                return round(result)
182            else:
183                return average_data
184
185    @property
186    def connect_json(self) -> dict | None:
187        """This function fetched the connect.json of an alt:V server.
188
189        Returns:
190            None: When an error occurred. But exceptions will still be logged!
191            dict: The connect.json
192
193        Raises:
194            FetchError: there was an error while getting the data
195        """
196        if not self.available or self.passworded:
197            raise error.FetchError(f"{self.publicId} is offline or password protected.")
198
199        if self.publicId:
200            if not self.useCdn:
201                # server is on masterlist but not using a cdn
202                cdn_request = request(f"http://{self.address}/connect.json", self)
203            else:
204                # server is on masterlist and using a cdn
205                match self.cdnUrl:
206                    case _ if ":80" in self.cdnUrl:
207                        cdn_request = request(
208                            f"http://{self.cdnUrl.replace(':80', '')}/connect.json", self
209                        )
210                    case _ if ":443" in self.cdnUrl:
211                        cdn_request = request(
212                            f"https://{self.cdnUrl.replace(':443', '')}/connect.json", self
213                        )
214                    case _:
215                        cdn_request = request(f"{self.cdnUrl}/connect.json", self)
216        else:
217            # server is not on masterlist
218            logger.info("getting connect.json by ip")
219            cdn_request = request(f"{self.address}:{self.port}/connect.json", self)
220
221        if cdn_request is None:
222            raise error.FetchError(f"There was an error while fetching connect.json for {self.publicId}")
223        else:
224            return cdn_request
225
226    @property
227    def permissions(self) -> enum.Permissions | None:
228        """This function returns the Permissions defined by the server. https://docs.altv.mp/articles/permissions.html
229
230        Returns:
231            None: When an error occurred. But exceptions will still be logged!
232            Permissions: The permissions of the server.
233
234        Raises:
235            FetchError: there was an error while getting the data
236        """
237
238        class Permission(Enum):
239            screen_capture = "Screen Capture"
240            webrtc = "WebRTC"
241            clipboard_access = "Clipboard Access"
242            optional = "optional-permissions"
243            required = "required-permissions"
244
245        try:
246            connect_json = self.connect_json
247        except error.FetchError as e:
248            logger.error(e)
249            raise error.FetchError(f"couln't get permissions {e} ")
250
251        optional = connect_json[Permission.optional.value]
252        required = connect_json[Permission.required.value]
253
254        permissions = enum.Permissions()
255
256        # Define a list of permission attributes to check
257        permission_keys = [
258            "screen_capture",
259            "webrtc",
260            "clipboard_access"
261        ]
262
263        # Assign values for optional permissions
264        for key in permission_keys:
265            if key in optional:
266                setattr(permissions.Optional, key, optional[key])
267            if key in required:
268                setattr(permissions.Required, key, required[key])
269
270        return permissions
271
272    def get_dtc_url(self, password=None) -> str | None:
273        """This function gets the direct connect protocol url of an alt:V Server.
274        (https://docs.altv.mp/articles/connectprotocol.html)
275
276        Args:
277            password (str): The password of the server.
278
279        Returns:
280            None: When an error occurred. But exceptions will still be logged!
281            str: The direct connect protocol url.
282        """
283        with StringIO() as dtc_url:
284            if self.useCdn:
285                tmp_url = quote(self.cdnUrl, safe='')
286                if "http" not in self.cdnUrl:
287                    dtc_url.write(f"altv://connect/http://{tmp_url}")
288                else:
289                    dtc_url.write(f"altv://connect/{tmp_url}")
290            else:
291                dtc_url.write(f"altv://connect/{quote(self.address, safe='')}")
292
293            if self.passworded and password is None:
294                logger.warning(
295                    "Your server is password protected but you did not supply a password for the Direct Connect Url."
296                )
297            if password is not None:
298                dtc_url.write(f"?password={quote(password, safe='')}")
299
300            return dtc_url.getvalue()
Server(server_id: str, no_fetch: bool = False)
113    def __init__(self, server_id: str, no_fetch: bool = False) -> None:
114        self.publicId = server_id
115        if not no_fetch:
116            self.fetch_data()
playersCount: int = 0
maxPlayersCount: int = 0
passworded: bool = False
port: int = 0
language: str = 'en'
useEarlyAuth: bool = False
earlyAuthUrl: str = ''
useCdn: bool = False
cdnUrl: str = ''
useVoiceChat: bool = False
version: str = ''
branch: str = ''
available: bool = False
banned: bool = False
name: str = ''
publicId: str = None
vanityUrl: str = ''
website: str = ''
gameMode: str = ''
description: str = ''
tags: str = ''
lastTimeUpdate: str = ''
verified: bool = False
promoted: bool = False
visible: bool = False
hasCustomSkin: bool = False
bannerUrl: str = ''
address: str = ''
group: Optional[altvmasterlist.enum.Group] = None
masterlist_icon_url: Optional[str] = None
masterlist_banner_url: Optional[str] = None
def fetch_data(self):
 99    def fetch_data(self):
100        try:
101            temp_data = request(enum.MasterlistUrls.specific_server.value.format(self.publicId))
102        except error.FetchError as e:
103            logger.error(f"there was an error fetching server data: {self.publicId} {e}")
104            return
105
106        if temp_data:
107            for key, value in temp_data.items():
108                if hasattr(self, key):
109                    setattr(self, key, value)
110            if "group" in temp_data and temp_data["group"]:
111                self.group = enum.Group(**temp_data["group"])
def update(self) -> None:
118    def update(self) -> None:
119        """Update the server data using the api."""
120        self.__post_init__()

Update the server data using the api.

def get_max(self, time: str = '1d') -> dict | None:
122    def get_max(self, time: str = "1d") -> dict | None:
123        """Maximum - Returns maximum data about the specified server (TIME = 1d, 7d, 31d)
124
125        Args:
126            time (str): The timerange of the data. Can be 1d, 7d, 31d.
127
128        Returns:
129            None: When an error occurs
130            dict: The maximum player data
131
132        Raises:
133            FetchError: there was an error while getting the data
134            NoPublicID: the server has no publicID
135        """
136        if not self.publicId:
137            logger.warning("server got no masterlist publicID")
138            raise error.NoPublicID(f"The server got no publicID")
139        else:
140            try:
141                tmp_data = request(enum.MasterlistUrls.specific_server_maximum.value.format(self.publicId, time))
142                return tmp_data
143            except error.FetchError as e:
144                logger.error(f"there was an error while getting max stats: {e}")
145                raise error.FetchError(f"there was an error while getting max stats: {e}")

Maximum - Returns maximum data about the specified server (TIME = 1d, 7d, 31d)

Args: time (str): The timerange of the data. Can be 1d, 7d, 31d.

Returns: None: When an error occurs dict: The maximum player data

Raises: FetchError: there was an error while getting the data NoPublicID: the server has no publicID

def get_avg(self, time: str = '1d', return_result: bool = False) -> dict | int | None:
147    def get_avg(
148        self, time: str = "1d", return_result: bool = False
149    ) -> dict | int | None:
150        """Averages - Returns averages data about the specified server (TIME = 1d, 7d, 31d)
151
152        Args:
153            time (str): The timerange of the data. Can be 1d, 7d, 31d.
154            return_result (bool): Define if you want the overall average.
155
156        Returns:
157            None: When an error occurs
158            dict: The maximum player data
159            int: Overall average of defined timerange
160
161        Raises:
162            FetchError: there was an error while getting the data
163            NoPublicID: the server has no publicID
164        """
165        if not self.publicId:
166            logger.warning("server got not masterlist publicID")
167            raise error.NoPublicID(f"The server got no publicID")
168        else:
169            average_data = request(
170                enum.MasterlistUrls.specific_server_average.value.format(self.publicId, time)
171            )
172
173            if not average_data:
174                raise error.FetchError(f"There was an error while fetching data for {self.publicId} {time} {return_result}")
175
176            if return_result:
177                players_all = 0
178                for entry in average_data:
179                    players_all = players_all + entry["c"]
180                result = players_all / len(average_data)
181                return round(result)
182            else:
183                return average_data

Averages - Returns averages data about the specified server (TIME = 1d, 7d, 31d)

Args: time (str): The timerange of the data. Can be 1d, 7d, 31d. return_result (bool): Define if you want the overall average.

Returns: None: When an error occurs dict: The maximum player data int: Overall average of defined timerange

Raises: FetchError: there was an error while getting the data NoPublicID: the server has no publicID

connect_json: dict | None
185    @property
186    def connect_json(self) -> dict | None:
187        """This function fetched the connect.json of an alt:V server.
188
189        Returns:
190            None: When an error occurred. But exceptions will still be logged!
191            dict: The connect.json
192
193        Raises:
194            FetchError: there was an error while getting the data
195        """
196        if not self.available or self.passworded:
197            raise error.FetchError(f"{self.publicId} is offline or password protected.")
198
199        if self.publicId:
200            if not self.useCdn:
201                # server is on masterlist but not using a cdn
202                cdn_request = request(f"http://{self.address}/connect.json", self)
203            else:
204                # server is on masterlist and using a cdn
205                match self.cdnUrl:
206                    case _ if ":80" in self.cdnUrl:
207                        cdn_request = request(
208                            f"http://{self.cdnUrl.replace(':80', '')}/connect.json", self
209                        )
210                    case _ if ":443" in self.cdnUrl:
211                        cdn_request = request(
212                            f"https://{self.cdnUrl.replace(':443', '')}/connect.json", self
213                        )
214                    case _:
215                        cdn_request = request(f"{self.cdnUrl}/connect.json", self)
216        else:
217            # server is not on masterlist
218            logger.info("getting connect.json by ip")
219            cdn_request = request(f"{self.address}:{self.port}/connect.json", self)
220
221        if cdn_request is None:
222            raise error.FetchError(f"There was an error while fetching connect.json for {self.publicId}")
223        else:
224            return cdn_request

This function fetched the connect.json of an alt:V server.

Returns: None: When an error occurred. But exceptions will still be logged! dict: The connect.json

Raises: FetchError: there was an error while getting the data

permissions: altvmasterlist.enum.Permissions | None
226    @property
227    def permissions(self) -> enum.Permissions | None:
228        """This function returns the Permissions defined by the server. https://docs.altv.mp/articles/permissions.html
229
230        Returns:
231            None: When an error occurred. But exceptions will still be logged!
232            Permissions: The permissions of the server.
233
234        Raises:
235            FetchError: there was an error while getting the data
236        """
237
238        class Permission(Enum):
239            screen_capture = "Screen Capture"
240            webrtc = "WebRTC"
241            clipboard_access = "Clipboard Access"
242            optional = "optional-permissions"
243            required = "required-permissions"
244
245        try:
246            connect_json = self.connect_json
247        except error.FetchError as e:
248            logger.error(e)
249            raise error.FetchError(f"couln't get permissions {e} ")
250
251        optional = connect_json[Permission.optional.value]
252        required = connect_json[Permission.required.value]
253
254        permissions = enum.Permissions()
255
256        # Define a list of permission attributes to check
257        permission_keys = [
258            "screen_capture",
259            "webrtc",
260            "clipboard_access"
261        ]
262
263        # Assign values for optional permissions
264        for key in permission_keys:
265            if key in optional:
266                setattr(permissions.Optional, key, optional[key])
267            if key in required:
268                setattr(permissions.Required, key, required[key])
269
270        return permissions

This function returns the Permissions defined by the server. https://docs.altv.mp/articles/permissions.html

Returns: None: When an error occurred. But exceptions will still be logged! Permissions: The permissions of the server.

Raises: FetchError: there was an error while getting the data

def get_dtc_url(self, password=None) -> str | None:
272    def get_dtc_url(self, password=None) -> str | None:
273        """This function gets the direct connect protocol url of an alt:V Server.
274        (https://docs.altv.mp/articles/connectprotocol.html)
275
276        Args:
277            password (str): The password of the server.
278
279        Returns:
280            None: When an error occurred. But exceptions will still be logged!
281            str: The direct connect protocol url.
282        """
283        with StringIO() as dtc_url:
284            if self.useCdn:
285                tmp_url = quote(self.cdnUrl, safe='')
286                if "http" not in self.cdnUrl:
287                    dtc_url.write(f"altv://connect/http://{tmp_url}")
288                else:
289                    dtc_url.write(f"altv://connect/{tmp_url}")
290            else:
291                dtc_url.write(f"altv://connect/{quote(self.address, safe='')}")
292
293            if self.passworded and password is None:
294                logger.warning(
295                    "Your server is password protected but you did not supply a password for the Direct Connect Url."
296                )
297            if password is not None:
298                dtc_url.write(f"?password={quote(password, safe='')}")
299
300            return dtc_url.getvalue()

This function gets the direct connect protocol url of an alt:V Server. (https://docs.altv.mp/articles/connectprotocol.html)

Args: password (str): The password of the server.

Returns: None: When an error occurred. But exceptions will still be logged! str: The direct connect protocol url.

def get_server_stats() -> dict | None:
303def get_server_stats() -> dict | None:
304    """Statistics - Player Count across all servers & The amount of servers online
305
306    Returns:
307        None: When an error occurs
308        dict: The stats
309
310    Raises:
311        FetchError: there was an error while getting the data
312    """
313    try:
314        tmp_data = request(enum.MasterlistUrls.all_server_stats.value)
315        return tmp_data
316    except error.FetchError as e:
317        logger.error(f"error while getting server stats: {e}")
318        raise error.FetchError(f"error while getting server stats: {e}")

Statistics - Player Count across all servers & The amount of servers online

Returns: None: When an error occurs dict: The stats

Raises: FetchError: there was an error while getting the data

def get_servers() -> list[Server] | None:
321def get_servers() -> list[Server] | None:
322    """Generates a list of all servers that are currently online.
323    Note that the server objects returned are not complete!
324
325    Returns:
326        None: When an error occurs
327        list: List object that contains all servers.
328
329    Raises:
330        FetchError: there was an error while getting the data
331    """
332    return_servers = []
333    try:
334        servers = request(enum.MasterlistUrls.all_servers.value)
335    except error.FetchError as e:
336        raise error.FetchError(f"failed to get servers: {e}")
337
338    server_attributes = [
339        "playersCount", "maxPlayersCount", "passworded", "language",
340        "useEarlyAuth", "earlyAuthUrl", "useCdn", "cdnUrl", "useVoiceChat",
341        "version", "branch", "available", "banned", "name", "publicId",
342        "vanityUrl", "website", "gameMode", "description", "tags",
343        "lastTimeUpdate", "verified", "promoted", "visible", "hasCustomSkin",
344        "bannerUrl", "address", "group", "masterlist_icon_url",
345        "masterlist_banner_url"
346    ]
347
348    for server in servers:
349        tmp_server = Server(server["publicId"], no_fetch=True)
350        for attr in server_attributes:
351            setattr(tmp_server, attr, server[attr])
352        return_servers.append(tmp_server)
353
354    return return_servers

Generates a list of all servers that are currently online. Note that the server objects returned are not complete!

Returns: None: When an error occurs list: List object that contains all servers.

Raises: FetchError: there was an error while getting the data

def validate_id(server_id: <built-in function any>) -> bool:
357def validate_id(server_id: any) -> bool:
358    """Validate a server id
359
360    Args:
361        server_id (any): The id you want to check.
362
363    Returns:
364        bool: True = valid, False = invalid
365    """
366    regex = compile(r"^[\da-zA-Z]{7}$")
367    return isinstance(server_id, str) and regex.match(server_id) is not None

Validate a server id

Args: server_id (any): The id you want to check.

Returns: bool: True = valid, False = invalid