Fix warnings

Fix warnings

/**
 * vim: set ts=4 :
 * =============================================================================
 * Jetpack Plus
 * TODO: Describe this plugin
 *
 * Copyright 2014 CrimsonTautology
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <sdktools>
#include <particle>
#include <smlib/entities>

#define PLUGIN_VERSION "0.1"

#define MOVECOLLIDE_DEFAULT		0
#define MOVECOLLIDE_FLY_BOUNCE	1

#define MAX_JETPACK_TYPES 64

public Plugin:myinfo =
{
    name = "Jetpack Plus",
    author = "CrimsonTautology",
    description = "TODO: description",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_jetpack_plus"
};

new Handle:g_Cvar_Enabled = INVALID_HANDLE;
new Handle:g_Cvar_JetpackSpeed = INVALID_HANDLE;

new g_Offset_movecollide = -1;

new bool:g_IsUsingJetpack[MAXPLAYERS+1] = {false, ...};

//Player options
new g_ClientSelectedJetpackType[MAXPLAYERS+1] = {0, ...};
new g_JetpackParticle[MAXPLAYERS+1][2]; //TODO

//Parallel arrays to store types of jetpacks
new String:g_JetpackTypeName[MAX_JETPACK_TYPES][PLATFORM_MAX_PATH];
new String:g_JetpackTypeParticle[MAX_JETPACK_TYPES][PLATFORM_MAX_PATH];
new String:g_JetpackTypeSound[MAX_JETPACK_TYPES][PLATFORM_MAX_PATH];
new g_JetpackTypeCount = 0;

public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
{
    if (LibraryExists("jetpack_plus"))
    {
        strcopy(error, err_max, "Jetpack Plus already loaded, aborting.");
        return APLRes_Failure;
    }

    RegPluginLibrary("jetpack_plus");

    return APLRes_Success;
}

public OnPluginStart()
{
    LoadTranslations("jetpack_plus.phrases");

    g_Cvar_Enabled = CreateConVar(
            "sm_jetpack",
            "1",
            "Set to 1 to enable the jetpack plugin");
    g_Cvar_JetpackSpeed = CreateConVar(
            "sm_jetpack_speed",
            "8.0",
            "Speed of the jetpack"
            );

    if((g_Offset_movecollide = FindSendPropOffs("CBaseEntity", "movecollide")) == -1)
        LogError("Could not find offset for CBaseEntity::movecollide");
}

public OnMapStart()
{
    ReadJetpacks();
}

ReadJetpacks()
{
    g_JetpackTypeCount = 0;
    new Handle:kv = CreateKeyValues("Jetpacks");

    decl String:path[PLATFORM_MAX_PATH];
    BuildPath(Path_SM, path, sizeof(path), "configs/jetpacks.cfg");

    if(FileExists(path))
    {
        FileToKeyValues(kv, path);

        do
        {
            KvGetString(kv, "name", g_JetpackTypeName[g_JetpackTypeCount], PLATFORM_MAX_PATH);
            KvGetString(kv, "particle", g_JetpackTypeParticle[g_JetpackTypeCount], PLATFORM_MAX_PATH);
            KvGetString(kv, "sound", g_JetpackTypeSound[g_JetpackTypeCount], PLATFORM_MAX_PATH);
            PrecacheSound(g_JetpackTypeSound[g_JetpackTypeCount], true);
            g_JetpackTypeCount++;

        } while(KvGotoNextKey(kv) && g_JetpackTypeCount < MAX_JETPACK_TYPES);

    } else {
        LogError("File Not Found: %s", path);
    }

    CloseHandle(kv);
}

public OnClientDisconnect(client)
{
    StopJetpack(client);
}

public OnGameFrame()
{
    if(AreJetpacksEnabled())
    {
        for (new client=1; client <= MaxClients; client++)
        {
            //For each client using a jetpack
            if(IsClientUsingJetpack(client))
            {
                JetpackStep(client);
            }

        }

    }
}

public Action:OnPlayerRunCmd(client, &buttons, &impulse, Float:vel[3], Float:angles[3], &weapon, &subtype, &cmdnum, &tickcount, &seed, mouse[2])
{
    if(!AreJetpacksEnabled()) return Plugin_Continue;

    if(!IsClientUsingJetpack(client) && buttons & IN_JUMP)
    {
        new player = GetClientUserId(client);
        CreateTimer(0.17, HeldJump, player);
    }

    return Plugin_Continue;
}

//Call back for when a player presses jump.  Checks if jump is still held down
public Action:HeldJump(Handle:timer, any:player)
{
    new client = GetClientOfUserId(player);

    if(!IsClientInGame(client)) return Plugin_Handled;

    if(!IsClientUsingJetpack(client) && GetClientButtons(client) & IN_JUMP)
    {
        StartJetpack(client);
    }

    return Plugin_Handled;
}

bool:IsClientUsingJetpack(client)
{
    return g_IsUsingJetpack[client];
}

bool:AreJetpacksEnabled()
{
    return GetConVarBool(g_Cvar_Enabled);
}

StartJetpack(client)
{
    SetEntityMoveType(client, MOVETYPE_FLY);
    SetEntityMoveCollide(client, MOVECOLLIDE_FLY_BOUNCE);
    g_IsUsingJetpack[client] = true;

    new selected = g_ClientSelectedJetpackType[client];
    EmitSoundToAll(g_JetpackTypeSound[selected], client, SNDCHAN_AUTO);

    static const Float:ang[3] = { -25.0, 90.0, 0.0 };
    static const Float:pos[3] = {   0.0, 10.0, 1.0 };
    g_JetpackParticle[client][0] = CreateParticle(g_JetpackTypeParticle[selected], 0.0, client, Attach, "flag", pos, ang);

    //https://forums.alliedmods.net/showthread.php?t=127111
    //pyrovision_flaming_arrow
    //pyrovision_flying_flaming_arrow
    //ghost_pumpkin
    //ghost_pumpkin_flyingbits

    //burningplayer_corpse_rainbow_stars
    //burningplayer_rainbow_OLD
    //flamethrower_rainbow_bubbles02
    //burninggibs
    //electrocuted_blue
    //spell_batball_blue
    //g_JetpackParticle[client][0] = CreateParticle("halloween_rockettrail", 0.0, client, Attach, "flag", pos, ang);
}

StopJetpack(client)
{
    //TODO handle changes in sound and particle while jetpack active
    SetEntityMoveType(client, MOVETYPE_WALK);
    SetEntityMoveCollide(client, MOVECOLLIDE_DEFAULT);
    g_IsUsingJetpack[client] = false;

    new selected = g_ClientSelectedJetpackType[client];
    StopSound(client, SNDCHAN_AUTO, g_JetpackTypeSound[selected]);
    DeleteParticle(g_JetpackParticle[client][0]);
}

//Called each frame a client is using a jetpack
JetpackStep(client)
{
    new buttons = GetClientButtons(client);
    if(IsPlayerAlive(client) && (buttons & IN_JUMP))
    {
        JetpackPush(client, GetConVarFloat(g_Cvar_JetpackSpeed));
    }
    else
    {
        StopJetpack(client);
    }
}

JetpackPush(client, Float:force)
{
    new Float:vec[3];
    Entity_GetBaseVelocity(client, vec);
    vec[2] += force;
    Entity_SetBaseVelocity(client, vec);
}

SetEntityMoveCollide(entity, movecollide)
{
    if(g_Offset_movecollide == -1) return;
    SetEntData(entity, g_Offset_movecollide, movecollide);
}

/**
 * vim: set ts=4 :
 * =============================================================================
 * Jetpack Plus
 * TODO: Describe this plugin
 *
 * Copyright 2014 CrimsonTautology
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <sdktools>
#include <particle>
#include <smlib/entities>

#define PLUGIN_VERSION "0.1"

#define MOVECOLLIDE_DEFAULT		0
#define MOVECOLLIDE_FLY_BOUNCE	1

#define MAX_JETPACK_TYPES 64

public Plugin:myinfo =
{
    name = "Jetpack Plus",
    author = "CrimsonTautology",
    description = "TODO: description",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_jetpack_plus"
};

new Handle:g_Cvar_Enabled = INVALID_HANDLE;
new Handle:g_Cvar_JetpackSpeed = INVALID_HANDLE;

new g_Offset_movecollide = -1;

new bool:g_IsUsingJetpack[MAXPLAYERS+1] = {false, ...};

//Player options
new g_ClientSelectedJetpackType[MAXPLAYERS+1] = {0, ...};
new g_JetpackParticle[MAXPLAYERS+1][2]; //TODO

//Parallel arrays to store types of jetpacks
new String:g_JetpackTypeName[MAX_JETPACK_TYPES][PLATFORM_MAX_PATH];
new String:g_JetpackTypeParticle[MAX_JETPACK_TYPES][PLATFORM_MAX_PATH];
new String:g_JetpackTypeSound[MAX_JETPACK_TYPES][PLATFORM_MAX_PATH];
new g_JetpackTypeCount = 0;

public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
{
    if (LibraryExists("jetpack_plus"))
    {
        strcopy(error, err_max, "Jetpack Plus already loaded, aborting.");
        return APLRes_Failure;
    }

    RegPluginLibrary("jetpack_plus");

    return APLRes_Success;
}

public OnPluginStart()
{
    LoadTranslations("jetpack_plus.phrases");

    g_Cvar_Enabled = CreateConVar(
            "sm_jetpack",
            "1",
            "Set to 1 to enable the jetpack plugin");
    g_Cvar_JetpackSpeed = CreateConVar(
            "sm_jetpack_speed",
            "8.0",
            "Speed of the jetpack"
            );

    if((g_Offset_movecollide = FindSendPropOffs("CBaseEntity", "movecollide")) == -1)
        LogError("Could not find offset for CBaseEntity::movecollide");
}

public OnMapStart()
{
    ReadJetpacks();
}

ReadJetpacks()
{
    g_JetpackTypeCount = 0;
    kv = CreateKeyValues("Jetpacks");

    decl String:path[PLATFORM_MAX_PATH];
    BuildPath(Path_SM, path, sizeof(path), "configs/jetpacks.cfg");

    if(FileExists(path))
    {
        FileToKeyValues(kv, path);

        do
        {
            KvGetString(kv, "name", g_JetpackTypeName[g_JetpackTypeCount], PLATFORM_MAX_PATH);
            KvGetString(kv, "particle", g_JetpackTypeParticle[g_JetpackTypeCount], PLATFORM_MAX_PATH);
            KvGetString(kv, "sound", g_JetpackTypeSound[g_JetpackTypeCount], PLATFORM_MAX_PATH);
            PrecacheSound(g_JetpackTypeSound[g_JetpackTypeCount], true);
            g_JetpackTypeCount++;

        } while(KvGotoNextKey(kv) && g_JetpackTypeCount < MAX_JETPACK_TYPES);

    } else {
        LogError("File Not Found: %s", path);
    }

    CloseHandle(kv);
}

public OnClientDisconnect(client)
{
    StopJetpack(client);
}

public OnGameFrame()
{
    if(AreJetpacksEnabled())
    {
        for (new client=1; client <= MaxClients; client++)
        {
            //For each client using a jetpack
            if(IsClientUsingJetpack(client))
            {
                JetpackStep(client);
            }

        }

    }
}

public Action:OnPlayerRunCmd(client, &buttons, &impulse, Float:vel[3], Float:angles[3], &weapon, &subtype, &cmdnum, &tickcount, &seed, mouse[2])
{
    if(!AreJetpacksEnabled()) return Plugin_Continue;

    if(!IsClientUsingJetpack(client) && buttons & IN_JUMP)
    {
        new player = GetClientUserId(client);
        CreateTimer(0.17, HeldJump, player);
    }

    return Plugin_Continue;
}

//Call back for when a player presses jump.  Checks if jump is still held down
public Action:HeldJump(Handle:timer, any:player)
{
    new client = GetClientOfUserId(player);

    if(!IsClientInGame(client)) return Plugin_Handled;

    if(!IsClientUsingJetpack(client) && GetClientButtons(client) & IN_JUMP)
    {
        StartJetpack(client);
    }

    return Plugin_Handled;
}

bool:IsClientUsingJetpack(client)
{
    return g_IsUsingJetpack[client];
}

bool:AreJetpacksEnabled()
{
    return GetConVarBool(g_Cvar_Enabled);
}

StartJetpack(client)
{
    SetEntityMoveType(client, MOVETYPE_FLY);
    SetEntityMoveCollide(client, MOVECOLLIDE_FLY_BOUNCE);
    g_IsUsingJetpack[client] = true;

    selected = g_ClientSelectedJetpackType[client];
    EmitSoundToAll(g_JetpackTypeSound[selected], client, SNDCHAN_AUTO);

    static const Float:ang[3] = { -25.0, 90.0, 0.0 };
    static const Float:pos[3] = {   0.0, 10.0, 1.0 };
    g_JetpackParticle[client][0] = CreateParticle(g_JetpackTypeParticle[selected], 0.0, client, Attach, "flag", pos, ang);

    //https://forums.alliedmods.net/showthread.php?t=127111
    //pyrovision_flaming_arrow
    //pyrovision_flying_flaming_arrow
    //ghost_pumpkin
    //ghost_pumpkin_flyingbits

    //burningplayer_corpse_rainbow_stars
    //burningplayer_rainbow_OLD
    //flamethrower_rainbow_bubbles02
    //burninggibs
    //electrocuted_blue
    //spell_batball_blue
    //g_JetpackParticle[client][0] = CreateParticle("halloween_rockettrail", 0.0, client, Attach, "flag", pos, ang);
}

StopJetpack(client)
{
    //TODO handle changes in sound and particle while jetpack active
    SetEntityMoveType(client, MOVETYPE_WALK);
    SetEntityMoveCollide(client, MOVECOLLIDE_DEFAULT);
    g_IsUsingJetpack[client] = false;

    selected = g_ClientSelectedJetpackType[client];
    StopSound(client, SNDCHAN_AUTO, g_JetpackTypeSound[selected]);
    DeleteParticle(g_JetpackParticle[client][0]);
}

//Called each frame a client is using a jetpack
JetpackStep(client)
{
    new buttons = GetClientButtons(client);
    if(IsPlayerAlive(client) && (buttons & IN_JUMP))
    {
        JetpackPush(client, GetConVarFloat(g_Cvar_JetpackSpeed));
    }
    else
    {
        StopJetpack(client);
    }
}

JetpackPush(client, Float:force)
{
    new Float:vec[3];
    Entity_GetBaseVelocity(client, vec);
    vec[2] += force;
    Entity_SetBaseVelocity(client, vec);
}

SetEntityMoveCollide(entity, movecollide)
{
    if(g_Offset_movecollide == -1) return;
    SetEntData(entity, g_Offset_movecollide, movecollide);
}


OnBanClient song

OnBanClient song

/**
 * vim: set ts=4 :
 * =============================================================================
 * in_game_audio
 * TODO - Add your project's description
 *
 * Copyright 2013 ???
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <clientprefs>
#include <steamtools>
#include <base64>
#include <smjansson>
#undef REQUIRE_PLUGIN
#include <donator>

#define PLUGIN_VERSION "0.1"

public Plugin:myinfo =
{
    name = "In Game Audio",
    author = "CrimsonTautology",
    description = "Interact with the In Game Audio web api",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_in_game_audio"
};

#define QUERY_SONG_ROUTE "/v1/api/query_song"
#define MAP_THEME_ROUTE "/v1/api/map_theme"
#define USER_THEME_ROUTE "/v1/api/user_theme"
#define AUTHORIZE_USER_ROUTE "/v1/api/authorize_user"
#define SONGS_ROUTE "/songs"
#define DIRECTORIES_ROUTE "/directories"

#define MAX_STEAMID_LENGTH 21
#define MAX_COMMUNITYID_LENGTH 18
#define MAX_SONG_LENGTH 64

new Handle:g_Cvar_IGAApiKey = INVALID_HANDLE;
new Handle:g_Cvar_IGAUrl = INVALID_HANDLE;
new Handle:g_Cvar_IGADonatorsOnly = INVALID_HANDLE;
new Handle:g_Cvar_IGAEnabled = INVALID_HANDLE;
new Handle:g_Cvar_IGARequestCooldownTime = INVALID_HANDLE;

new Handle:g_Cookie_PallEnabled = INVALID_HANDLE;
new Handle:g_Cookie_Volume = INVALID_HANDLE;

new bool:g_IsInCooldown[MAXPLAYERS+1];
new bool:g_IsPallEnabled[MAXPLAYERS+1];
new bool:g_IsDonator[MAXPLAYERS+1];
new String:g_CurrentPallDescription[64];
new String:g_CurrentPallPath[64];
new String:g_CurrentPlastSongId[64];
new g_PNextFree[MAXPLAYERS+1];
new g_PallNextFree = 0;
new g_Volume[MAXPLAYERS+1];

new bool:g_DonatorLibrary = false;
public OnPluginStart()
{

    g_Cvar_IGAApiKey = CreateConVar("sm_iga_api_key", "", "API Key for your IGA webpage");
    g_Cvar_IGAUrl = CreateConVar("sm_iga_url", "", "URL to your IGA webpage");
    g_Cvar_IGADonatorsOnly = CreateConVar("sm_iga_donators_only", "1", "Whether or not only donators have access to pall");
    g_Cvar_IGAEnabled = CreateConVar("sm_iga_enabled", "1", "Whether or not pall is enabled");
    g_Cvar_IGARequestCooldownTime = CreateConVar("sm_iga_request_cooldown_time", "2.0", "How long in seconds before a client can send another http request");

    RegConsoleCmd("sm_p", Command_P, "Play a song for yourself");
    RegConsoleCmd("sm_pall", Command_Pall, "Play a song for everyone");
    RegConsoleCmd("sm_plist", Command_Plist, "Pop-up the song list");
    RegConsoleCmd("sm_stop", Command_Stop, "Stop the current song");
    RegAdminCmd("sm_fstop", Command_Fstop, ADMFLAG_VOTE, "[ADMIN] Stop the current pall for everyone");
    RegAdminCmd("sm_fpall", Command_Fpall, ADMFLAG_VOTE, "[ADMIN] Force everyone to listen to a song");
    RegConsoleCmd("sm_vol", Command_Vol, "Adjust your play volume");
    RegConsoleCmd("sm_nopall", Command_Nopall, "Turn off pall for yourself");
    RegConsoleCmd("sm_yespall", Command_Yespall, "Turn on pall for yourself");
    RegConsoleCmd("sm_plast", Command_Plast, "Play the last played song for yourself");
    RegConsoleCmd("sm_authorize_iga", Command_AuthorizeIGA, "Declare that you want to upload songs to the website.  This will set you as an uploader.");

    g_Cookie_Volume = RegClientCookie("iga_volume", "Volume to play at [0-10]; 0 muted, 10 loudest", CookieAccess_Private);
    g_Cookie_PallEnabled = RegClientCookie("iga_pall_enabled", "Whether you want pall enabled or not. If yes, you will hear music when other players call !pall", CookieAccess_Private);

    g_DonatorLibrary = LibraryExists("donators");

    HookEvent("teamplay_game_over", Event_MapChange);
}

public OnLibraryRemoved(const String:name[])
{
	if (StrEqual(name, "donators"))
	{
		g_DonatorLibrary = false;
	}
}

public OnLibraryAdded(const String:name[])
{
	if (StrEqual(name, "donators"))
	{
		g_DonatorLibrary = true;
	}
}

public OnClientConnected(client)
{
    g_IsInCooldown[client] = false;
    g_PNextFree[client] = 0;
    g_Volume[client] = 7;
    g_IsPallEnabled[client] = true;

}
public OnClientCookiesCached(client)
{
    new String:buffer[11];

    GetClientCookie(client, g_Cookie_Volume, buffer, sizeof(buffer));
    if (strlen(buffer) > 0){
        g_Volume[client] = StringToInt(buffer);
    }

    GetClientCookie(client, g_Cookie_PallEnabled, buffer, sizeof(buffer));
    if (strlen(buffer) > 0){
        g_IsPallEnabled[client] = bool:StringToInt(buffer);
    }

    if (DonatorCheck(client)){
        UserTheme(client);
    }
}

public OnPostDonatorCheck(client)
{
    g_IsDonator[client] = true;
}

public OnClientDisconnect(client)
{
    g_IsDonator[client] = false;
}

public OnMapStart()
{
    g_PallNextFree = 0;
}

public OnMapVoteStarted()
{
    MapTheme("current_map");
}

public Action:OnBanClient(client, time, flags, const String:reason[], const String:kick_message[], const String:command[], any:source)
{
    //TODO test
    PlaySongAll("187", false);
    return Plugin_Continue;
}

public Action:Command_P(client, args)
{
    if(IsClientInCooldown(client))
    {
        ReplyToCommand(client, "[IGA] User in cooldown.");
        return Plugin_Handled;
    }

    if(!GetConVarBool(g_Cvar_IGAEnabled))
    {
        ReplyToCommand(client, "[IGA] IGA not enabled.");
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client)){
        decl String:path[MAX_SONG_LENGTH];
        GetCmdArgString(path, sizeof(path));
        QuerySong(client, path, false);
    }

    return Plugin_Handled;
}

public Action:Command_Pall(client, args)
{
    if(IsClientInCooldown(client))
    {
        ReplyToCommand(client, "[IGA] User in cooldown.");
        return Plugin_Handled;
    }

    if(!GetConVarBool(g_Cvar_IGAEnabled))
    {
        ReplyToCommand(client, "[IGA] IGA not enabled.");
        return Plugin_Handled;
    }

    if(!DonatorCheck(client))
    {
        ReplyToCommand(client, "[IGA] Only donators can use this command.");
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client)){
        decl String:path[MAX_SONG_LENGTH];
        GetCmdArgString(path, sizeof(path));
        QuerySong(client, path, true);
    }

    return Plugin_Handled;
}

public Action:Command_Plist(client, args)
{
    if(client && IsClientAuthorized(client)){
        SongList(client);
    }

    return Plugin_Handled;
}

public Action:Command_Stop(client, args)
{
    StopSong(client);
    return Plugin_Handled;
}

public Action:Command_Fstop(client, args)
{
    StopSongAll();
    return Plugin_Handled;
}

public Action:Command_Fpall(client, args)
{
    if(client && IsClientAuthorized(client)){
        decl String:path[MAX_SONG_LENGTH];
        GetCmdArgString(path, sizeof(path));
        QuerySong(client, path, true, true);
    }

    return Plugin_Handled;
}

public Action:Command_Vol(client, args)
{
    if (client && args != 1)
    {
        ReplyToCommand(client, "[IGA] usage \"!vol [0-10]\".  Currently %d.", g_Volume[client]);
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client))
    {
        decl String:buffer[11];
        new volume;
        GetCmdArgString(buffer, sizeof(buffer));
        volume = StringToInt(buffer);
        if (volume >=0 && volume <= 10)
        {
            SetClientCookie(client, g_Cookie_Volume, buffer);
            g_Volume[client] = volume;
            ReplyToCommand(client, "[IGA] Set volume to %d.", volume);
        }else{
            ReplyToCommand(client, "[IGA] usage \"!vol [0-10]\".");
        }
    }

    return Plugin_Handled;
}

public Action:Command_Nopall(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        SetClientCookie(client, g_Cookie_PallEnabled, "0");
        g_IsPallEnabled[client] = false;
        ReplyToCommand(client, "[IGA] Disabled pall.  Type !yespall to renable it.");
    }
    return Plugin_Handled;
}

public Action:Command_Yespall(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        SetClientCookie(client, g_Cookie_PallEnabled, "1");
        g_IsPallEnabled[client] = true;
        ReplyToCommand(client, "[IGA] Enabled pall.  Type !nopall to disable it.");
    }
    return Plugin_Handled;
}

public Action:Command_Plast(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        PlaySong(client, g_CurrentPlastSongId);
    }
    return Plugin_Handled;
}

public Action:Command_AuthorizeIGA(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        AuthorizeUser(client);
    }
    return Plugin_Handled;
}

public Action:Event_MapChange(Handle:event, const String:name[], bool:dontBroadcast)
{
    //TODO get next map
    MapTheme("current_map");
    return Plugin_Continue;
}

public Steam_SetHTTPRequestGetOrPostParameterInt(&HTTPRequestHandle:request, const String:param[], value)
{
    new String:tmp[64];
    IntToString(value, tmp, sizeof(tmp));
    Steam_SetHTTPRequestGetOrPostParameter(request, param, tmp);
}

public SetAccessCode(&HTTPRequestHandle:request)
{
    decl String:api_key[128];
    GetConVarString(g_Cvar_IGAApiKey, api_key, sizeof(api_key));
    Steam_SetHTTPRequestGetOrPostParameter(request, "access_token", api_key);
}

public HTTPRequestHandle:CreateIGARequest(const String:route[])
{
    decl String:base_url[256], String:url[512];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));
    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(trim_length < 0)
    {
        //IGA Url not set
        return INVALID_HTTP_HANDLE;
    }

    //check for forward slash after base_url;
    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    Format(url, sizeof(url),
            "%s%s", base_url, route);

    new HTTPRequestHandle:request = Steam_CreateHTTPRequest(HTTPMethod_POST, url);
    SetAccessCode(request);

    return request;
}

public StartCooldown(client)
{
    //Ignore the server console
    if (client == 0)
        return;

    g_IsInCooldown[client] = true;
    CreateTimer(GetConVarFloat(g_Cvar_IGARequestCooldownTime), RemoveCooldown, client);
}

public bool:IsClientInCooldown(client)
{
    if(client == 0)
        return false;
    else
        return g_IsInCooldown[client];
}

public Action:RemoveCooldown(Handle:timer, any:client)
{
    g_IsInCooldown[client] = false;
}

public bool:IsInPall()
{
    return GetTime() < g_PallNextFree;
}
public bool:IsInP(client)
{
    return GetTime() < g_PNextFree[client];
}

//True if client can use a donator action. If donations are not enabled this
//will always be true, otherwise check if client is a donator.
public bool:DonatorCheck(client)
{
    if(!g_DonatorLibrary || !GetConVarBool(g_Cvar_IGADonatorsOnly))
        return true;
    else
        return g_IsDonator[client];
}

stock QuerySong(client, String:path[MAX_SONG_LENGTH], bool:pall = false, bool:force=false)
{
    new HTTPRequestHandle:request = CreateIGARequest(QUERY_SONG_ROUTE);
    new player = client > 0 ? GetClientUserId(client) : 0;

    if(request == INVALID_HTTP_HANDLE)
    {
        ReplyToCommand(client, "[IGA] sm_iga_url invalid; cannot create HTTP request");
        return;
    }

    Steam_SetHTTPRequestGetOrPostParameterInt(request, "pall", pall);
    Steam_SetHTTPRequestGetOrPostParameterInt(request, "force", force);
    Steam_SetHTTPRequestGetOrPostParameter(request, "path", path);

    Steam_SendHTTPRequest(request, ReceiveQuerySong, player);

    StartCooldown(client);
}

public ReceiveQuerySong(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    new client = GetClientOfUserId(userid);
    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[IGA] Error at RecivedQuerySong (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    decl String:data[4096];
    Steam_GetHTTPResponseBodyData(request, data, sizeof(data));
    Steam_ReleaseHTTPRequest(request);

    new Handle:json = json_load(data);
    new bool:found = json_object_get_bool(json, "found");

    if(found)
    {
        new duration = json_object_get_int(json, "duration");
        new bool:pall = json_object_get_bool(json, "pall");
        new bool:force = json_object_get_bool(json, "force");
        new String:song_id[64], String:full_path[64], String:description[64], String:duration_formated[64];
        json_object_get_string(json, "song_id", song_id, sizeof(song_id));
        json_object_get_string(json, "full_path", full_path, sizeof(full_path));
        json_object_get_string(json, "description", description, sizeof(description));
        json_object_get_string(json, "duration_formated", duration_formated, sizeof(duration_formated));

        if(pall)
        {
            if(!IsInPall())
            {
                g_PallNextFree = duration + GetTime();

                PrintToChatAll("[IGA] Started Playing \"%s\" to all.", description);
                PrintToChatAll("Duration %s.", duration_formated);
                PrintToChatAll("Type !stop to cancel or !nopall to mute.");

                strcopy(g_CurrentPallPath, 64, full_path);
                strcopy(g_CurrentPallDescription, 64, description);

                PlaySongAll(song_id, force);
            }else{
                new minutes = (g_PallNextFree - GetTime()) / 60;
                new seconds = (g_PallNextFree - GetTime()) % 60;

                if (minutes > 1)
                    PrintToChat(client, "[IGA] pall currently playing %s \"%s\". Please wait %d more minutes.", g_CurrentPallPath, g_CurrentPallDescription, minutes);
                else
                    PrintToChat(client, "[IGA] pall currently playing %s \"%s\". Please wait %d more seconds.", g_CurrentPallPath, g_CurrentPallDescription, seconds);
            }
        }else if(client > 0){
            decl String:name[64];
            GetClientName(client, name, sizeof(name));

            g_PNextFree[client] = duration + GetTime();

            //PrintToChat(client, "[IGA] Started Playing \"%s\"", description);
            PrintToChatAll("[IGA] %s is currently playing \"%s\", type !p %s to play for yourself.", name, description, full_path);
            PrintToChat(client, "Duration %s.", duration_formated);
            PrintToChat(client, "Type !stop to cancel.");

            strcopy(g_CurrentPlastSongId, 64, song_id);

            PlaySong(client, song_id);
        }
    }else{
        PrintToChat(client, "[IGA] Could not find specified sound or directory.");
    }

    CloseHandle(json);
}

stock UserTheme(client)
{
    new HTTPRequestHandle:request = CreateIGARequest(USER_THEME_ROUTE);

    if(request == INVALID_HTTP_HANDLE)
    {
        PrintToConsole(0, "[IGA] sm_iga_url invalid; cannot create HTTP request");
        return;
    }

    //Find the user's theme
    decl String:uid[MAX_COMMUNITYID_LENGTH];
    Steam_GetCSteamIDForClient(client, uid, sizeof(uid));
    Steam_SetHTTPRequestGetOrPostParameter(request, "uid", uid);

    Steam_SendHTTPRequest(request, ReceiveTheme, 0);
}

stock MapTheme(String:map_theme[] ="")
{
    new HTTPRequestHandle:request = CreateIGARequest(MAP_THEME_ROUTE);

    if(request == INVALID_HTTP_HANDLE)
    {
        PrintToConsole(0, "[IGA] sm_iga_url invalid; cannot create HTTP request");
        return;
    }

    Steam_SetHTTPRequestGetOrPostParameter(request, "map", map_theme);
    Steam_SendHTTPRequest(request, ReceiveTheme, 0);
}

public ReceiveTheme(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[IGA] Error at RecivedTheme (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    decl String:data[4096];
    Steam_GetHTTPResponseBodyData(request, data, sizeof(data));
    Steam_ReleaseHTTPRequest(request);

    new Handle:json = json_load(data);
    new bool:found = json_object_get_bool(json, "found");

    if(found)
    {
        new bool:force = json_object_get_bool(json, "force");
        new String:song_id[64];
        json_object_get_string(json, "song_id", song_id, sizeof(song_id));

        if(force || !IsInPall())
        {
            g_PallNextFree = 0;
            PlaySongAll(song_id, force);
        }
    }

    CloseHandle(json);
}

stock AuthorizeUser(client)
{
    new HTTPRequestHandle:request = CreateIGARequest(AUTHORIZE_USER_ROUTE);
    new player = client > 0 ? GetClientUserId(client) : 0;

    if(request == INVALID_HTTP_HANDLE)
    {
        ReplyToCommand(client, "[IGA] sm_iga_url invalid; cannot create HTTP request");
        return;
    }

    decl String:uid[MAX_COMMUNITYID_LENGTH];
    Steam_GetCSteamIDForClient(client, uid, sizeof(uid));
    Steam_SetHTTPRequestGetOrPostParameter(request, "uid", uid);

    Steam_SendHTTPRequest(request, ReceiveAuthorizeUser, player);

    StartCooldown(client);
}

public ReceiveAuthorizeUser(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    new client = GetClientOfUserId(userid);
    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[IGA] Error at RecivedAuthorizeUser (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    Steam_ReleaseHTTPRequest(request);
    if(client)
    {
        PrintToChat(client, "[IGA] You are now authorized to upload songs.");
    }
}

public PlaySongAll(String:song[], bool:force)
{
    for (new client=1; client <= MaxClients; client++)
    {
        if ( ClientHasPallEnabled(client) && (force || !IsInP(client)) )
        {
            PlaySong(client, song);
        }
    }
}

public PlaySong(client, String:song_id[])
{
    if(!IsClientInGame(client))
    {
        return;
    }
    decl String:url[256], String:base_url[128];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));

    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    decl String:api_key[128];
    GetConVarString(g_Cvar_IGAApiKey, api_key, sizeof(api_key));

    Format(url, sizeof(url),
            "%s%s/%s/play?access_token=%s&volume=%f", base_url, SONGS_ROUTE, song_id, api_key, (g_Volume[client] / 10.0));

    new Handle:panel = CreateKeyValues("data");
    KvSetString(panel, "title", "In Game Audio");
    KvSetNum(panel, "type", MOTDPANEL_TYPE_URL);
    KvSetString(panel, "msg", url);

    ShowVGUIPanel(client, "info", panel, false);
    CloseHandle(panel);
    return;
}

public StopSong(client)
{
    g_PNextFree[client] = 0;
    PlaySong(client, "stop");//TODO
}
public StopSongAll()
{
    g_PallNextFree = 0;
    PlaySongAll("stop", true);//TODO
}

public bool:ClientHasPallEnabled(client)
{
    return g_IsPallEnabled[client];
}

public SongList(client)
{
    decl String:url[256], String:base_url[128];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));

    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    Format(url, sizeof(url),
            "%s%s", base_url, DIRECTORIES_ROUTE);

    ShowMOTDPanel(client, "Song List", url, MOTDPANEL_TYPE_URL);

}

/**
 * vim: set ts=4 :
 * =============================================================================
 * in_game_audio
 * TODO - Add your project's description
 *
 * Copyright 2013 ???
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <clientprefs>
#include <steamtools>
#include <base64>
#include <smjansson>
#undef REQUIRE_PLUGIN
#include <donator>

#define PLUGIN_VERSION "0.1"

public Plugin:myinfo =
{
    name = "In Game Audio",
    author = "CrimsonTautology",
    description = "Interact with the In Game Audio web api",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_in_game_audio"
};

#define QUERY_SONG_ROUTE "/v1/api/query_song"
#define MAP_THEME_ROUTE "/v1/api/map_theme"
#define USER_THEME_ROUTE "/v1/api/user_theme"
#define AUTHORIZE_USER_ROUTE "/v1/api/authorize_user"
#define SONGS_ROUTE "/songs"
#define DIRECTORIES_ROUTE "/directories"

#define MAX_STEAMID_LENGTH 21
#define MAX_COMMUNITYID_LENGTH 18
#define MAX_SONG_LENGTH 64

new Handle:g_Cvar_IGAApiKey = INVALID_HANDLE;
new Handle:g_Cvar_IGAUrl = INVALID_HANDLE;
new Handle:g_Cvar_IGADonatorsOnly = INVALID_HANDLE;
new Handle:g_Cvar_IGAEnabled = INVALID_HANDLE;
new Handle:g_Cvar_IGARequestCooldownTime = INVALID_HANDLE;

new Handle:g_Cookie_PallEnabled = INVALID_HANDLE;
new Handle:g_Cookie_Volume = INVALID_HANDLE;

new bool:g_IsInCooldown[MAXPLAYERS+1];
new bool:g_IsPallEnabled[MAXPLAYERS+1];
new bool:g_IsDonator[MAXPLAYERS+1];
new String:g_CurrentPallDescription[64];
new String:g_CurrentPallPath[64];
new String:g_CurrentPlastSongId[64];
new g_PNextFree[MAXPLAYERS+1];
new g_PallNextFree = 0;
new g_Volume[MAXPLAYERS+1];

new bool:g_DonatorLibrary = false;
public OnPluginStart()
{

    g_Cvar_IGAApiKey = CreateConVar("sm_iga_api_key", "", "API Key for your IGA webpage");
    g_Cvar_IGAUrl = CreateConVar("sm_iga_url", "", "URL to your IGA webpage");
    g_Cvar_IGADonatorsOnly = CreateConVar("sm_iga_donators_only", "1", "Whether or not only donators have access to pall");
    g_Cvar_IGAEnabled = CreateConVar("sm_iga_enabled", "1", "Whether or not pall is enabled");
    g_Cvar_IGARequestCooldownTime = CreateConVar("sm_iga_request_cooldown_time", "2.0", "How long in seconds before a client can send another http request");

    RegConsoleCmd("sm_p", Command_P, "Play a song for yourself");
    RegConsoleCmd("sm_pall", Command_Pall, "Play a song for everyone");
    RegConsoleCmd("sm_plist", Command_Plist, "Pop-up the song list");
    RegConsoleCmd("sm_stop", Command_Stop, "Stop the current song");
    RegAdminCmd("sm_fstop", Command_Fstop, ADMFLAG_VOTE, "[ADMIN] Stop the current pall for everyone");
    RegAdminCmd("sm_fpall", Command_Fpall, ADMFLAG_VOTE, "[ADMIN] Force everyone to listen to a song");
    RegConsoleCmd("sm_vol", Command_Vol, "Adjust your play volume");
    RegConsoleCmd("sm_nopall", Command_Nopall, "Turn off pall for yourself");
    RegConsoleCmd("sm_yespall", Command_Yespall, "Turn on pall for yourself");
    RegConsoleCmd("sm_plast", Command_Plast, "Play the last played song for yourself");
    RegConsoleCmd("sm_authorize_iga", Command_AuthorizeIGA, "Declare that you want to upload songs to the website.  This will set you as an uploader.");

    g_Cookie_Volume = RegClientCookie("iga_volume", "Volume to play at [0-10]; 0 muted, 10 loudest", CookieAccess_Private);
    g_Cookie_PallEnabled = RegClientCookie("iga_pall_enabled", "Whether you want pall enabled or not. If yes, you will hear music when other players call !pall", CookieAccess_Private);

    g_DonatorLibrary = LibraryExists("donators");

    HookEvent("teamplay_game_over", Event_MapChange);
    //TODO hook pre-rtv map change
    //HookEvent("map_change", Event_MapChange);
}

public OnLibraryRemoved(const String:name[])
{
	if (StrEqual(name, "donators"))
	{
		g_DonatorLibrary = false;
	}
}

public OnLibraryAdded(const String:name[])
{
	if (StrEqual(name, "donators"))
	{
		g_DonatorLibrary = true;
	}
}

public OnClientConnected(client)
{
    g_IsInCooldown[client] = false;
    g_PNextFree[client] = 0;
    g_Volume[client] = 7;
    g_IsPallEnabled[client] = true;

}
public OnClientCookiesCached(client)
{
    new String:buffer[11];

    GetClientCookie(client, g_Cookie_Volume, buffer, sizeof(buffer));
    if (strlen(buffer) > 0){
        g_Volume[client] = StringToInt(buffer);
    }

    GetClientCookie(client, g_Cookie_PallEnabled, buffer, sizeof(buffer));
    if (strlen(buffer) > 0){
        g_IsPallEnabled[client] = bool:StringToInt(buffer);
    }

    if (DonatorCheck(client)){
        UserTheme(client);
    }
}

public OnPostDonatorCheck(client)
{
    g_IsDonator[client] = true;
}

public OnClientDisconnect(client)
{
    g_IsDonator[client] = false;
}

public OnMapStart()
{
    g_PallNextFree = 0;
}

public Action:Command_P(client, args)
{
    if(IsClientInCooldown(client))
    {
        ReplyToCommand(client, "[IGA] User in cooldown.");
        return Plugin_Handled;
    }

    if(!GetConVarBool(g_Cvar_IGAEnabled))
    {
        ReplyToCommand(client, "[IGA] IGA not enabled.");
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client)){
        decl String:path[MAX_SONG_LENGTH];
        GetCmdArgString(path, sizeof(path));
        QuerySong(client, path, false);
    }

    return Plugin_Handled;
}

public Action:Command_Pall(client, args)
{
    if(IsClientInCooldown(client))
    {
        ReplyToCommand(client, "[IGA] User in cooldown.");
        return Plugin_Handled;
    }

    if(!GetConVarBool(g_Cvar_IGAEnabled))
    {
        ReplyToCommand(client, "[IGA] IGA not enabled.");
        return Plugin_Handled;
    }

    if(!DonatorCheck(client))
    {
        ReplyToCommand(client, "[IGA] Only donators can use this command.");
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client)){
        decl String:path[MAX_SONG_LENGTH];
        GetCmdArgString(path, sizeof(path));
        QuerySong(client, path, true);
    }

    return Plugin_Handled;
}

public Action:Command_Plist(client, args)
{
    if(client && IsClientAuthorized(client)){
        SongList(client);
    }

    return Plugin_Handled;
}

public Action:Command_Stop(client, args)
{
    StopSong(client);
    return Plugin_Handled;
}

public Action:Command_Fstop(client, args)
{
    StopSongAll();
    return Plugin_Handled;
}

public Action:Command_Fpall(client, args)
{
    if(client && IsClientAuthorized(client)){
        decl String:path[MAX_SONG_LENGTH];
        GetCmdArgString(path, sizeof(path));
        QuerySong(client, path, true, true);
    }

    return Plugin_Handled;
}

public Action:Command_Vol(client, args)
{
    if (client && args != 1)
    {
        ReplyToCommand(client, "[IGA] usage \"!vol [0-10]\".  Currently %d.", g_Volume[client]);
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client))
    {
        decl String:buffer[11];
        new volume;
        GetCmdArgString(buffer, sizeof(buffer));
        volume = StringToInt(buffer);
        if (volume >=0 && volume <= 10)
        {
            SetClientCookie(client, g_Cookie_Volume, buffer);
            g_Volume[client] = volume;
            ReplyToCommand(client, "[IGA] Set volume to %d.", volume);
        }else{
            ReplyToCommand(client, "[IGA] usage \"!vol [0-10]\".");
        }
    }

    return Plugin_Handled;
}

public Action:Command_Nopall(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        SetClientCookie(client, g_Cookie_PallEnabled, "0");
        g_IsPallEnabled[client] = false;
        ReplyToCommand(client, "[IGA] Disabled pall.  Type !yespall to renable it.");
    }
    return Plugin_Handled;
}

public Action:Command_Yespall(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        SetClientCookie(client, g_Cookie_PallEnabled, "1");
        g_IsPallEnabled[client] = true;
        ReplyToCommand(client, "[IGA] Enabled pall.  Type !nopall to disable it.");
    }
    return Plugin_Handled;
}

public Action:Command_Plast(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        PlaySong(client, g_CurrentPlastSongId);
    }
    return Plugin_Handled;
}

public Action:Command_AuthorizeIGA(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        AuthorizeUser(client);
    }
    return Plugin_Handled;
}

public Action:Event_MapChange(Handle:event, const String:name[], bool:dontBroadcast)
{
    //TODO get current map
    MapTheme("current_map");
    return Plugin_Continue;
}

public Steam_SetHTTPRequestGetOrPostParameterInt(&HTTPRequestHandle:request, const String:param[], value)
{
    new String:tmp[64];
    IntToString(value, tmp, sizeof(tmp));
    Steam_SetHTTPRequestGetOrPostParameter(request, param, tmp);
}

public SetAccessCode(&HTTPRequestHandle:request)
{
    decl String:api_key[128];
    GetConVarString(g_Cvar_IGAApiKey, api_key, sizeof(api_key));
    Steam_SetHTTPRequestGetOrPostParameter(request, "access_token", api_key);
}

public HTTPRequestHandle:CreateIGARequest(const String:route[])
{
    decl String:base_url[256], String:url[512];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));
    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(trim_length < 0)
    {
        //IGA Url not set
        return INVALID_HTTP_HANDLE;
    }

    //check for forward slash after base_url;
    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    Format(url, sizeof(url),
            "%s%s", base_url, route);

    new HTTPRequestHandle:request = Steam_CreateHTTPRequest(HTTPMethod_POST, url);
    SetAccessCode(request);

    return request;
}

public StartCooldown(client)
{
    //Ignore the server console
    if (client == 0)
        return;

    g_IsInCooldown[client] = true;
    CreateTimer(GetConVarFloat(g_Cvar_IGARequestCooldownTime), RemoveCooldown, client);
}

public bool:IsClientInCooldown(client)
{
    if(client == 0)
        return false;
    else
        return g_IsInCooldown[client];
}

public Action:RemoveCooldown(Handle:timer, any:client)
{
    g_IsInCooldown[client] = false;
}

public bool:IsInPall()
{
    return GetTime() < g_PallNextFree;
}
public bool:IsInP(client)
{
    return GetTime() < g_PNextFree[client];
}

//True if client can use a donator action. If donations are not enabled this
//will always be true, otherwise check if client is a donator.
public bool:DonatorCheck(client)
{
    if(!g_DonatorLibrary || !GetConVarBool(g_Cvar_IGADonatorsOnly))
        return true;
    else
        return g_IsDonator[client];
}

stock QuerySong(client, String:path[MAX_SONG_LENGTH], bool:pall = false, bool:force=false)
{
    new HTTPRequestHandle:request = CreateIGARequest(QUERY_SONG_ROUTE);
    new player = client > 0 ? GetClientUserId(client) : 0;

    if(request == INVALID_HTTP_HANDLE)
    {
        ReplyToCommand(client, "[IGA] sm_iga_url invalid; cannot create HTTP request");
        return;
    }

    Steam_SetHTTPRequestGetOrPostParameterInt(request, "pall", pall);
    Steam_SetHTTPRequestGetOrPostParameterInt(request, "force", force);
    Steam_SetHTTPRequestGetOrPostParameter(request, "path", path);

    Steam_SendHTTPRequest(request, ReceiveQuerySong, player);

    StartCooldown(client);
}

public ReceiveQuerySong(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    new client = GetClientOfUserId(userid);
    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[IGA] Error at RecivedQuerySong (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    decl String:data[4096];
    Steam_GetHTTPResponseBodyData(request, data, sizeof(data));
    Steam_ReleaseHTTPRequest(request);

    new Handle:json = json_load(data);
    new bool:found = json_object_get_bool(json, "found");

    if(found)
    {
        new duration = json_object_get_int(json, "duration");
        new bool:pall = json_object_get_bool(json, "pall");
        new bool:force = json_object_get_bool(json, "force");
        new String:song_id[64], String:full_path[64], String:description[64], String:duration_formated[64];
        json_object_get_string(json, "song_id", song_id, sizeof(song_id));
        json_object_get_string(json, "full_path", full_path, sizeof(full_path));
        json_object_get_string(json, "description", description, sizeof(description));
        json_object_get_string(json, "duration_formated", duration_formated, sizeof(duration_formated));

        if(pall)
        {
            if(!IsInPall())
            {
                g_PallNextFree = duration + GetTime();

                PrintToChatAll("[IGA] Started Playing \"%s\" to all.", description);
                PrintToChatAll("Duration %s.", duration_formated);
                PrintToChatAll("Type !stop to cancel or !nopall to mute.");

                strcopy(g_CurrentPallPath, 64, full_path);
                strcopy(g_CurrentPallDescription, 64, description);

                PlaySongAll(song_id, force);
            }else{
                PrintToChat(client, "[IGA] pall currently playing %s \"%s\".", g_CurrentPallPath, g_CurrentPallDescription);
            }
        }else if(client > 0){
            decl String:name[64];
            GetClientName(client, name, sizeof(name));

            g_PNextFree[client] = duration + GetTime();

            //PrintToChat(client, "[IGA] Started Playing \"%s\"", description);
            PrintToChatAll("[IGA] %s is currently playing \"%s\", type !p %s to play for yourself.", name, description, full_path);
            PrintToChat(client, "Duration %s.", duration_formated);
            PrintToChat(client, "Type !stop to cancel.");

            strcopy(g_CurrentPlastSongId, 64, song_id);

            PlaySong(client, song_id);
        }
    }else{
        PrintToChat(client, "[IGA] Could not find specified sound or directory.");
    }

    CloseHandle(json);
}

stock UserTheme(client)
{
    new HTTPRequestHandle:request = CreateIGARequest(USER_THEME_ROUTE);

    if(request == INVALID_HTTP_HANDLE)
    {
        PrintToConsole(0, "[IGA] sm_iga_url invalid; cannot create HTTP request");
        return;
    }

    //Find the user's theme
    decl String:uid[MAX_COMMUNITYID_LENGTH];
    Steam_GetCSteamIDForClient(client, uid, sizeof(uid));
    Steam_SetHTTPRequestGetOrPostParameter(request, "uid", uid);

    Steam_SendHTTPRequest(request, ReceiveTheme, 0);
}

stock MapTheme(String:map_theme[] ="")
{
    new HTTPRequestHandle:request = CreateIGARequest(MAP_THEME_ROUTE);

    if(request == INVALID_HTTP_HANDLE)
    {
        PrintToConsole(0, "[IGA] sm_iga_url invalid; cannot create HTTP request");
        return;
    }

    Steam_SetHTTPRequestGetOrPostParameter(request, "map", map_theme);
    Steam_SendHTTPRequest(request, ReceiveTheme, 0);
}

public ReceiveTheme(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[IGA] Error at RecivedTheme (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    decl String:data[4096];
    Steam_GetHTTPResponseBodyData(request, data, sizeof(data));
    Steam_ReleaseHTTPRequest(request);

    new Handle:json = json_load(data);
    new bool:found = json_object_get_bool(json, "found");

    if(found)
    {
        new bool:force = json_object_get_bool(json, "force");
        new String:song_id[64];
        json_object_get_string(json, "song_id", song_id, sizeof(song_id));

        if(force || !IsInPall())
        {
            g_PallNextFree = 0;
            PlaySongAll(song_id, force);
        }
    }

    CloseHandle(json);
}

stock AuthorizeUser(client)
{
    new HTTPRequestHandle:request = CreateIGARequest(AUTHORIZE_USER_ROUTE);
    new player = client > 0 ? GetClientUserId(client) : 0;

    if(request == INVALID_HTTP_HANDLE)
    {
        ReplyToCommand(client, "[IGA] sm_iga_url invalid; cannot create HTTP request");
        return;
    }

    decl String:uid[MAX_COMMUNITYID_LENGTH];
    Steam_GetCSteamIDForClient(client, uid, sizeof(uid));
    Steam_SetHTTPRequestGetOrPostParameter(request, "uid", uid);

    Steam_SendHTTPRequest(request, ReceiveAuthorizeUser, player);

    StartCooldown(client);
}

public ReceiveAuthorizeUser(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    new client = GetClientOfUserId(userid);
    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[IGA] Error at RecivedAuthorizeUser (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    Steam_ReleaseHTTPRequest(request);
    if(client)
    {
        PrintToChat(client, "[IGA] You are now authorized to upload songs.");
    }
}

public PlaySongAll(String:song[], bool:force)
{
    for (new client=1; client <= MaxClients; client++)
    {
        if ( ClientHasPallEnabled(client) && (force || !IsInP(client)) )
        {
            PlaySong(client, song);
        }
    }
}

public PlaySong(client, String:song_id[])
{
    if(!IsClientInGame(client))
    {
        return;
    }
    decl String:url[256], String:base_url[128];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));

    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    decl String:api_key[128];
    GetConVarString(g_Cvar_IGAApiKey, api_key, sizeof(api_key));

    Format(url, sizeof(url),
            "%s%s/%s/play?access_token=%s&volume=%f", base_url, SONGS_ROUTE, song_id, api_key, (g_Volume[client] / 10.0));

    new Handle:panel = CreateKeyValues("data");
    KvSetString(panel, "title", "In Game Audio");
    KvSetNum(panel, "type", MOTDPANEL_TYPE_URL);
    KvSetString(panel, "msg", url);

    ShowVGUIPanel(client, "info", panel, false);
    CloseHandle(panel);
    return;
}

public StopSong(client)
{
    g_PNextFree[client] = 0;
    PlaySong(client, "stop");//TODO
}
public StopSongAll()
{
    g_PallNextFree = 0;
    PlaySongAll("stop", true);//TODO
}

public bool:ClientHasPallEnabled(client)
{
    return g_IsPallEnabled[client];
}

public SongList(client)
{
    decl String:url[256], String:base_url[128];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));

    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    Format(url, sizeof(url),
            "%s%s", base_url, DIRECTORIES_ROUTE);

    ShowMOTDPanel(client, "Song List", url, MOTDPANEL_TYPE_URL);

}


Update URL for sourcemod data feed.

Update URL for sourcemod data feed.

/*
CHANGE LOG
7/25/13		Converted SQL queries for nowplaying data
				to now use an http download of a txt file instead
			Added an autogen config file called...um...something
7/26/13		Switched to using SteamTools from cURL due to VC++ issues
7/27/13		Fixed HTTP caching issue with download
7/28/13		Fixed bug with calling station by name
			Fixed bug with station info storage array
8/7/13		Fixed bug with sending client in timer rather than clienid
			Fixed bug of using IsClientConnected rather than IsClientInGame
TO DO LIST
	Make a sm_resume command to quickly reload station stream in case another plugin uses the html page
	Make a sm_mute command
	Make an Admin command to cease playpack for everypony
*/
#pragma semicolon 1
#include <sourcemod>
#include <colors>
#include <steamtools>

#define PLUGIN_VERSION "2.2.3"

public Plugin:myinfo =
{
	name = "[ANY]Ponyville Live Radio",
	author = "WeAreBorg",
	description = "Bucking awesome now with no SQL",
	version = PLUGIN_VERSION,
	url = "www.ponyvillelive.com"
}

new Handle:A_Station = INVALID_HANDLE;		//why are there 5 arrays?
new Handle:A_Song = INVALID_HANDLE;			//Because Ponies drove me to drink
new Handle:A_Artist = INVALID_HANDLE;
new Handle:A_ID = INVALID_HANDLE;
new Handle:A_Listeners = INVALID_HANDLE;
new HTTPRequestHandle:g_HTTPRequest = INVALID_HTTP_HANDLE;

new Handle:g_timer = INVALID_HANDLE;
new Handle:g_volume = INVALID_HANDLE;
new Handle:g_Adverts = INVALID_HANDLE;

new tunedstation[MAXPLAYERS] = -1;			//this is the arrayid
new tunedvolume[MAXPLAYERS];
new reconnect[MAXPLAYERS];

public OnPluginStart()
{
	CreateConVar("PonyRadio_version", PLUGIN_VERSION, "Installed version of Pony Radio on this server", FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY|FCVAR_DONTRECORD);
	g_timer = CreateConVar("PonyRadio_updatetimer", "15.0", "How often to check for new song info in seconds",FCVAR_PLUGIN|FCVAR_NOTIFY, true, 15.0, true, 60.0);
	g_volume = CreateConVar("PonyRadio_volume", "30", "Default Volume Percent",FCVAR_PLUGIN|FCVAR_NOTIFY, true, 0.0, true, 100.0);
	g_Adverts = CreateConVar("PonyRadio_Advertchance", "50", "Chance of advertisement of radio playing ",FCVAR_PLUGIN|FCVAR_NOTIFY, true, 0.0, true, 100.0);

	RegConsoleCmd("sm_radio", DisplayStations);
	RegAdminCmd("sm_radioall", RadioAll, ADMFLAG_SLAY);
	RegConsoleCmd("sm_radiooff", EndTransmission);
	RegConsoleCmd("sm_radiostop", EndTransmission);
	RegConsoleCmd("sm_radiohelp", FlashTest);
	RegConsoleCmd("sm_nowplaying", GetSongs);
	RegConsoleCmd("sm_np", GetSongs);
	RegConsoleCmd("sm_vol", SetVolume);
	RegConsoleCmd("sm_volume", SetVolume);

	AutoExecConfig(true, "PVLPonyRadio");

	HookEvent("player_connect", Event_PlayerConnect);
	HookEvent("player_spawn", Hook_PlayerSpawn);

	A_Station = CreateArray( 32 );
	A_Song = CreateArray( 100 );
	A_Artist = CreateArray( 100 );
	A_ID = CreateArray();
	A_Listeners = CreateArray();
	for(new i=1;i < MaxClients;i++) tunedstation[i] = -1;

	UpdateStations();
	CreateTimer(GetConVarFloat(g_timer), HTTPTimer, _, TIMER_REPEAT);
}

