Skip to content

Instantly share code, notes, and snippets.

@Hidden50
Created December 2, 2016 11:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Hidden50/8ade6c00cec861c41715001a319ff415 to your computer and use it in GitHub Desktop.
Save Hidden50/8ade6c00cec861c41715001a319ff415 to your computer and use it in GitHub Desktop.
Scrappie's PS Auction bot script (has a lot of features grown on top of each other. sorry, this code is quite ugly..)
'use strict';
////////////////////////////////////////////////////////////////////////////////
// //
// Requested by Ana1ytic, this is an spl auction implementation //
// //
////////////////////////////////////////////////////////////////////////////////
// //
// Documentation //
// .makeauction NAME, STARTMONEY //
// if used in a room and user is RO, the auction will bind to the room. //
// .auction bind/unbind //
// .auction delete //
// .auction addmanagers //
// .auction allownom TEAMNAME //
// .auction stopnom //
// .auction nom PLAYER //
// .auction bid AMOUNT //
// .auction teaminfo TEAMNAME //
// //
////////////////////////////////////////////////////////////////////////////////
var auctions = global.auctions = require('./../data/auctions.json');
var auction_timer = {};
var auctionsfile_busy = false;
var auctionsfile_needUpdate = false;
function saveAuctions() {
if (auctionsfile_busy) {
auctionsfile_needUpdate = true; // file is busy, save afterwards
} else {
auctionsfile_needUpdate = false;
auctionsfile_busy = true;
// saving now..
fs.writeFile('./data/auctions.json', JSON.stringify(auctions, null, 4),
function (err) {
global.auctions = auctions;
auctionsfile_busy = false;
// after save.. if it's been modified we need to save again.
if (err) return console.log(err);
if (auctionsfile_needUpdate)
saveAuctions();
}
);
}
}
function ExtractKeyword(argString, Keywords) {
// argString must be an object, so that it can be modified like a var parameter
var pos;
var target = argString.toLowerCase() + ' ';
for (var k in Keywords) {
pos = target.indexOf(Keywords[k].toLowerCase() + ' ');
if (pos >= 0) {
if (argString.substring(0, pos).trim())
continue; // there's letters before the keyword. But it's only a match if it's the first word.
var temp = argString.substring(pos + Keywords[k].length).trim();
argString.valueOf = argString.toSource = argString.toString = function() {
return temp;
};
return Keywords[k];
}
}
return '';
}
//commandparser.Context.prototype.aucBroadcast = function (auction, message) {
// if (auction.rooms.indexOf(this.room) < 0) this.pmReply(message);
// for (var r in auction.rooms) Bot.say(auction.rooms[r], message);
//}
Settings.addPermissions(['makeauction', 'auction']);
const aucCmds = ['delete', 'bind', 'unbind', 'addmanagers', 'addmanager', 'addteams',
'addteam', 'addplayer', 'addplayers', 'allownom', 'stopnom', 'report', 'run', 'stop', 'reset', 'nom', 'nominate', 'bid', 'team', 'teaminfo', 'players'];
exports.commands = {
makeauction: function(arg, by, room, cmd) {
/* creates an auction with (by) as the only owner. If (by) is a room owner, the room the command is
used in becomes linked to the auction and any events within it will be broadcast to the room. */
arg = arg.split(',').map( s => s.trim() );
by = toId(by);
arg[0] = toId(arg[0]);
if (!arg[0])
return this.restrictReply('Usage: ``.makeauction NAME`` or ``.makeauction NAME, STARTMONEY``. If the command is used by a room owner inside a room, the auction will auto-bind to that room and broadcast to it.', 'makeauction');
if (auctions[arg[0]])
return this.restrictReply('An auction with this name already exists.', 'makeauction');
var auction = auctions[arg[0]] = {
name: arg[0],
startmoney: arg[1] || 100000,
owners: [by],
rooms: [], // rooms that events in this auction will broadcast to
managers: {}, // users able to use management commands
teams: {}, // teams that may bid in this auction
running: false,
allownom: false, // the team currently allowed to nominate
nomorder: false, // array that specifies in which order teams get to nominate
nomqueue: false, // array with the next planned nominations (last to first)
nommedplayer: false, // id of the nominated player
bid: false, // current bid (in credits)
bidder: false, // last team to bid
players: {} // list of players up for auctioning
};
this.restrictReply('Auction **' + arg[0] + '** has been created.', 'makeauction');
if (this.can('makeauction')) {
auction.rooms.push(room);
var oldAuction = auctions.defaults[room];
if (oldAuction) auctions[oldAuction].rooms.filter( s => s !== room );
auctions.defaults[room] = arg[0];
this.restrictReply('**' + auction.name + '** has been bound to this room. ' +
'The room will now be notified of any events in this auction. (Do ``.auction unbind`` to reverse this.)', 'auction');
}
return saveAuctions();
},
auc: 'auction',
auction: function(arg, by, room, cmd) {
by = toId(by);
arg = Object(arg); // required for ExtractKeyword() to modify this string
// which auction is this referring to and which action should be performed?
var auction = ExtractKeyword(arg, Object.keys(auctions));
var keyword = ExtractKeyword(arg, aucCmds);
if (!auction) {
// no auction specified? Maybe there is a default for this room.
if (auctions.defaults[room]) auction = auctions.defaults[room];
else {
keyword = ''; // no auction specified. Give usage help.
this.restrictReply('No auction specified.', 'auction');
}
}
auction = auctions[auction];
arg = arg.toString();
switch (keyword) {
/* Usage Help ****************************************************************/
case '': {
if (auction) {
this.restrictReply('Usage: ``.auction COMMAND``.', 'auction');
} else {
this.restrictReply('Usage: ``.auction NAME COMMAND``. NAME can be ommitted if the room has a default auction. ' +
'Room owners can use ``.auction NAME bind`` to set the default auction.', 'auction');
}
this.restrictReply('Command list: Room Owners - ``bind, unbind``. ' +
'Auction Owners - ``unbind, delete, addmanagers, allownom TEAMNAME, stopnom``. ' +
'Team Managers - ``nom PLAYER, bid AMOUNT``. Anyone - ``teaminfo TEAMNAME``', 'auction');
return;
}
/* Auction Owner Commands ****************************************************/
case 'delete': {
if (auction.owners.indexOf(by) < 0 && !this.isRanked('~'))
return this.restrictReply('You do not have permission to do this.');
for (var r in auction.rooms) {
if (auctions.defaults[auction.rooms[r]])
delete auctions.defaults[auction.rooms[r]];
}
var temp = auction.name;
delete auctions[auction.name];
this.aucBroadcast(auction, 'Auction **' + temp + '** has been deleted.');
return saveAuctions();
}
case 'bind': {
let rank;
if (Settings.settings.makeauction && Settings.settings.makeauction[room])
rank = Settings.settings.makeauction[room];
else rank = "#";
if (!this.can('makeauction'))
return this.restrictReply('Rank ' + rank + ' required for binding auctions to this room.', 'auction');
if (auction.rooms.indexOf(room) >= 0 && auctions.defaults[room] === auction.name)
return this.restrictReply('This auction is already broadcasting to this room.', 'auction');
auction.rooms.push(room);
auctions.defaults[room] = auction.name;
this.restrictReply('**' + auction.name + '** has been bound to this room. ' +
'The room will now be notified of events in this auction.', 'auction');
return saveAuctions();
}
case 'unbind': {
let rank;
if (Settings.settings.makeauction && Settings.settings.makeauction[room])
rank = Settings.settings.makeauction[room];
else rank = "#";
if (!this.can('makeauction') && auction.owners.indexOf(by) < 0)
return this.restrictReply('Rank ' + rank + ' required for unbinding auctions from this room.', 'auction');
if (auction.rooms.indexOf(room) < 0 && auctions.defaults[room] !== auction.name)
return this.restrictReply('That auction is not broadcasting to this room.', 'auction');
auction.rooms.filter( r => r !== room );
delete auctions.defaults[room];
this.restrictReply('**' + auction.name + '** has been unbound from this room. ' +
'The room will no longer be notified of events in this auction.', 'auction');
return saveAuctions();
}
case 'addmanagers':
case 'addmanager':
case 'addteams':
case 'addteam': {
if (auction.owners.indexOf(by) < 0 && !this.isRanked('~'))
return this.restrictReply('Only auction owners can modify teams.');
if (!arg.toString()) return this.restrictReply('Usage: ``.auction NAME addmanagers ' +
'TEAMNAME:MANAGER,MANAGER2,..``. Accepts multiple teams at once, separated by ``;``.');
arg = arg.split(';').map( s => s.split(':') );
for (var t in arg) {
var teamname = toId(arg[t][0]);
if (!teamname) continue;
if (!auction.teams[teamname]) {
auction.teams[teamname] = {
name: teamname,
credits: auction.startmoney,
managers: [],
players: []
};
}
if (arg[t][1]) {
var newManagers = arg[t][1].split(',').map(toId);
for (var m in newManagers) {
if (auction.teams[teamname].managers.indexOf(newManagers[m]) < 0) {
auction.teams[teamname].managers.push(newManagers[m]);
auction.managers[newManagers[m]] = teamname;
}
}
}
}
this.aucBroadcast(auction, 'Teams in auction **' + auction.name + '** are now: ' +
Object.keys(auction.teams).join(', ') + '. Type ``.auction team ' +
'TEAMNAME`` for info on a team.', 'auction');
return saveAuctions();
}
case 'addplayer':
case 'addplayers': {
if (auction.owners.indexOf(by) < 0 && !this.isRanked('~'))
return this.restrictReply('Only auction owners can modify the player list.');
if (!arg.toString()) return this.restrictReply('Usage: ``.auction NAME addplayers ' +
'PLAYERNAME:DESCRIPTION``. Accepts multiple players at once, separated by ``;``.');
arg = arg.split(';').map( s => s.split(':') );
for (var t in arg) {
var playername = toId(arg[t][0]);
if (!playername) continue;
auction.players[playername] = {
name: playername,
description: arg[t][1] ? arg[t][1].trim() : "",
team: false
};
}
this.aucBroadcast(auction, 'Players in auction **' + auction.name + '** are now: ' +
Object.keys(auction.players).join(', ') + '.');
return saveAuctions();
}
case 'report': {
if (auction.owners.indexOf(by) < 0 && !this.isRanked('~'))
return this.restrictReply('You do not have permission to do this.');
Tools.uploadToHastebin(JSON.stringify(auction, null, 4),
function(success, hastebinLink) {
if (success) return this.restrictReply('Auction object: ' + hastebinLink, 'auction');
else this.restrictReply('An error occured while uploading to hastebin.com/', 'auction');
}.bind(this)
);
return;
}
case 'run': {
if (auction.owners.indexOf(by) < 0 && !this.isRanked('~'))
return this.restrictReply('You do not have permission to do this.');
if (auction.running)
return this.restrictReply('The auction is already running.', 'auction');
auction.nomorder = Object.keys(auction.teams).reverse().concat(Object.keys(auction.teams));
auction.nomqueue = [].concat(auction.nomorder);
auction.running = true;
this.restrictReply('**' + auction.name + '** is now autoselecting who gets to nominate players.', 'auction');
while (auction.teams[auction.allownom = auction.nomqueue.pop()].credits < 3000) {
if (auction.nomqueue.length === 0) {
auction.running = false;
this.aucBroadcast(auction, 'Auction **' + auction.name + '** has ended because every team has less than 3000 Credits left.');
return saveAuctions();
}
}
this.aucBroadcast(auction, 'It is now **' + auction.allownom + '**\'s turn to nominate a player for auction.');
return saveAuctions();
}
case 'allownom': {
if (auction.owners.indexOf(by) < 0 && !this.isRanked('~'))
return this.restrictReply('You do not have permission to do this.');
if (!auction.teams[arg]) return this.restrictReply('Usage: ``.auction allownom teamname``', 'auction');
if (auction.nomorder.indexOf(arg) >= 0) {
if (auction.nomqueue.length < auction.nomorder.length)
auction.nomqueue = auction.nomorder.concat(auction.nomqueue);
while (auction.nomqueue.pop() !== arg); // pop elements until the team is found
}
auction.allownom = arg;
this.aucBroadcast(auction, 'It is now **' + arg + '**\'s turn to nominate a player for auction.');
return saveAuctions();
}
case 'stop': {
if (auction.owners.indexOf(by) < 0 && !this.isRanked('~'))
return this.restrictReply('You do not have permission to do this.');
if (!auction.running)
return this.restrictReply('The auction is already stopped.', 'auction');
auction.running = false;
saveAuctions(); // ".auction stop" also calls ".auction stopnom", so no return statement here
}
case 'stopnom': {
if (auction.owners.indexOf(by) < 0 && !this.isRanked('~'))
return this.restrictReply('You do not have permission to do this.');
var theNom;
if (auction.allownom) theNom = auction.allownom;
else if (auction.nommedplayer) theNom = auction.nommedplayer;
else return this.restrictReply('There is no nomination to be canceled.', 'auction');
clearTimeout(auction_timer[toId(auction.name)]);
auction.allownom = false;
auction.nommedplayer = false;
auction.bid = false;
auction.bidder = false;
this.aucBroadcast(auction, theNom + '\'s nomination has been cancelled.');
return saveAuctions();
}
case 'reset': {
if (auction.owners.indexOf(by) < 0 && !this.isRanked('~'))
return this.restrictReply('You do not have permission to do this.');
for (var p in auction.players)
auction.players[p].team = false;
for (var t in auction.teams) {
auction.teams[t].credits = auction.startmoney;
auction.teams[t].players = [];
}
auction.running = false;
auction.allownom = false;
auction.nomqueue = false;
auction.nommedplayer = false;
auction.bid = false;
auction.bidder = false;
this.aucBroadcast(auction, 'Auction **' + auction.name + '** has been reset and will start from zero again. All teams have ``' + auction.startmoney + '`` credits, and all players are available for purchase.');
return saveAuctions(); // ".auction stop" also calls ".auction stopnom", so no return statement here
}
/* Team Manager Commands *****************************************************/
case 'nom':
case 'nominate': {
arg = toId(arg);
if (!auction.players[arg])
return this.restrictReply('Player not recognized. Usage: ``.auction nominate PLAYERNAME``.', 'auction');
if (auction.players[arg].team)
return this.restrictReply('That player is already on team ' + auction.players[arg].team + '.', 'auction');
if (auction.managers[by] !== auction.allownom && auction.owners.indexOf(by) < 0 && !this.isRanked('~')) {
if (!auction.managers[by])
return this.restrictReply('Only team managers and auction owners may nominate players.', 'auction');
return this.restrictReply('It is not your turn to nominate yet. (Auction owners may use ' +
'``.allownom TEAMNAME`` to give permission.)', 'auction');
}
if (auction.nommedplayer)
return this.restrictReply('Already nominated ' + auction.nommedplayer, 'auction');
auction.nommedplayer = arg;
this.aucBroadcast(auction, by + ' from ' + auction.managers[by] + ' has nominated **' + arg + '** for auction. ' + auction.players[arg].description);
arg = "3000";
// no return statement. nominate always calls bid, nomming a player bids the min bid on them.
}
case 'bid': {
if (!auction.managers[by]) return this.restrictReply('Only team managers may bid on players.', 'auction');
if (!auction.nommedplayer) return this.restrictReply('No player is up for auction.', 'auction');
if (Number(arg) != arg)
return this.restrictReply('Please enter the amount of credits you want to bid, in multiples of 500.', 'auction');
if (arg % 500 !== 0)
arg *= 1000;
if (arg % 500 !== 0)
return this.restrictReply('Please enter the amount of credits you want to bid, in multiples of 500.', 'auction');
if (Number(arg) <= auction.bid)
return this.restrictReply('You have to bid more credits than the previous offer.', 'auction');
if (Number(arg) > auction.teams[auction.managers[by]].credits) {
return this.restrictReply('Your team does not have enough funds. Remaining credits: ``' +
auction.teams[auction.managers[by]].credits + '``.', 'auction');
}
var restmoney = auction.teams[auction.managers[by]].credits - Number(arg);
if (1 + restmoney / 3000 + auction.teams[auction.managers[by]].players.length < 10)
return this.restrictReply('Your team does not have enough funds. Remaining credits: ``' +
auction.teams[auction.managers[by]].credits + '``. Remember you will need to buy at least 10 players.', 'auction');
auction.bidder = auction.managers[by];
auction.bid = arg;
this.aucBroadcast(auction, by + '[' + auction.managers[by] + ']: **' + auction.bid + '**.');
clearTimeout(auction_timer[toId(auction.name)]);
auction_timer[toId(auction.name)] = setTimeout(() => {
this.aucBroadcast(auction, '__Five seconds remaining!__');
auction_timer[toId(auction.name)] = setTimeout(() => {
this.aucBroadcast(auction, '**' + auction.bidder + ' has bought ' + auction.players[auction.nommedplayer].name + ' for ' + auction.bid + '!**');
auction.teams[auction.bidder].credits -= arg;
auction.teams[auction.bidder].players.push(auction.nommedplayer);
auction.players[auction.nommedplayer].team = auction.bidder;
auction.nommedplayer = auction.bid = auction.bidder = false;
if (!auction.running)
auction.allownom = false;
else {
if (auction.nomqueue.length < auction.nomorder.length)
auction.nomqueue = auction.nomorder.concat(auction.nomqueue);
while (auction.teams[auction.allownom = auction.nomqueue.pop()].credits < 3000) {
if (auction.nomqueue.length === 0) {
auction.running = false;
this.aucBroadcast(auction, 'Auction **' + auction.name + '** has ended because every team has less than 3000 Credits left.');
return saveAuctions();
}
}
if (Tools.equalOrHigherRank(Bot.rooms[room].users[toId(this.botName)][0], '*')) {
var aucPlayerpool = [];
for (p in auction.players) {
if (!auction.players[p].team)
aucPlayerpool.push(auction.players[p].name);
}
var htmlTable = "<table style='border: 1px solid #6688aa; border-radius: 10px'><tr><th style='border-bottom: 1px solid #94b8b8; padding: 5px'>Team</th><th style='border-bottom: 1px solid #94b8b8; padding: 5px'>Credits</th><th style='border-bottom: 1px solid #94b8b8; padding: 5px'>Players</th></tr>";
var i = 0;
for (t in auction.teams) {
var style = "style='background-color: rgba(242, 247, 250, 0.7); color: black'";
if (++i % 2 === 0) style = "style='background-color: rgba(203, 203, 203, 0.7); color: black'";
htmlTable += "<tr " + style + "><td>" + t + "</td><td align='right'>" + auction.teams[t].credits + "</td><td>" + auction.teams[t].players.join(', ') + "</td></tr>";
}
htmlTable += "</table>"
this.reply("/addhtmlbox <div style='max-height: 400px; overflow-y: auto'><center>" + htmlTable + "</center><p>Players remaining in the pool (" + aucPlayerpool.length + "): " + aucPlayerpool.join(", ") + "</p></div>");
}
this.aucBroadcast(auction, 'It is now **' + auction.allownom + '**\'s turn to nominate a player for auction.');
}
return saveAuctions();
}, 5000);
}, 7000);
return saveAuctions();
}
/* Reg-User Commands *********************************************************/
case 'teaminfo':
case 'team': {
var theTeam = auction.teams[arg];
if (!theTeam) {
if (auction.managers[by] && !arg) theTeam = auction.teams[auction.managers[by]];
else return this.restrictReply('No team goes by that name here.', 'auction');
}
this.restrictReply('Team ' + theTeam.name + ': ``' + theTeam.credits + '`` credits. ' +
'Managers: ' + theTeam.managers.join(', ') + '. ``' + theTeam.players.length + '`` Players: ' + theTeam.players.join(', '), 'auction');
return;
}
case 'players': {
var result = [];
for (t in auction.teams)
result.push(t + ' (' + auction.teams[t].credits + '): ' + auction.teams[t].players.join(', '));
result.push('\r\nPlayers remaining in the pool' + (arg ? ' with a description containing ' + arg : '') + ':');
for (p in auction.players) {
if (!auction.players[p].team && (!arg || auction.players[p].description.toLowerCase().indexOf(arg.toLowerCase()) >= 0))
result.push(auction.players[p].name + ': ' + auction.players[p].description);
}
Tools.uploadToHastebin(result.join('\r\n'),
function(success, hastebinLink) {
if (success) return this.restrictReply('Players in ' + auction.name + ': ' + hastebinLink, 'auction');
else this.restrictReply('An error occured while uploading to hastebin.com/', 'auction');
}.bind(this)
);
return;
}
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment