add upstream source (adch++ 2.12.1 from sourceforge)

This commit is contained in:
2020-03-22 01:45:29 -07:00
parent 3574617350
commit 2cfbcf1301
9637 changed files with 1934407 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
Import('dev source_path')
ret = dev.build('src/')
Return('ret')

View File

@@ -0,0 +1,220 @@
/*
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "stdinc.h"
#include "BloomManager.h"
#include <adchpp/LogManager.h>
#include <adchpp/Client.h>
#include <adchpp/AdcCommand.h>
#include <adchpp/Util.h>
#include <adchpp/PluginManager.h>
#include <adchpp/Core.h>
using namespace std;
using namespace std::placeholders;
using namespace adchpp;
const string BloomManager::className = "BloomManager";
// TODO Make configurable
const size_t h = 24;
struct PendingItem {
PendingItem(size_t m_, size_t k_) : m(m_), k(k_) { buffer.reserve(m/8); }
ByteVector buffer;
size_t m;
size_t k;
};
BloomManager::BloomManager(Core &core) : searches(0), tthSearches(0), stopped(0), core(core) {
LOG(className, "Starting");
}
void BloomManager::init() {
auto &cm = core.getClientManager();
receiveConn = manage(cm.signalReceive().connect(std::bind(&BloomManager::onReceive, this, _1, _2, _3)));
sendConn = manage(cm.signalSend().connect(std::bind(&BloomManager::onSend, this, _1, _2, _3)));
auto &pm = core.getPluginManager();
bloomHandle = pm.registerPluginData(&PluginData::simpleDataDeleter<HashBloom>);
pendingHandle = pm.registerPluginData(&PluginData::simpleDataDeleter<PendingItem>);
statsConn = manage(pm.onCommand("stats", std::bind(&BloomManager::onStats, this, _1)));
}
bool BloomManager::hasBloom(Entity& c) const {
return c.getPluginData(bloomHandle);
}
bool BloomManager::hasTTH(Entity& c,const TTHValue& tth) const {
HashBloom* bloom = reinterpret_cast<HashBloom*>(c.getPluginData(bloomHandle));
return !bloom || bloom->match(tth);
}
int64_t BloomManager::getSearches() const {
return searches;
}
int64_t BloomManager::getTTHSearches() const {
return tthSearches;
}
int64_t BloomManager::getStoppedSearches() const {
return stopped;
}
BloomManager::~BloomManager() {
LOG(className, "Shutting down");
}
static const uint32_t FEATURE = AdcCommand::toFourCC("BLO0");
void BloomManager::onReceive(Entity& e, AdcCommand& cmd, bool& ok) {
string tmp;
Client* cc = dynamic_cast<Client*>(&e);
if(!cc) {
return;
}
Client& c = *cc;
if(cmd.getCommand() == AdcCommand::CMD_INF && c.hasSupport(FEATURE)) {
if(cmd.getParam("SF", 0, tmp)) {
if(e.getPluginData(pendingHandle)) {
// Already getting a blom - we'll end up with an old bloom but there's no trivial
// way to avoid it...
// TODO Queue the blom get?
return;
}
size_t n = adchpp::Util::toInt(tmp);
if(n == 0) {
return;
}
e.clearPluginData(bloomHandle);
size_t k = HashBloom::get_k(n, h);
size_t m = HashBloom::get_m(n, k);
e.setPluginData(pendingHandle, new PendingItem(m, k));
AdcCommand get(AdcCommand::CMD_GET);
get.addParam("blom");
get.addParam("/");
get.addParam("0");
get.addParam(Util::toString(m/8));
get.addParam("BK", Util::toString(k));
get.addParam("BH", Util::toString(h));
c.send(get);
}
} else if(cmd.getCommand() == AdcCommand::CMD_SND) {
if(cmd.getParameters().size() < 4) {
return;
}
if(cmd.getParam(0) != "blom") {
return;
}
PendingItem* pending = reinterpret_cast<PendingItem*>(e.getPluginData(pendingHandle));
if(!pending) {
c.send(AdcCommand(AdcCommand::SEV_FATAL, AdcCommand::ERROR_BAD_STATE, "Unexpected bloom filter update"));
c.disconnect(Util::REASON_BAD_STATE);
ok = false;
return;
}
int64_t bytes = Util::toInt(cmd.getParam(3));
if(bytes != static_cast<int64_t>(pending->m / 8)) {
dcdebug("Disconnecting for invalid number of bytes: %d, %d\n", (int)bytes, (int)pending->m / 8);
c.send(AdcCommand(AdcCommand::SEV_FATAL, AdcCommand::ERROR_PROTOCOL_GENERIC, "Invalid number of bytes"));
c.disconnect(Util::REASON_PLUGIN);
ok = false;
e.clearPluginData(pendingHandle);
return;
}
c.setDataMode(bind(&BloomManager::onData, this, _1, _2, _3), bytes);
ok = false;
}
}
void BloomManager::onSend(Entity& c, const AdcCommand& cmd, bool& ok) {
if(!ok)
return;
if(cmd.getCommand() == AdcCommand::CMD_SCH) {
searches++;
string tmp;
if(cmd.getParam("TR", 0, tmp)) {
tthSearches++;
if(!hasTTH(c,TTHValue(tmp)) || !adchpp::Util::toInt(c.getField("SF"))) {
ok = false;
stopped++;
}
}
}
}
std::pair<size_t, size_t> BloomManager::getBytes() const {
std::pair<size_t, size_t> bytes;
auto &cm = core.getClientManager();
for(auto i = cm.getEntities().begin(), iend = cm.getEntities().end(); i != iend; ++i) {
auto bloom = reinterpret_cast<HashBloom*>(i->second->getPluginData(bloomHandle));
if(bloom) {
bytes.first++;
bytes.second += bloom->size();
}
}
return bytes;
}
void BloomManager::onData(Entity& c, const uint8_t* data, size_t len) {
PendingItem* pending = reinterpret_cast<PendingItem*>(c.getPluginData(pendingHandle));
if(!pending) {
// Shouldn't happen
return;
}
pending->buffer.insert(pending->buffer.end(), data, data + len);
if(pending->buffer.size() == pending->m / 8) {
HashBloom* bloom = new HashBloom();
c.setPluginData(bloomHandle, bloom);
bloom->reset(pending->buffer, pending->k, h);
c.clearPluginData(pendingHandle);
/* Mark the new filter as received */
signalBloomReady_(c);
}
}
void BloomManager::onStats(Entity& c) {
string stats = "\nBloom filter statistics:";
stats += "\nTotal outgoing searches: " + Util::toString(searches);
stats += "\nOutgoing TTH searches: " + Util::toString(tthSearches) + " (" + Util::toString(tthSearches * 100. / searches) + "% of total)";
stats += "\nStopped outgoing searches: " + Util::toString(stopped) + " (" + Util::toString(stopped * 100. / searches) + "% of total, " + Util::toString(stopped * 100. / tthSearches) + "% of TTH searches";
auto bytes = getBytes();
size_t clients = core.getClientManager().getEntities().size();
stats += "\nClient support: " + Util::toString(bytes.first) + "/" + Util::toString(clients) + " (" + Util::toString(bytes.first * 100. / clients) + "%)";
stats += "\nApproximate memory usage: " + Util::formatBytes(bytes.second) + ", " + Util::formatBytes(static_cast<double>(bytes.second) / clients) + "/client";
c.send(AdcCommand(AdcCommand::CMD_MSG).addParam(stats));
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef BLOOM_MANAGER_H
#define BLOOM_MANAGER_H
#include <tuple>
#include <adchpp/forward.h>
#include <adchpp/Exception.h>
#include <adchpp/ClientManager.h>
#include <adchpp/Plugin.h>
#include <adchpp/Signal.h>
#include "HashBloom.h"
STANDARD_EXCEPTION(BloomException);
class ADCHPP_VISIBLE BloomManager : public Plugin {
public:
BloomManager(Core &core);
virtual ~BloomManager();
virtual int getVersion() { return 2; }
void init();
/*Check if the entity has a bloom filter*/
PLUGIN_EXPORT bool hasBloom(Entity& c) const;
/*Check if the entity may have the desired TTH according to the filter*/
PLUGIN_EXPORT bool hasTTH(Entity& c, const TTHValue& tth) const;
/*Get the number of searches sent (to clients)*/
PLUGIN_EXPORT int64_t getSearches() const;
/*Get the number of searches by TTH sent (to clients)*/
PLUGIN_EXPORT int64_t getTTHSearches() const;
/*Get the number of sent searches stopped*/
PLUGIN_EXPORT int64_t getStoppedSearches() const;
static const std::string className;
/*This signal is sent when a BloomFilter has been received*/
typedef SignalTraits<void (Entity&)> SignalBloomReady;
PLUGIN_EXPORT SignalBloomReady::Signal& signalBloomReady() { return signalBloomReady_; }
private:
PluginDataHandle bloomHandle;
PluginDataHandle pendingHandle;
int64_t searches;
int64_t tthSearches;
int64_t stopped;
ClientManager::SignalReceive::ManagedConnection receiveConn;
ClientManager::SignalSend::ManagedConnection sendConn;
ClientManager::SignalReceive::ManagedConnection statsConn;
std::pair<size_t, size_t> getBytes() const;
void onReceive(Entity& c, AdcCommand& cmd, bool&);
void onSend(Entity& c, const AdcCommand& cmd, bool&);
void onData(Entity& c, const uint8_t* data, size_t len);
void onStats(Entity& c);
Core &core;
SignalBloomReady::Signal signalBloomReady_;
};
#endif //ACCESSMANAGER_H

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "stdinc.h"
#include "BloomManager.h"
#include <adchpp/PluginManager.h>
#ifdef _WIN32
BOOL APIENTRY DllMain(HANDLE /*hModule */, DWORD /* reason*/, LPVOID /*lpReserved*/) {
return TRUE;
}
#endif
extern "C" {
int PLUGIN_API pluginGetVersion() { return PLUGINVERSION; }
int PLUGIN_API pluginLoad(PluginManager *pm) {
auto bm = make_shared<BloomManager>(pm->getCore());
bm->init();
pm->registerPlugin("BloomManager", bm);
return 0;
}
void PLUGIN_API pluginUnload() {
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "stdinc.h"
#include "HashBloom.h"
#include <cmath>
size_t HashBloom::get_k(size_t n, size_t h) {
for(size_t k = TTHValue::BITS/h; k > 1; --k) {
uint64_t m = get_m(n, k);
if(m >> 24 == 0) {
return k;
}
}
return 1;
}
uint64_t HashBloom::get_m(size_t n, size_t k) {
uint64_t m = (static_cast<uint64_t>(ceil(static_cast<double>(n) * k / log(2.))));
// 64-bit boundary as per spec
return ((m + 63ULL) / 64ULL) * 64ULL;
}
void HashBloom::add(const TTHValue& tth) {
for(size_t i = 0; i < k; ++i) {
bloom[pos(tth, i)] = true;
}
}
bool HashBloom::match(const TTHValue& tth) const {
if(bloom.empty()) {
return false;
}
for(size_t i = 0; i < k; ++i) {
if(!bloom[pos(tth, i)]) {
return false;
}
}
return true;
}
void HashBloom::push_back(bool v) {
bloom.push_back(v);
}
void HashBloom::reset(ByteVector& v, size_t k_, size_t h_) {
k = k_;
h = h_;
bloom.resize(v.size() * 8);
for(size_t i = 0; i < v.size(); ++i) {
for(size_t j = 0; j < 8; ++j) {
bloom[i*8 + j] = (((v[i] >> j) & 1) != 0);
}
}
}
size_t HashBloom::pos(const TTHValue& tth, size_t n) const {
if((n+1)*h > TTHValue::BITS) {
return 0;
}
uint64_t x = 0;
size_t start = n * h;
for(size_t i = 0; i < h; ++i) {
size_t bit = start + i;
size_t byte = bit / 8;
size_t pos = bit % 8;
if(tth.data[byte] & (1 << pos)) {
x |= (1LL << i);
}
}
return x % bloom.size();
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef HASHBLOOM_H_
#define HASHBLOOM_H_
#include "HashValue.h"
/**
* According to http://www.eecs.harvard.edu/~michaelm/NEWWORK/postscripts/BloomFilterSurvey.pdf
* the optimal number of hashes k is (m/n)*ln(2), m = number of bits in the filter and n = number
* of items added. The largest k that we can get from a single TTH value depends on the number of
* bits we need to address the bloom structure, which in turn depends on m, so the optimal size
* for our filter is m = n * k / ln(2) where n is the number of TTH values, or in our case, number of
* files in share since each file is identified by one TTH value. We try that for each even dividend
* of the key size (2, 3, 4, 6, 8, 12) and if m fits within the bits we're able to address (2^(keysize/k)),
* we can use that value when requesting the bloom filter.
*/
class HashBloom {
public:
HashBloom() : k(0), h(0) { }
/** Return a suitable value for k based on n */
static size_t get_k(size_t n, size_t h);
/** Optimal number of bits to allocate for n elements when using k hashes */
static uint64_t get_m(size_t n, size_t k);
void add(const TTHValue& tth);
bool match(const TTHValue& tth) const;
void reset(ByteVector& v, size_t k, size_t h);
void push_back(bool v);
size_t size() const { return bloom.size(); }
private:
size_t pos(const TTHValue& tth, size_t n) const;
std::vector<bool> bloom;
size_t k;
size_t h;
};
#endif /*HASHBLOOM_H_*/

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2001-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef BLOOM_HASH_VALUE_H
#define BLOOM_HASH_VALUE_H
#include <adchpp/TigerHash.h>
#include <adchpp/Encoder.h>
template<class Hasher>
struct HashValue {
static const size_t BITS = Hasher::BITS;
static const size_t BYTES = Hasher::BYTES;
HashValue() { }
explicit HashValue(uint8_t* aData) { memcpy(data, aData, BYTES); }
explicit HashValue(const std::string& base32) { Encoder::fromBase32(base32.c_str(), data, BYTES); }
HashValue(const HashValue& rhs) { memcpy(data, rhs.data, BYTES); }
HashValue& operator=(const HashValue& rhs) { memcpy(data, rhs.data, BYTES); return *this; }
bool operator!=(const HashValue& rhs) const { return !(*this == rhs); }
bool operator==(const HashValue& rhs) const { return memcmp(data, rhs.data, BYTES) == 0; }
bool operator<(const HashValue& rhs) const { return memcmp(data, rhs.data, BYTES) < 0; }
std::string toBase32() const { return Encoder::toBase32(data, BYTES); }
std::string& toBase32(std::string& tmp) const { return Encoder::toBase32(data, BYTES, tmp); }
uint8_t data[BYTES];
};
namespace std {
template<typename T>
struct hash<HashValue<T> > {
size_t operator()(const HashValue<T>& rhs) const { return *(size_t*)rhs.data; }
};
}
typedef HashValue<TigerHash> TTHValue;
#endif // !defined(HASH_VALUE_H)

View File

@@ -0,0 +1,11 @@
Import('dev source_path')
env, target, sources = dev.prepare_build(source_path, 'Bloom', shared_precompiled_header = 'stdinc')
env['SHLIBPREFIX'] = ''
env.Append(CPPPATH = ['#'])
env.Append(LIBS = ['adchpp'])
ret = env.SharedLibrary(target, sources)
Return('ret')

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "stdinc.h"

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef BLOOM_STDINC_H
#define BLOOM_STDINC_H
#include <adchpp/adchpp.h>
#include <adchpp/common.h>
using namespace adchpp;
#ifdef _WIN32
#define PLUGIN_EXPORT __declspec(dllexport)
#else
#define PLUGIN_EXPORT __attribute__ ((visibility("default")))
#endif
#endif

View File

@@ -0,0 +1,5 @@
Import('dev source_path')
ret = dev.build('src/')
Return('ret')

View File

@@ -0,0 +1,925 @@
-- This script contains commands and settings related to banning and muting users
local base=_G
module("access.bans")
base.require("luadchpp")
local adchpp = base.luadchpp
local access = base.require("access")
local aio = base.require('aio')
local autil = base.require("autil")
local json = base.require("json")
local os = base.require("os")
local string = base.require("string")
local table = base.require("table")
-- Where to read/write ban database
local bans_file = adchpp.Util_getCfgPath() .. "bans.txt"
bans = {}
bans.cids = {}
bans.ips = {}
bans.nicks = {}
bans.nicksre = {}
bans.msgsre = {}
bans.schsre = {}
bans.muted = {}
local settings = access.settings
local commands = access.commands
local is_op = access.is_op
local cm = adchpp.getCM()
local sm = adchpp.getSM()
local lm = adchpp.getLM()
local function log(message)
lm:log(_NAME, message)
end
local function load_bans()
bans = {}
bans.cids = {}
bans.ips = {}
bans.nicks = {}
bans.nicksre = {}
bans.msgsre = {}
bans.schsre = {}
bans.muted = {}
local ok, list, err = aio.load_file(bans_file, aio.json_loader)
if err then
log('Ban loading: ' .. err)
end
if not ok then
return
end
bans = list
if not bans.cids then
bans.cids = {}
end
if not bans.ips then
bans.ips = {}
end
if not bans.nicks then
bans.nicks = {}
end
if not bans.nicksre then
bans.nicksre = {}
end
if not bans.msgsre then
bans.msgsre = {}
end
if not bans.schsre then
bans.schsre = {}
end
if not bans.muted then
bans.muted = {}
end
end
function save_bans()
local err = aio.save_file(bans_file, json.encode(bans))
if err then
log('Bans not saved: ' .. err)
end
end
local function ban_expiration_diff(ban)
return os.difftime(ban.expires, os.time())
end
local function clear_expired_bans()
local save = false
for _, ban_array in base.pairs(bans) do
for k, ban in base.pairs(ban_array) do
if ban.expires and ban_expiration_diff(ban) <= 0 then
ban_array[k] = nil
save = true
end
end
end
if save then
save_bans()
end
end
local function ban_expiration_string(ban)
if ban.expires then
local diff = ban_expiration_diff(ban)
if diff > 0 then
return "in " .. access.format_seconds(diff)
else
return "expired"
end
else
return "never"
end
end
local function ban_info_string(ban, sep)
if not sep then
sep = "\t | "
end
local str = "Level: " .. ban.level
if ban.reason then
str = str .. sep .. "Reason: " .. ban.reason
end
str = str .. sep .. "Expires: " .. ban_expiration_string(ban)
if ban.action then
str = str .. sep .. "Bans for"
if ban.action < 0 then
str = str .. "ever"
else
str = str .. " " .. base.tostring(ban.action) .. " minutes"
end
end
return str
end
local function ban_added_string(ban)
return ban_info_string(ban, ") (")
end
local function ban_return_info(ban)
local str = " (expires: " .. ban_expiration_string(ban) .. ")"
if ban.reason then
str = str .. " (reason: " .. ban.reason .. ")"
end
return str
end
local function dump_banned(c, ban)
local str = "You are banned" .. ban_return_info(ban)
autil.dump(c, adchpp.AdcCommand_ERROR_BANNED_GENERIC, function(cmd)
cmd:addParam("MS" .. str)
local expires
if ban.expires then
expires = ban_expiration_diff(ban)
else
expires = -1
end
cmd:addParam("TL" .. base.tostring(expires))
end)
end
local function make_ban(level, reason, minutes)
local ban = { level = level }
if string.len(reason) > 0 then
ban.reason = reason
end
if minutes and string.len(minutes) > 0 then
ban.expires = os.time() + minutes * 60
end
return ban
end
commands.ban = {
alias = { banuser = true },
command = function(c, parameters)
local level = access.get_level(c)
if level < settings.oplevel.value then
return
end
local minutes_pos, _, minutes = parameters:find(" (%d*)$")
if minutes_pos then
parameters = parameters:sub(1, minutes_pos - 1)
if #parameters <= 0 then
autil.reply(c, "Bad arguments")
return
end
end
local nick, reason = parameters:match("^(%S+) ?(.*)")
if not nick then
autil.reply(c, "You need to supply a nick")
return
end
local victim = cm:findByNick(nick)
if victim then
victim = victim:asClient()
end
if not victim then
autil.reply(c, "No user nick-named \"" .. nick .. "\"")
return
end
local victim_cid = victim:getCID():toBase32()
if level <= access.get_level(victim) then
autil.reply(c, "You can't ban users whose level is higher or equal than yours")
return
end
if base.tonumber(minutes) ~= 0 then
local ban = make_ban(level, reason, minutes)
bans.cids[victim_cid] = ban
save_bans()
dump_banned(victim, ban)
autil.reply(c, "\"" .. nick .. "\" (CID: " .. victim_cid .. ") is now banned (" .. ban_added_string(ban) .. ")")
return
end
if bans.cids[victim_cid] then
bans.cids[victim_cid] = nil
save_bans()
autil.reply(c, "\"" .. nick .. "\" (CID: " .. victim_cid .. ") is now un-banned")
else
autil.reply(c, "\"" .. nick .. "\" (CID: " .. victim_cid .. ") is not in the banlist")
end
end,
help = "nick [reason] [minutes] - ban an online user (set minutes to 0 to un-ban)",
protected = is_op,
user_command = {
hub_params = {
autil.ucmd_line("Nick"),
autil.ucmd_line("Reason (facultative)"),
autil.ucmd_line("Minutes (facultative)")
},
name = "Hub management" .. autil.ucmd_sep .. "Punish" .. autil.ucmd_sep .. "Ban",
user_params = {
"%[userNI]",
autil.ucmd_line("Reason (facultative)"),
autil.ucmd_line("Minutes (facultative)")
}
}
}
commands.bancid = {
command = function(c, parameters)
local level = access.get_level(c)
if level < settings.oplevel.value then
return
end
local minutes_pos, _, minutes = parameters:find(" (%d*)$")
if minutes_pos then
parameters = parameters:sub(1, minutes_pos - 1)
if #parameters <= 0 then
autil.reply(c, "Bad arguments")
return
end
end
local cid, reason = parameters:match("^(%S+) ?(.*)")
if not cid then
autil.reply(c, "You need to supply a CID")
return
end
if base.tonumber(minutes) ~= 0 then
local ban = make_ban(level, reason, minutes)
bans.cids[cid] = ban
save_bans()
autil.reply(c, "The CID \"" .. cid .. "\" is now banned (" .. ban_added_string(ban) .. ")")
return
end
if bans.cids[cid] then
bans.cids[cid] = nil
save_bans()
autil.reply(c, "The CID \"" .. cid .. "\" is now un-banned")
else
autil.reply(c, "The CID \"" .. cid .. "\" is not in the banlist")
end
end,
help = "CID [reason] [minutes] (set minutes to 0 to un-ban)",
protected = is_op,
user_command = {
hub_params = {
autil.ucmd_line("CID"),
autil.ucmd_line("Reason (facultative)"),
autil.ucmd_line("Minutes (facultative)")
},
name = "Hub management" .. autil.ucmd_sep .. "Punish" .. autil.ucmd_sep .. "Ban CID",
user_params = {
"%[userCID]",
autil.ucmd_line("Reason (facultative)"),
autil.ucmd_line("Minutes (facultative)")
}
}
}
commands.banip = {
command = function(c, parameters)
local level = access.get_level(c)
if level < settings.oplevel.value then
return
end
local minutes_pos, _, minutes = parameters:find(" (%d*)$")
if minutes_pos then
parameters = parameters:sub(1, minutes_pos - 1)
if #parameters <= 0 then
autil.reply(c, "Bad arguments")
return
end
end
local ip, reason = parameters:match("^(%S+) ?(.*)")
if not ip then
autil.reply(c, "You need to supply an IP address")
return
end
if base.tonumber(minutes) ~= 0 then
local ban = make_ban(level, reason, minutes)
bans.ips[ip] = ban
save_bans()
autil.reply(c, "The IP address \"" .. ip .. "\" is now banned (" .. ban_added_string(ban) .. ")")
return
end
if bans.ips[ip] then
bans.ips[ip] = nil
save_bans()
autil.reply(c, "The IP address \"" .. ip .. "\" is now un-banned")
else
autil.reply(c, "The IP address \"" .. ip .. "\" is not found in the banlist")
end
end,
help = "IP [reason] [minutes] (set minutes to 0 to un-ban)",
protected = is_op,
user_command = {
hub_params = {
autil.ucmd_line("IP"),
autil.ucmd_line("Reason (facultative)"),
autil.ucmd_line("Minutes (facultative)")
},
name = "Hub management" .. autil.ucmd_sep .. "Punish" .. autil.ucmd_sep .. "Ban IP",
user_params = {
"%[userI4]",
autil.ucmd_line("Reason (facultative)"),
autil.ucmd_line("Minutes (facultative)")
}
}
}
commands.bannick = {
command = function(c, parameters)
local level = access.get_level(c)
if level < settings.oplevel.value then
return
end
local minutes_pos, _, minutes = parameters:find(" (%d*)$")
if minutes_pos then
parameters = parameters:sub(1, minutes_pos - 1)
if #parameters <= 0 then
autil.reply(c, "Bad arguments")
return
end
end
local nick, reason = parameters:match("^(%S+) ?(.*)")
if not nick then
autil.reply(c, "You need to supply a nick")
return
end
if base.tonumber(minutes) ~= 0 then
local ban = make_ban(level, reason, minutes)
bans.nicks[nick] = ban
save_bans()
autil.reply(c, "The nick \"" .. nick .. "\" is now banned (" .. ban_added_string(ban) .. ")")
return
end
if bans.nicks[nick] then
bans.nicks[nick] = nil
save_bans()
autil.reply(c, "The nick \"" .. nick .. "\" is now un-banned")
else
autil.reply(c, "The nick \"" .. nick .. "\" is not found in the banlist")
end
end,
help = "nick [reason] [minutes] (set minutes to 0 to un-ban)",
protected = is_op,
user_command = {
hub_params = {
autil.ucmd_line("Nick"),
autil.ucmd_line("Reason (facultative)"),
autil.ucmd_line("Minutes (facultative)")
},
name = "Hub management" .. autil.ucmd_sep .. "Punish" .. autil.ucmd_sep .. "Ban nick",
user_params = {
"%[userNI]",
autil.ucmd_line("Reason (facultative)"),
autil.ucmd_line("Minutes (facultative)")
}
}
}
commands.bannickre = {
command = function(c, parameters)
local level = access.get_level(c)
if level < settings.oplevel.value then
return
end
local minutes_pos, _, minutes = parameters:find(" (%d*)$")
if minutes_pos then
parameters = parameters:sub(1, minutes_pos - 1)
if #parameters <= 0 then
autil.reply(c, "Bad arguments")
return
end
end
local action_pos, _, action = parameters:find("^(-?%d*) ")
if action_pos then
parameters = parameters:sub(action_pos + 2)
if #parameters <= 0 then
autil.reply(c, "Bad arguments")
return
end
end
local re, reason = parameters:match("<([^>]+)> ?(.*)")
if not re then
autil.reply(c, "You need to supply a reg exp (within '<' and '>' brackets)")
return
end
if base.tonumber(minutes) ~= 0 then
local ban = make_ban(level, reason, minutes)
if action_pos then
ban.action = base.tonumber(action)
end
bans.nicksre[re] = ban
save_bans()
autil.reply(c, "Nicks that match \"" .. re .. "\" will be blocked (" .. ban_added_string(ban) .. ")")
return
end
if bans.nicksre[re] then
bans.nicksre[re] = nil
save_bans()
autil.reply(c, "Nicks that match \"" .. re .. "\" won't be blocked anymore")
else
autil.reply(c, "Nicks that match \"" .. re .. "\" are not being blocked")
end
end,
help = "[action] <nick-reg-exp> [reason] [expiration] - block nicks that match the given reg exp (must be within '<' and '>' brackets); action is optional, skip for simple block, set to -1 to ban forever or >= 0 to set how many minutes to ban for; expiration is also optional, defines when this rule expires (in minutes), skip for permanent rule, set to 0 to remove banre",
protected = is_op,
user_command = {
name = "Hub management" .. autil.ucmd_sep .. "Punish" .. autil.ucmd_sep .. "Ban nick (reg exp)",
params = {
autil.ucmd_line("Ban duration (facultative, in minutes; -1 = forever)"),
"<" .. autil.ucmd_line("Reg exp of nicks to forbid") .. ">",
autil.ucmd_line("Reason (facultative)"),
autil.ucmd_line("Rule expiration (facultative, in minutes)")
}
}
}
commands.banmsgre = {
command = function(c, parameters)
local level = access.get_level(c)
if level < settings.oplevel.value then
return
end
local minutes_pos, _, minutes = parameters:find(" (%d*)$")
if minutes_pos then
parameters = parameters:sub(1, minutes_pos - 1)
if #parameters <= 0 then
autil.reply(c, "Bad arguments")
return
end
end
local action_pos, _, action = parameters:find("^(-?%d*) ")
if action_pos then
parameters = parameters:sub(action_pos + 2)
if #parameters <= 0 then
autil.reply(c, "Bad arguments")
return
end
end
local re, reason = parameters:match("<([^>]+)> ?(.*)")
if not re then
autil.reply(c, "You need to supply a reg exp (within '<' and '>' brackets)")
return
end
if base.tonumber(minutes) ~= 0 then
local ban = make_ban(level, reason, minutes)
if action_pos then
ban.action = base.tonumber(action)
end
re = string.lower(re)
bans.msgsre[re] = ban
save_bans()
autil.reply(c, "Messages that match \"" .. re .. "\" will be blocked (" .. ban_added_string(ban) .. ")")
return
end
if bans.msgsre[re] then
bans.msgsre[re] = nil
save_bans()
autil.reply(c, "Messages that match \"" .. re .. "\" won't be blocked anymore")
else
autil.reply(c, "Messages that match \"" .. re .. "\" are not being blocked")
end
end,
help = "[action] <chat-reg-exp> [reason] [expiration] - block chatmessages that match the given reg exp (must be within '<' and '>' brackets); action is optional, skip for simple block, set to -1 to ban forever or >= 0 to set how many minutes to ban for; expiration is also optional, defines when this rule expires (in minutes), skip for permanent rule, set to 0 to remove banre",
protected = is_op,
user_command = {
name = "Hub management" .. autil.ucmd_sep .. "Punish" .. autil.ucmd_sep .. "Ban chat (reg exp)",
params = {
autil.ucmd_line("Ban duration (facultative, in minutes; -1 = forever)"),
"<" .. autil.ucmd_line("Reg exp of chat messages to forbid") .. ">",
autil.ucmd_line("Reason (facultative)"),
autil.ucmd_line("Rule expiration (facultative, in minutes)")
}
}
}
commands.banschre = {
command = function(c, parameters)
local level = access.get_level(c)
if level < settings.oplevel.value then
return
end
local minutes_pos, _, minutes = parameters:find(" (%d*)$")
if minutes_pos then
parameters = parameters:sub(1, minutes_pos - 1)
if #parameters <= 0 then
autil.reply(c, "Bad arguments")
return
end
end
local action_pos, _, action = parameters:find("^(-?%d*) ")
if action_pos then
parameters = parameters:sub(action_pos + 2)
if #parameters <= 0 then
autil.reply(c, "Bad arguments")
return
end
end
local re, reason = parameters:match("<([^>]+)> ?(.*)")
if not re then
autil.reply(c, "You need to supply a reg exp (within '<' and '>' brackets)")
return
end
if base.tonumber(minutes) ~= 0 then
local ban = make_ban(level, reason, minutes)
if action_pos then
ban.action = base.tonumber(action)
end
bans.schsre[re] = ban
save_bans()
autil.reply(c, "Searches that match \"" .. re .. "\" will be blocked (" .. ban_added_string(ban) .. ")")
return
end
if bans.schsre[re] then
bans.schsre[re] = nil
save_bans()
autil.reply(c, "Searches that match \"" .. re .. "\" won't be blocked anymore")
else
autil.reply(c, "Searches that match \"" .. re .. "\" are not being blocked")
end
end,
help = "[action] <search-reg-exp> [reason] [expiration] - block searches that match the given reg exp (must be within '<' and '>' brackets); action is optional, skip for simple block, set to -1 to ban forever or >= 0 to set how many minutes to ban for; expiration is also optional, defines when this rule expires (in minutes), skip for permanent rule, set to 0 to remove banre",
protected = is_op,
user_command = {
name = "Hub management" .. autil.ucmd_sep .. "Punish" .. autil.ucmd_sep .. "Ban search (reg exp)",
params = {
autil.ucmd_line("Ban duration (facultative, in minutes; -1 = forever)"),
"<" .. autil.ucmd_line("Reg exp of search messages to forbid") .. ">",
autil.ucmd_line("Reason (facultative)"),
autil.ucmd_line("Rule expiration (facultative, in minutes)")
}
}
}
commands.listbans = {
alias = { listban = true, listbanned = true, showban = true, showbans = true, showbanned = true, banlist = true, banslist = true },
command = function(c)
local level = access.get_level(c)
if level < settings.oplevel.value then
return
end
local str = "\nCID bans:"
for cid, ban in base.pairs(bans.cids) do
str = str .. "\n\tCID: " .. cid .. "\t | " .. ban_info_string(ban)
end
str = str .. "\n\nIP bans:"
for ip, ban in base.pairs(bans.ips) do
str = str .. "\n\tIP: " .. ip .. "\t | " .. ban_info_string(ban)
end
str = str .. "\n\nNick bans:"
for nick, ban in base.pairs(bans.nicks) do
str = str .. "\n\tNick: " .. nick .. "\t | " .. ban_info_string(ban)
end
str = str .. "\n\nNick bans (reg exp):"
for nickre, ban in base.pairs(bans.nicksre) do
str = str .. "\n\tReg exp: " .. nickre .. "\t | " .. ban_info_string(ban)
end
str = str .. "\n\nMessage bans (reg exp):"
for msgre, ban in base.pairs(bans.msgsre) do
str = str .. "\n\tReg exp: " .. msgre .. "\t | " .. ban_info_string(ban)
end
str = str .. "\n\nSearch bans (reg exp):"
for schre, ban in base.pairs(bans.schsre) do
str = str .. "\n\tReg exp: " .. schre .. "\t | " .. ban_info_string(ban)
end
str = str .. "\n\nMuted:"
for cid, ban in base.pairs(bans.muted) do
str = str .. "\n\tCID: " .. cid .. "\t | " .. ban_info_string(ban)
end
autil.reply(c, str)
end,
protected = is_op,
user_command = { name = "Hub management" .. autil.ucmd_sep .. "List bans" }
}
commands.loadbans = {
alias = { reloadbans = true },
command = function(c)
local level = access.get_level(c)
if level < settings.oplevel.value then
return
end
load_bans()
autil.reply(c, "Ban list reloaded")
end,
help = "- reload the ban list",
protected = is_op,
user_command = { name = "Hub management" .. autil.ucmd_sep .. "Reload bans" }
}
commands.mute = {
alias = { stfu = true },
command = function(c, parameters)
local level = access.get_level(c)
if level < settings.oplevel.value then
return
end
local minutes_pos, _, minutes = parameters:find(" (%d*)$")
if minutes_pos then
parameters = parameters:sub(1, minutes_pos - 1)
if #parameters <= 0 then
autil.reply(c, "Bad arguments")
return
end
end
local nick, reason = parameters:match("^(%S+) ?(.*)")
if not nick then
autil.reply(c, "You need to supply a nick")
return
end
local victim = cm:findByNick(nick)
if victim then
victim = victim:asClient()
end
if not victim then
autil.reply(c, "No user nick-named \"" .. nick .. "\"")
return
end
local victim_cid = victim:getCID():toBase32()
if level <= access.get_level(victim) then
autil.reply(c, "You can't mute users whose level is higher or equal than yours")
return
end
if base.tonumber(minutes) ~= 0 then
local ban = make_ban(level, reason, minutes)
bans.muted[victim_cid] = ban
save_bans()
autil.reply(c, "\"" .. nick .. "\" (CID: " .. victim_cid .. ") is now muted (" .. ban_added_string(ban) .. ")")
return
end
if bans.muted[victim_cid] then
bans.muted[victim_cid] = nil
save_bans()
autil.reply(c, "\"" .. nick .. "\" (CID: " .. victim_cid .. ") is now removed from the mutelist")
else
autil.reply(c, "\"" .. nick .. "\" (CID: " .. victim_cid .. ") is not found in the mutelist")
end
end,
help = "nick [reason] [minutes] - mute an online user (set minutes to 0 to un-mute)",
protected = access.is_op,
user_command = {
hub_params = {
autil.ucmd_line("Nick"),
autil.ucmd_line("Reason (facultative)"),
autil.ucmd_line("Minutes (facultative)")
},
name = "Hub management" .. autil.ucmd_sep .. "Punish" .. autil.ucmd_sep .. "Mute",
user_params = {
"%[userNI]",
autil.ucmd_line("Reason (facultative)"),
autil.ucmd_line("Minutes (facultative)")
}
}
}
local function onMSG(c, cmd)
local muted = bans.muted[c:getCID():toBase32()]
if muted then
autil.reply(c, "You are muted" .. ban_return_info(muted))
return false
end
local level = access.get_level(c)
local msg = string.lower(cmd:getParam(0))
for re, reban in base.pairs(bans.msgsre) do
if reban.level > level and msg:match(re) then
local str = "Message blocked"
if reban.reason then
str = str .. ": " .. reban.reason
end
if reban.action then
local ban = { level = reban.level, reason = str }
if reban.action == 0 then
ban.expires = 0
else
if reban.action > 0 then
ban.expires = os.time() + reban.action * 60
end
bans.cids[c:getCID():toBase32()] = ban
save_bans()
end
dump_banned(c, ban)
else
autil.reply(c, str)
end
return false
end
end
return true
end
local function onSCH(c, cmd)
local level = access.get_level(c)
local sch
local tr = cmd:getParam('TR', 0)
if #tr > 0 then
return true
else
local vars = {}
local params = cmd:getParameters()
local params_size = params:size()
if params_size > 0 then
for i = 0, params_size - 1 do
local param = params[i]
if #param > 2 then
local field = string.sub(param, 1, 2)
if field == 'AN' then
local var = string.sub(param, 3)
table.insert(vars, string.lower(var))
end
end
end
end
sch = table.concat(vars, ' ')
end
for re, reban in base.pairs(bans.schsre) do
if reban.level > level and sch:match(re) then
local str = "Search blocked"
if reban.reason then
str = str .. ": " .. reban.reason
end
if reban.action then
local ban = { level = reban.level, reason = str }
if reban.action == 0 then
ban.expires = 0
else
if reban.action > 0 then
ban.expires = os.time() + reban.action * 60
end
bans.cids[c:getCID():toBase32()] = ban
save_bans()
end
dump_banned(c, ban)
else
autil.reply(c, str)
end
return false
end
end
return true
end
local function onINF(c, cmd)
local cid, nick
if c:getState() == adchpp.Entity_STATE_NORMAL then
cid = c:getCID():toBase32()
nick = c:getField("NI")
else
cid = cmd:getParam("ID", 0)
nick = cmd:getParam("NI", 0)
end
local ban = nil
if bans.cids[cid] then
ban = bans.cids[cid]
elseif bans.ips[c:getIp()] then
ban = bans.ips[c:getIp()]
elseif bans.nicks[nick] then
ban = bans.nicks[nick]
else
for re, reban in base.pairs(bans.nicksre) do
if nick:match(re) and reban.level > access.get_level(c) then
local str = "Nick blocked"
if reban.reason then
str = str .. ": " .. reban.reason
end
ban = { level = reban.level, reason = str }
if reban.action and reban.action ~= 0 then
if reban.action > 0 then
ban.expires = os.time() + reban.action * 60
end
bans.cids[c:getCID():toBase32()] = ban
save_bans()
else
ban.expires = 0
end
break
end
end
end
if ban and ban.level > access.get_level(c) then
dump_banned(c, ban)
return false
end
return true
end
load_bans()
access.register_handler(adchpp.AdcCommand_CMD_MSG, onMSG, true)
access.register_handler(adchpp.AdcCommand_CMD_SCH, onSCH, true)
access.register_handler(adchpp.AdcCommand_CMD_INF, onINF)
cancel_timer = sm:addTimedJob(1000, clear_expired_bans)
autil.on_unloading(_NAME, cancel_timer)

View File

@@ -0,0 +1,117 @@
-- This script contains settings and commands related to the bot. If this script is not loaded, the bot will not appear...
-- The main bot managed by this script is stored in the public "main_bot" variable.
local base=_G
module("access.bot")
base.require("luadchpp")
local adchpp = base.luadchpp
local access = base.require("access")
local string = base.require('string')
local autil = base.require("autil")
local settings = access.settings
local commands = access.commands
local cm = adchpp.getCM()
main_bot = nil
access.add_setting('botcid', {
alias = { botid = true },
help = "CID of the bot, restart the hub after the change",
value = adchpp.CID_generate():toBase32(),
validate = function(new)
if adchpp.CID(new.value):toBase32() ~= new.value then
return "the CID must be a valid 39-byte base32 representation"
end
end
})
access.add_setting('botname', {
alias = { botnick = true, botni = true },
change = function()
if main_bot then
main_bot:setField("NI", settings.botname.value)
cm:sendToAll(adchpp.AdcCommand(adchpp.AdcCommand_CMD_INF, adchpp.AdcCommand_TYPE_BROADCAST, main_bot:getSID()):addParam("NI", settings.botname.value):getBuffer())
end
end,
help = "name of the hub bot",
value = "Bot",
validate = access.validate_ni
})
access.add_setting('botdescription', {
alias = { botdescr = true, botde = true },
change = function()
if main_bot then
main_bot:setField("DE", settings.botdescription.value)
cm:sendToAll(adchpp.AdcCommand(adchpp.AdcCommand_CMD_INF, adchpp.AdcCommand_TYPE_BROADCAST, main_bot:getSID()):addParam("DE", settings.botdescription.value):getBuffer())
end
end,
help = "description of the hub bot",
value = "",
validate = access.validate_de
})
access.add_setting('botemail', {
alias = { botmail = true, botem = true },
change = function()
if main_bot then
main_bot:setField("EM", settings.botemail.value)
cm:sendToAll(adchpp.AdcCommand(adchpp.AdcCommand_CMD_INF, adchpp.AdcCommand_TYPE_BROADCAST, main_bot:getSID()):addParam("EM", settings.botemail.value):getBuffer())
end
end,
help = "e-mail of the hub bot",
value = ""
})
local function onMSG(c, cmd)
if autil.reply_from and autil.reply_from:getSID() == main_bot:getSID() then
local msg = cmd:getParam(0)
if access.handle_plus_command(c, msg) then
return false
end
autil.reply(c, 'Invalid command, send "+help" for a list of available commands')
return false
end
return true
end
local function makeBot()
local bot = cm:createSimpleBot()
bot:setCID(adchpp.CID(settings.botcid.value))
bot:setField("ID", settings.botcid.value)
bot:setField("NI", settings.botname.value)
bot:setField("DE", settings.botdescription.value)
bot:setField("EM", settings.botemail.value)
bot:setFlag(adchpp.Entity_FLAG_OP)
bot:setFlag(adchpp.Entity_FLAG_SU)
bot:setFlag(adchpp.Entity_FLAG_OWNER)
return bot
end
main_bot = makeBot()
cm:regBot(main_bot)
autil.on_unloaded(_NAME, function()
main_bot:disconnect(adchpp.Util_REASON_PLUGIN)
end)
access.register_handler(adchpp.AdcCommand_CMD_MSG, onMSG)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,219 @@
-- This script contains a few useful op commands such as kick and redirect
local base=_G
module("access.op")
base.require("luadchpp")
local adchpp = base.luadchpp
local access = base.require("access")
local autil = base.require("autil")
local string = base.require("string")
local commands = access.commands
local settings = access.settings
local get_level = access.get_level
local is_op = access.is_op
local cm = adchpp.getCM()
commands.kick = {
alias = { drop = true, dropuser = true, kickuser = true },
command = function(c, parameters)
local level = get_level(c)
if level < settings.oplevel.value then
return
end
local nick, reason = parameters:match("^(%S+) ?(.*)")
if not nick then
autil.reply(c, "You need to supply a nick")
return
end
local victim = cm:findByNick(nick)
if victim then
victim = victim:asClient()
end
if not victim then
autil.reply(c, "No user nick-named \"" .. nick .. "\"")
return
end
local victim_cid = victim:getCID():toBase32()
if level <= get_level(victim) then
autil.reply(c, "You can't kick users whose level is higher or equal than yours")
return
end
local text = "You have been kicked"
if string.len(reason) > 0 then
text = text .. " (reason: " .. reason .. ")"
end
autil.dump(victim, adchpp.AdcCommand_ERROR_BANNED_GENERIC, function(cmd)
cmd:addParam("ID" .. adchpp.AdcCommand_fromSID(c:getSID()))
:addParam("MS" .. text)
end)
autil.reply(c, "\"" .. nick .. "\" (CID: " .. victim_cid .. ") has been kicked")
end,
help = "user [reason] - disconnect the user, she can reconnect whenever she wants to",
protected = is_op,
user_command = {
hub_params = {
autil.ucmd_line("User"),
autil.ucmd_line("Reason (facultative)")
},
name = "Hub management" .. autil.ucmd_sep .. "Punish" .. autil.ucmd_sep .. "Kick",
user_params = {
"%[userNI]",
autil.ucmd_line("Reason (facultative)")
}
}
}
commands.mass = {
alias = { massmessage = true },
command = function(c, parameters)
if not commands.mass.protected(c) then
return
end
local level_pos, _, level = parameters:find(" ?(%d*)$")
local message = parameters:sub(0, level_pos - 1)
if #message <= 0 then
autil.reply(c, "You need to supply a message")
return
end
if string.len(level) > 0 then
level = base.tonumber(level)
else
level = 0
end
local entities = cm:getEntities()
local size = entities:size()
if size == 0 then
return
end
local mass_cmd
if access.bot then
mass_cmd = autil.pm(message, access.bot.main_bot:getSID(), 0)
else
mass_cmd = autil.info(message)
end
local count = 0
for i = 0, size - 1 do
local other = entities[i]:asClient()
if other then
local ok = level <= 0
if not ok then
ok = get_level(other) >= level
end
if ok then
mass_cmd:setTo(other:getSID())
other:send(mass_cmd)
count = count + 1
end
end
end
autil.reply(c, "Message sent to " .. count .. " users")
end,
help = "message [min-level] -no min-level means send to everyone",
protected = is_op,
user_command = {
name = "Hub management" .. autil.ucmd_sep .. "Mass message",
params = {
autil.ucmd_line("Message"),
autil.ucmd_line("Minimum level (facultative)")
}
}
}
commands.redirect = {
alias = { forward = true },
command = function(c, parameters)
local level = get_level(c)
if level < settings.oplevel.value then
return
end
local nick_pos, _, nick = parameters:find("^(%S+)")
if nick_pos then
parameters = parameters:sub(nick_pos + 2)
if #parameters <= 0 then
autil.reply(c, "Bad arguments")
return
end
end
if not nick then
autil.reply(c, "You need to supply a nick")
return
end
local victim = cm:findByNick(nick)
if victim then
victim = victim:asClient()
end
if not victim then
autil.reply(c, "No user nick-named \"" .. nick .. "\"")
return
end
local address, reason = parameters:match("<([^>]+)> ?(.*)")
if not address then
autil.reply(c, "You need to supply a redirect address (within '<' and '>' brackets)")
return
end
local victim_cid = victim:getCID():toBase32()
if level <= get_level(victim) then
autil.reply(c, "You can't redirect users whose level is higher or equal than yours")
return
end
local text = "You have been redirected"
if string.len(reason) > 0 then
text = text .. " (reason: " .. reason .. ")"
end
autil.dump(victim, adchpp.AdcCommand_ERROR_BANNED_GENERIC, function(cmd)
cmd:addParam("ID" .. adchpp.AdcCommand_fromSID(c:getSID()))
:addParam("MS" .. text)
cmd:addParam("RD" .. address)
end)
autil.reply(c, "\"" .. nick .. "\" (CID: " .. victim_cid .. ") has been redirected to \"" .. address .. "\"")
end,
help = "nick <address> [reason] - redirects the user to the given address (must be within '<' and '>' brackets); reason is optional ",
protected = is_op,
user_command = {
hub_params = {
autil.ucmd_line("Nick"),
"<" .. autil.ucmd_line("Redirect Address") .. ">",
autil.ucmd_line("Reason (facultative)")
},
name = "Hub management" .. autil.ucmd_sep .. "Punish" .. autil.ucmd_sep .. "Redirect",
user_params = {
"%[userNI]",
"<" .. autil.ucmd_line("Redirect Address") .. ">",
autil.ucmd_line("Reason (facultative)")
}
}
}

View File

@@ -0,0 +1,107 @@
-- I/O utilities for adchpp.
local base = _G
module('aio')
local io = base.require('io')
local json = base.require('json')
-- forward declarations.
local read_file, write_file
-- read a file. if reading the file fails, try to load its backup (.tmp file).
-- post_load: optional function called after a successful read.
-- return values:
-- * boolean success flag.
-- * file contents on success.
-- * error / debug message.
function load_file(path, post_load)
-- try to read the file.
local ok, str = read_file(path, post_load)
if ok then
return true, str
end
-- couldn't read the file; try to read the backup.
local ok2, str2 = read_file(path .. '.tmp', post_load)
if ok2 then
-- copy the backup to the actual file.
local ok3, str3 = read_file(path .. '.tmp') -- read without post_load.
if ok3 then
write_file(path, str3)
end
return true, str2, str .. '; loaded backup from ' .. path .. '.tmp'
end
-- couldn't read anything.
return false, nil, str .. '; ' .. str2
end
-- write to a file after having backed it up to a .tmp file.
-- return value: error message on failure, nothing otherwise.
function save_file(path, contents)
-- start by saving a backup, in case writing fails.
local ok, str = read_file(path)
if ok then
write_file(path .. '.tmp', str)
end
-- the file has been backed up; now write to it.
return write_file(path, contents)
end
-- wrapper around a json decoder, suitable for use as the post_load param of load_file.
function json_loader(str)
local ok, ret = base.pcall(json.decode, str)
if ok then
return true, ret
end
return false, 'Corrupted file, unable to decode the JSON data'
end
-- utility function that reads a file.
-- post_load: optional function called after a successful read.
-- return values:
-- * boolean success flag.
-- * file contents on success; error message otherwise.
read_file = function(path, post_load)
local ok, file = base.pcall(io.open, path, 'r')
if not ok or not file then
return false, 'Unable to open ' .. path .. ' for reading'
end
local str
base.pcall(function()
str = file:read('*a')
file:close()
end)
if not str or #str == 0 then
return false, 'Unable to read ' .. path
end
if post_load then
return post_load(str)
end
return true, str
end
-- utility function that writes to a file.
-- return value: error message on failure, nothing otherwise.
write_file = function(path, contents)
local ok, file = base.pcall(io.open, path, 'w')
if not ok or not file then
return 'Unable to open ' .. path .. ' for writing'
end
base.pcall(function()
file:write(contents)
file:close()
end)
end

View File

@@ -0,0 +1,125 @@
-- Various utilities for adchpp
local base = _G
module("autil")
base.require('luadchpp')
local adchpp = base.luadchpp
local string = base.require('string')
local table = base.require('table')
ucmd_sep = "/"
function ucmd_line(str)
return "%[line:" .. str .. "]"
end
function ucmd_list(title, options, selected)
return ucmd_line(title .. "/" .. base.tostring(selected or 0) .. "/" .. table.concat(options, "/"))
end
function info(m)
return adchpp.AdcCommand(adchpp.AdcCommand_CMD_MSG, adchpp.AdcCommand_TYPE_INFO, adchpp.AdcCommand_HUB_SID)
:addParam(m)
end
-- "from" and "to" are SIDs (numbers).
function pm(m, from, to)
local command = adchpp.AdcCommand(adchpp.AdcCommand_CMD_MSG, adchpp.AdcCommand_TYPE_DIRECT, from)
:addParam(m)
:addParam("PM", adchpp.AdcCommand_fromSID(from))
command:setTo(to)
return command
end
function reply(c, m)
local command
if reply_from then
command = pm(m, reply_from:getSID(), c:getSID())
else
command = info(m)
end
c:send(command)
end
-- params: either a message string or a function(AdcCommand QUI_command).
function dump(c, code, params)
local msg
local cmd = adchpp.AdcCommand(adchpp.AdcCommand_CMD_QUI, adchpp.AdcCommand_TYPE_INFO, adchpp.AdcCommand_HUB_SID)
:addParam(adchpp.AdcCommand_fromSID(c:getSID())):addParam("DI1")
if base.type(params) == "function" then
params(cmd)
msg = cmd:getParam("MS", 1)
else
msg = params
cmd:addParam("MS" .. msg)
end
c:send(adchpp.AdcCommand(adchpp.AdcCommand_CMD_STA, adchpp.AdcCommand_TYPE_INFO, adchpp.AdcCommand_HUB_SID)
:addParam(adchpp.AdcCommand_SEV_FATAL .. code):addParam(msg))
c:send(cmd)
c:disconnect(adchpp.Util_REASON_PLUGIN)
end
local function file_of_name(file, name)
return string.sub(file, -4 - #name) == name .. '.lua'
end
local loading = {}
function on_loading(name, f)
table.insert(loading, { name = name, f = f })
end
base.loading = function(file)
local ret = false
for _, v in base.pairs(loading) do
if file_of_name(file, v.name) then
if v.f() then
ret = true
end
end
end
return ret
end
local loaded = {}
function on_loaded(name, f)
table.insert(loaded, { name = name, f = f })
end
base.loaded = function(file)
for _, v in base.pairs(loaded) do
if file_of_name(file, v.name) then
v.f()
end
end
end
local unloading = {}
function on_unloading(name, f)
table.insert(unloading, { name = name, f = f })
end
base.unloading = function(file)
local ret = false
for _, v in base.pairs(unloading) do
if file_of_name(file, v.name) then
if v.f() then
ret = true
end
end
end
return ret
end
local unloaded = {}
function on_unloaded(name, f)
table.insert(unloaded, { name = name, f = f })
end
base.unloaded = function(file)
for _, v in base.pairs(unloaded) do
if file_of_name(file, v.name) then
v.f()
end
end
end

View File

@@ -0,0 +1,31 @@
local base = _G
-- Notifies a user he may have empty files in his share according to his BLOOM
-- Requires that the Bloom plugin is loaded
module("checkempty")
base.require('luadchpp')
local adchpp = base.luadchpp
base.require('luadchppbloom')
local badchpp = base.luadchppbloom
local autil = base.require('autil')
local bm = badchpp.getBM()
-- Checking function
local function checker (entity)
-- Only run the check in NORMAL state
if entity:getState() == adchpp.Entity_STATE_NORMAL then
-- If no bloom is available hasTTH has undefined behaviour
if bm:hasBloom(entity) and bm:hasTTH(entity,"LWPNACQDBZRYXW3VHJVCJ64QBZNGHOHHHZWCLNQ") then
autil.reply(entity, "It's possible you have empty files in your share")
end
end
end
-- Checks for possible bllom update that may happen before getting into NORMAL
checkold = adchpp.getCM():signalState():connect(checker)
-- Checks for bloom updates happening after getting into NORMAL
checkempty = bm:signalBloomReady():connect(checker)

View File

@@ -0,0 +1,91 @@
-- This is an example script that scripters might want to use as a basis for their work. It
-- documents the bare minimum needed to make an Lua script interface with ADCH++.
-- For more detailed information, peeking into other, more evolved (but less documented) example
-- scripts as well as the Doxygen documentation of ADCH++ <http://adchpp.sourceforge.net/doc> would
-- be a good idea.
-- Generally, to reach member "bar" of the class "foo" of ADCH++, one has to use: adchpp.foo_bar.
-- Examples: adchpp.Util_getCfgPath(), adchpp.AdcCommand_CMD_MSG, adchpp.Entity_STATE_NORMAL...
-- Feel free to use <https://answers.launchpad.net/adchpp> or <www.adcportal.com> if you need help.
-- When multiple scripts are loaded within the same global Lua engine, they can access each other's
-- public exports. It is therefore a convention to run each script in its own module.
-- Global Lua functions can still be accessed via _G, but we alias it to "base" for conveniance.
-- Global functions can then be called as such: base.print('blah'), base.pcall(protected_func)...
local base = _G
module('example') -- Give each module a unique name so they don't clash.
-- Import the ADCH++ Lua DLL (luadchpp.dll).
base.require('luadchpp')
local adchpp = base.luadchpp
-- Import various base sets of Lua methods. Only import those you need for your specific module.
local io = base.require('io')
local math = base.require('math')
local os = base.require('os')
local string = base.require('string')
local table = base.require('table')
-- Import some utilitary Lua Scripts; these don't need to be explicitly loaded by ADCH++ (eg if
-- using the adchppd daemon, they don't need to be referenced in Scripts.xml).
local autil = base.require('autil')
local json = base.require('json')
-- Cache pointers to some managers of ADCH++ that we frequently use.
local cm = adchpp.getCM() -- ClientManager
local pm = adchpp.getPM() -- PluginManager
-- Listeners to connect to ADCH++. Define one unique identifier for each listener (i chose to call
-- them example_1, example_2 and so on) to make sure the variable holding the listener doesn't get
-- collected by Lua's garbage collector until the program is over.
-- ClientManager::signalConnected: called when an Entity entity has just connected.
example_1 = cm:signalConnected():connect(function(entity)
-- Process signalConnected here.
end)
-- ClientManager::signalReady: called when an Entity entity is now ready for read / write
-- operations (TLS handshake completed).
example_2 = cm:signalReady():connect(function(entity)
-- Process signalReady here.
end)
-- ClientManager::signalReceive: called when an AdcCommand cmd is received from Entity entity.
example_3 = cm:signalReceive():connect(function(entity, cmd, ok)
local res = (function(entity, cmd, ok)
-- Skip messages that have been handled and deemed as discardable by others.
if not ok then
return ok
end
-- Process signalReceive here.
-- Return true to let the command be dispatched, false to block it.
end)(entity, cmd, ok)
if not res then
cmd:setPriority(adchpp.AdcCommand_PRIORITY_IGNORE)
end
return res
end)
-- ClientManager::signalState: called after the state of an online Entity entity has changed.
example_4 = cm:signalState():connect(function(entity)
-- Process signalState here.
end)
-- ClientManager::signalDisconnected: called after an Entity entity has disconnected.
example_5 = cm:signalDisconnected():connect(function(entity)
-- Process signalDisconnected here.
end)
-- PluginManager::getCommandSignal(string): called when a +command managed by another plugin is
-- being executed.
example_6 = pm:getCommandSignal("blah"):connect(function(entity, list, ok)
-- Skip messages that have been handled and deemed as discardable by others.
if not ok then
return ok
end
-- Process getCommandSignal here.
-- Return true to let the command be executed, false to block it.
end)

View File

@@ -0,0 +1,258 @@
-- Simple history script that displays the last n history items
-- History is persisted across restarts
local base = _G
module("history")
base.require('luadchpp')
local adchpp = base.luadchpp
base.assert(base['access'], 'access.lua must be loaded and running before history.lua')
local access = base.access
-- Where to read/write history file - set to nil to disable persistent history
local history_file = adchpp.Util_getCfgPath() .. "history.txt"
local os = base.require('os')
local json = base.require('json')
local string = base.require('string')
local aio = base.require('aio')
local autil = base.require('autil')
local table = base.require('table')
local cm = adchpp.getCM()
local sm = adchpp.getSM()
local lm = adchpp.getLM()
local pos = 1
local messages_saved = true
local messages = {}
access.add_setting('history_max', {
help = "number of messages to keep for +history",
value = 500
})
access.add_setting('history_default', {
help = "number of messages to display in +history if the user doesn't select anything else",
value = 50
})
access.add_setting('history_connect', {
help = "number of messages to display to the users on connect",
value = 10
})
access.add_setting('history_method', {
help = "strategy used by the +history script to record messages, restart the hub after the change, 1 = use a hidden bot, 0 = direct ADCH++ interface",
value = 1
})
access.add_setting('history_prefix', {
help = "prefix to put before each message in +history",
value = "[%Y-%m-%d %H:%M:%S] "
})
local function log(message)
lm:log(_NAME, message)
end
local function get_items(c)
local items = 1
local user = access.get_user_c(c)
local from = user.lastofftime
if from then
for hist, data in base.pairs(messages) do
if data.htime > from then
items = items + 1
end
end
end
return items
end
local function get_lines(num)
if num > access.settings.history_max.value then
num = access.settings.history_max.value + 1
end
local s = 1
if table.getn(messages) > access.settings.history_max.value then
s = pos - access.settings.history_max.value + 1
end
if num < pos then
s = pos - num + 1
end
local e = pos
local lines = "Displaying the last " .. (e - s) .. " messages"
while s <= e and messages[s] do
lines = lines .. "\r\n" .. messages[s].message
s = s + 1
end
return lines
end
access.commands.history = {
alias = { hist = true },
command = function(c, parameters)
local items
if #parameters > 0 then
items = base.tonumber(parameters) + 1
if not items then
return
end
else
if access.get_level(c) > 0 then
items = get_items(c)
end
end
if not items then
items = access.settings.history_default.value + 1
end
autil.reply(c, get_lines(items))
end,
help = "[lines] - display main chat messages logged by the hub (no lines=default / since last logoff)",
user_command = {
name = "Chat history",
params = { autil.ucmd_line("Number of msg's to display (empty=default / since last logoff)") }
}
}
local function save_messages()
if not history_file then
return
end
local s = 1
local e = pos
if table.getn(messages) >= access.settings.history_max.value then
s = pos - access.settings.history_max.value
e = table.getn(messages)
end
local list = {}
while s <= e and messages[s] do
table.insert(list, messages[s])
s = s + 1
end
messages = list
pos = table.getn(messages) + 1
local err = aio.save_file(history_file, json.encode(list))
if err then
log('History not saved: ' .. err)
else
messages_saved = true
end
end
local function load_messages()
if not history_file then
return
end
local ok, list, err = aio.load_file(history_file, aio.json_loader)
if err then
log('History loading: ' .. err)
end
if not ok then
return
end
for k, v in base.pairs(list) do
messages[k] = v
pos = pos + 1
end
end
local function maybe_save_messages()
if not messages_saved then
save_messages()
end
end
local function parse(cmd)
if cmd:getCommand() ~= adchpp.AdcCommand_CMD_MSG or cmd:getType() ~= adchpp.AdcCommand_TYPE_BROADCAST then
return
end
local from = cm:getEntity(cmd:getFrom())
if not from then
return
end
local nick = from:getField("NI")
if #nick < 1 then
return
end
local now = os.date(access.settings.history_prefix.value)
local message
if cmd:getParam("ME", 1) == "1" then
message = now .. '* ' .. nick .. ' ' .. cmd:getParam(0)
else
message = now .. '<' .. nick .. '> ' .. cmd:getParam(0)
end
messages[pos] = { message = message, htime = os.time() }
pos = pos + 1
messages_saved = false
end
history_1 = cm:signalState():connect(function(entity)
if access.settings.history_connect.value > 0 and entity:getState() == adchpp.Entity_STATE_NORMAL then
autil.reply(entity, get_lines(access.settings.history_connect.value + 1))
end
end)
load_messages()
if access.settings.history_method.value == 0 then
history_1 = cm:signalReceive():connect(function(entity, cmd, ok)
if not ok then
return ok
end
parse(cmd)
return true
end)
else
hidden_bot = cm:createBot(function(bot, buffer)
parse(adchpp.AdcCommand(buffer))
end)
hidden_bot:setField('ID', hidden_bot:getCID():toBase32())
hidden_bot:setField('NI', _NAME .. '-hidden_bot')
hidden_bot:setField('DE', 'Hidden bot used by the ' .. _NAME .. ' script')
hidden_bot:setFlag(adchpp.Entity_FLAG_HIDDEN)
cm:regBot(hidden_bot)
autil.on_unloaded(_NAME, function()
hidden_bot:disconnect(adchpp.Util_REASON_PLUGIN)
end)
end
save_messages_timer = sm:addTimedJob(900000, maybe_save_messages)
autil.on_unloading(_NAME, save_messages_timer)
autil.on_unloading(_NAME, maybe_save_messages)

View File

@@ -0,0 +1,376 @@
-----------------------------------------------------------------------------
-- JSON4Lua: JSON encoding / decoding support for the Lua language.
-- json Module.
-- Author: Craig Mason-Jones
-- Homepage: http://json.luaforge.net/
-- Version: 0.9.20
-- This module is released under the The GNU General Public License (GPL).
-- Please see LICENCE.txt for details.
--
-- USAGE:
-- This module exposes two functions:
-- encode(o)
-- Returns the table / string / boolean / number / nil / json.null value as a JSON-encoded string.
-- decode(json_string)
-- Returns a Lua object populated with the data encoded in the JSON string json_string.
--
-- REQUIREMENTS:
-- compat-5.1 if using Lua 5.0
--
-- CHANGELOG
-- 0.9.20 Introduction of local Lua functions for private functions (removed _ function prefix).
-- Fixed Lua 5.1 compatibility issues.
-- Introduced json.null to have null values in associative arrays.
-- encode() performance improvement (more than 50%) through table.concat rather than ..
-- Introduced decode ability to ignore /**/ comments in the JSON string.
-- 0.9.10 Fix to array encoding / decoding to correctly manage nil/null values in arrays.
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Imports and dependencies
-----------------------------------------------------------------------------
local math = require('math')
local string = require("string")
local table = require("table")
local base = _G
-----------------------------------------------------------------------------
-- Module declaration
-----------------------------------------------------------------------------
module("json")
-- Public functions
-- Private functions
local decode_scanArray
local decode_scanComment
local decode_scanConstant
local decode_scanNumber
local decode_scanObject
local decode_scanString
local decode_scanWhitespace
local encodeString
local isArray
local isEncodable
-----------------------------------------------------------------------------
-- PUBLIC FUNCTIONS
-----------------------------------------------------------------------------
--- Encodes an arbitrary Lua object / variable.
-- @param v The Lua object / variable to be JSON encoded.
-- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode)
function encode (v)
-- Handle nil values
if v==nil then
return "null"
end
local vtype = base.type(v)
-- Handle strings
if vtype=='string' then
return '"' .. encodeString(v) .. '"' -- Need to handle encoding in string
end
-- Handle booleans
if vtype=='number' or vtype=='boolean' then
return base.tostring(v)
end
-- Handle tables
if vtype=='table' then
local rval = {}
-- Consider arrays separately
local bArray, maxCount = isArray(v)
if bArray then
for i = 1,maxCount do
table.insert(rval, encode(v[i]))
end
else -- An object, not an array
for i,j in base.pairs(v) do
if isEncodable(i) and isEncodable(j) then
table.insert(rval, '"' .. encodeString(i) .. '":' .. encode(j))
end
end
end
if bArray then
return '[' .. table.concat(rval,',') ..']'
else
return '{' .. table.concat(rval,',') .. '}'
end
end
-- Handle null values
if vtype=='function' and v==null then
return 'null'
end
base.assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. base.tostring(v))
end
--- Decodes a JSON string and returns the decoded value as a Lua data structure / value.
-- @param s The string to scan.
-- @param [startPos] Optional starting position where the JSON string is located. Defaults to 1.
-- @param Lua object, number The object that was scanned, as a Lua table / string / number / boolean or nil,
-- and the position of the first character after
-- the scanned JSON object.
function decode(s, startPos)
startPos = startPos and startPos or 1
startPos = decode_scanWhitespace(s,startPos)
base.assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']')
local curChar = string.sub(s,startPos,startPos)
-- Object
if curChar=='{' then
return decode_scanObject(s,startPos)
end
-- Array
if curChar=='[' then
return decode_scanArray(s,startPos)
end
-- Number
if string.find("+-0123456789.e", curChar, 1, true) then
return decode_scanNumber(s,startPos)
end
-- String
if curChar==[["]] or curChar==[[']] then
return decode_scanString(s,startPos)
end
if string.sub(s,startPos,startPos+1)=='/*' then
return decode(s, decode_scanComment(s,startPos))
end
-- Otherwise, it must be a constant
return decode_scanConstant(s,startPos)
end
--- The null function allows one to specify a null value in an associative array (which is otherwise
-- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null }
function null()
return null -- so json.null() will also return null ;-)
end
-----------------------------------------------------------------------------
-- Internal, PRIVATE functions.
-- Following a Python-like convention, I have prefixed all these 'PRIVATE'
-- functions with an underscore.
-----------------------------------------------------------------------------
--- Scans an array from JSON into a Lua object
-- startPos begins at the start of the array.
-- Returns the array and the next starting position
-- @param s The string being scanned.
-- @param startPos The starting position for the scan.
-- @return table, int The scanned array as a table, and the position of the next character to scan.
function decode_scanArray(s,startPos)
local array = {} -- The return value
local stringLen = string.len(s)
base.assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s )
startPos = startPos + 1
-- Infinite loop for array elements
repeat
startPos = decode_scanWhitespace(s,startPos)
base.assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.')
local curChar = string.sub(s,startPos,startPos)
if (curChar==']') then
return array, startPos+1
end
if (curChar==',') then
startPos = decode_scanWhitespace(s,startPos+1)
end
base.assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.')
object, startPos = decode(s,startPos)
table.insert(array,object)
until false
end
--- Scans a comment and discards the comment.
-- Returns the position of the next character following the comment.
-- @param string s The JSON string to scan.
-- @param int startPos The starting position of the comment
function decode_scanComment(s, startPos)
base.assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos)
local endPos = string.find(s,'*/',startPos+2)
base.assert(endPos~=nil, "Unterminated comment in string at " .. startPos)
return endPos+2
end
--- Scans for given constants: true, false or null
-- Returns the appropriate Lua type, and the position of the next character to read.
-- @param s The string being scanned.
-- @param startPos The position in the string at which to start scanning.
-- @return object, int The object (true, false or nil) and the position at which the next character should be
-- scanned.
function decode_scanConstant(s, startPos)
local consts = { ["true"] = true, ["false"] = false, ["null"] = nil }
local constNames = {"true","false","null"}
for i,k in base.pairs(constNames) do
--print ("[" .. string.sub(s,startPos, startPos + string.len(k) -1) .."]", k)
if string.sub(s,startPos, startPos + string.len(k) -1 )==k then
return consts[k], startPos + string.len(k)
end
end
base.assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos)
end
--- Scans a number from the JSON encoded string.
-- (in fact, also is able to scan numeric +- eqns, which is not
-- in the JSON spec.)
-- Returns the number, and the position of the next character
-- after the number.
-- @param s The string being scanned.
-- @param startPos The position at which to start scanning.
-- @return number, int The extracted number and the position of the next character to scan.
function decode_scanNumber(s,startPos)
local endPos = startPos+1
local stringLen = string.len(s)
local acceptableChars = "+-0123456789.e"
while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true)
and endPos<=stringLen
) do
endPos = endPos + 1
end
local stringValue = 'return ' .. string.sub(s,startPos, endPos-1)
local stringEval = base.loadstring(stringValue)
base.assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos)
return stringEval(), endPos
end
--- Scans a JSON object into a Lua object.
-- startPos begins at the start of the object.
-- Returns the object and the next starting position.
-- @param s The string being scanned.
-- @param startPos The starting position of the scan.
-- @return table, int The scanned object as a table and the position of the next character to scan.
function decode_scanObject(s,startPos)
local object = {}
local stringLen = string.len(s)
local key, value
base.assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s)
startPos = startPos + 1
repeat
startPos = decode_scanWhitespace(s,startPos)
base.assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.')
local curChar = string.sub(s,startPos,startPos)
if (curChar=='}') then
return object,startPos+1
end
if (curChar==',') then
startPos = decode_scanWhitespace(s,startPos+1)
end
base.assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.')
-- Scan the key
key, startPos = decode(s,startPos)
base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
startPos = decode_scanWhitespace(s,startPos)
base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
base.assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos)
startPos = decode_scanWhitespace(s,startPos+1)
base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
value, startPos = decode(s,startPos)
object[key]=value
until false -- infinite loop while key-value pairs are found
end
--- Scans a JSON string from the opening inverted comma or single quote to the
-- end of the string.
-- Returns the string extracted as a Lua string,
-- and the position of the next non-string character
-- (after the closing inverted comma or single quote).
-- @param s The string being scanned.
-- @param startPos The starting position of the scan.
-- @return string, int The extracted string as a Lua string, and the next character to parse.
function decode_scanString(s,startPos)
base.assert(startPos, 'decode_scanString(..) called without start position')
local startChar = string.sub(s,startPos,startPos)
base.assert(startChar==[[']] or startChar==[["]],'decode_scanString called for a non-string')
local escaped = false
local endPos = startPos + 1
local bEnded = false
local stringLen = string.len(s)
repeat
local curChar = string.sub(s,endPos,endPos)
if not escaped then
if curChar==[[\]] then
escaped = true
else
bEnded = curChar==startChar
end
else
-- If we're escaped, we accept the current character come what may
escaped = false
end
endPos = endPos + 1
base.assert(endPos <= stringLen+1, "String decoding failed: unterminated string at position " .. endPos)
until bEnded
local stringValue = 'return ' .. string.sub(s, startPos, endPos-1)
local stringEval = base.loadstring(stringValue)
base.assert(stringEval, 'Failed to load string [ ' .. stringValue .. '] in JSON4Lua.decode_scanString at position ' .. startPos .. ' : ' .. endPos)
return stringEval(), endPos
end
--- Scans a JSON string skipping all whitespace from the current start position.
-- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached.
-- @param s The string being scanned
-- @param startPos The starting position where we should begin removing whitespace.
-- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string
-- was reached.
function decode_scanWhitespace(s,startPos)
local whitespace=" \n\r\t"
local stringLen = string.len(s)
while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true) and startPos <= stringLen) do
startPos = startPos + 1
end
return startPos
end
--- Encodes a string to be JSON-compatible.
-- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-)
-- @param s The string to return as a JSON encoded (i.e. backquoted string)
-- @return The string appropriately escaped.
function encodeString(s)
s = string.gsub(s,'\\','\\\\')
s = string.gsub(s,'"','\\"')
s = string.gsub(s,"'","\\'")
s = string.gsub(s,'\r','\\r')
s = string.gsub(s,'\n','\\n')
s = string.gsub(s,'\t','\\t')
return s
end
-- Determines whether the given Lua type is an array or a table / dictionary.
-- We consider any table an array if it has indexes 1..n for its n items, and no
-- other data in the table.
-- I think this method is currently a little 'flaky', but can't think of a good way around it yet...
-- @param t The table to evaluate as an array
-- @return boolean, number True if the table can be represented as an array, false otherwise. If true,
-- the second returned value is the maximum
-- number of indexed elements in the array.
function isArray(t)
-- Next we count all the elements, ensuring that any non-indexed elements are not-encodable
-- (with the possible exception of 'n')
local maxIndex = 0
for k,v in base.pairs(t) do
if (base.type(k)=='number' and math.floor(k)==k and 1<=k) then -- k,v is an indexed pair
if (not isEncodable(v)) then return false end -- All array elements must be encodable
maxIndex = math.max(maxIndex,k)
else
if (k=='n') then
if v ~= table.getn(t) then return false end -- False if n does not hold the number of elements
else -- Else of (k=='n')
if isEncodable(v) then return false end
end -- End of (k~='n')
end -- End of k,v not an indexed pair
end -- End of loop across all pairs
return true, maxIndex
end
--- Determines whether the given Lua object / table / variable can be JSON encoded. The only
-- types that are JSON encodable are: string, boolean, number, nil, table and json.null.
-- In this implementation, all other types are ignored.
-- @param o The object to examine.
-- @return boolean True if the object should be JSON encoded, false if it should be ignored.
function isEncodable(o)
local t = base.type(o)
return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null)
end

View File

@@ -0,0 +1,86 @@
-- Script to display the contents of text files:
-- * When users connect.
-- * On user commands.
-- TODO Command to reload texts.
-- TODO Allow customizing the texts via user commands.
local base = _G
module('texts')
local this = _NAME
base.require('luadchpp')
local adchpp = base.luadchpp
local aio = base.require('aio')
local autil = base.require('autil')
for _, dep in base.ipairs({ 'access' }) do
base.assert(base[dep], dep .. '.lua must be loaded and running before ' .. this .. '.lua')
end
-- Store the texts and their settings here. Fields:
-- * label: The user-friendly title of this text.
-- * path: Where the text resides.
-- * user_command: Add a user-command for this text.
-- * user_connect: Display contents to users when they connect.
-- * text: The contents.
local texts = {}
texts.about = {
label = 'About this hub',
path = adchpp.Util_getCfgPath() .. 'about.txt',
user_command = true,
user_connect = true,
}
texts.motd = {
label = 'Message of the day',
path = adchpp.Util_getCfgPath() .. 'motd.txt',
user_command = true,
user_connect = true,
}
texts.rules = {
label = 'Rules',
path = adchpp.Util_getCfgPath() .. 'rules.txt',
user_command = true,
user_connect = true,
}
-- Load texts when the hub starts.
for text_name, text_info in base.pairs(texts) do
local ok, str, err = aio.load_file(text_info.path)
if not ok then
adchpp.getLM():log(_NAME, 'Could not load the text for "' .. text_name .. '": ' .. err)
end
text_info.text = str
end
-- Register user commands.
for text_name, text_info in base.pairs(texts) do
if text_info.user_command then
base.access.commands[text_name] = {
command = function(c)
if text_info.text then
autil.reply(c, text_info.text)
else
autil.reply(c, 'Sorry, this information is unavailable.')
end
end,
help = 'Display "' .. text_info.label .. '"',
user_command = { name = text_info.label },
}
end
end
-- Send texts when users connect.
texts_signal_state = adchpp.getCM():signalState():connect(function(entity)
if entity:getState() == adchpp.Entity_STATE_NORMAL then
for _, text_info in base.pairs(texts) do
if text_info.user_connect and text_info.text then
autil.reply(entity, text_info.text)
end
end
end
end)

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef ENGINE_H_
#define ENGINE_H_
#include <string>
#include <unordered_map>
class Script;
typedef std::unordered_map<std::string, std::string> ParameterMap;
class Engine {
public:
virtual ~Engine() { }
virtual Script* loadScript(const std::string& path, const std::string& filename, const ParameterMap& parameters) = 0;
virtual void unloadScript(Script* script, bool force = false) = 0;
virtual void getStats(std::string& str) const = 0;
private:
};
#endif /*ENGINE_H_*/

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef LUACOMMON_H_
#define LUACOMMON_H_
namespace {
template<typename T>
struct pointer_wrapper {
pointer_wrapper() : t(0) { }
explicit pointer_wrapper(T* t_) : t(t_) { }
operator T*() { return t; }
T* t;
};
}
namespace luabind {
template<typename T>
pointer_wrapper<const T>*
get_const_holder(pointer_wrapper<T>*) { return 0; }
template<typename T>
T* get_pointer(pointer_wrapper<T>& p) {
return (T*)p;
}
}
#include <luabind/luabind.hpp>
#endif /*LUACOMMON_H_*/