public Action:HTTPTimer(Handle:timer)
{
	UpdateStations();
}

public OnClientConnected(client)
{
	reconnect[client] = 0;
}

public Event_PlayerConnect(Handle:event, const String:name[], bool:dontBroadcast)
{
	new client=GetClientOfUserId(GetEventInt(event,"userid"));
	tunedvolume[client] = 0;
	tunedstation[client] = -1;
}

public Hook_PlayerSpawn(Handle:event, const String:name[], bool:dontBroadcast) //restart radio if they had it playing on first spawn
{
	//launch station precache
	new client = GetClientOfUserId(GetEventInt(event, "userid"));

	if (reconnect[client] == 2) {
		if(tunedstation[client] == -1) tunedstation[client] = GetRandomInt(0, GetArraySize( A_ID ));
		//need to time it cause too close to class select menu
		CreateTimer(15.0, RestartRadioTimer, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE);
	}
	reconnect[client]++;
}

public Action:RestartRadioTimer(Handle:timer, any: clientid)
{
	new client = GetClientOfUserId(clientid);
	if (tunedvolume[client] != 0) OutputSongs(client, tunedstation[client]);
	PlayerOTD(client);
}

public UpdateStations()
{
	//download nowplaying data
	if (g_HTTPRequest != INVALID_HTTP_HANDLE) g_HTTPRequest = INVALID_HTTP_HANDLE;

	//as seen on www.ponyvillelive.com
	g_HTTPRequest = Steam_CreateHTTPRequest(HTTPMethod_GET, "http://ponyvillelive.com/sourcemod/sourcemod.php");

	//asherkin is awesome
	Steam_SetHTTPRequestHeaderValue(g_HTTPRequest, "Pragma", "no-cache");
	Steam_SetHTTPRequestHeaderValue(g_HTTPRequest, "Cache-Control", "no-cache");
	Steam_SendHTTPRequest(g_HTTPRequest, OnDownloadComplete);
}

