Git Product home page Git Product logo

neverscapealone-api's Introduction

NeverScapeAlone-API Repository

Socket I/O Logic

Okhttp request object looks like this:

Request request = new Request.Builder()
        .url(url)
        .addHeader("User-Agent", RuneLite.USER_AGENT) // RuneLite-38fh3 commit
        .addHeader("Login", username) // "Ferrariic"
        .addHeader("Discord", discord) // base64 encoded! example: QEZlcnJhcmlpYyMwMDAx
        .addHeader("Discord_ID", discord_id) // example: 178965680266149888
        .addHeader("Token", token) // example: y5BAz6OiYaZO-8YpQf_OLUWT6Q3WK-6K
        .addHeader("Time", Instant.now().toString()) // doesn't really matter
        .addHeader("Version", pluginVersionSupplier.get()) // plugin version, ex. v3.0.0-alpha
        .build();
  1. Connect to "/V#/lobby/{group_identifier}/{passcode}" The group identifier is the group name, such as battle-shanty-hooded The passcode is a plain-text passcode that would be used for private matches. Tip: group_identifier = 0 is the default lobby. Tip: passcode entry does not matter if the lobby is private, you can put anything there.

Request schema for client -> API

{
    "title": "request",
    "description": "incoming request model from the client",
    "type": "object",
    "properties": {
        "detail": {
            "title": "Detail",
            "type": "string"
        },
        "chat_message": {
            "$ref": "#/definitions/chat"
        },
        "like": {
            "title": "Like",
            "type": "integer"
        },
        "dislike": {
            "title": "Dislike",
            "type": "integer"
        },
        "kick": {
            "title": "Kick",
            "type": "integer"
        },
        "promote": {
            "title": "Promote",
            "type": "integer"
        },
        "status": {
            "$ref": "#/definitions/status"
        },
        "location": {
            "$ref": "#/definitions/location"
        },
        "inventory": {
            "title": "Inventory",
            "type": "array",
            "items": {
                "$ref": "#/definitions/inventory_item"
            }
        },
        "stats": {
            "$ref": "#/definitions/stats"
        },
        "prayer": {
            "title": "Prayer",
            "type": "array",
            "items": {
                "$ref": "#/definitions/prayer_slot"
            }
        },
        "equipment": {
            "$ref": "#/definitions/equipment"
        },
        "ping_payload": {
            "$ref": "#/definitions/ping"
        },
        "search": {
            "title": "Search",
            "type": "string"
        },
        "match_list": {
            "title": "Match List",
            "type": "array",
            "items": {
                "type": "string"
            }
        },
        "gamestate": {
            "title": "Gamestate",
            "type": "integer"
        },
        "create_match": {
            "$ref": "#/definitions/create_match"
        }
    },
    "required": [
        "detail"
    ],
    "definitions": {
        "chat": {
            "title": "chat",
            "description": "chat model",
            "type": "object",
            "properties": {
                "username": {
                    "title": "Username",
                    "type": "string"
                },
                "message": {
                    "title": "Message",
                    "type": "string"
                },
                "timestamp": {
                    "title": "Timestamp",
                    "type": "integer"
                }
            },
            "required": [
                "message"
            ]
        },
        "status": {
            "title": "status",
            "description": "player status",
            "type": "object",
            "properties": {
                "hp": {
                    "title": "Hp",
                    "type": "integer"
                },
                "base_hp": {
                    "title": "Base Hp",
                    "type": "integer"
                },
                "prayer": {
                    "title": "Prayer",
                    "type": "integer"
                },
                "base_prayer": {
                    "title": "Base Prayer",
                    "type": "integer"
                },
                "run_energy": {
                    "title": "Run Energy",
                    "type": "integer"
                },
                "special_attack": {
                    "title": "Special Attack",
                    "type": "integer"
                }
            },
            "required": [
                "hp",
                "base_hp",
                "prayer",
                "base_prayer",
                "run_energy",
                "special_attack"
            ]
        },
        "location": {
            "title": "location",
            "description": "location model",
            "type": "object",
            "properties": {
                "x": {
                    "title": "X",
                    "type": "integer"
                },
                "y": {
                    "title": "Y",
                    "type": "integer"
                },
                "regionX": {
                    "title": "Regionx",
                    "type": "integer"
                },
                "regionY": {
                    "title": "Regiony",
                    "type": "integer"
                },
                "regionID": {
                    "title": "Regionid",
                    "type": "integer"
                },
                "plane": {
                    "title": "Plane",
                    "type": "integer"
                },
                "world": {
                    "title": "World",
                    "type": "integer"
                }
            },
            "required": [
                "x",
                "y",
                "regionX",
                "regionY",
                "regionID",
                "plane",
                "world"
            ]
        },
        "inventory_item": {
            "title": "inventory_item",
            "description": "inventory_item model",
            "type": "object",
            "properties": {
                "item_id": {
                    "title": "Item Id",
                    "type": "integer"
                },
                "item_amount": {
                    "title": "Item Amount",
                    "type": "integer"
                }
            },
            "required": [
                "item_id",
                "item_amount"
            ]
        },
        "stat_information": {
            "title": "stat_information",
            "type": "object",
            "properties": {
                "boosted": {
                    "title": "Boosted",
                    "type": "integer"
                },
                "real": {
                    "title": "Real",
                    "type": "integer"
                },
                "experience": {
                    "title": "Experience",
                    "type": "integer"
                }
            },
            "required": [
                "boosted",
                "real",
                "experience"
            ]
        },
        "stats": {
            "title": "stats",
            "description": "player skills",
            "type": "object",
            "properties": {
                "Attack": {
                    "$ref": "#/definitions/stat_information"
                },
                "Strength": {
                    "$ref": "#/definitions/stat_information"
                },
                "Defence": {
                    "$ref": "#/definitions/stat_information"
                },
                "Ranged": {
                    "$ref": "#/definitions/stat_information"
                },
                "Prayer": {
                    "$ref": "#/definitions/stat_information"
                },
                "Magic": {
                    "$ref": "#/definitions/stat_information"
                },
                "Runecraft": {
                    "$ref": "#/definitions/stat_information"
                },
                "Construction": {
                    "$ref": "#/definitions/stat_information"
                },
                "Hitpoints": {
                    "$ref": "#/definitions/stat_information"
                },
                "Agility": {
                    "$ref": "#/definitions/stat_information"
                },
                "Herblore": {
                    "$ref": "#/definitions/stat_information"
                },
                "Thieving": {
                    "$ref": "#/definitions/stat_information"
                },
                "Crafting": {
                    "$ref": "#/definitions/stat_information"
                },
                "Fletching": {
                    "$ref": "#/definitions/stat_information"
                },
                "Slayer": {
                    "$ref": "#/definitions/stat_information"
                },
                "Hunter": {
                    "$ref": "#/definitions/stat_information"
                },
                "Mining": {
                    "$ref": "#/definitions/stat_information"
                },
                "Smithing": {
                    "$ref": "#/definitions/stat_information"
                },
                "Fishing": {
                    "$ref": "#/definitions/stat_information"
                },
                "Cooking": {
                    "$ref": "#/definitions/stat_information"
                },
                "Firemaking": {
                    "$ref": "#/definitions/stat_information"
                },
                "Woodcutting": {
                    "$ref": "#/definitions/stat_information"
                },
                "Farming": {
                    "$ref": "#/definitions/stat_information"
                },
                "Overall": {
                    "$ref": "#/definitions/stat_information"
                }
            },
            "required": [
                "Attack",
                "Strength",
                "Defence",
                "Ranged",
                "Prayer",
                "Magic",
                "Runecraft",
                "Construction",
                "Hitpoints",
                "Agility",
                "Herblore",
                "Thieving",
                "Crafting",
                "Fletching",
                "Slayer",
                "Hunter",
                "Mining",
                "Smithing",
                "Fishing",
                "Cooking",
                "Firemaking",
                "Woodcutting",
                "Farming",
                "Overall"
            ]
        },
        "prayer_slot": {
            "title": "prayer_slot",
            "description": "prayer slot",
            "type": "object",
            "properties": {
                "prayer_name": {
                    "title": "Prayer Name",
                    "type": "string"
                },
                "prayer_varbit": {
                    "title": "Prayer Varbit",
                    "type": "integer"
                }
            },
            "required": [
                "prayer_name",
                "prayer_varbit"
            ]
        },
        "equipment_item": {
            "title": "equipment_item",
            "type": "object",
            "properties": {
                "item_id": {
                    "title": "Item Id",
                    "type": "integer"
                },
                "item_amount": {
                    "title": "Item Amount",
                    "type": "integer"
                }
            },
            "required": [
                "item_id"
            ]
        },
        "equipment": {
            "title": "equipment",
            "type": "object",
            "properties": {
                "head": {
                    "$ref": "#/definitions/equipment_item"
                },
                "cape": {
                    "$ref": "#/definitions/equipment_item"
                },
                "amulet": {
                    "$ref": "#/definitions/equipment_item"
                },
                "ammo": {
                    "$ref": "#/definitions/equipment_item"
                },
                "weapon": {
                    "$ref": "#/definitions/equipment_item"
                },
                "body": {
                    "$ref": "#/definitions/equipment_item"
                },
                "shield": {
                    "$ref": "#/definitions/equipment_item"
                },
                "legs": {
                    "$ref": "#/definitions/equipment_item"
                },
                "gloves": {
                    "$ref": "#/definitions/equipment_item"
                },
                "boots": {
                    "$ref": "#/definitions/equipment_item"
                },
                "ring": {
                    "$ref": "#/definitions/equipment_item"
                }
            }
        },
        "ping": {
            "title": "ping",
            "description": "ping model",
            "type": "object",
            "properties": {
                "username": {
                    "title": "Username",
                    "type": "string"
                },
                "x": {
                    "title": "X",
                    "type": "integer"
                },
                "y": {
                    "title": "Y",
                    "type": "integer"
                },
                "regionX": {
                    "title": "Regionx",
                    "type": "integer"
                },
                "regionY": {
                    "title": "Regiony",
                    "type": "integer"
                },
                "regionID": {
                    "title": "Regionid",
                    "type": "integer"
                },
                "plane": {
                    "title": "Plane",
                    "type": "integer"
                },
                "color_r": {
                    "title": "Color R",
                    "type": "integer"
                },
                "color_g": {
                    "title": "Color G",
                    "type": "integer"
                },
                "color_b": {
                    "title": "Color B",
                    "type": "integer"
                },
                "color_alpha": {
                    "title": "Color Alpha",
                    "type": "integer"
                },
                "isAlert": {
                    "title": "Isalert",
                    "type": "boolean"
                }
            },
            "required": [
                "username",
                "x",
                "y",
                "regionX",
                "regionY",
                "regionID",
                "plane",
                "color_r",
                "color_g",
                "color_b",
                "color_alpha",
                "isAlert"
            ]
        },
        "create_match": {
            "title": "create_match",
            "description": "create match payload",
            "type": "object",
            "properties": {
                "activity": {
                    "title": "Activity",
                    "type": "string"
                },
                "party_members": {
                    "title": "Party Members",
                    "type": "string"
                },
                "experience": {
                    "title": "Experience",
                    "type": "string"
                },
                "split_type": {
                    "title": "Split Type",
                    "type": "string"
                },
                "accounts": {
                    "title": "Accounts",
                    "type": "string"
                },
                "regions": {
                    "title": "Regions",
                    "type": "string"
                },
                "RuneGuard": {
                    "title": "Runeguard",
                    "type": "string"
                },
                "notes": {
                    "title": "Notes",
                    "type": "string"
                },
                "group_passcode": {
                    "title": "Group Passcode",
                    "type": "string"
                }
            },
            "required": [
                "activity",
                "party_members",
                "experience",
                "split_type",
                "accounts",
                "regions",
                "RuneGuard",
                "notes",
                "group_passcode"
            ]
        }
    }
}

Request schema for API -> client

public class Payload {
    // General information payload
    @SerializedName("detail") // server detail, or subject line. What is the message about?
    ServerStatusCode status;
    @SerializedName("server_message") // server message, is there any flavor text the server is sending as well?
    ServerMessage serverMessage;
    @SerializedName("join") // the group ID to join on a create_match request
    String group_id;
    @SerializedName("passcode") // the passcode that is sent on a create_match request
    String passcode;
    @SerializedName("RuneGuard") // the passcode that is sent on a create_match request
    boolean RuneGuard;
    @SerializedName("search_match_data") // limited data to be sent over to the client, this is mainly for selecting a match
    SearchMatches search;
    @SerializedName("match_data") // data regarding the match itself
    MatchData matchData;
    @SerializedName("ping_data") // incoming ping data
    PingData pingData;
    @SerializedName("chat_data") // incoming ping data
    ChatData chatData;
}

detail

public enum ServerStatusCode {
    @SerializedName("request join new match")
    JOIN_NEW_MATCH,
    @SerializedName("disconnected")
    DISCONNECTED,
    @SerializedName("successful connection")
    SUCCESSFUL_CONNECTION,
    @SerializedName("bad passcode")
    BAD_PASSCODE,
    @SerializedName("match update")
    MATCH_UPDATE,
    @SerializedName("global message")
    GLOBAL_MESSAGE,
    @SerializedName("search match data")
    SEARCH_MATCH_DATA,
    @SerializedName("incoming ping")
    INCOMING_PING,
    @SerializedName("incoming chat")
    INCOMING_CHAT;
}

