function SedPlugin(bot) {
	var self = this;
	self.name = "sed";
	self.help = "Sed replacement plugin";
	self.depend = ["history", "util", "cmd"];

	self.sedCmdRe = new RegExp(
		"^(?:(\\S+)[:,]?\\s+)?" +
		"(.*)");

	self.sedRe = new RegExp(
		"^" +
		"(?:/?((?:\\\\/|[^/])+)/([a-z]*))?" +
		"(\\d+)?" +
		"(?:s([^\\w\\s])((?:\\\\\\4|(?!\\4).)+)" +
		"\\4((?:\\\\\\4|(?!\\4).)*?)" +
		"\\4([a-z]*))?" +
		"$");

	self.msgSed = true;

	self.load = function(data) {
		if (data)
			self.msgSed = data.msgSed !== undefined ? data.msgSed : true;
	};

	self.save = function() {
		return {msgSed: self.msgSed};
	};

	self.sed = function(expr, filter) {
		var m = expr.match(self.sedRe);
		if (m) {
			var sedPrere = new RegExp(m[1] || ".*", m[2]);
			var sedCnt = m[3] ? parseInt(m[3]) : null;
			var sedRe = null;
			var sedRepl = null;
			if (m[5]) {
				sedRe = new RegExp(m[5], m[7]);
				sedRepl = bot.plugins.util.strUnescape(m[6]);
			}

			filter = filter || function(){ return true; };

			return function(line) {
				if (filter(line) && sedPrere.test(line)) {
					var out = sedRe !== null ? line.replace(sedRe, sedRepl).replace(/[\r\n]/g, "") : line;

					if (sedCnt === null) {
						if (sedRe === null || (sedRe !== null && sedRe.test(line))) {
							return out;
						}
					}
					else {
						sedCnt--;
						if (sedCnt == 0) {
							return out;
						}
					}
				}

				return true;
			};
		}
		else
			return null;
	};

	self.sedCmd = function(text) {
		var m = text.match(self.sedCmdRe);
		if (m && self.sedRe.test(m[2])) {
			return m;
		}

		return null;
	};

	self.chanSed = function(nick, to, cmd, filter) {
		var m = self.sedCmd(cmd);
		if (m) {
			var sedNick = m[1] || nick;
			var sed = self.sed(m[2], filter || function(){ return true; });

			if (sed) { // possibly unneeded check due to self.sedCmd
				bot.out.log("sed", nick + " in " + to + ": " + m[0]);

				var re = new RegExp("^\\[[\\d:]{8}\\] (<$nick>|\\* $nick) (.*)$".replace(/\$nick/g, sedNick), "i");

				var cnt = 0;
				bot.plugins.history.iterate(to, function(line) {
					cnt++;
					var m2 = line.match(re);
					if (m2) {
						var text2 = m2[2];
						var s = sed(text2);

						if (s === false)
							return false;
						else if (s !== true) // string returned
						{
							bot.say(to, m2[1] + " " + s);
							return false;
						}
					}

					return true;
				}, function(found) {
					//bot.out.debug("sed", "line " + cnt + " " + found);
				});
			}
		}
	};

	self.events = {
		"message": function(nick, to, text) {
			if (!self.msgSed)
				return;

			self.chanSed(nick, to, text, function(line) {
				return !self.sedCmd(line);
			});
		},

		"cmd#sed": function(nick, to, args) {
			self.chanSed(nick, to, args[0], function(line) {
				var m = line.match(bot.plugins.cmd.chanRe);
				return !m || m[1] != "sed";
			});
		}
	};
}

module.exports = SedPlugin;

@@ -401,24 +401,102 @@
  +%0A%09%09%22$%22);%0A%0A
+%09self.grepRe = new RegExp(%0A%09%09%22%5E%22 +%0A%09%09%22/((?:%5C%5C%5C%5C/%7C%5B%5E/%5D)+)/(%5Ba-z%5D*)%22 +%0A%09%09%22$%22);%0A%0A
 %09self.msgSed
@@ -1502,24 +1502,352 @@
  null;%0A%09%7D;%0A%0A
+%09self.grep = function(expr, filter) %7B%0A%09%09var m = expr.match(self.grepRe);%0A%09%09if (m) %7B%0A%09%09%09var grepRe = new RegExp(m%5B1%5D, m%5B2%5D);%0A%0A%09%09%09filter = filter %7C%7C function()%7B return true; %7D;%0A%0A%09%09%09return function(line) %7B%0A%09%09%09%09if (filter(line) && grepRe.test(line)) %7B%0A%09%09%09%09%09return line;%0A%09%09%09%09%7D%0A%0A%09%09%09%09return true;%0A%09%09%09%7D;%0A%09%09%7D%0A%09%09else%0A%09%09%09return null;%0A%09%7D;%0A%0A
 %09self.sedCmd

add analogous grep generator