public OnDownloadComplete(HTTPRequestHandle:HTTPRequest, bool:requestSuccessful, HTTPStatusCode:statusCode)
{
	if (HTTPRequest != g_HTTPRequest || !requestSuccessful || statusCode != HTTPStatusCode_OK)
	{
		LogError("[PVL Pony Radio] Something went wrong with the HTTP download");	//I need some real error handling
		return;
	}
	decl String:data[6000];

	Steam_GetHTTPResponseBodyData(g_HTTPRequest, data, sizeof(data));

	Steam_ReleaseHTTPRequest(g_HTTPRequest);
	g_HTTPRequest = INVALID_HTTP_HANDLE;

	//Begin the fun

	new String:HTTPStations[9][1000] = {"", "", "", "", "", "", "", "", "" }; //TODO: fix this
	//id|Station Name|listeners|title|artist|...

	//First Exlpode into seperate station data to protect against errors. Silver Eagles idea
	ExplodeString(data,"<>",HTTPStations, sizeof(HTTPStations), sizeof(HTTPStations[]));	//I wanted a fancy delimiter with strange UTF-8 chars

	new stationid;
	new listeners;
	new String:station[32];
	new String:artist[100];
	new String:song[100];
	new String:Lsong[100];
	new count = 0;			//This is the array id

	new Float:AnnounceDelay = 10.0;		//Some tweaking to make sure song is announced when its actually playing

	decl String:TempArray[5][100];
	new count_b = 0;
	while (count_b < 9 && strlen(HTTPStations[count_b]) > 20)		//need to catch some trash chars too
	{
		//Explode each station data string based on seperate delimiter
		//id|Station Name|listeners|title|artist|...

		ExplodeString(HTTPStations[count_b],"|",TempArray, sizeof(TempArray), sizeof(TempArray[]));

		stationid = StringToInt(TempArray[0]);
		strcopy(station, sizeof(station), TempArray[1]);
		listeners = StringToInt(TempArray[2]);
		strcopy(song, sizeof(song), TempArray[3]);
		strcopy(artist, sizeof(artist), TempArray[4]);

		count_b++;

		//if (FindValueInArray(A_ID, stationid) == -1){		//Had some bs hitting this field
		if (FindStringInArray(A_Station, station) == -1){
			PushArrayCell( A_ID, stationid);
			PushArrayString( A_Station, station );
			PushArrayString( A_Song, song );						//if we start with NULLs w/e
			PushArrayString( A_Artist, artist );					//no more SQL queries...
			PushArrayCell( A_Listeners, listeners);
		} else {
			GetArrayString(A_Song, count, Lsong, sizeof(Lsong) ); 	//hold stations 'old' song

			SetArrayCell( A_Listeners, count, listeners);
			if (strlen(song) > 0) {
				SetArrayString(A_Song, count, song);
			} else SetArrayString(A_Song, count, "Unknown");		//If returned a NULL set to Unknown

			if (strlen(artist) > 0) {
				SetArrayString(A_Artist, count, artist);
			} else SetArrayString(A_Artist, count, "Unknown");		//If returned a NULL set to Unknown

			if (!StrEqual(Lsong, song)) {							//If both return NULL assume error and dont announce
				if (strlen(song) > 0 && strlen(artist) > 0)CreateTimer(AnnounceDelay, DelayAnnounceSong, count);
			}
		}
		count++;
	}
}