SearchMatches

{
    "title": "all_search_match_info",
    "type": "object",
    "properties": {
        "search_matches": {
            "title": "Search Matches",
            "type": "array",
            "items": {
                "$ref": "#/definitions/search_match_info"
            }
        }
    },
    "required": [
        "search_matches"
    ],
    "definitions": {
        "search_match_info": {
            "title": "search_match_info",
            "type": "object",
            "properties": {
                "ID": {
                    "title": "Id",
                    "type": "string"
                },
                "activity": {
                    "title": "Activity",
                    "type": "string"
                },
                "party_members": {
                    "title": "Party Members",
                    "type": "string"
                },
                "isPrivate": {
                    "title": "Isprivate",
                    "type": "boolean"
                },
                "RuneGuard": {
                    "title": "Runeguard",
                    "type": "boolean"
                },
                "experience": {
                    "title": "Experience",
                    "type": "string"
                },
                "split_type": {
                    "title": "Split Type",
                    "type": "string"
                },
                "accounts": {
                    "title": "Accounts",
                    "type": "string"
                },
                "regions": {
                    "title": "Regions",
                    "type": "string"
                },
                "player_count": {
                    "title": "Player Count",
                    "type": "string"
                },
                "party_leader": {
                    "title": "Party Leader",
                    "type": "string"
                },
                "match_version": {
                    "title": "Match Version",
                    "type": "string"
                },
                "notes": {
                    "title": "Notes",
                    "type": "string"
                }
            },
            "required": [
                "ID",
                "activity",
                "party_members",
                "isPrivate",
                "RuneGuard",
                "experience",
                "split_type",
                "accounts",
                "regions",
                "player_count",
                "party_leader",
                "match_version"
            ]
        }
    }
}

MatchData

