Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[ ==============================================================================================
- WoW Kitsunebot Script
- ===================================================================================================
- written by twitch.tv/aggixx | questions to @PawketsWoW
- How to setup Kitsunebot:
- 1) Go to http://www.twitch.tv/kitsunebot and type !join to make Kitsunebot join your channel.
- 2) In your channel, set this to the startup script using the following chat command:
- !kitsunebot MOD.StartupScript( "86bgZ2jJ" ); MOD.Reload();
- 3) Use MOD.SetVar to set the required options described in the following sections. Ex:
- !kitsunebot MOD.SetVar( "armoryDefaultRegion", "us" );
- !kitsunebot MOD.SetVar( "guild", "Huge in Japan", "WarcraftLogs" );
- =================================== Armory Info Command =======================================
- !armory (US|EU) Character-Realm ex: !armory US Pawketz-Kil'jaeden
- Returns information from the armory about the specified character. The US/EU parameter is
- optional if var "armoryDefaultRegion" is set. You can set a character for the command to
- default to when all parameters are ommitted by setting var "armoryDefaultCharacter".
- ================================== WarcraftLogs Log Tracking ====================================
- To use the tracking you must first set the following variables in the "WarcraftLogs" table:
- - "apiKey" - You can get an API key at the bottom of the settings page on warcraftlogs.com, use
- the public key.
- - "character" - Name of the character you would like to track for ranks (currently unused but
- you must set it anyway). Just char name, no
- realm.
- - "guild" - WCL guild name to use for log auto-select (you can always explicitly track a log on
- a different guild).
- - "realm" - Realm of said guild.
- - "region" - Region of said guild.
- Tracking a log:
- Use "!wcl init" to select a log. You can explicitly pick a log by passing the log ID as a
- parameter, otherwise Kitsunebot will present a list of recently active logs from your guild's
- page and you can pick one. If there's only 1 recently active log it will automatically choose
- it for you.
- Once you've selected a log, tracking is now active and Kitsunebot will announce and link to
- your boss kills as they happen. Tracking will automatically turn off if your stream is offline
- for more than 2 minutes, and you can also use "!wcl (on|off)" to manually toggle tracking.
- --]]
- if not ( MOD.Timer and MOD.GetStream ) then
- MOD.Ticks=0;
- --timer
- local timerevents = {};
- --example tick function
- local function Tick(channel)
- if type(MOD.EventTick) == "function" then
- local ret = MOD.EventTick(user,msg);
- if ret ~= nil then
- return ret;
- end
- end
- MOD.Ticks = MOD.Ticks + 1;
- local now = os.time();
- for k,v in pairs(timerevents)do
- if v.start + v.interval <= now then
- if v.action ~= nil then
- if v.action ~= nil then
- if type(v.action) == "function" then
- v.action();
- return;
- else
- print(tostring(v.action));
- end
- end
- end
- if v.recurring then
- v.start = v.start + v.interval;
- else
- timerevents[k] = nil;
- end
- end
- end
- end
- function MOD.GetTimers()
- local list = {};
- for k,v in pairs(timerevents)do
- table.insert(list,k);
- end
- return list;
- end
- function MOD.Timer( name, seconds, action, recurring )
- if action == nil then
- timerevents[name] = nil;
- return;
- end
- if recurring == nil then
- recurring = false;
- elseif type( recurring ) ~= "boolean" then
- error( "Recurring must be a boolean value" );
- return;
- end
- if type(action) ~= "function" and type(action) ~= "string" then
- error("Action must be a function or a string");
- return;
- end
- timerevents[name] = {
- ["start"] = os.time(),
- ["action"] = action,
- ["recurring"] = recurring,
- ["interval"] = seconds,
- };
- end
- MOD.SetTickEvent(Tick);
- function MOD.GetStream(name)
- local body, code, headers, status = MOD.HTTPSGet("https://api.twitch.tv/kraken/streams/"..name);
- if body == nil then
- return nil,"not found";
- end
- local raw = JSONDecode(body);
- if raw == nil then
- return nil,"could not decode response";
- elseif raw.error ~= nil then
- return nil,tostring(raw.error);
- elseif type(raw.stream) ~= "table" then
- return nil,"offline";
- end
- local ret = {};
- ret.viewers = raw.stream.viewers;
- ret.game = raw.stream.game;
- ret.name = raw.stream.channel.display_name;
- ret.partner = raw.stream.channel.partner;
- ret.views = raw.stream.channel.views;
- ret.language = raw.stream.channel.language;
- ret.mature = raw.stream.channel.mature;
- ret.status = raw.stream.channel.status;
- ret.started = raw.stream.created_at;
- ret.fps = raw.stream.average_fps;
- ret.followers = raw.stream.channel.followers;
- return ret;
- end
- end
- local classes = { "Warrior", "Paladin", "Hunter", "Rogue", "Priest", "Death Knight", "Shaman", "Mage", "Warlock", "Monk", "Druid" };
- local races = {
- [1] = "Human",
- [2] = "Orc",
- [3] = "Dwarf",
- [4] = "Night Elf",
- [5] = "Undead",
- [6] = "Tauren",
- [7] = "Gnome",
- [8] = "Troll",
- [9] = "Goblin",
- [10] = "Blood Elf",
- [11] = "Draenei",
- [22] = "Worgen",
- [24] = "Pandaren",
- };
- --[[
- --Google URL Shortener API
- local shorten;
- do
- local key = "";
- local http;
- local ltn12;
- function shorten( url )
- if not http and ltn12 then
- -- loads the HTTP module and any libraries it requires
- http = require("socket.http");
- ltn12 = require("ltn12");
- end
- local request = ;
- local response = {}
- local body, code, headers, status = http.request {
- url = "https://www.googleapis.com/urlshortener/v1/url?key=" .. key,
- method = "POST",
- source = JSONEncode( {
- ["longUrl"] = url,
- } ),
- headers = {
- ["Content-Type"] = "application/json",
- },
- sink = ltn12.sink.table( response ),
- };
- print('body:' .. tostring(body))
- print('code:' .. tostring(code))
- print('headers:' .. util.tableToString(headers))
- print('status:' .. tostring(status))
- end
- end
- --]]
- local function url_encode( str )
- if (str) then
- str = string.gsub (str, "\n", "\r\n")
- str = string.gsub (str, "([^%w %-%_%.%~%?=#:/])",
- function (c) return string.format ("%%%02X", string.byte(c)) end)
- str = string.gsub (str, " ", "+")
- end
- return str
- end
- MOD.url_encode = url_encode;
- local function slugify( str )
- return string.gsub( string.gsub( str:lower(), "%s", "-" ), "%A", "" );
- end
- --[[ ================================================================
- !armory command
- ================================================================ --]]
- local function armory( msg )
- local region, character, realm;
- if not msg or string.match( msg, "^%s*$" ) then
- character, realm = string.match( MOD.GetVar( "armoryDefaultCharacter" ), "^([^-%s]+)-(.+%S)%s*$" );
- region = MOD.GetVar( "armoryDefaultRegion" );
- elseif string.match( msg, "^%a%a%s[^-%s]+-.+%S%s*$" ) then
- region, character, realm = string.match( msg, "^(%a%a)%s([^-%s]+)-(.+%S)%s*$" );
- region = region:lower();
- else
- character, realm = string.match( msg, "^([^-%s]+)-(.+%S)%s*$" );
- region = MOD.GetVar( "armoryDefaultRegion" );
- end
- if not ( realm and character and region and ( region == "us" or region == "eu" ) ) then
- print("Please specify a valid character, ex: !armory Pawketz-Kil'jaeden");
- return;
- end
- local d = MOD.HTTPSGet( "http://" .. region .. ".battle.net/api/wow/character/" .. realm .. "/" .. character .. "?fields=items,titles,talents" );
- d = JSONDecode(d);
- if d.status and d.status == "nok" then
- print('Character "' .. character .. '" not found on realm "' .. realm .. '".');
- return;
- end
- local title = "%s";
- for i=1,#d.titles do
- if d.titles[i].selected then
- title = d.titles[i].name;
- break;
- end
- end
- local spec = "";
- for i=1,#d.talents do
- if d.talents[i].selected then
- spec = " " .. d.talents[i].spec.name;
- break;
- end
- end
- print( string.format( "%s | %d %s %s %s | iLvl %d (%d) | %s",
- string.format( title, d.name ),
- d.level,
- races[d.race],
- spec,
- classes[d.class],
- d.items.averageItemLevelEquipped,
- d.items.averageItemLevel,
- url_encode( string.format( "http://%s.battle.net/wow/en/character/%s/%s/advanced", region, slugify( realm ), character ) )
- ) );
- end
- MOD.AddCom("armory", armory);
- --[[ ================================================================
- WarcraftLogs Tracking
- ================================================================ --]]
- do
- local WCL = {};
- local report;
- local reportSelect = {};
- local activityTimeout = 60*60*2; -- 2 hours
- local trackStatus = false;
- local streamTimeout = 0;
- local function getWCLConfig()
- local config = MOD.GetArray( "WarcraftLogs" );
- if not ( config.apiKey and config.apiKey ~= "" ) then
- return "bad apiKey";
- elseif not ( config.realm and config.realm ~= "" ) then
- return "bad realm";
- elseif not ( config.region and config.region ~= "" ) then
- return "bad region";
- elseif not ( config.guild and config.guild ~= "" ) then
- return "bad guild";
- elseif not ( config.character and config.character ~= "" ) then
- return "bad character";
- end
- return config;
- end
- local zoneData, classData, kills;
- local function initZoneData( config )
- if not config then
- config = getWCLConfig();
- if ( type(config) == "string" ) then
- print( config );
- return;
- end
- end
- if not ( zoneData and zoneData ~= {} ) then
- zoneData = JSONDecode( MOD.HTTPSGet( string.format( "https://www.warcraftlogs.com/v1/zones?api_key=%s", config.apiKey ) ) );
- end
- end
- local function initClassData( config )
- if not config then
- config = getWCLConfig();
- if ( type(config) == "string" ) then
- print( config );
- return;
- end
- end
- if not ( classData and classData ~= {} ) then
- classData = JSONDecode( MOD.HTTPSGet( string.format( "https://www.warcraftlogs.com/v1/classes?api_key=%s", config.apiKey ) ) );
- end
- end
- local function initTracking( config )
- if not config then
- config = getWCLConfig();
- if ( type(config) == "string" ) then
- print( config );
- return;
- end
- end
- initClassData( config );
- initZoneData( config );
- kills = {};
- local data = JSONDecode( MOD.HTTPSGet( string.format( "https://www.warcraftlogs.com/v1/report/fights/%s?api_key=%s", report, config.apiKey ) ) );
- for i=1,#data.fights do
- if data.fights[i].boss > 0 and data.fights[i].kill and not kills[data.fights[i].id] then
- local t = data.fights[i];
- local id = t.id;
- t.id = nil;
- kills[id] = t;
- end
- end
- end
- local function track()
- if MOD.GetStream(string.match(Channel(), "^#(.+)")) then
- streamTimeout = 0;
- else
- streamTimeout = streamTimeout + 1;
- if streamTimeout >= 5 then
- WCL.setTrackingStatus( false );
- print( "WCL tracking has been disabled due to stream inactivity." );
- return;
- end
- end
- local config = getWCLConfig();
- if ( type(config) == "string" ) then
- print( config );
- return;
- end
- local data = JSONDecode( MOD.HTTPSGet( string.format( "https://www.warcraftlogs.com/v1/report/fights/%s?api_key=%s", report, config.apiKey ) ) );
- for i=1,#data.fights do
- if data.fights[i].boss > 0 and data.fights[i].kill and not kills[data.fights[i].id] then
- local t = data.fights[i];
- local id = t.id;
- t.id = nil;
- kills[id] = t;
- local killSec = ( t.end_time - t.start_time ) / 1000;
- local timeStr = math.floor( killSec / 60 ) .. "m" .. math.floor( killSec % 60 ) .. "s";
- -- Print slain notice
- print( string.format( "%s has been slain in %s!", t.name, timeStr ) );
- --[[
- -- Figure out corresponding zone id from boss id
- local zone;
- for j=1,#zoneData do
- for k=1,#zoneData[j].encounters do
- if zoneData[j].encounters[k].id == t.boss then
- zone = zoneData[j].id;
- break;
- end
- end
- if zone then break end
- end
- if type(zone) ~= "number" then
- error( "zone is not a number" );
- return;
- end
- -- Pull rank data for boss id
- local rData = JSONDecode( MOD.HTTPSGet( string.format( "https://www.warcraftlogs.com/v1/rankings/character/%s/%s/%s?zone=%d&metric=dps&limit=5000&api_key=%s", config.character, slugify( config.realm ), config.region, zone, config.apiKey ) ) );
- -- Parse rank data and print rank, if found
- for j=1,#rData do
- if rData[j].encounter == t.boss and rData[j].reportID == report then
- local className, specName;
- for k=1,#classData do
- if classData[k].id == rData[j].class then
- className = classData[k].name;
- for l=1,#classData[k].specs do
- if classData[k].specs[l].id == rData[j].spec then
- specName = classData[k].specs[l].name;
- break;
- end
- end
- break;
- end
- end
- local pct = math.floor( ( 1 - ( rData[j].rank / rData[j].outOf ) ) * 100 + 0.5 );
- print( string.format( "%s set a new personal best of %d DPS, ranking %d out of %d %s %ss (%d%%)!", config.character, rData[j].total, rData[j].rank, rData[j].outOf, specName, className, pct ) );
- end
- end
- --]]
- -- Print URL to log
- print( url_encode( string.format( "https://www.warcraftlogs.com/reports/%s#fight=%d", report, id ) ) );
- -- Break out so we only report one kill at a time
- return;
- end
- end
- end
- function WCL.setTrackingStatus( status )
- assert( type(status) == "boolean" );
- if trackStatus ~= status then
- if status then
- initTracking();
- MOD.Timer( "WCLtrack", 30, track, true );
- else
- MOD.Timer( "WCLtrack" );
- end
- end
- trackStatus = status;
- end
- local function setActiveReport( r )
- report = r;
- print( report .. " is now the active report." );
- WCL.setTrackingStatus( true );
- end
- MOD.AddCom("wcl", function( msg, user )
- if string.match( msg, "^init" ) and UserStatus( user ) == 2 then
- local config = getWCLConfig();
- if ( type(config) == "string" ) then
- print( config );
- return;
- end
- if string.match( msg, "^init %w+" ) then
- local id = string.match( msg, "^init (%w+)" );
- local data = JSONDecode( MOD.HTTPSGet( string.format( "https://www.warcraftlogs.com/v1/report/fights/%s?api_key=%s", id, config.apiKey ) ) );
- if data.error then
- print( data.status .. ": " .. data.error );
- else
- setActiveReport( id );
- end
- else
- local data = JSONDecode( MOD.HTTPSGet( string.format( "https://www.warcraftlogs.com/v1/reports/guild/%s/%s/%s?api_key=%s&start=%s", config.guild, slugify( config.realm ), config.region, config.apiKey, ( os.time() - 60*60*24 ) * 1000 ) ) ); -- filter to the last 24 hours
- local activeReports = {};
- for i=1,#data do
- if data[i]["end"] > ( os.time() - activityTimeout ) * 1000 then
- table.insert( activeReports, i );
- end
- end
- if #activeReports == 0 then
- print( "No reports found." );
- elseif #activeReports == 1 then
- setActiveReport( data[activeReports[1] ].id );
- else
- print( "The following recently active reports were found, please select one (!wcl select x):" );
- reportSelect = {};
- for i=1,#activeReports do
- if i > 1 then
- print("|");
- end
- print( string.format( "%s. %s by %s", i, data[activeReports[i] ].title, data[activeReports[i] ].owner ) );
- table.insert( reportSelect, data[activeReports[i] ].id );
- end
- end
- end
- elseif string.match( msg, "^select %d+$") and UserStatus( user ) == 2 then
- if reportSelect ~= {} then
- local i = tonumber( string.match( msg, "^select (%d+)$") );
- setActiveReport( reportSelect[i] );
- reportSelect = {};
- end
- elseif msg:lower() == "on" and UserStatus( user ) == 2 then
- if report ~= "" then
- print("WCL tracking is now on.");
- WCL.setTrackingStatus( true );
- else
- print("No report is selected. Please use !wcl init");
- end
- elseif msg:lower() == "off" and UserStatus( user ) == 2 then
- if report ~= "" then
- print("WCL tracking is now off.");
- WCL.setTrackingStatus( false );
- else
- print("No report is selected. Please use !wcl init");
- end
- --[[elseif string.match(msg, "^record .+ [NHM]$") and UserStatus( user ) == 2 then
- local name, difficulty = string.match(msg, "^record (.+) ([NHM])$"):lower();
- if difficulty == "N" then
- difficulty = 3;
- elseif difficulty == "H" then
- difficulty = 4;
- elseif difficulty == "M" then
- difficulty = 5;
- end
- local config = getWCLConfig();
- if ( type(config) == "string" ) then
- print( config );
- return;
- end
- -- Find the boss' id
- initZoneData( config );
- local id, zoneId;
- for i=1,#zoneData do
- for j=1,#zoneData[i].encounters do
- if zoneData[i].encounters[j].name:lower() == name then
- id = zoneData[i].encounters[j].id;
- zoneId = zoneData[i].id;
- break
- end
- end
- if id and zoneId then break end
- end
- if not id and zoneId then
- print( "Could not find a boss by that name." );
- return;
- end
- -- Pull reports in the last 30 days
- local reportData = JSONDecode( MOD.HTTPSGet( string.format( "https://www.warcraftlogs.com/v1/reports/guild/%s/%s/%s?api_key=%s&start=%s", config.guild, slugify( config.realm ), config.region, config.apiKey, ( os.time() - 30*60*60*24 ) * 1000 ) ) );
- local reports = {}
- for i=1,#reportData do
- if reportData[i].zone == zoneId then
- table.insert( reports, reportData[i].id );
- end
- end
- local kills = {}
- -- Find all kills of that boss & difficulty
- for i=1,#reports do
- local fightData = JSONDecode( MOD.HTTPSGet( string.format( "https://www.warcraftlogs.com/v1/report/fights/%s?api_key=%s", reports[i], config.apiKey ) ) );
- for j=1,#fightData.fights do
- if fightData.fights[j].boss == id and fightData.fights[j].kill and fightData.fights[j].difficulty == difficulty then
- table.insert( kills, {
- ["time"] = fightData.fights[j].end_time - fightData.fights[j].start_time,
- ["fightId"] = fightData.fights[j].id,
- ["reportId"] = reports[i],
- ["date"] = fightData.fights[j].start_time,
- });
- end
- end
- end
- -- Sort by kill time
- table.sort( kills, function(a, b)
- return a.time < b.time;
- end);
- if #kills > 0 then
- print( "Top " .. min( 3, #kills ) .. " kill times for " .. name .. ":" );
- for i=1,min( 3, #kills ) do
- local s = ""
- if i > 1 then
- s = s .. "| ";
- end
- print( s .. string.format( "%dm%02ds https://www.warcraftlogs.com/reports/%s#fight=%d", kills[i].time % 60, math.floor( kills[i].time / 60 ), kills[i].reportId, kills[i].fightId ) );
- end
- end --]]
- elseif UserStatus( user ) == 2 then
- print( "bad input" );
- end
- end);
- end
Advertisement
Add Comment
Please, Sign In to add comment