public Action:DelayAnnounceSong(Handle:timer, any:arrayid)
{
	AnnounceSong(arrayid);
}

public AnnounceSong(arrayid)
{
	for (new i = 1; i <= MaxClients; i++)
	{
		if (IsClientInGame(i) && !IsFakeClient(i) && tunedstation[i] == arrayid)
		{
			if (tunedvolume[i] != 0)
			{
				OutputSongs(i, arrayid);
			} else
			{
				new chance = GetRandomInt(0,100);
				if (chance > GetConVarInt(g_Adverts)) CPrintToChat(i, "[{green}PVL Radio{default}] {lightgreen}Tune in{default} now to hear some great {olive}Pony Music {default}from {lightgreen}Ponyville Live!");
			}
		}
	}
}

public Action:DisplayStations(client, args)
{
	UpdateStations();
	CPrintToChat(client,"[{green}PVL Radio{default}] Type {olive}!radio [station] {default}to skip menu, {olive}!radiooff{default} to stop, {olive}!vol {default}or {olive}!vol [0-100] {default}to set volume, {olive}!np {default}for song info, or {olive}!radiohelp{default} for help.");

	new String:station[32];
	new stationid = -1;

	if (args > 0) {
		new String:arg1[32];
		GetCmdArg(1, arg1, sizeof(arg1));
		new size = GetArraySize( A_Station );
		for (new i = 0; i < size; i++) {
			GetArrayString( A_Station, i, station, sizeof(station) );
			if (StrContains(station, arg1, false) != -1 && stationid == -1) stationid = i;
			else if (StrContains(station, arg1) != -1 && stationid != -1) {
				CPrintToChat(client,"[{green}PVL Radio{default}] More than one radio station matches that name.");
				break;
			}
		}

		if (stationid == -1) {
			CPrintToChat(client,"[{green}PVL Radio{default}] No radio station matches that name.");
		}
		else if (stationid != -1) {
			tunedstation[client] = stationid;
			if (tunedvolume[client] == 0) tunedvolume[client] = GetConVarInt(g_volume);
			PlayerOTD(client);
		}
	}

	if (stationid <= 0) {
		//menu can only have 1024 chars
		new String:artist[18];
		new String:song[25];
		new String:buffer[255];

		new Handle:panel = CreatePanel();
		SetPanelTitle(panel, "Ponyville Live!");

		new size = GetArraySize( A_Station );
		for (new i = 0; i < size; i++) {
			GetArrayString( A_Station, i, station, sizeof(station) );
			GetArrayString( A_Song, i, song, sizeof(song) );
			GetArrayString( A_Artist, i, artist, sizeof(artist) );
			//	👂 ੭  ୭ ᠀ ᧙ ☊ ♫   Yeah....just try and guess what i was doing here
			Format(buffer, sizeof(buffer), "%s  ☊%d", station, GetArrayCell(A_Listeners,i));
			DrawPanelItem(panel, buffer);

			//Truncate artist/song if they fill their strings
			new artist_size = sizeof(artist)-1;
			if (strlen(artist) == artist_size) {
				artist[artist_size] = '\0';
				artist[artist_size-1] = '.';
				artist[artist_size-2] = '.';
				artist[artist_size-3] = '.';
			}
			new song_size = sizeof(song)-1;
			if (strlen(song) == song_size) {
				song[song_size] = '\0';
				song[song_size-1] = '.';
				song[song_size-2] = '.';
				song[song_size-3] = '.';
			}
			Format(buffer, sizeof(buffer), "%s--%s", artist, song);
			DrawPanelText(panel, buffer);
		}
		DrawPanelItem(panel, "Cancel");
		SendPanelToClient(panel, client, PanelHandler, 45);
	}
}

public PanelHandler(Handle:panel, MenuAction:action, param1, param2)
{
	if (action == MenuAction_Select)
	{
		if (param2 <= GetArraySize(A_Station)) {
			tunedstation[param1] = param2 - 1;	//subtract one to account for menu reposition
			if (tunedvolume[param1] == 0) tunedvolume[param1] = GetConVarInt(g_volume);		//reset volume to default if muted
			PlayerOTD(param1);
		}
	} else if( action == MenuAction_Cancel )
	{
		if( param2 == MenuCancel_ExitBack )
		{
			CloseHandle(panel);
		}
	}
}

public Action:RadioAll(client, args)
{
	if(tunedstation[client] != -1)
	{
		for(new i=1; i < MAXPLAYERS; i++) {
			if (reconnect[i] > 2) {
				CPrintToChat(i,"[{green}PVL Radio{default}] An admin has turned on your radio! Type {olive}!radiooff{default} to end playback, {olive}!vol{default} to set volume or open vol menu, {olive}!np{default} for current song info");
				tunedstation[i] = tunedstation[client];
				if (tunedvolume[i] < 5) tunedvolume[i] = GetConVarInt(g_volume);
				PlayerOTD(i);
			}
		}
	}
	else {
		CPrintToChat(client,"[{green}PVL Radio{default}] Listen to a radio yourself first!");	//yay for laziness
	}
}

public PlayerOTD(client)
{
	new URLId = GetStationID(tunedstation[client] );
	if (URLId != -1) {
		new String:url[128];
		Format(url, sizeof(url), "http://ponyvillelive.com/index/tunein/id/%d/showonlystation/true/volume/%d/autoplay/true", URLId, tunedvolume[client] );

		new Handle:setup = CreateKeyValues("data");

		KvSetNum(setup, "type", MOTDPANEL_TYPE_URL);
		KvSetString(setup, "title", "PVL Player");
		KvSetString(setup, "msg", url);

		ShowVGUIPanel(client, "info", setup, false);
		CloseHandle(setup);
	}
}