{
    "title": "match",
    "description": "match model",
    "type": "object",
    "properties": {
        "discord_invite": {
            "title": "Discord Invite",
            "type": "string"
        },
        "ID": {
            "title": "Id",
            "type": "string"
        },
        "activity": {
            "title": "Activity",
            "type": "string"
        },
        "party_members": {
            "title": "Party Members",
            "type": "string"
        },
        "group_passcode": {
            "title": "Group Passcode",
            "type": "string"
        },
        "isPrivate": {
            "title": "Isprivate",
            "type": "boolean"
        },
        "RuneGuard": {
            "title": "Runeguard",
            "type": "boolean"
        },
        "match_version": {
            "title": "Match Version",
            "type": "string"
        },
        "notes": {
            "title": "Notes",
            "type": "string"
        },
        "ban_list": {
            "title": "Ban List",
            "type": "array",
            "items": {
                "type": "integer"
            }
        },
        "requirement": {
            "$ref": "#/definitions/requirement"
        },
        "players": {
            "title": "Players",
            "type": "array",
            "items": {
                "$ref": "#/definitions/player"
            }
        }
    },
    "required": [
        "ID",
        "activity",
        "party_members",
        "group_passcode",
        "isPrivate",
        "RuneGuard",
        "match_version",
        "requirement",
        "players"
    ],
    "definitions": {
        "requirement": {
            "title": "requirement",
            "description": "match requirements",
            "type": "object",
            "properties": {
                "experience": {
                    "title": "Experience",
                    "type": "string"
                },
                "split_type": {
                    "title": "Split Type",
                    "type": "string"
                },
                "accounts": {
                    "title": "Accounts",
                    "type": "string"
                },
                "regions": {
                    "title": "Regions",
                    "type": "string"
                }
            },
            "required": [
                "experience",
                "split_type",
                "accounts",
                "regions"
            ]
        },
        "stat_information": {
            "title": "stat_information",
            "type": "object",
            "properties": {
                "boosted": {
                    "title": "Boosted",
                    "type": "integer"
                },
                "real": {
                    "title": "Real",
                    "type": "integer"
                },
                "experience": {
                    "title": "Experience",
                    "type": "integer"
                }
            },
            "required": [
                "boosted",
                "real",
                "experience"
            ]
        },
        "stats": {
            "title": "stats",
            "description": "player skills",
            "type": "object",
            "properties": {
                "Attack": {
                    "$ref": "#/definitions/stat_information"
                },
                "Strength": {
                    "$ref": "#/definitions/stat_information"
                },
                "Defence": {
                    "$ref": "#/definitions/stat_information"
                },
                "Ranged": {
                    "$ref": "#/definitions/stat_information"
                },
                "Prayer": {
                    "$ref": "#/definitions/stat_information"
                },
                "Magic": {
                    "$ref": "#/definitions/stat_information"
                },
                "Runecraft": {
                    "$ref": "#/definitions/stat_information"
                },
                "Construction": {
                    "$ref": "#/definitions/stat_information"
                },
                "Hitpoints": {
                    "$ref": "#/definitions/stat_information"
                },
                "Agility": {
                    "$ref": "#/definitions/stat_information"
                },
                "Herblore": {
                    "$ref": "#/definitions/stat_information"
                },
                "Thieving": {
                    "$ref": "#/definitions/stat_information"
                },
                "Crafting": {
                    "$ref": "#/definitions/stat_information"
                },
                "Fletching": {
                    "$ref": "#/definitions/stat_information"
                },
                "Slayer": {
                    "$ref": "#/definitions/stat_information"
                },
                "Hunter": {
                    "$ref": "#/definitions/stat_information"
                },
                "Mining": {
                    "$ref": "#/definitions/stat_information"
                },
                "Smithing": {
                    "$ref": "#/definitions/stat_information"
                },
                "Fishing": {
                    "$ref": "#/definitions/stat_information"
                },
                "Cooking": {
                    "$ref": "#/definitions/stat_information"
                },
                "Firemaking": {
                    "$ref": "#/definitions/stat_information"
                },
                "Woodcutting": {
                    "$ref": "#/definitions/stat_information"
                },
                "Farming": {
                    "$ref": "#/definitions/stat_information"
                },
                "Overall": {
                    "$ref": "#/definitions/stat_information"
                }
            },
            "required": [
                "Attack",
                "Strength",
                "Defence",
                "Ranged",
                "Prayer",
                "Magic",
                "Runecraft",
                "Construction",
                "Hitpoints",
                "Agility",
                "Herblore",
                "Thieving",
                "Crafting",
                "Fletching",
                "Slayer",
                "Hunter",
                "Mining",
                "Smithing",
                "Fishing",
                "Cooking",
                "Firemaking",
                "Woodcutting",
                "Farming",
                "Overall"
            ]
        },
        "status": {
            "title": "status",
            "description": "player status",
            "type": "object",
            "properties": {
                "hp": {
                    "title": "Hp",
                    "type": "integer"
                },
                "base_hp": {
                    "title": "Base Hp",
                    "type": "integer"
                },
                "prayer": {
                    "title": "Prayer",
                    "type": "integer"
                },
                "base_prayer": {
                    "title": "Base Prayer",
                    "type": "integer"
                },
                "run_energy": {
                    "title": "Run Energy",
                    "type": "integer"
                },
                "special_attack": {
                    "title": "Special Attack",
                    "type": "integer"
                }
            },
            "required": [
                "hp",
                "base_hp",
                "prayer",
                "base_prayer",
                "run_energy",
                "special_attack"
            ]
        },
        "location": {
            "title": "location",
            "description": "location model",
            "type": "object",
            "properties": {
                "x": {
                    "title": "X",
                    "type": "integer"
                },
                "y": {
                    "title": "Y",
                    "type": "integer"
                },
                "regionX": {
                    "title": "Regionx",
                    "type": "integer"
                },
                "regionY": {
                    "title": "Regiony",
                    "type": "integer"
                },
                "regionID": {
                    "title": "Regionid",
                    "type": "integer"
                },
                "plane": {
                    "title": "Plane",
                    "type": "integer"
                },
                "world": {
                    "title": "World",
                    "type": "integer"
                }
            },
            "required": [
                "x",
                "y",
                "regionX",
                "regionY",
                "regionID",
                "plane",
                "world"
            ]
        },
        "inventory_item": {
            "title": "inventory_item",
            "description": "inventory_item model",
            "type": "object",
            "properties": {
                "item_id": {
                    "title": "Item Id",
                    "type": "integer"
                },
                "item_amount": {
                    "title": "Item Amount",
                    "type": "integer"
                }
            },
            "required": [
                "item_id",
                "item_amount"
            ]
        },
        "prayer_slot": {
            "title": "prayer_slot",
            "description": "prayer slot",
            "type": "object",
            "properties": {
                "prayer_name": {
                    "title": "Prayer Name",
                    "type": "string"
                },
                "prayer_varbit": {
                    "title": "Prayer Varbit",
                    "type": "integer"
                }
            },
            "required": [
                "prayer_name",
                "prayer_varbit"
            ]
        },
        "equipment_item": {
            "title": "equipment_item",
            "type": "object",
            "properties": {
                "item_id": {
                    "title": "Item Id",
                    "type": "integer"
                },
                "item_amount": {
                    "title": "Item Amount",
                    "type": "integer"
                }
            },
            "required": [
                "item_id"
            ]
        },
        "equipment": {
            "title": "equipment",
            "type": "object",
            "properties": {
                "head": {
                    "$ref": "#/definitions/equipment_item"
                },
                "cape": {
                    "$ref": "#/definitions/equipment_item"
                },
                "amulet": {
                    "$ref": "#/definitions/equipment_item"
                },
                "ammo": {
                    "$ref": "#/definitions/equipment_item"
                },
                "weapon": {
                    "$ref": "#/definitions/equipment_item"
                },
                "body": {
                    "$ref": "#/definitions/equipment_item"
                },
                "shield": {
                    "$ref": "#/definitions/equipment_item"
                },
                "legs": {
                    "$ref": "#/definitions/equipment_item"
                },
                "gloves": {
                    "$ref": "#/definitions/equipment_item"
                },
                "boots": {
                    "$ref": "#/definitions/equipment_item"
                },
                "ring": {
                    "$ref": "#/definitions/equipment_item"
                }
            }
        },
        "player": {
            "title": "player",
            "description": "player model",
            "type": "object",
            "properties": {
                "discord": {
                    "title": "Discord",
                    "type": "string"
                },
                "stats": {
                    "$ref": "#/definitions/stats"
                },
                "status": {
                    "$ref": "#/definitions/status"
                },
                "location": {
                    "$ref": "#/definitions/location"
                },
                "inventory": {
                    "title": "Inventory",
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/inventory_item"
                    }
                },
                "prayer": {
                    "title": "Prayer",
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/prayer_slot"
                    }
                },
                "equipment": {
                    "$ref": "#/definitions/equipment"
                },
                "runewatch": {
                    "title": "Runewatch",
                    "type": "string"
                },
                "wdr": {
                    "title": "Wdr",
                    "type": "string"
                },
                "gamestate": {
                    "title": "Gamestate",
                    "type": "integer"
                },
                "verified": {
                    "title": "Verified",
                    "type": "boolean"
                },
                "rating": {
                    "title": "Rating",
                    "type": "integer"
                },
                "kick_list": {
                    "title": "Kick List",
                    "type": "array",
                    "items": {
                        "type": "integer"
                    }
                },
                "promote_list": {
                    "title": "Promote List",
                    "type": "array",
                    "items": {
                        "type": "integer"
                    }
                },
                "user_id": {
                    "title": "User Id",
                    "type": "integer"
                },
                "login": {
                    "title": "Login",
                    "type": "string"
                },
                "isPartyLeader": {
                    "title": "Ispartyleader",
                    "default": false,
                    "type": "boolean"
                }
            },
            "required": [
                "discord",
                "user_id",
                "login"
            ]
        }
    }
}

PingData

{
    "title": "ping",
    "description": "ping model",
    "type": "object",
    "properties": {
        "username": {
            "title": "Username",
            "type": "string"
        },
        "x": {
            "title": "X",
            "type": "integer"
        },
        "y": {
            "title": "Y",
            "type": "integer"
        },
        "regionX": {
            "title": "Regionx",
            "type": "integer"
        },
        "regionY": {
            "title": "Regiony",
            "type": "integer"
        },
        "regionID": {
            "title": "Regionid",
            "type": "integer"
        },
        "plane": {
            "title": "Plane",
            "type": "integer"
        },
        "color_r": {
            "title": "Color R",
            "type": "integer"
        },
        "color_g": {
            "title": "Color G",
            "type": "integer"
        },
        "color_b": {
            "title": "Color B",
            "type": "integer"
        },
        "color_alpha": {
            "title": "Color Alpha",
            "type": "integer"
        },
        "isAlert": {
            "title": "Isalert",
            "type": "boolean"
        }
    },
    "required": [
        "username",
        "x",
        "y",
        "regionX",
        "regionY",
        "regionID",
        "plane",
        "color_r",
        "color_g",
        "color_b",
        "color_alpha",
        "isAlert"
    ]
}

Setting up your environment

Requirements

  • Ubuntu 22.04 or a server with Ubuntu 22.04

Setup Environment Automatically

You can use this script to setup most of your environment:

  • Nginx
  • Mysql & Open it to the world on port 3306
  • Php
  • Docker
  • Docker-Compose
  • Phpmyadmin & Open it to the world on http://$ip/phpmyadmin
  • Redis & Open it to the world on port 6379
mkdir setup && cd setup
wget -4 https://raw.githubusercontent.com/NeverScapeAlone/NeverScapeAlone-API/develop/setup.sh
sudo chmod +x setup.sh
./setup.sh

Setup Environment Manually

Installing nginx

sudo apt update
sudo apt install nginx
sudo ufw allow 'Nginx HTTP'
sudo ufw allow 22 #ssh
sudo ufw allow 80 # http
sudo ufw allow 443 # https
sudo ufw allow 3306 # Mysql
sudo ufw allow 5500 # NeverScapeAlone main branch
sudo ufw allow 5501 # NeverScapeAlone development branch
sudo ufw allow 6379 # Redis
sudo ufw enable
sudo ufw reload
sudo ufw status

Installing Mysql

sudo apt install mysql-server
sudo mysql
> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';
> FLUSH PRIVILEGES;
> exit

Installing php

sudo apt install php-fpm php-mysql
sudo nano /etc/nginx/sites-available/site

Add this to the /etc/nginx/sites-available/site file

server {
        listen 80;
        root /var/www/html;
        index index.php index.html index.htm index.nginx-debian.html;
        server_name site;

        location / {
                try_files $uri $uri/ =404;
        }

        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
                fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        }

        location ~ /\.ht {
                deny all;
        }
}
sudo ln -s /etc/nginx/sites-available/site /etc/nginx/sites-enabled/
sudo unlink /etc/nginx/sites-enabled/default
sudo systemctl reload nginx

Installing Docker

sudo apt-get update
sudo apt-get install \
                ca-certificates \
                curl \
                gnupg \
                lsb-release

sudo mkdir -p /etc/apt/keyrings

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

sudo service docker start
sudo docker run hello-world

Installing Docker-Compose

sudo apt install docker-compose

Opening Mysql to the world

sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

Change bind address

bind_address = {YOUR IPV4 HERE}
sudo service mysql restart
systemctl status mysql.service
sudo mysql -u root -p

Enter your password, "password" if you didn't change the default

> CREATE USER 'username'@'%' IDENTIFIED BY 'chooseyourpassword';
> GRANT ALL PRIVILEGES ON *.* TO 'username'@'%';
> FLUSH PRIVILEGES;
> exit
sudo systemctl restart nginx

Installing phpmyadmin

sudo apt install phpmyadmin
sudo nano /etc/nginx/snippets/phpmyadmin.conf

Add this to /etc/nginx/snippets/phpmyadmin.conf

location /phpmyadmin {
    root /usr/share/;
    index index.php index.html index.htm;
    location ~ ^/phpmyadmin/(.+\.php)$ {
        try_files $uri =404;
        root /usr/share/;
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include /etc/nginx/fastcgi_params;
    }

    location ~* ^/phpmyadmin/(.+\.(jpg|jpeg|gif|css|png|js|ico|html|xml|txt))$ {
        root /usr/share/;
    }
}
sudo nano /etc/nginx/sites-available/site

Replace /etc/nginx/sites-available/site with this

server {
        listen 80;
        root /var/www/html;
        index index.php index.html index.htm index.nginx-debian.html;
        server_name site;
        include snippets/phpmyadmin.conf;

        location / {
                try_files $uri $uri/ =404;
        }

        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
                fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        }

        location ~ /\.ht {
                deny all;
        }
}
sudo service nginx restart

you can now go to http://{youripv4domain}.com/phpmyadmin and login to your mysql database

Entering in the NeverScapeAlone-API mysql files:

Use the file located here to generate a series of tables that you will use for your mysql database: https://github.com/NeverScapeAlone/NeverScapeAlone-SQL/blob/main/full_setup.sql

Installing redis

sudo apt update
sudo apt install redis-server
sudo nano /etc/redis/redis.conf

In the redis.conf file, change the following lines:

> supervised no -> supervised systemd
> bind 127.0.0.1 ::-1 -> bind 0.0.0.0
> #requirepass -> requirepass <put a strong password here>
sudo systemctl restart redis.service
sudo systemctl status redis
redis-cli
> Auth <your very strong password from requirepass>
> ping
>> PONG
> exit

Github Repository

  1. Install VsCode
  2. Install Github Desktop
  3. Fork this repository.
  4. Create a .env file in the root directory with the following parameters:
sql_uri = mysql+asyncmy://username:password@serveripv4:3306/databasename
discord_route_token = "a discord bot token goes here"
redis_password = "redis password goes here"
redis_database="1"
redis_port=6379
rate_limit_minute=120
rate_limit_hour=7200
match_version="v0.0.0-alpha"
  1. go to the notes.md file and run the following. This will put you in a python venv, you will need to install python on your system prior.
python -m venv venv
venv\Scripts\activate
python -m pip install --upgrade pip
pip install -r requirements.txt
  1. Run uvicorn api.app:app --reload in the terminal. This will give you a local instance of the API to develop on. To deploy this, you can use the current workflow commands to execute it on your server.

  2. Prior to running this on your site, make sure to correctly configure the your ports, and to set GITHUB SECRETS! Check the .github/workflows file for the github secrets you'll need, and the branches that will be activated.

Feel free to leave a question in the issues page of this discord if you need help setting up your environment.

Add a github runner for your fork

  1. On your fork of the repository go to:
  2. Settings > Actions > Runners > new self-hosted runner
  3. Follow the commands listed
  4. Set up your runner as a service here: https://docs.github.com/en/actions/hosting-your-own-runners/configuring-the-self-hosted-runner-application-as-a-service
sudo ./svc.sh install
sudo ./svc.sh start

neverscapealone-api's People

Contributors

ferrariic avatar

Stargazers

Wade avatar  avatar Alice Lia Stapleton avatar  avatar

Watchers

 avatar

neverscapealone-api's Issues

websockets.exceptions.ConnectionClosedError: no close frame received or sent

