Licensor

Python License Client

Complete Python implementation for integrating WiLicensor into Python applications.

Installation

pip install requests

Complete Client Class

import requests
import platform
import subprocess
import re
from dataclasses import dataclass
from datetime import datetime
from typing import Optional, Dict
from urllib.parse import urlencode, quote


@dataclass
class LicenseResponse:
    key: str = ""
    condition: str = ""
    type: str = ""
    machine_id: str = ""
    machine_name: str = ""
    product_id: int = 0
    email: str = ""
    settings: str = ""
    end_at: Optional[datetime] = None
    activated_at: Optional[datetime] = None
    error: Optional[str] = None

    @property
    def is_valid(self) -> bool:
        return self.condition in ("activated", "purchased")

    @property
    def is_expired(self) -> bool:
        if self.condition == "expired":
            return True
        if self.end_at and self.end_at < datetime.utcnow():
            return True
        return False

    @classmethod
    def from_dict(cls, data: dict) -> "LicenseResponse":
        end_at = None
        if data.get("endAt"):
            try:
                end_at = datetime.fromisoformat(data["endAt"].replace("Z", "+00:00"))
            except:
                pass

        activated_at = None
        if data.get("activatedAt"):
            try:
                activated_at = datetime.fromisoformat(data["activatedAt"].replace("Z", "+00:00"))
            except:
                pass

        return cls(
            key=data.get("key", ""),
            condition=data.get("condition", ""),
            type=data.get("type", ""),
            machine_id=data.get("machineId", ""),
            machine_name=data.get("machineName", ""),
            product_id=data.get("productId", 0),
            email=data.get("email", ""),
            settings=data.get("settings", ""),
            end_at=end_at,
            activated_at=activated_at,
            error=data.get("error")
        )


class LicenseClient:
    def __init__(self, base_url: str, product_name: str, timeout: int = 30):
        self.base_url = base_url.rstrip("/")
        self.product_name = product_name
        self.timeout = timeout

    def check_key(self, license_key: str) -> LicenseResponse:
        """Check if a license key is valid."""
        try:
            url = f"{self.base_url}/api/check?term={quote(license_key)}"
            response = requests.get(url, timeout=self.timeout)
            return LicenseResponse.from_dict(response.json())
        except Exception as e:
            return LicenseResponse(error=str(e))

    def activate_license(self, email: str) -> LicenseResponse:
        """Find and activate a license with machine binding."""
        try:
            machine_id = self.get_machine_id()
            machine_name = platform.node()
            encrypted_key = EncryptKey.make_password(machine_id, "358")

            params = {
                "key": encrypted_key,
                "email": email,
                "product": self.product_name,
                "machineId": machine_id,
                "machineName": machine_name
            }

            url = f"{self.base_url}/api/find?{urlencode(params)}"
            response = requests.get(url, timeout=self.timeout)
            return LicenseResponse.from_dict(response.json())
        except Exception as e:
            return LicenseResponse(error=str(e))

    def get_trial(self, email: str) -> LicenseResponse:
        """Generate or retrieve a trial license."""
        try:
            machine_id = self.get_machine_id()
            machine_name = platform.node()

            params = {
                "product": self.product_name,
                "machineId": machine_id,
                "machineName": machine_name,
                "email": email
            }

            url = f"{self.base_url}/api/gen?{urlencode(params)}"
            response = requests.get(url, timeout=self.timeout)
            return LicenseResponse.from_dict(response.json())
        except Exception as e:
            return LicenseResponse(error=str(e))

    def transfer_license(self, email: str, old_machine_id: str) -> LicenseResponse:
        """Transfer license to a new machine."""
        try:
            new_machine_id = self.get_machine_id()
            machine_name = platform.node()
            new_encrypted_key = EncryptKey.make_password(new_machine_id, "358")

            params = {
                "key": new_encrypted_key,
                "email": email,
                "product": self.product_name,
                "machineIdOld": old_machine_id,
                "machineIdNew": new_machine_id,
                "machineName": machine_name
            }

            url = f"{self.base_url}/api/replace?{urlencode(params)}"
            response = requests.get(url, timeout=self.timeout)
            return LicenseResponse.from_dict(response.json())
        except Exception as e:
            return LicenseResponse(error=str(e))

    @staticmethod
    def get_machine_id() -> str:
        """Get unique machine identifier."""
        system = platform.system()

        if system == "Windows":
            try:
                output = subprocess.check_output(
                    "wmic baseboard get serialnumber",
                    shell=True, stderr=subprocess.DEVNULL
                ).decode()
                lines = [l.strip() for l in output.split("\n") if l.strip()]
                if len(lines) > 1:
                    return lines[1]
            except:
                pass

            try:
                output = subprocess.check_output(
                    "wmic logicaldisk get volumeserialnumber",
                    shell=True, stderr=subprocess.DEVNULL
                ).decode()
                lines = [l.strip() for l in output.split("\n") if l.strip()]
                if len(lines) > 1:
                    return lines[1]
            except:
                pass

        elif system == "Linux":
            try:
                with open("/etc/machine-id", "r") as f:
                    return f.read().strip()
            except:
                pass

        elif system == "Darwin":  # macOS
            try:
                output = subprocess.check_output(
                    "ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformSerialNumber",
                    shell=True, stderr=subprocess.DEVNULL
                ).decode()
                match = re.search(r'"IOPlatformSerialNumber" = "(.+)"', output)
                if match:
                    return match.group(1)
            except:
                pass

        return platform.node()


class EncryptKey:
    """Key encryption algorithm - must match server implementation."""

    @staticmethod
    def make_password(st: str, identifier: str) -> str:
        if len(identifier) != 3:
            raise ValueError("Identifier must be 3 characters long")

        num = [int(identifier[0]), int(identifier[1]), int(identifier[2])]

        st = EncryptKey._boring(st)
        st = EncryptKey._inverse_by_base(st, num[0])
        st = EncryptKey._inverse_by_base(st, num[1])
        st = EncryptKey._inverse_by_base(st, num[2])

        result = ""
        for ch in st:
            result += EncryptKey._change_char(ch, num)

        return result

    @staticmethod
    def _inverse_by_base(st: str, move_base: int) -> str:
        if move_base == 0:
            move_base = 1
        result = ""
        for i in range(0, len(st), move_base):
            c = min(move_base, len(st) - i)
            result += st[i:i+c][::-1]
        return result

    @staticmethod
    def _boring(st: str) -> str:
        chars = list(st)
        for i in range(len(chars)):
            new_place = (i * ord(chars[i])) % len(chars)
            ch = chars[i]
            chars.pop(i)
            chars.insert(new_place, ch)
        return "".join(chars)

    @staticmethod
    def _change_char(ch: str, en_code: list) -> str:
        ch = ch.upper()
        ch_value = ord(ch)

        if 'A' <= ch <= 'H':
            return chr(ch_value + 2 * en_code[0])
        elif 'I' <= ch <= 'P':
            return chr(ch_value - en_code[2])
        elif 'Q' <= ch <= 'Z':
            return chr(ch_value - en_code[1])
        elif '0' <= ch <= '4':
            return chr(ch_value + 5)
        elif '5' <= ch <= '9':
            return chr(ch_value - 5)
        else:
            return '0'


def parse_settings(settings: str) -> Dict[str, str]:
    """Parse settings string into dictionary."""
    result = {}
    if not settings:
        return result

    # Format: "key1": "value1", "key2": "value2"
    pattern = r'"([^"]+)"\s*:\s*"([^"]*)"'
    for match in re.finditer(pattern, settings):
        result[match.group(1)] = match.group(2)

    return result

Usage Examples

Check License at Startup

client = LicenseClient("https://license.yourserver.com", "YourProduct")

# Check existing license
result = client.check_key(saved_license_key)

if result.is_valid:
    print(f"License valid until: {result.end_at}")
elif result.is_expired:
    print("License has expired. Please renew.")
else:
    print(f"License error: {result.error or result.condition}")

Activate License

client = LicenseClient("https://license.yourserver.com", "YourProduct")

result = client.activate_license("user@example.com")

if result.is_valid:
    # Save license key for future checks
    save_license_key(result.key)
    print(f"License activated! Type: {result.type}")
else:
    print(f"Activation failed: {result.error}")

Request Trial License

client = LicenseClient("https://license.yourserver.com", "YourProduct")

result = client.get_trial("user@example.com")

if result.is_valid:
    print(f"Trial started! Expires: {result.end_at}")

Full Application Example

import json
import os

LICENSE_FILE = "license.json"

def load_saved_license():
    if os.path.exists(LICENSE_FILE):
        with open(LICENSE_FILE, "r") as f:
            return json.load(f)
    return None

def save_license(license_data):
    with open(LICENSE_FILE, "w") as f:
        json.dump({
            "key": license_data.key,
            "email": license_data.email,
            "machine_id": license_data.machine_id
        }, f)

def check_license(email: str) -> bool:
    client = LicenseClient("https://license.yourserver.com", "YourProduct")

    # Try saved license first
    saved = load_saved_license()
    if saved:
        result = client.check_key(saved["key"])
        if result.is_valid:
            print(f"License valid. Type: {result.type}")
            return True
        elif result.is_expired:
            print("License expired. Attempting reactivation...")

    # Try to activate
    result = client.activate_license(email)
    if result.is_valid:
        save_license(result)
        print(f"License activated! Expires: {result.end_at}")
        return True

    # Try trial
    result = client.get_trial(email)
    if result.is_valid:
        save_license(result)
        print(f"Trial activated! Expires: {result.end_at}")
        return True

    print(f"License check failed: {result.error or result.condition}")
    return False


if __name__ == "__main__":
    if check_license("user@example.com"):
        print("Application starting...")
        # Your application code here
    else:
        print("Cannot start without valid license.")

Using Settings

result = client.activate_license("user@example.com")

if result.is_valid:
    settings = parse_settings(result.settings)

    sync_days = int(settings.get("nextSync", "7"))
    grace_period = int(settings.get("GracePeriodDays", "0"))
    custom_message = settings.get("customMessage", "")

    print(f"Sync every {sync_days} days")
    if custom_message:
        print(f"Message: {custom_message}")

Back to Overview | C# Client | JavaScript Client