public Action:SetVolume(client, args)
{
	if (args == 0)
	{
		DisplayVolumeMenu(client);
	} else {
		new String:arg1[32];
		GetCmdArg(1, arg1, sizeof(arg1));
		new volume = StringToInt(arg1);
		if (volume < 0) volume = 0;
		if (volume > 100) volume = 100;
		new old = tunedvolume[client];
		tunedvolume[client] = volume;

		if (old == volume){
			CPrintToChat(client, "[{green}PVL Radio{default}] Volume is Already set to {olive}%d", volume);
		} else {
			if(tunedstation[client] != -1) PlayerOTD(client);
			CPrintToChat(client, "[{green}PVL Radio{default}] Volume set to {olive}%d", volume);
		}
	}
}

DisplayVolumeMenu(client) {
	new Handle:menu = CreateMenu(MenuHandler_VolumeMenu);
	decl String:buffer[100];
	Format (buffer, sizeof(buffer), "PVL Volume current:%d", tunedvolume[client]);
	SetMenuTitle(menu, buffer);
	AddMenuItem(menu, "100", "100");
	AddMenuItem(menu, "85", "85");
	AddMenuItem(menu, "70", "70");
	AddMenuItem(menu, "55", "55");
	AddMenuItem(menu, "40", "40");
	AddMenuItem(menu, "25", "25");
	AddMenuItem(menu, "10", "10");
	SetMenuExitBackButton(menu, true);
	DisplayMenu(menu, client, MENU_TIME_FOREVER);
}

public MenuHandler_VolumeMenu(Handle:menu, MenuAction:action, param1, param2)
{
	if( action == MenuAction_Cancel )
	{
		if( param2 == MenuCancel_ExitBack )
		{
			CloseHandle(menu);
		}
	}
	else if (action == MenuAction_Select){
		decl String:info[128];
		GetMenuItem(menu, param2, info, sizeof(info));
		tunedvolume[param1] = StringToInt(info);
		if(tunedstation[param1] != -1) PlayerOTD(param1);
	}
}

public Action:GetSongs(client, args)
{
	UpdateStations();
	new id = tunedstation[client];
	if (args > 0)
	{
		new String:arg1[32];
		GetCmdArg(1, arg1, sizeof(arg1));
		id = StringToInt(arg1) - 1;
	}
	new size = GetArraySize( A_Station );
	if (tunedvolume[client] == 0) id = -1;
	if (id > (size - 1) || id < -1) {
		ReplyToCommand(client, "Invalid Station input");
	} else OutputSongs(client, id);
}

public OutputSongs(client, id)	//array id
{
	new String:station[32];
	new String:artist[100];
	new String:song[100];
	new size = GetArraySize( A_Station );
	if (id == -1)
	{
		for (new i = 0; i < size; i++) { 					//if they arnt listening to a station,
			GetArrayString( A_Station, i, station, sizeof(station) );
			GetArrayString( A_Song, i, song, sizeof(song) );
			GetArrayString( A_Artist, i, artist, sizeof(artist) );
			//prevent updating song info if there is info
			CPrintToChat(client, "[{green}PVL Radio{default}] {lightgreen}%s{default} is playing {olive}%s {default}-- {lightgreen}%s", station, artist, song);
		}
	} else {
		GetArrayString( A_Station, id, station, sizeof(station) );
		GetArrayString( A_Song, id, song, sizeof(song) );
		GetArrayString( A_Artist, id, artist, sizeof(artist) );
		CPrintToChat(client, "[{green}PVL Radio{default}] {lightgreen}%s{default} is playing {olive}%s {default}-- {lightgreen}%s", station, artist, song);
	}
	return;
}

public Action:EndTransmission(client, args)
{
	tunedstation[client] = -1;
	new Handle:setup = CreateKeyValues("data");

	KvSetNum(setup, "type", MOTDPANEL_TYPE_URL);
	KvSetString(setup, "title", "PVL Player");
	KvSetString(setup, "msg", "about:blank");
	ShowVGUIPanel(client, "info", setup, false);
	CloseHandle(setup);
}

public Action:FlashTest(client, args)
{
	CPrintToChat(client, "[{green}PVL Radio{default}] Make sure you have flash installed for OTHER BROWSERS. If you can't see the radio page at all, go to {olive}Options -> Multiplayer -> Advanced {default} then scroll down and uncheck {olive} Disable HTML MOTDs");
}

stock GetStationID(arrayid)
{
	if (GetArraySize(A_ID) > 0) {
		return GetArrayCell(A_ID, arrayid);
	}
	else return -1;
}

/*
CHANGE LOG
7/25/13		Converted SQL queries for nowplaying data
				to now use an http download of a txt file instead
			Added an autogen config file called...um...something
7/26/13		Switched to using SteamTools from cURL due to VC++ issues
7/27/13		Fixed HTTP caching issue with download
7/28/13		Fixed bug with calling station by name
			Fixed bug with station info storage array
8/7/13		Fixed bug with sending client in timer rather than clienid
			Fixed bug of using IsClientConnected rather than IsClientInGame
TO DO LIST
	Make a sm_resume command to quickly reload station stream in case another plugin uses the html page
	Make a sm_mute command
	Make an Admin command to cease playpack for everypony
*/
#pragma semicolon 1
#include <sourcemod>
#include <colors>
#include <steamtools>

#define PLUGIN_VERSION "2.2.3"

public Plugin:myinfo =
{
	name = "[ANY]Ponyville Live Radio",
	author = "WeAreBorg",
	description = "Bucking awesome now with no SQL",
	version = PLUGIN_VERSION,
	url = "www.ponyvillelive.com"
}

new Handle:A_Station = INVALID_HANDLE;		//why are there 5 arrays?
new Handle:A_Song = INVALID_HANDLE;			//Because Ponies drove me to drink
new Handle:A_Artist = INVALID_HANDLE;
new Handle:A_ID = INVALID_HANDLE;
new Handle:A_Listeners = INVALID_HANDLE;
new HTTPRequestHandle:g_HTTPRequest = INVALID_HTTP_HANDLE;

new Handle:g_timer = INVALID_HANDLE;
new Handle:g_volume = INVALID_HANDLE;
new Handle:g_Adverts = INVALID_HANDLE;

new tunedstation[MAXPLAYERS] = -1;			//this is the arrayid
new tunedvolume[MAXPLAYERS];
new reconnect[MAXPLAYERS];

public OnPluginStart()
{
	CreateConVar("PonyRadio_version", PLUGIN_VERSION, "Installed version of Pony Radio on this server", FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY|FCVAR_DONTRECORD);
	g_timer = CreateConVar("PonyRadio_updatetimer", "15.0", "How often to check for new song info in seconds",FCVAR_PLUGIN|FCVAR_NOTIFY, true, 15.0, true, 60.0);
	g_volume = CreateConVar("PonyRadio_volume", "30", "Default Volume Percent",FCVAR_PLUGIN|FCVAR_NOTIFY, true, 0.0, true, 100.0);
	g_Adverts = CreateConVar("PonyRadio_Advertchance", "50", "Chance of advertisement of radio playing ",FCVAR_PLUGIN|FCVAR_NOTIFY, true, 0.0, true, 100.0);

	RegConsoleCmd("sm_radio", DisplayStations);
	RegAdminCmd("sm_radioall", RadioAll, ADMFLAG_SLAY);
	RegConsoleCmd("sm_radiooff", EndTransmission);
	RegConsoleCmd("sm_radiostop", EndTransmission);
	RegConsoleCmd("sm_radiohelp", FlashTest);
	RegConsoleCmd("sm_nowplaying", GetSongs);
	RegConsoleCmd("sm_np", GetSongs);
	RegConsoleCmd("sm_vol", SetVolume);
	RegConsoleCmd("sm_volume", SetVolume);

	AutoExecConfig(true, "PVLPonyRadio");

	HookEvent("player_connect", Event_PlayerConnect);
	HookEvent("player_spawn", Hook_PlayerSpawn);

	A_Station = CreateArray( 32 );
	A_Song = CreateArray( 100 );
	A_Artist = CreateArray( 100 );
	A_ID = CreateArray();
	A_Listeners = CreateArray();
	for(new i=1;i < MaxClients;i++) tunedstation[i] = -1;

	UpdateStations();
	CreateTimer(GetConVarFloat(g_timer), HTTPTimer, _, TIMER_REPEAT);
}

public Action:HTTPTimer(Handle:timer)
{
	UpdateStations();
}

public OnClientConnected(client)
{
	reconnect[client] = 0;
}

public Event_PlayerConnect(Handle:event, const String:name[], bool:dontBroadcast)
{
	new client=GetClientOfUserId(GetEventInt(event,"userid"));
	tunedvolume[client] = 0;
	tunedstation[client] = -1;
}

public Hook_PlayerSpawn(Handle:event, const String:name[], bool:dontBroadcast) //restart radio if they had it playing on first spawn
{
	//launch station precache
	new client = GetClientOfUserId(GetEventInt(event, "userid"));

	if (reconnect[client] == 2) {
		if(tunedstation[client] == -1) tunedstation[client] = GetRandomInt(0, GetArraySize( A_ID ));
		//need to time it cause too close to class select menu
		CreateTimer(15.0, RestartRadioTimer, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE);
	}
	reconnect[client]++;
}

public Action:RestartRadioTimer(Handle:timer, any: clientid)
{
	new client = GetClientOfUserId(clientid);
	if (tunedvolume[client] != 0) OutputSongs(client, tunedstation[client]);
	PlayerOTD(client);
}

public UpdateStations()
{
	//download nowplaying data
	if (g_HTTPRequest != INVALID_HTTP_HANDLE) g_HTTPRequest = INVALID_HTTP_HANDLE;

	//as seen on www.ponyvillelive.com
	g_HTTPRequest = Steam_CreateHTTPRequest(HTTPMethod_GET, "http://ponyvillelive.com/sourcemod.php");

	//asherkin is awesome
	Steam_SetHTTPRequestHeaderValue(g_HTTPRequest, "Pragma", "no-cache");
	Steam_SetHTTPRequestHeaderValue(g_HTTPRequest, "Cache-Control", "no-cache");
	Steam_SendHTTPRequest(g_HTTPRequest, OnDownloadComplete);
}

public OnDownloadComplete(HTTPRequestHandle:HTTPRequest, bool:requestSuccessful, HTTPStatusCode:statusCode)
{
	if (HTTPRequest != g_HTTPRequest || !requestSuccessful || statusCode != HTTPStatusCode_OK)
	{
		LogError("[PVL Pony Radio] Something went wrong with the HTTP download");	//I need some real error handling
		return;
	}
	decl String:data[6000];

	Steam_GetHTTPResponseBodyData(g_HTTPRequest, data, sizeof(data));

	Steam_ReleaseHTTPRequest(g_HTTPRequest);
	g_HTTPRequest = INVALID_HTTP_HANDLE;

	//Begin the fun

	new String:HTTPStations[9][1000] = {"", "", "", "", "", "", "", "", "" }; //TODO: fix this
	//id|Station Name|listeners|title|artist|...

	//First Exlpode into seperate station data to protect against errors. Silver Eagles idea
	ExplodeString(data,"<>",HTTPStations, sizeof(HTTPStations), sizeof(HTTPStations[]));	//I wanted a fancy delimiter with strange UTF-8 chars

	new stationid;
	new listeners;
	new String:station[32];
	new String:artist[100];
	new String:song[100];
	new String:Lsong[100];
	new count = 0;			//This is the array id

	new Float:AnnounceDelay = 10.0;		//Some tweaking to make sure song is announced when its actually playing

	decl String:TempArray[5][100];
	new count_b = 0;
	while (count_b < 9 && strlen(HTTPStations[count_b]) > 20)		//need to catch some trash chars too
	{
		//Explode each station data string based on seperate delimiter
		//id|Station Name|listeners|title|artist|...

		ExplodeString(HTTPStations[count_b],"|",TempArray, sizeof(TempArray), sizeof(TempArray[]));

		stationid = StringToInt(TempArray[0]);
		strcopy(station, sizeof(station), TempArray[1]);
		listeners = StringToInt(TempArray[2]);
		strcopy(song, sizeof(song), TempArray[3]);
		strcopy(artist, sizeof(artist), TempArray[4]);

		count_b++;

		//if (FindValueInArray(A_ID, stationid) == -1){		//Had some bs hitting this field
		if (FindStringInArray(A_Station, station) == -1){
			PushArrayCell( A_ID, stationid);
			PushArrayString( A_Station, station );
			PushArrayString( A_Song, song );						//if we start with NULLs w/e
			PushArrayString( A_Artist, artist );					//no more SQL queries...
			PushArrayCell( A_Listeners, listeners);
		} else {
			GetArrayString(A_Song, count, Lsong, sizeof(Lsong) ); 	//hold stations 'old' song

			SetArrayCell( A_Listeners, count, listeners);
			if (strlen(song) > 0) {
				SetArrayString(A_Song, count, song);
			} else SetArrayString(A_Song, count, "Unknown");		//If returned a NULL set to Unknown

			if (strlen(artist) > 0) {
				SetArrayString(A_Artist, count, artist);
			} else SetArrayString(A_Artist, count, "Unknown");		//If returned a NULL set to Unknown

			if (!StrEqual(Lsong, song)) {							//If both return NULL assume error and dont announce
				if (strlen(song) > 0 && strlen(artist) > 0)CreateTimer(AnnounceDelay, DelayAnnounceSong, count);
			}
		}
		count++;
	}
}

public Action:DelayAnnounceSong(Handle:timer, any:arrayid)
{
	AnnounceSong(arrayid);
}