{"ts": "2022-08-09 19:32:07,009", "name": "api.routers.lobbies", "function": "websocket_endpoint", "level": "DEBUG", "msg": "\"XXX.XXX.XXX.XXX => no close frame received or sent\""}
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/site-packages/websockets/legacy/protocol.py", line 945, in transfer_data
    message = await self.read_message()
  File "/usr/local/lib/python3.10/site-packages/websockets/legacy/protocol.py", line 1015, in read_message
    frame = await self.read_data_frame(max_size=self.max_size)
  File "/usr/local/lib/python3.10/site-packages/websockets/legacy/protocol.py", line 1090, in read_data_frame
    frame = await self.read_frame(max_size)
  File "/usr/local/lib/python3.10/site-packages/websockets/legacy/protocol.py", line 1145, in read_frame
    frame = await Frame.read(
  File "/usr/local/lib/python3.10/site-packages/websockets/legacy/framing.py", line 70, in read
    data = await reader(2)
  File "/usr/local/lib/python3.10/asyncio/streams.py", line 706, in readexactly
    raise exceptions.IncompleteReadError(incomplete, n)
asyncio.exceptions.IncompleteReadError: 0 bytes read on a total of 2 expected bytes

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/code/./api/routers/lobbies.py", line 506, in websocket_endpoint
    await manager.broadcast(
  File "/code/./api/routers/lobbies.py", line 167, in broadcast
    await connection.send_json(payload)
  File "/usr/local/lib/python3.10/site-packages/starlette/websockets.py", line 137, in send_json
    await self.send({"type": "websocket.send", "text": text})
  File "/usr/local/lib/python3.10/site-packages/starlette/websockets.py", line 68, in send
    await self._send(message)
  File "/usr/local/lib/python3.10/site-packages/uvicorn/protocols/websockets/websockets_impl.py", line 252, in asgi_send
    await self.send(data)
  File "/usr/local/lib/python3.10/site-packages/websockets/legacy/protocol.py", line 620, in send
    await self.ensure_open()
  File "/usr/local/lib/python3.10/site-packages/websockets/legacy/protocol.py", line 916, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedError: no close frame received or sent

AttributeError: 'NoneType' object has no attribute 'decode' reference to return [ast.literal_eval(bytes_encoded.decode("utf-8"))]

Exception in ASGI application
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/site-packages/anyio/streams/memory.py", line 81, in receive
    return self.receive_nowait()
  File "/usr/local/lib/python3.10/site-packages/anyio/streams/memory.py", line 76, in receive_nowait
    raise WouldBlock
anyio.WouldBlock

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.10/site-packages/starlette/middleware/base.py", line 41, in call_next
    message = await recv_stream.receive()
  File "/usr/local/lib/python3.10/site-packages/anyio/streams/memory.py", line 101, in receive
    raise EndOfStream
anyio.EndOfStream

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 367, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/usr/local/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
    return await self.app(scope, receive, send)
  File "/usr/local/lib/python3.10/site-packages/fastapi/applications.py", line 212, in __call__
    await super().__call__(scope, receive, send)
  File "/usr/local/lib/python3.10/site-packages/starlette/applications.py", line 112, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/usr/local/lib/python3.10/site-packages/starlette/middleware/errors.py", line 181, in __call__
    raise exc
  File "/usr/local/lib/python3.10/site-packages/starlette/middleware/errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "/usr/local/lib/python3.10/site-packages/starlette/middleware/base.py", line 63, in __call__
    response = await self.dispatch_func(request, call_next)
  File "/code/./api/middleware.py", line 20, in request_handler
    response = await process_request(request=request, call_next=call_next)
  File "/code/./api/middleware.py", line 68, in process_request
    response = await call_next(request)
  File "/usr/local/lib/python3.10/site-packages/starlette/middleware/base.py", line 44, in call_next
    raise app_exc
  File "/usr/local/lib/python3.10/site-packages/starlette/middleware/base.py", line 34, in coro
    await self.app(scope, request.receive, send_stream.send)
  File "/usr/local/lib/python3.10/site-packages/starlette/middleware/cors.py", line 84, in __call__
    await self.app(scope, receive, send)
  File "/usr/local/lib/python3.10/site-packages/starlette/exceptions.py", line 82, in __call__
    raise exc
  File "/usr/local/lib/python3.10/site-packages/starlette/exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "/usr/local/lib/python3.10/site-packages/starlette/routing.py", line 656, in __call__
    await route.handle(scope, receive, send)
  File "/usr/local/lib/python3.10/site-packages/starlette/routing.py", line 259, in handle
    await self.app(scope, receive, send)
  File "/usr/local/lib/python3.10/site-packages/starlette/routing.py", line 61, in app
    response = await func(request)
  File "/usr/local/lib/python3.10/site-packages/fastapi/routing.py", line 226, in app
    raw_response = await run_endpoint_function(
  File "/usr/local/lib/python3.10/site-packages/fastapi/routing.py", line 159, in run_endpoint_function
    return await dependant.call(**values)
  File "/code/./api/routers/discord.py", line 169, in post_invites
    key, m = await get_match_from_ID(group_identifier=am.ID)
  File "/code/./api/database/functions.py", line 470, in get_match_from_ID
    data = await redis_decode(bytes_encoded=match)
  File "/code/./api/database/functions.py", line 74, in redis_decode
    return [ast.literal_eval(bytes_encoded.decode("utf-8"))]
AttributeError: 'NoneType' object has no attribute 'decode'

Add discord grouping

Allow for users to click and join a pre-designated discord channel for their session.

manager: handle socket stacking

{"ts": "2022-08-09 20:06:59,913", "name": "api.routers.lobbies", "function": "disconnect", "level": "INFO", "msg": "\"Ferrariic << 0\""}
{"ts": "2022-08-09 20:06:59,913", "name": "api.routers.lobbies", "function": "disconnect", "level": "INFO", "msg": "\"Ferrariic << 0\""}
{"ts": "2022-08-09 20:06:59,913", "name": "api.routers.lobbies", "function": "disconnect", "level": "INFO", "msg": "\"Ferrariic << 0\""}
{"ts": "2022-08-09 20:06:59,914", "name": "api.routers.lobbies", "function": "disconnect", "level": "INFO", "msg": "\"Ferrariic << 0\""}
{"ts": "2022-08-09 20:06:59,914", "name": "api.routers.lobbies", "function": "disconnect", "level": "INFO", "msg": "\"Ferrariic << 0\""}
{"ts": "2022-08-09 20:06:59,945", "name": "api.routers.lobbies", "function": "disconnect", "level": "INFO", "msg": "\"Ferrariic << 0\""}
{"ts": "2022-08-09 20:06:59,946", "name": "api.routers.lobbies", "function": "disconnect", "level": "INFO", "msg": "\"Ferrariic << 0\""}
{"ts": "2022-08-09 20:06:59,946", "name": "api.routers.lobbies", "function": "disconnect", "level": "INFO", "msg": "\"Ferrariic << 0\""}
{"ts": "2022-08-09 20:06:59,946", "name": "api.routers.lobbies", "function": "disconnect", "level": "INFO", "msg": "\"Ferrariic << 0\""}
{"ts": "2022-08-09 20:06:59,947", "name": "api.routers.lobbies", "function": "disconnect", "level": "INFO", "msg": "\"Ferrariic << 0\""}
{"ts": "2022-08-09 20:06:59,947", "name": "api.routers.lobbies", "function": "disconnect", "level": "INFO", "msg": "\"Ferrariic << 0\""}
{"ts": "2022-08-09 20:06:59,947", "name": "api.routers.lobbies", "function": "disconnect", "level": "INFO", "msg": "\"Ferrariic << 0\""}
{"ts": "2022-08-09 20:06:59,948", "name": "api.routers.lobbies", "function": "disconnect", "level": "INFO", "msg": "\"Ferrariic << 0\""}

Change matchID encoding from base16 to base62

import time

def encode(num):
    alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    if num == 0:
        return alphabet[0]
    arr = []
    arr_append = arr.append
    _divmod = divmod
    base = len(alphabet)
    while num:
        num, rem = _divmod(num, base)
        arr_append(alphabet[rem])
    arr.reverse()
    return ''.join(arr)

def matchID():
    ID = encode(time.time_ns())[3:][::-1]
    ID = "-".join([ID[i : i + 4] for i in range(0, len(ID), 4)])
    return ID

This has the benefit of changing the ID from xxxx-xxxx-xxxx to xxxx-xxxx

websockets.exceptions.ConnectionClosedOK: received 1000 (OK) Exiting match; then sent 1000 (OK) Exiting match

{"ts": "2022-08-09 19:46:46,116", "name": "api.routers.lobbies", "function": "websocket_endpoint", "level": "DEBUG", "msg": "\"XX.XX.XX.XXX => received 1000 (OK) Exiting match; then sent 1000 (OK) Exiting match\""}
Traceback (most recent call last):
  File "/code/./api/routers/lobbies.py", line 400, in websocket_endpoint
    await manager.broadcast(
  File "/code/./api/routers/lobbies.py", line 167, in broadcast
    await connection.send_json(payload)
  File "/usr/local/lib/python3.10/site-packages/starlette/websockets.py", line 137, in send_json
    await self.send({"type": "websocket.send", "text": text})
  File "/usr/local/lib/python3.10/site-packages/starlette/websockets.py", line 68, in send
    await self._send(message)
  File "/usr/local/lib/python3.10/site-packages/uvicorn/protocols/websockets/websockets_impl.py", line 252, in asgi_send
    await self.send(data)
  File "/usr/local/lib/python3.10/site-packages/websockets/legacy/protocol.py", line 620, in send
    await self.ensure_open()
  File "/usr/local/lib/python3.10/site-packages/websockets/legacy/protocol.py", line 930, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: received 1000 (OK) Exiting match; then sent 1000 (OK) Exiting match

Safer disconnect handling

Atm orphan members exist in groups, should be handled through regular 'ping pong' checks as w/ starlette/fastapi/uvicorn, but also with occasional 'health' pings of the server-client interface, handled in manager class.

self.active_connections[group_identifier].remove(websocket) KeyError: 'e808-a241-dc34'

Traceback (most recent call last):
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\.\api\routers\lobbies.py", line 226, in websocket_endpoint
    request = await websocket.receive_json()
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\venv\lib\site-packages\starlette\websockets.py", line 96, in receive_json
    assert self.application_state == WebSocketState.CONNECTED
AssertionError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\.\api\routers\lobbies.py", line 230, in websocket_endpoint
    await manager.disconnect(
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\.\api\routers\lobbies.py", line 98, in disconnect
    self.active_connections[group_identifier].remove(websocket)
KeyError: 'e808-a241-dc34'

Headers({'user-agent': 'RuneLite/1.8.30-1ae953b', 'login': 'Ferrariic', 'discord': 'QEZlcnJhcmlpYyMwMDAx', 'discord_id': '178965680266149888', 'token': '', 'time': '2022-08-14T22:33:55.978612800Z', 'upgrade': 'websocket', 'connection': 'Upgrade', 'sec-websocket-key': '', 'sec-websocket-version': '13', 'host': '127.0.0.1:8000', 'accept-encoding': 'gzip'})
Exception in ASGI application
Traceback (most recent call last):
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\.\api\routers\lobbies.py", line 226, in websocket_endpoint
    request = await websocket.receive_json()
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\venv\lib\site-packages\starlette\websockets.py", line 96, in receive_json
    assert self.application_state == WebSocketState.CONNECTED
AssertionError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\.\api\routers\lobbies.py", line 230, in websocket_endpoint
    await manager.disconnect(
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\.\api\routers\lobbies.py", line 98, in disconnect
    self.active_connections[group_identifier].remove(websocket)
KeyError: 'e808-a241-dc34'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\venv\lib\site-packages\uvicorn\protocols\websockets\websockets_impl.py", line 184, in run_asgi
    result = await self.app(self.scope, self.asgi_receive, self.asgi_send)
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\venv\lib\site-packages\uvicorn\middleware\proxy_headers.py", line 75, in __call__
    return await self.app(scope, receive, send)
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\venv\lib\site-packages\fastapi\applications.py", line 212, in __call__
    await super().__call__(scope, receive, send)
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\venv\lib\site-packages\starlette\applications.py", line 112, in __call__
    await self.middleware_stack(scope, receive, send)
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\venv\lib\site-packages\starlette\middleware\errors.py", line 146, in __call__
    await self.app(scope, receive, send)
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\venv\lib\site-packages\starlette\middleware\base.py", line 22, in __call__
    await self.app(scope, receive, send)
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\venv\lib\site-packages\starlette\middleware\cors.py", line 76, in __call__
    await self.app(scope, receive, send)
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\venv\lib\site-packages\starlette\exceptions.py", line 58, in __call__
    await self.app(scope, receive, send)
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\venv\lib\site-packages\starlette\routing.py", line 656, in __call__
    await route.handle(scope, receive, send)
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\venv\lib\site-packages\starlette\routing.py", line 315, in handle
    await self.app(scope, receive, send)
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\venv\lib\site-packages\starlette\routing.py", line 77, in app
    await func(session)
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\venv\lib\site-packages\fastapi\routing.py", line 273, in app
    await dependant.call(**values)
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\.\api\routers\lobbies.py", line 540, in websocket_endpoint
    await manager.disconnect(websocket=websocket, group_identifier=group_identifier)
  File "C:\Users\thear\Documents\GitHub\NeverScapeAlone-API\.\api\routers\lobbies.py", line 98, in disconnect
    self.active_connections[group_identifier].remove(websocket)
KeyError: 'e808-a241-dc34'

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.