View File

@@ -0,0 +1,136 @@
/*
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "stdinc.h"
#include "LuaEngine.h"
#include "LuaScript.h"
#include <adchpp/PluginManager.h>
#include <adchpp/File.h>
#include <adchpp/Util.h>
#include <adchpp/Core.h>
extern "C" {
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}
using namespace std;
namespace {
void prepare_cpath(lua_State* L, const string& path) {
lua_getfield(L, LUA_GLOBALSINDEX, "package");
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
return;
}
lua_getfield(L, -1, "cpath");
if (!lua_isstring(L, -1)) {
lua_pop(L, 2);
return;
}
string oldpath = lua_tostring(L, -1);
oldpath += ";" + path + "?.so";
lua_pushstring(L, oldpath.c_str());
lua_setfield(L, -3, "cpath");
// Pop table
lua_pop(L, 2);
}
void setScriptPath(lua_State* L, const string& path) {
lua_pushstring(L, path.c_str());
lua_setglobal(L, "scriptPath");
}
}
LuaEngine::LuaEngine(Core &core) : core(core) {
l = lua_open();
luaL_openlibs(l);
lua_pushlightuserdata(l, &core);
lua_setglobal(l, "currentCore");
prepare_cpath(l, core.getPluginManager().getPluginPath());
setScriptPath(l, Util::emptyString);
}
LuaEngine::~LuaEngine() {
std::vector<LuaScript*>::reverse_iterator it;
while((it = scripts.rbegin()) != scripts.rend())
unloadScript(*it, true);
if(l)
lua_close(l);
}
/// @todo lambda
void LuaEngine::loadScript_(const string& path, const string& filename, LuaScript* script) {
script->loadFile(path, filename);
scripts.push_back(script);
}
Script* LuaEngine::loadScript(const string& path, const string& filename, const ParameterMap&) {
setScriptPath(l, File::makeAbsolutePath(path));
if(call("loading", filename))
return 0;
LuaScript* script = new LuaScript(this);
core.getPluginManager().attention(std::bind(&LuaEngine::loadScript_, this, path, filename, script));
return script;
}
void LuaEngine::unloadScript(Script* s, bool force) {
if(call("unloading", dynamic_cast<LuaScript*>(s)->filename) && !force)
return;
scripts.erase(remove(scripts.begin(), scripts.end(), s), scripts.end());
delete s;
}
void LuaEngine::getStats(string& str) const {
str += "Lua engine\n";
str += "\tUsed memory: " + Util::toString(lua_gc(l, LUA_GCCOUNT, 0)) + " KiB\n";
str += "The following Lua scripts are loaded:\n";
for(vector<LuaScript*>::const_iterator i = scripts.begin(); i != scripts.end(); ++i) {
str += "\t";
(*i)->getStats(str);
}
}
bool LuaEngine::call(const string& f, const string& arg) {
lua_getfield(l, LUA_GLOBALSINDEX, f.c_str());
if(!lua_isfunction(l, -1)) {
lua_pop(l, 1);
return false;
}
lua_pushstring(l, arg.c_str());
if(lua_pcall(l, 1, 1, 0) != 0) {
lua_pop(l, 1);
return false;
}
bool ret = lua_toboolean(l, -1);
lua_pop(l, 1);
return ret;
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef LUAENGINE_H_
#define LUAENGINE_H_
#include <adchpp/forward.h>
#include "Engine.h"
struct lua_State;
class LuaScript;
class LuaEngine : public Engine {
public:
LuaEngine(adchpp::Core &core);
virtual ~LuaEngine();
virtual Script* loadScript(const std::string& path, const std::string& filename, const ParameterMap& parameters);
virtual void unloadScript(Script* script, bool force = false);
virtual void getStats(std::string& str) const;
private:
friend class LuaScript;
bool call(const std::string& f, const std::string& arg);
void loadScript_(const std::string& path, const std::string& filename, LuaScript* script);
lua_State* l;
std::vector<LuaScript*> scripts;
adchpp::Core &core;
};
#endif /*LUAENGINE_H_*/

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "stdinc.h"
#include "LuaScript.h"
#include "LuaEngine.h"
#include <adchpp/File.h>
#include <adchpp/LogManager.h>
#include <adchpp/Util.h>
#ifdef _WIN32
#include <direct.h>
#else
#ifndef MAX_PATH
#define MAX_PATH PATH_MAX
#endif
#endif
extern "C" {
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}
using namespace std;
const string LuaScript::className = "LuaScript";
LuaScript::LuaScript(Engine* engine) : Script(engine) {
}
LuaScript::~LuaScript() {
getEngine()->call("unloaded", filename);
}
void LuaScript::loadFile(const string& path, const string& filename_) {
filename = filename_;
char old_dir[MAX_PATH];
if(!getcwd(old_dir, MAX_PATH)) {
old_dir[0] = 0;
}
auto absPath = File::makeAbsolutePath(path);
if(chdir(absPath.c_str()) != 0) {
//LOG(className, "Unable to change to directory " + absPath);
} else {
int error = luaL_loadfile(getEngine()->l, filename.c_str()) || lua_pcall(getEngine()->l, 0, 0, 0);
if(error) {
fprintf(stderr, "Error loading file: %s\n", lua_tostring(getEngine()->l, -1));
//LOG(className, string("Error loading file: ") + lua_tostring(getEngine()->l, -1));
} else {
//LOG(className, "Loaded " + filename);
getEngine()->call("loaded", filename);
}
if(old_dir[0]) {
chdir(old_dir);
}
}
}
void LuaScript::getStats(string& str) const {
str += filename + "\n";
}
LuaEngine* LuaScript::getEngine() const {
return static_cast<LuaEngine*>(engine);
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef LUASCRIPT_H_
#define LUASCRIPT_H_
#include "Script.h"
class Engine;
class LuaEngine;
class LuaScript : public Script {
public:
LuaScript(Engine* engine);
virtual ~LuaScript();
void loadFile(const std::string& path, const std::string& filename);
void getStats(std::string& str) const;
static const std::string className;
private:
friend class LuaEngine;
LuaEngine* getEngine() const;
std::string filename;
};
#endif /*LUASCRIPT_H_*/

View File

@@ -0,0 +1,13 @@
Import('dev source_path setLuaEnv')
env, target, sources = dev.prepare_build(source_path, 'Script', shared_precompiled_header = 'stdinc')
env['SHLIBPREFIX'] = ''
env.Append(CPPPATH = ['.', '#'])
env.Append(LIBS = ['adchpp'])
setLuaEnv(env)
ret = env.SharedLibrary(target, sources)
Return('ret')

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef SCRIPT_H_
#define SCRIPT_H_
class Engine;
class Script {
public:
Script(Engine* engine_) : engine(engine_) { }
virtual ~Script() { }
void unload();
protected:
Engine* engine;
};
#endif /*SCRIPT_H_*/

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "stdinc.h"
#include "ScriptManager.h"
#include "Engine.h"
#include "LuaEngine.h"
#include <adchpp/SimpleXML.h>
#include <adchpp/File.h>
#include <adchpp/LogManager.h>
#include <adchpp/Util.h>
#include <adchpp/AdcCommand.h>
#include <adchpp/Client.h>
#include <adchpp/PluginManager.h>
#include <adchpp/Core.h>
using namespace std;
using namespace std::placeholders;
const string ScriptManager::className = "ScriptManager";
ScriptManager::ScriptManager(Core &core) : core(core) {
LOG(className, "Starting");
auto &pm = core.getPluginManager();
reloadConn = manage(pm.onCommand("reload", std::bind(&ScriptManager::onReload, this, _1)));
statsConn = manage(pm.onCommand("stats", std::bind(&ScriptManager::onStats, this, _1)));
}
ScriptManager::~ScriptManager() {
LOG(className, "Shutting down");
clearEngines();
}
void ScriptManager::clearEngines() {
engines.clear();
}
void ScriptManager::load() {
try {
SimpleXML xml;
xml.fromXML(File(core.getConfigPath() + "Script.xml", File::READ).read());
xml.stepIn();
while(xml.findChild("Engine")) {
const std::string& scriptPath = xml.getChildAttrib("scriptPath");
const std::string& language = xml.getChildAttrib("language");
if(language.empty() || language == "lua") {
engines.push_back(std::unique_ptr<LuaEngine>(new LuaEngine(core)));
} else {
LOG(className, "Unrecognized language " + language);
continue;
}
xml.stepIn();
while(xml.findChild("Script")) {
engines.back()->loadScript(scriptPath, xml.getChildData(), ParameterMap());
}
xml.stepOut();
}
xml.stepOut();
} catch(const Exception& e) {
LOG(className, "Failed to load settings: " + e.getError());
return;
}
}
void ScriptManager::reload() {
clearEngines();
load();
}
void ScriptManager::onReload(Entity& c) {
core.getPluginManager().attention(std::bind(&ScriptManager::reload, this));
c.send(AdcCommand(AdcCommand::CMD_MSG).addParam("Reloading scripts"));
}
void ScriptManager::onStats(Entity& c) {
string tmp("Currently loaded scripts:\n");
for(auto i = engines.begin(), iend = engines.end(); i != iend; ++i) {
(*i)->getStats(tmp);
}
c.send(AdcCommand(AdcCommand::CMD_MSG).addParam(tmp));
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef SCRIPT_MANAGER_H
#define SCRIPT_MANAGER_H
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include <adchpp/Exception.h>
#include <adchpp/ClientManager.h>
#include <adchpp/Plugin.h>
STANDARD_EXCEPTION(ScriptException);
class Engine;
namespace adchpp {
class SimpleXML;
class Client;
class AdcCommand;
}
class ScriptManager : public Plugin {
public:
ScriptManager(Core &core);
virtual ~ScriptManager();
virtual int getVersion() { return 0; }
void load();
static const std::string className;
private:
std::vector<std::unique_ptr<Engine>> engines;
void reload();
void clearEngines();
ClientManager::SignalReceive::ManagedConnection reloadConn;
ClientManager::SignalReceive::ManagedConnection statsConn;
void onReload(Entity& c);
void onStats(Entity& c);
Core &core;
};
#endif //ACCESSMANAGER_H

View File

@@ -0,0 +1,46 @@
/*
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "stdinc.h"
#include "ScriptManager.h"
#include <adchpp/PluginManager.h>
#ifdef _WIN32
BOOL APIENTRY DllMain(HANDLE /*hModule */, DWORD /* reason*/, LPVOID /*lpReserved*/) {
return TRUE;
}
#endif
extern "C" {
int PLUGIN_API pluginGetVersion() { return PLUGINVERSION; }
int PLUGIN_API pluginLoad(PluginManager *pm) {
auto sm = make_shared<ScriptManager>(pm->getCore());
sm->load();
pm->registerPlugin("ScriptManager", sm);
return 0;
}
void PLUGIN_API pluginUnload() {
}
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "stdinc.h"

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef SCRIPT_STDINC_H
#define SCRIPT_STDINC_H
#include <adchpp/adchpp.h>
#include <adchpp/common.h>
using namespace adchpp;
#endif //ACCESS_STDINC_H