public AnnounceSong(arrayid)
{
	for (new i = 1; i <= MaxClients; i++)
	{
		if (IsClientInGame(i) && !IsFakeClient(i) && tunedstation[i] == arrayid)
		{
			if (tunedvolume[i] != 0)
			{
				OutputSongs(i, arrayid);
			} else
			{
				new chance = GetRandomInt(0,100);
				if (chance > GetConVarInt(g_Adverts)) CPrintToChat(i, "[{green}PVL Radio{default}] {lightgreen}Tune in{default} now to hear some great {olive}Pony Music {default}from {lightgreen}Ponyville Live!");
			}
		}
	}
}

public Action:DisplayStations(client, args)
{
	UpdateStations();
	CPrintToChat(client,"[{green}PVL Radio{default}] Type {olive}!radio [station] {default}to skip menu, {olive}!radiooff{default} to stop, {olive}!vol {default}or {olive}!vol [0-100] {default}to set volume, {olive}!np {default}for song info, or {olive}!radiohelp{default} for help.");

	new String:station[32];
	new stationid = -1;

	if (args > 0) {
		new String:arg1[32];
		GetCmdArg(1, arg1, sizeof(arg1));
		new size = GetArraySize( A_Station );
		for (new i = 0; i < size; i++) {
			GetArrayString( A_Station, i, station, sizeof(station) );
			if (StrContains(station, arg1, false) != -1 && stationid == -1) stationid = i;
			else if (StrContains(station, arg1) != -1 && stationid != -1) {
				CPrintToChat(client,"[{green}PVL Radio{default}] More than one radio station matches that name.");
				break;
			}
		}

		if (stationid == -1) {
			CPrintToChat(client,"[{green}PVL Radio{default}] No radio station matches that name.");
		}
		else if (stationid != -1) {
			tunedstation[client] = stationid;
			if (tunedvolume[client] == 0) tunedvolume[client] = GetConVarInt(g_volume);
			PlayerOTD(client);
		}
	}

	if (stationid <= 0) {
		//menu can only have 1024 chars
		new String:artist[18];
		new String:song[25];
		new String:buffer[255];

		new Handle:panel = CreatePanel();
		SetPanelTitle(panel, "Ponyville Live!");

		new size = GetArraySize( A_Station );
		for (new i = 0; i < size; i++) {
			GetArrayString( A_Station, i, station, sizeof(station) );
			GetArrayString( A_Song, i, song, sizeof(song) );
			GetArrayString( A_Artist, i, artist, sizeof(artist) );
			//	👂 ੭  ୭ ᠀ ᧙ ☊ ♫   Yeah....just try and guess what i was doing here
			Format(buffer, sizeof(buffer), "%s  ☊%d", station, GetArrayCell(A_Listeners,i));
			DrawPanelItem(panel, buffer);

			//Truncate artist/song if they fill their strings
			new artist_size = sizeof(artist)-1;
			if (strlen(artist) == artist_size) {
				artist[artist_size] = '\0';
				artist[artist_size-1] = '.';
				artist[artist_size-2] = '.';
				artist[artist_size-3] = '.';
			}
			new song_size = sizeof(song)-1;
			if (strlen(song) == song_size) {
				song[song_size] = '\0';
				song[song_size-1] = '.';
				song[song_size-2] = '.';
				song[song_size-3] = '.';
			}
			Format(buffer, sizeof(buffer), "%s--%s", artist, song);
			DrawPanelText(panel, buffer);
		}
		DrawPanelItem(panel, "Cancel");
		SendPanelToClient(panel, client, PanelHandler, 45);
	}
}

public PanelHandler(Handle:panel, MenuAction:action, param1, param2)
{
	if (action == MenuAction_Select)
	{
		if (param2 <= GetArraySize(A_Station)) {
			tunedstation[param1] = param2 - 1;	//subtract one to account for menu reposition
			if (tunedvolume[param1] == 0) tunedvolume[param1] = GetConVarInt(g_volume);		//reset volume to default if muted
			PlayerOTD(param1);
		}
	} else if( action == MenuAction_Cancel )
	{
		if( param2 == MenuCancel_ExitBack )
		{
			CloseHandle(panel);
		}
	}
}

public Action:RadioAll(client, args)
{
	if(tunedstation[client] != -1)
	{
		for(new i=1; i < MAXPLAYERS; i++) {
			if (reconnect[i] > 2) {
				CPrintToChat(i,"[{green}PVL Radio{default}] An admin has turned on your radio! Type {olive}!radiooff{default} to end playback, {olive}!vol{default} to set volume or open vol menu, {olive}!np{default} for current song info");
				tunedstation[i] = tunedstation[client];
				if (tunedvolume[i] < 5) tunedvolume[i] = GetConVarInt(g_volume);
				PlayerOTD(i);
			}
		}
	}
	else {
		CPrintToChat(client,"[{green}PVL Radio{default}] Listen to a radio yourself first!");	//yay for laziness
	}
}

public PlayerOTD(client)
{
	new URLId = GetStationID(tunedstation[client] );
	if (URLId != -1) {
		new String:url[128];
		Format(url, sizeof(url), "http://ponyvillelive.com/index/tunein/id/%d/showonlystation/true/volume/%d/autoplay/true", URLId, tunedvolume[client] );

		new Handle:setup = CreateKeyValues("data");

		KvSetNum(setup, "type", MOTDPANEL_TYPE_URL);
		KvSetString(setup, "title", "PVL Player");
		KvSetString(setup, "msg", url);

		ShowVGUIPanel(client, "info", setup, false);
		CloseHandle(setup);
	}
}

public Action:SetVolume(client, args)
{
	if (args == 0)
	{
		DisplayVolumeMenu(client);
	} else {
		new String:arg1[32];
		GetCmdArg(1, arg1, sizeof(arg1));
		new volume = StringToInt(arg1);
		if (volume < 0) volume = 0;
		if (volume > 100) volume = 100;
		new old = tunedvolume[client];
		tunedvolume[client] = volume;

		if (old == volume){
			CPrintToChat(client, "[{green}PVL Radio{default}] Volume is Already set to {olive}%d", volume);
		} else {
			if(tunedstation[client] != -1) PlayerOTD(client);
			CPrintToChat(client, "[{green}PVL Radio{default}] Volume set to {olive}%d", volume);
		}
	}
}

DisplayVolumeMenu(client) {
	new Handle:menu = CreateMenu(MenuHandler_VolumeMenu);
	decl String:buffer[100];
	Format (buffer, sizeof(buffer), "PVL Volume current:%d", tunedvolume[client]);
	SetMenuTitle(menu, buffer);
	AddMenuItem(menu, "100", "100");
	AddMenuItem(menu, "85", "85");
	AddMenuItem(menu, "70", "70");
	AddMenuItem(menu, "55", "55");
	AddMenuItem(menu, "40", "40");
	AddMenuItem(menu, "25", "25");
	AddMenuItem(menu, "10", "10");
	SetMenuExitBackButton(menu, true);
	DisplayMenu(menu, client, MENU_TIME_FOREVER);
}

public MenuHandler_VolumeMenu(Handle:menu, MenuAction:action, param1, param2)
{
	if( action == MenuAction_Cancel )
	{
		if( param2 == MenuCancel_ExitBack )
		{
			CloseHandle(menu);
		}
	}
	else if (action == MenuAction_Select){
		decl String:info[128];
		GetMenuItem(menu, param2, info, sizeof(info));
		tunedvolume[param1] = StringToInt(info);
		if(tunedstation[param1] != -1) PlayerOTD(param1);
	}
}

public Action:GetSongs(client, args)
{
	UpdateStations();
	new id = tunedstation[client];
	if (args > 0)
	{
		new String:arg1[32];
		GetCmdArg(1, arg1, sizeof(arg1));
		id = StringToInt(arg1) - 1;
	}
	new size = GetArraySize( A_Station );
	if (tunedvolume[client] == 0) id = -1;
	if (id > (size - 1) || id < -1) {
		ReplyToCommand(client, "Invalid Station input");
	} else OutputSongs(client, id);
}

public OutputSongs(client, id)	//array id
{
	new String:station[32];
	new String:artist[100];
	new String:song[100];
	new size = GetArraySize( A_Station );
	if (id == -1)
	{
		for (new i = 0; i < size; i++) { 					//if they arnt listening to a station,
			GetArrayString( A_Station, i, station, sizeof(station) );
			GetArrayString( A_Song, i, song, sizeof(song) );
			GetArrayString( A_Artist, i, artist, sizeof(artist) );
			//prevent updating song info if there is info
			CPrintToChat(client, "[{green}PVL Radio{default}] {lightgreen}%s{default} is playing {olive}%s {default}-- {lightgreen}%s", station, artist, song);
		}
	} else {
		GetArrayString( A_Station, id, station, sizeof(station) );
		GetArrayString( A_Song, id, song, sizeof(song) );
		GetArrayString( A_Artist, id, artist, sizeof(artist) );
		CPrintToChat(client, "[{green}PVL Radio{default}] {lightgreen}%s{default} is playing {olive}%s {default}-- {lightgreen}%s", station, artist, song);
	}
	return;
}

public Action:EndTransmission(client, args)
{
	tunedstation[client] = -1;
	new Handle:setup = CreateKeyValues("data");

	KvSetNum(setup, "type", MOTDPANEL_TYPE_URL);
	KvSetString(setup, "title", "PVL Player");
	KvSetString(setup, "msg", "about:blank");
	ShowVGUIPanel(client, "info", setup, false);
	CloseHandle(setup);
}

public Action:FlashTest(client, args)
{
	CPrintToChat(client, "[{green}PVL Radio{default}] Make sure you have flash installed for OTHER BROWSERS. If you can't see the radio page at all, go to {olive}Options -> Multiplayer -> Advanced {default} then scroll down and uncheck {olive} Disable HTML MOTDs");
}

stock GetStationID(arrayid)
{
	if (GetArraySize(A_ID) > 0) {
		return GetArrayCell(A_ID, arrayid);
	}
	else return -1;
}


Renamed callable native in SourceSleuth

Renamed callable native in SourceSleuth

// *************************************************************************
//  This file is part of SourceBans++.
//
//  Copyright (C) 2014-2016 SourceBans++ Dev Team <https://github.com/sbpp>
//
//  SourceBans++ is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, per version 3 of the License.
//
//  SourceBans++ is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with SourceBans++. If not, see <http://www.gnu.org/licenses/>.
//
//  This file is based off work(s) covered by the following copyright(s):
//
//   SourceSleuth 1.3 fix
//   Copyright (C) 2013-2015 ecca
//   Licensed under GNU GPL version 3, or later.
//   Page: <https://forums.alliedmods.net/showthread.php?p=1818793> - <https://github.com/ecca/SourceMod-Plugins>
//
// *************************************************************************

#pragma semicolon 1
#include <sourcemod>
#undef REQUIRE_PLUGIN
#include <sourcebans>

#define PLUGIN_VERSION "1.6.0"

#define LENGTH_ORIGINAL 1
#define LENGTH_CUSTOM 2
#define LENGTH_DOUBLE 3
#define LENGTH_NOTIFY 4

//- Handles -//
new Handle:hDatabase = INVALID_HANDLE;
new Handle:g_hAllowedArray = INVALID_HANDLE;

//- ConVars -//
ConVar g_cVar_actions;
ConVar g_cVar_banduration;
ConVar g_cVar_sbprefix;
ConVar g_cVar_bansAllowed;
ConVar g_cVar_bantype;
ConVar g_cVar_bypass;

//- Bools -//
new bool:CanUseSourcebans = false;

public Plugin:myinfo =
{
	name = "SourceBans++: SourceSleuth",
	author = "ecca, SourceBans++ Dev Team",
	description = "Useful for TF2 servers. Plugin will check for banned ips and ban the player.",
	version = PLUGIN_VERSION,
	url = "https://sbpp.sarabveer.me/"
};

public OnPluginStart()
{
	LoadTranslations("sourcesleuth.phrases");

	CreateConVar("sm_sourcesleuth_version", PLUGIN_VERSION, "SourceSleuth plugin version", FCVAR_SPONLY | FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DONTRECORD);

	g_cVar_actions = CreateConVar("sm_sleuth_actions", "3", "Sleuth Ban Type: 1 - Original Length, 2 - Custom Length, 3 - Double Length, 4 - Notify Admins Only", 0, true, 1.0, true, 4.0);
	g_cVar_banduration = CreateConVar("sm_sleuth_duration", "0", "Required: sm_sleuth_actions 1: Bantime to ban player if we got a match (0 = permanent (defined in minutes) )", 0);
	g_cVar_sbprefix = CreateConVar("sm_sleuth_prefix", "sb", "Prexfix for sourcebans tables: Default sb", 0);
	g_cVar_bansAllowed = CreateConVar("sm_sleuth_bansallowed", "0", "How many active bans are allowed before we act", 0);
	g_cVar_bantype = CreateConVar("sm_sleuth_bantype", "0", "0 - ban all type of lengths, 1 - ban only permanent bans", 0, true, 0.0, true, 1.0);
	g_cVar_bypass = CreateConVar("sm_sleuth_adminbypass", "0", "0 - Inactivated, 1 - Allow all admins with ban flag to pass the check", 0, true, 0.0, true, 1.0);

	g_hAllowedArray = CreateArray(256);

	AutoExecConfig(true, "Sm_SourceSleuth");

	SQL_TConnect(SQL_OnConnect, "sourcebans");

	RegAdminCmd("sm_sleuth_reloadlist", ReloadListCallBack, ADMFLAG_ROOT);

	LoadWhiteList();
}

public OnAllPluginsLoaded()
{
	CanUseSourcebans = LibraryExists("sourcebans");
}

public OnLibraryAdded(const String:name[])
{
	if (StrEqual("sourcebans", name))
	{
		CanUseSourcebans = true;
	}
}

public OnLibraryRemoved(const String:name[])
{
	if (StrEqual("sourcebans", name))
	{
		CanUseSourcebans = false;
	}
}

public SQL_OnConnect(Handle:owner, Handle:hndl, const String:error[], any:data)
{
	if (hndl == INVALID_HANDLE)
	{
		LogError("SourceSleuth: Database connection error: %s", error);
	}
	else
	{
		hDatabase = hndl;
	}
}

public Action:ReloadListCallBack(client, args)
{
	ClearArray(g_hAllowedArray);

	LoadWhiteList();

	LogMessage("%L reloaded the whitelist", client);

	if (client != 0)
	{
		PrintToChat(client, "[SourceSleuth] WhiteList has been reloaded!");
	}

	return Plugin_Continue;
}

public OnClientPostAdminCheck(client)
{
	if (CanUseSourcebans && !IsFakeClient(client))
	{
		new String:steamid[32];
		GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid));

		if (g_cVar_bypass.BoolValue && CheckCommandAccess(client, "sleuth_admin", ADMFLAG_BAN, false))
		{
			return;
		}

		if (FindStringInArray(g_hAllowedArray, steamid) == -1)
		{
			new String:IP[32], String:Prefix[64];
			GetClientIP(client, IP, sizeof(IP));

			g_cVar_sbprefix.GetString(Prefix, sizeof(Prefix));

			new String:query[1024];

			FormatEx(query, sizeof(query), "SELECT * FROM %s_bans WHERE ip='%s' AND RemoveType IS NULL AND (ends > %d OR length = 0)", Prefix, IP, g_cVar_bantype.IntValue == 0 ? GetTime() : 0);

			new Handle:datapack = CreateDataPack();

			WritePackCell(datapack, GetClientUserId(client));
			WritePackString(datapack, steamid);
			WritePackString(datapack, IP);
			ResetPack(datapack);

			SQL_TQuery(hDatabase, SQL_CheckHim, query, datapack);
		}
	}
}

public SQL_CheckHim(Handle:owner, Handle:hndl, const String:error[], any:datapack)
{
	new client;
	decl String:steamid[32], String:IP[32];

	if (datapack != INVALID_HANDLE)
	{
		client = GetClientOfUserId(ReadPackCell(datapack));
		ReadPackString(datapack, steamid, sizeof(steamid));
		ReadPackString(datapack, IP, sizeof(IP));
		CloseHandle(datapack);
	}

	if (hndl == INVALID_HANDLE)
	{
		LogError("SourceSleuth: Database query error: %s", error);
		return;
	}

	if (SQL_FetchRow(hndl))
	{
		new TotalBans = SQL_GetRowCount(hndl);

		if (TotalBans > g_cVar_bansAllowed.IntValue)
		{
			switch (g_cVar_actions.IntValue)
			{
				case LENGTH_ORIGINAL:
				{
					new length = SQL_FetchInt(hndl, 6);
					new time = length * 60;

					BanPlayer(client, time);
				}
				case LENGTH_CUSTOM:
				{
					new time = g_cVar_banduration.IntValue;
					BanPlayer(client, time);
				}
				case LENGTH_DOUBLE:
				{
					new length = SQL_FetchInt(hndl, 6);

					new time = 0;

					if (length != 0)
					{
						time = length / 60 * 2;
					}

					BanPlayer(client, time);
				}
				case LENGTH_NOTIFY:
				{
					/* Notify Admins when a client with an ip on the bans list connects */
					PrintToAdmins("[SourceSleuth] %t", "sourcesleuth_admintext", client, steamid, IP);
				}
			}
		}
	}
}

stock BanPlayer(client, time)
{
	decl String:Reason[255];
	Format(Reason, sizeof(Reason), "[SourceSleuth] %t", "sourcesleuth_banreason");
	SourceBans_BanPlayer(0, client, time, Reason);
}

PrintToAdmins(const String:format[], any:...)
{
	new String:g_Buffer[256];

	for (new i = 1; i <= MaxClients; i++)
	{
		if (CheckCommandAccess(i, "sm_sourcesleuth_printtoadmins", ADMFLAG_BAN) && IsClientInGame(i))
		{
			VFormat(g_Buffer, sizeof(g_Buffer), format, 2);

			PrintToChat(i, "%s", g_Buffer);
		}
	}
}

public LoadWhiteList()
{
	decl String:path[PLATFORM_MAX_PATH], String:line[256];

	BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "configs/sourcesleuth_whitelist.cfg");

	new Handle:fileHandle = OpenFile(path, "r");

	if (fileHandle == INVALID_HANDLE)
	{
		LogError("Could not find the config file (addons/sourcemod/configs/sourcesleuth_whitelist.cfg)");

		return;
	}

	while (!IsEndOfFile(fileHandle) && ReadFileLine(fileHandle, line, sizeof(line)))
	{
		ReplaceString(line, sizeof(line), "\n", "", false);

		PushArrayString(g_hAllowedArray, line);
	}

	CloseHandle(fileHandle);
}

// *************************************************************************
//  This file is part of SourceBans++.
//
//  Copyright (C) 2014-2016 SourceBans++ Dev Team <https://github.com/sbpp>
//
//  SourceBans++ is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, per version 3 of the License.
//
//  SourceBans++ is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with SourceBans++. If not, see <http://www.gnu.org/licenses/>.
//
//  This file is based off work(s) covered by the following copyright(s):
//
//   SourceSleuth 1.3 fix
//   Copyright (C) 2013-2015 ecca
//   Licensed under GNU GPL version 3, or later.
//   Page: <https://forums.alliedmods.net/showthread.php?p=1818793> - <https://github.com/ecca/SourceMod-Plugins>
//
// *************************************************************************

#pragma semicolon 1
#include <sourcemod>
#undef REQUIRE_PLUGIN
#include <sourcebans>

#define PLUGIN_VERSION "1.6.0"

#define LENGTH_ORIGINAL 1
#define LENGTH_CUSTOM 2
#define LENGTH_DOUBLE 3
#define LENGTH_NOTIFY 4

//- Handles -//
new Handle:hDatabase = INVALID_HANDLE;
new Handle:g_hAllowedArray = INVALID_HANDLE;

//- ConVars -//
ConVar g_cVar_actions;
ConVar g_cVar_banduration;
ConVar g_cVar_sbprefix;
ConVar g_cVar_bansAllowed;
ConVar g_cVar_bantype;
ConVar g_cVar_bypass;

//- Bools -//
new bool:CanUseSourcebans = false;

public Plugin:myinfo =
{
	name = "SourceBans++: SourceSleuth",
	author = "ecca, SourceBans++ Dev Team",
	description = "Useful for TF2 servers. Plugin will check for banned ips and ban the player.",
	version = PLUGIN_VERSION,
	url = "https://sbpp.sarabveer.me/"
};

public OnPluginStart()
{
	LoadTranslations("sourcesleuth.phrases");

	CreateConVar("sm_sourcesleuth_version", PLUGIN_VERSION, "SourceSleuth plugin version", FCVAR_SPONLY | FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DONTRECORD);

	g_cVar_actions = CreateConVar("sm_sleuth_actions", "3", "Sleuth Ban Type: 1 - Original Length, 2 - Custom Length, 3 - Double Length, 4 - Notify Admins Only", 0, true, 1.0, true, 4.0);
	g_cVar_banduration = CreateConVar("sm_sleuth_duration", "0", "Required: sm_sleuth_actions 1: Bantime to ban player if we got a match (0 = permanent (defined in minutes) )", 0);
	g_cVar_sbprefix = CreateConVar("sm_sleuth_prefix", "sb", "Prexfix for sourcebans tables: Default sb", 0);
	g_cVar_bansAllowed = CreateConVar("sm_sleuth_bansallowed", "0", "How many active bans are allowed before we act", 0);
	g_cVar_bantype = CreateConVar("sm_sleuth_bantype", "0", "0 - ban all type of lengths, 1 - ban only permanent bans", 0, true, 0.0, true, 1.0);
	g_cVar_bypass = CreateConVar("sm_sleuth_adminbypass", "0", "0 - Inactivated, 1 - Allow all admins with ban flag to pass the check", 0, true, 0.0, true, 1.0);

	g_hAllowedArray = CreateArray(256);

	AutoExecConfig(true, "Sm_SourceSleuth");

	SQL_TConnect(SQL_OnConnect, "sourcebans");

	RegAdminCmd("sm_sleuth_reloadlist", ReloadListCallBack, ADMFLAG_ROOT);

	LoadWhiteList();
}

public OnAllPluginsLoaded()
{
	CanUseSourcebans = LibraryExists("sourcebans");
}

public OnLibraryAdded(const String:name[])
{
	if (StrEqual("sourcebans", name))
	{
		CanUseSourcebans = true;
	}
}

public OnLibraryRemoved(const String:name[])
{
	if (StrEqual("sourcebans", name))
	{
		CanUseSourcebans = false;
	}
}

public SQL_OnConnect(Handle:owner, Handle:hndl, const String:error[], any:data)
{
	if (hndl == INVALID_HANDLE)
	{
		LogError("SourceSleuth: Database connection error: %s", error);
	}
	else
	{
		hDatabase = hndl;
	}
}

public Action:ReloadListCallBack(client, args)
{
	ClearArray(g_hAllowedArray);

	LoadWhiteList();

	LogMessage("%L reloaded the whitelist", client);

	if (client != 0)
	{
		PrintToChat(client, "[SourceSleuth] WhiteList has been reloaded!");
	}

	return Plugin_Continue;
}

public OnClientPostAdminCheck(client)
{
	if (CanUseSourcebans && !IsFakeClient(client))
	{
		new String:steamid[32];
		GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid));

		if (g_cVar_bypass.BoolValue && CheckCommandAccess(client, "sleuth_admin", ADMFLAG_BAN, false))
		{
			return;
		}

		if (FindStringInArray(g_hAllowedArray, steamid) == -1)
		{
			new String:IP[32], String:Prefix[64];
			GetClientIP(client, IP, sizeof(IP));

			g_cVar_sbprefix.GetString(Prefix, sizeof(Prefix));

			new String:query[1024];

			FormatEx(query, sizeof(query), "SELECT * FROM %s_bans WHERE ip='%s' AND RemoveType IS NULL AND (ends > %d OR length = 0)", Prefix, IP, g_cVar_bantype.IntValue == 0 ? GetTime() : 0);

			new Handle:datapack = CreateDataPack();

			WritePackCell(datapack, GetClientUserId(client));
			WritePackString(datapack, steamid);
			WritePackString(datapack, IP);
			ResetPack(datapack);

			SQL_TQuery(hDatabase, SQL_CheckHim, query, datapack);
		}
	}
}

public SQL_CheckHim(Handle:owner, Handle:hndl, const String:error[], any:datapack)
{
	new client;
	decl String:steamid[32], String:IP[32];

	if (datapack != INVALID_HANDLE)
	{
		client = GetClientOfUserId(ReadPackCell(datapack));
		ReadPackString(datapack, steamid, sizeof(steamid));
		ReadPackString(datapack, IP, sizeof(IP));
		CloseHandle(datapack);
	}

	if (hndl == INVALID_HANDLE)
	{
		LogError("SourceSleuth: Database query error: %s", error);
		return;
	}

	if (SQL_FetchRow(hndl))
	{
		new TotalBans = SQL_GetRowCount(hndl);

		if (TotalBans > g_cVar_bansAllowed.IntValue)
		{
			switch (g_cVar_actions.IntValue)
			{
				case LENGTH_ORIGINAL:
				{
					new length = SQL_FetchInt(hndl, 6);
					new time = length * 60;

					BanPlayer(client, time);
				}
				case LENGTH_CUSTOM:
				{
					new time = g_cVar_banduration.IntValue;
					BanPlayer(client, time);
				}
				case LENGTH_DOUBLE:
				{
					new length = SQL_FetchInt(hndl, 6);

					new time = 0;

					if (length != 0)
					{
						time = length / 60 * 2;
					}

					BanPlayer(client, time);
				}
				case LENGTH_NOTIFY:
				{
					/* Notify Admins when a client with an ip on the bans list connects */
					PrintToAdmins("[SourceSleuth] %t", "sourcesleuth_admintext", client, steamid, IP);
				}
			}
		}
	}
}

stock BanPlayer(client, time)
{
	decl String:Reason[255];
	Format(Reason, sizeof(Reason), "[SourceSleuth] %t", "sourcesleuth_banreason");
	SBBanPlayer(0, client, time, Reason);
}

PrintToAdmins(const String:format[], any:...)
{
	new String:g_Buffer[256];

	for (new i = 1; i <= MaxClients; i++)
	{
		if (CheckCommandAccess(i, "sm_sourcesleuth_printtoadmins", ADMFLAG_BAN) && IsClientInGame(i))
		{
			VFormat(g_Buffer, sizeof(g_Buffer), format, 2);

			PrintToChat(i, "%s", g_Buffer);
		}
	}
}

public LoadWhiteList()
{
	decl String:path[PLATFORM_MAX_PATH], String:line[256];

	BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "configs/sourcesleuth_whitelist.cfg");

	new Handle:fileHandle = OpenFile(path, "r");

	if (fileHandle == INVALID_HANDLE)
	{
		LogError("Could not find the config file (addons/sourcemod/configs/sourcesleuth_whitelist.cfg)");

		return;
	}

	while (!IsEndOfFile(fileHandle) && ReadFileLine(fileHandle, line, sizeof(line)))
	{
		ReplaceString(line, sizeof(line), "\n", "", false);

		PushArrayString(g_hAllowedArray, line);
	}

	CloseHandle(fileHandle);
}