From df636e87062533e97c57d7b07653f591bbe13cb4 Mon Sep 17 00:00:00 2001 From: DarkFeather Date: Sun, 4 Dec 2016 19:55:52 -0600 Subject: [PATCH] r.searches function, fixes, SharedLibraries configuration inclusion --- ...sharp => AlreadyIdentifiedException.csharp | 0 ...exception.csharp => ExitedException.csharp | 0 Makefile | 12 +- Raven.csharp | 131 ++++++++---------- RavenCommand.csharp | 8 +- raven.service | 4 +- sample-conf/blacklist.txt | 1 - sample-conf/crowfacts.txt | 52 ------- sample-conf/helptext.txt | 1 - sample-conf/loginDefaults.txt | 4 - sample-conf/magic8.txt | 20 --- sample-conf/notifications.txt | 1 - sample-conf/rooms.txt | 3 - sample-conf/searches.txt | 11 -- sample-conf/whitelist.txt | 1 - sample.conf | 30 ++++ 16 files changed, 97 insertions(+), 182 deletions(-) rename alreadyidentifiedexception.csharp => AlreadyIdentifiedException.csharp (100%) rename exitedexception.csharp => ExitedException.csharp (100%) delete mode 100644 sample-conf/blacklist.txt delete mode 100644 sample-conf/crowfacts.txt delete mode 100644 sample-conf/helptext.txt delete mode 100644 sample-conf/loginDefaults.txt delete mode 100644 sample-conf/magic8.txt delete mode 100644 sample-conf/notifications.txt delete mode 100644 sample-conf/rooms.txt delete mode 100644 sample-conf/searches.txt delete mode 100644 sample-conf/whitelist.txt create mode 100644 sample.conf diff --git a/alreadyidentifiedexception.csharp b/AlreadyIdentifiedException.csharp similarity index 100% rename from alreadyidentifiedexception.csharp rename to AlreadyIdentifiedException.csharp diff --git a/exitedexception.csharp b/ExitedException.csharp similarity index 100% rename from exitedexception.csharp rename to ExitedException.csharp diff --git a/Makefile b/Makefile index 4b23501..b47ed1b 100644 --- a/Makefile +++ b/Makefile @@ -1,22 +1,20 @@ -CONFDIR := /usr/local/etc/TheRaven +CONFDIR = /usr/local/etc/TheRaven -compile: /usr/bin/mcs /usr/bin/mono clean ./raven.csharp ./connection.csharp ./ravencommand.csharp /bin/bash /usr/bin/mail /usr/bin/wget /usr/local/bin/djinni ./chatbot-support.bash +compile: clean /usr/bin/mcs /usr/bin/mono clean /bin/bash /usr/bin/mail /usr/bin/wget /usr/local/bin/djinni ./chatbot-support.bash if [ ! -d ../Djinni ]; then git clone -C '..' https://aninix.net/foundation/Djinni; fi git -C ../Djinni pull if [ ! -d ../SharedLibraries ]; then git clone -C '..' https://aninix.net/foundation/SharedLibraries; fi git -C ../SharedLibraries pull - mcs -out:raven.mono ../SharedLibraries/CSharp/*.csharp *.csharp + mcs -out:raven.mono ../SharedLibraries/CSharp/*.csharp *.csharp Raven.csharp clean: - if [ "$$(ls ./*~ 2>/dev/null | wc -l)" -gt 0 ]; then rm -Rf *~; fi - if [ "$$(ls ./*.mono 2>/dev/null | wc -l)" -gt 0 ]; then rm -Rf *.mono; fi - if [ "$$(ls ./\#* 2>/dev/null | wc -l)" -gt 0 ]; then rm -Rf \#*; fi + for i in raven.mono; do if [ -f $$i ]; then rm $$i; fi; done edit: emacs -nw raven.csharp test: compile - script -c "mono ./raven.mono -c ${CONFDIR}-Test -v" /tmp/raven-test.log + script -c "mono ./raven.mono -c raven-test.conf -v" /tmp/raven-test.log check-for-verbosity: grep Console.WriteLine *.csharp | egrep -v 'verbosity|raven.csharp'; echo diff --git a/Raven.csharp b/Raven.csharp index c3b60ee..a6d238f 100644 --- a/Raven.csharp +++ b/Raven.csharp @@ -17,16 +17,17 @@ namespace AniNIX.TheRaven { public string Nick { get; private set; } // This is the Nickname for this Raven to use. private string _nickServPass; // This is the password we will send to NickServ to identify private string _autoSend; // This is the command we will automatically send to the Host - private string configDir; // This is the configuration directory. + private string _configFile; // This is the configuration directory. private Connection _connection; //This is the socket to the Host - public List channels = new List(); //This is the list of channels to join - public List whitelist = new List(); //This is the list of admin users. - public List blacklist = new List(); // This is the list of blocked people. - public String helpText = null; // This is the text to send when people ask for help -- this is configurable to allow for skinning - public List searches = new List(); //These are the searches - public String[] magic8 = null; //These are the strings to return like a Magic 8-ball to questions. - public String[] crowFacts = null; //These are the possible CrowFacts + public List channels; //This is the list of channels to join + public List whitelist; //This is the list of admin users. + public List blacklist; // This is the list of blocked people. + public String helpText; // This is the text to send when people ask for help -- this is configurable to allow for skinning + public List searches; //These are the searches + public String searchesIndex; //This is the helptext for the searches + public List magic8; //These are the strings to return like a Magic 8-ball to questions. + public List crowFacts; //These are the possible CrowFacts public List crowFactsSubscribers = new List(); //These are the subscribers to CrowFacts public Dictionary notifications = new Dictionary(); // This is the notifications list for TheRaven. @@ -46,74 +47,55 @@ namespace AniNIX.TheRaven { sb.Append(String.Format("Nick: {0}\n",Nick)); sb.Append("NickServPass: ****\n"); sb.Append(String.Format("Auto: {0}\n",_autoSend)); - sb.Append(String.Format("Conf: {0}\n",configDir)); + sb.Append(String.Format("Conf: {0}\n",_configFile)); sb.Append(String.Format("Verbosity: {0}\n",ReportMessage.verbosity)); return sb.ToString(); } /// - /// Read from the files in the directory to configure this Raven + /// Read from the files in the /usr/local/etc/TheRaven directory to configure this Raven /// // TODO: This and ParseArgs may get punted into their own static class to improve readability. - private void ConfigureSelfFromFiles(String configDir) { + private void ConfigureSelfFromFiles() { - if (configDir==null || configDir == "" || !Directory.Exists(configDir)) { - ReportMessage.Log(Verbosity.Error,"Configuration directory does not exist!"); + String confFilePath = String.Format("/usr/local/etc/TheRaven/{0}",_configFile); + + if (!File.Exists(confFilePath)) { + ReportMessage.Log(Verbosity.Error,"Configuration file doesn't exist."); return; } - - ReportMessage.Log(Verbosity.Always,String.Format("Reading from files in {0}...",configDir)); + ReportMessage.Log(Verbosity.Always,String.Format("Reading from config file in /usr/local/etc/{0} and the global files in the same directory...",_configFile)); + Configure conf = new Configure(confFilePath); //These are locals that will be used throughout - ReportMessage.Log(Verbosity.Verbose,"Reading login defaults"); - String[] loginDefaults = RavenConfigure.ReadLineDelimitedFile(Path.Combine(configDir,"loginDefaults.txt")).ToArray(); - //We have to populate these properties fom the list explicitly - if (loginDefaults.Length < 4) { - ReportMessage.Log(Verbosity.Error,"Login defaults are incomplete. No changes made."); - } else { - Host = (Host==null) ? loginDefaults[0] : Host; - try { - Port = (Port == 0) ? Int32.Parse(loginDefaults[1]) : Port; - } catch (Exception e) { - ReportMessage.Log(Verbosity.Verbose,"Cannot parse Port."); - e.ToString(); - Port = 6667; - } - Nick = (Nick == null) ? loginDefaults[2] : Nick; - _nickServPass = (_nickServPass == null) ? loginDefaults[3] : _nickServPass; - } + Dictionary loginDefaults = conf.ReadSection("Login"); + this.Host = loginDefaults["host"]; + this.Port = Int32.Parse(loginDefaults["port"]); + this.Nick = loginDefaults["username"]; + this._nickServPass = loginDefaults["password"]; - //Read all the channels to join. - List tempChannels = RavenConfigure.ReadLineDelimitedFile(Path.Combine(configDir,"rooms.txt")); - // Because the convention is to use # for comments, channels in the rooms.txt file do not start with a # - foreach (String channel in tempChannels) { + channels=new List(); + foreach (String channel in conf.ReadSectionLines("Rooms")) { channels.Add(String.Format("#{0}",channel)); } - //Read the whitelist of folks allowed to execute administrative commands - whitelist = RavenConfigure.ReadLineDelimitedFile(Path.Combine(configDir,"whitelist.txt")); - - //Read the blacklist of folks not allowed to do anything. - blacklist = RavenConfigure.ReadLineDelimitedFile(Path.Combine(configDir,"blacklist.txt")); - - //Read the helptext - helpText = RavenConfigure.ReadFirstLineFromFile(Path.Combine(configDir,"helptext.txt")); - - //Read the searches to use - searches = RavenConfigure.ReadLineDelimitedFile(Path.Combine(configDir,"searches.txt")); - - //Read the Magic8 options - magic8 = RavenConfigure.ReadLineDelimitedFileToArr(Path.Combine(configDir,"magic8.txt")); - - //Read the CrowFacts - crowFacts = RavenConfigure.ReadLineDelimitedFileToArr(Path.Combine(configDir,"crowfacts.txt")); - - //Read the notifications - foreach (String combo in RavenConfigure.ReadLineDelimitedFileToArr(Path.Combine(configDir,"notifications.txt"))) { - String[] byPipe = combo.Split('|'); - notifications.Add(String.Format("#{0}",byPipe[0]),byPipe[1]); + //Parse the lists. + notifications = conf.ReadSection("Notifications"); + whitelist = conf.ReadSectionLines("Whitelist"); + blacklist = conf.ReadSectionLines("Blacklist"); + helpText = "Available commands are r.raven, r.magic8, r.uptime, r.heartbeat, r.msg , r.d , r.tinyurl , and r.searches"; + searches = conf.ReadSectionLines("Searches"); + StringBuilder searchIndexBuilder = new StringBuilder(); + foreach (String searchLine in searches) { + String[] byPipe = searchLine.Split('|'); + if (byPipe.Length > 3) searchIndexBuilder.Append(String.Format("{0} <{1} search>, ",byPipe[0],byPipe[3])); } + searchesIndex = searchIndexBuilder.ToString(); + + //Read the globals + magic8 = (new Configure("/usr/local/etc/TheRaven/magic8.txt")).GetLines(); + crowFacts = (new Configure("/usr/local/etc/TheRaven/crowfacts.txt")).GetLines(); } /// @@ -156,7 +138,10 @@ namespace AniNIX.TheRaven { //TODO Add helptext break; case "-c": - if (i < args.Length-1) configDir = args[++i]; + if (i < args.Length-1) _configFile = args[++i]; + break; + case "--version": + ReportMessage.Log(Verbosity.Always,"AniNIX::TheRaven version 0.2"); break; } } @@ -170,33 +155,24 @@ namespace AniNIX.TheRaven { /// The arguments for creating the bot /// public Raven(string[] args) { - ReportMessage.Log(Verbosity.Always,"Reading arguments..."); - // If we have arguments this.ParseArguments(args); - - this.ConfigureSelfFromFiles(configDir); - + this.ConfigureSelfFromFiles(); ReportMessage.Log(Verbosity.VeryVerbose,"Started with these values:"); ReportMessage.Log(Verbosity.VeryVerbose,this.ToString()); } - /// - /// Populate the name recognition - /// - - /// /// Create a raven with default settings. /// - public Raven(String host = "localhost", int port = 6667, String nick = "TheRaven-Guest", String nickServPass = "null", String autoSend = null, String configDir = "/usr/local/etc/TheRaven-Local", Verbosity verbosity = Verbosity.Verbose) { + public Raven(String host = "localhost", int port = 6667, String nick = "TheRaven-Guest", String nickServPass = "null", String autoSend = null, String _configFile = "raven.conf", Verbosity verbosity = Verbosity.Verbose) { this.Host = host; Port = port; Nick = nick; _nickServPass = nickServPass; _autoSend = autoSend; - this.configDir = configDir; + this._configFile = _configFile; ReportMessage.verbosity = verbosity; } @@ -219,8 +195,8 @@ namespace AniNIX.TheRaven { send.CreateCustomMessage(String.Format("NICK {0}\nUSER {0} * * :{0}",Nick)); _connection.Write(send); ReportMessage.Log(Verbosity.VeryVerbose,"USER and NICK sent"); - //thanks to cfrayne for the refactor + //thanks to cfrayne for the refactor do { response = _connection.Read(); if (response.msgCode != null && response.msgCode.Equals("433")) throw new AlreadyIdentifiedException(); @@ -284,16 +260,20 @@ namespace AniNIX.TheRaven { /* CROWFACTS the deserving */ else if (crowFactsSubscribers.Contains(response.user) && randomSeed.Next(10) < 8) { IRCClientMessage send = new IRCClientMessage(); - int location = randomSeed.Next(crowFacts.Length); + int location = randomSeed.Next(crowFacts.Count); send.PrivMsg(crowFacts[location],response.user); _connection.Write(send); } + // If the WebPage if (WebPageAPI.URLRegEx.Match(response.message).Success) { try { + String title = WebPageAPI.GetPageTitle(WebPageAPI.URLRegEx.Match(response.message).Value); + if (!String.IsNullOrWhiteSpace(title)) { IRCClientMessage send = new IRCClientMessage(); - send.PrivMsg(String.Format("Web page title: {0}",WebPageAPI.GetPageTitle(WebPageAPI.URLRegEx.Match(response.message).Value)),(response.target.Equals(Nick))?response.user:response.target); + send.PrivMsg(String.Format("Web page title: {0}",title),(response.target.Equals(Nick))?response.user:response.target); _connection.Write(send); + } } catch (Exception e) { e.ToString(); } @@ -362,8 +342,7 @@ namespace AniNIX.TheRaven { Host = null; Port = 0; _nickServPass = null; - _autoSend = null; - configDir = null; + _autoSend = null; _configFile = null; whitelist = null; blacklist = null; magic8 = null; diff --git a/RavenCommand.csharp b/RavenCommand.csharp index 233c395..0aff70e 100644 --- a/RavenCommand.csharp +++ b/RavenCommand.csharp @@ -46,7 +46,7 @@ namespace AniNIX.TheRaven { if (theRaven.magic8 == null) { send.PrivMsg("Magic8 not loaded",(incoming.target.Equals(theRaven.Nick))?incoming.user:incoming.target); } else { - int location = theRaven.randomSeed.Next(theRaven.magic8.Length); + int location = theRaven.randomSeed.Next(theRaven.magic8.Count); send.PrivMsg(theRaven.magic8[location],(incoming.target.Equals(theRaven.Nick))?incoming.user:incoming.target); } connection.Write(send); @@ -135,7 +135,10 @@ namespace AniNIX.TheRaven { send.PrivMsg("Can't get heartbeat",incoming.user); } return; - + case "r.searches": + send.PrivMsg(theRaven.searchesIndex,(incoming.target.Equals(theRaven.Nick))?incoming.user:incoming.target); + connection.Write(send); + return; } /* SEARCHES */ @@ -168,7 +171,6 @@ namespace AniNIX.TheRaven { connection.Write(send); } send.PrivMsg("End subscribers",incoming.user); - send.PrivMsg(theRaven.helpText,(incoming.target.Equals(theRaven.Nick))?incoming.user:incoming.target); } else if (theRaven.crowFacts == null) { send.PrivMsg("CrowFacts not loaded.",(incoming.target.Equals(theRaven.Nick))?incoming.user:incoming.target); } else { diff --git a/raven.service b/raven.service index a6a26ae..86a429f 100644 --- a/raven.service +++ b/raven.service @@ -1,9 +1,9 @@ [Unit] -Description=AniNIX::Raven IRC Bot for ACWiki +Description=AniNIX::Raven IRC Bot After=network.target [Service] -ExecStart=/usr/bin/mono /opt/raven.mono -c /usr/local/etc/TheRaven +ExecStart=/usr/bin/mono /opt/raven.mono -c raven.conf ExecReload=/bin/kill -HUP $MAINPID KillMode=process Restart=always diff --git a/sample-conf/blacklist.txt b/sample-conf/blacklist.txt deleted file mode 100644 index b6f720a..0000000 --- a/sample-conf/blacklist.txt +++ /dev/null @@ -1 +0,0 @@ -TheRaven diff --git a/sample-conf/crowfacts.txt b/sample-conf/crowfacts.txt deleted file mode 100644 index 83166d1..0000000 --- a/sample-conf/crowfacts.txt +++ /dev/null @@ -1,52 +0,0 @@ -There's an old nonsense song called the Old Carrion Crow. -From 2000 to 2003 the world's best wooden roller coaster was called The Raven. -The average raven has a four-foot wingspan. -The average crow has a three-foot wingspan. -Crows and ravens are members of the corvid family, the most adaptable and intelligent family of birds in the world. -Crows and ravens can mimic sounds and associate sounds with events. -Crows roost in flocks of several thousand in the winter. -Crows are omnivorous, making them capable scavangers and opportunistic hunters. -Crows prefer coniferous trees to build their homes at least 60 feet above the ground. -Paired males and females share in the raising of the four to six eggs. -One crow baby will frequently remain in the nest to assist in the caring for the next nestlings. -While crows have a reputation for eating corn, they often eat the bugs that plague crops. -Crows and ravens are territorial when young are in the nest, dive-bombing passersby. -The Sioux have a story of a white crow warning buffalo of approaching hunting parties. The bird turned black when a hunter threw it into the fire in rage. -Crows have been hunted and even had bounties placed on them by several governments, including by Kings of England. -Crows can use a number of calls to communicate situations and emotions. -Crows mate for life and share the care of nestlings. -Crows are often challenged by larger hunting birds, like owls and hawks; they use superior numbers when outmatched. -Crows only migrate long distances in harsh winters. -A group of crows or ravens is called a murder. -Crows live everywhere except Antarctica. -Crows are susceptible to West Nile Virus, which has felled many of them since 1999. -Crows' association with death comes from their scavenger natures -- they are often seen near the dead on battlefields. -Ravens are acrobatic fliers on par with hawks and falcons. In mating season, they put on acrobatic shows for potential mates. -Native Americans often honor ravens in their stories for their playful nature. -Ravens hunt in groups to bring down prey too large for a single bird. -Ravens range from the Arctic to the Mediterranean, building large stick nests. -Ravens have an average wild lifespan of 13 years. -Common ravens range from 24 to 30 inches long with up to a 5 foot (1.5m) wingspan, weighing in at 2.3 pounds. -Legend has it that if ravens leave the Tower of London, the fortress will fall and the British kingdom along with it. -Ravens are the largest passerine (perching) birds in North America. -Ravens can live up to 40 years in captivity or protected conditions. -https://upload.wikimedia.org/wikipedia/commons/9/92/Krummi_1.jpg -Ravens have one of the largest bills of perching birds. -Common ravens have quarrelsome family lives, but they are extremely devoted to their families. -Common ravens (Corvus corax) store food, hiding it from other ravens. -A group of ravens is commonly called a flock. -Ravens don't migrate except in the harshest winters. -In addition to their bills, ravens may drop rocks as weapons. -Young ravens are fascinated with all things new, but older ravens become more cautious and neophobic with experience. -Ravens are known to play, sliding down snowdrifts for fun and play games with wolves and other animals. -Ravens are one of only a few species known to make toys, breaking off branches for social play. -The raven is the national bird of Bhutan and the official bird of the Yukon. -The raven was the first bird sent forth by Noah but didn't return until the flood waters receded. -Ravens feed the prophet Elijah in 1 Kings 17:1 and are a subject of a parable in Luke 12:24, as a sign for man not to be materialistic. -The Native Americans saw the Raven as a creator and world-shaper. -Native American mythology holds that Raven brought the sun, moon, stars, and fire into the world. -The Norse god Odin had two ravens Huginn and Muninn (Thought and Mind) to serve as his eyes in the world. -The raven is the symbol of the Celtic figure Morrigan and the namesake of Lugh, the god responsible for creating arts and science. -The raven appears in the Quran but once, only to teach man to bury the dead in the story of Cain and Abel. -GI_Auditore finds Mutated Ravens in Prototype 2 extremely annoying. -Lost_Fragment will lose in a fight with TheRaven. diff --git a/sample-conf/helptext.txt b/sample-conf/helptext.txt deleted file mode 100644 index 63e709b..0000000 --- a/sample-conf/helptext.txt +++ /dev/null @@ -1 +0,0 @@ -Available commands are r.raven, r.magic8, r.msg , r.google , r.sound , r.image , r.wiki , r.dict , r.yt , r.urban , r.hoogle , r.so , r.man , and r.tropes diff --git a/sample-conf/loginDefaults.txt b/sample-conf/loginDefaults.txt deleted file mode 100644 index a151797..0000000 --- a/sample-conf/loginDefaults.txt +++ /dev/null @@ -1,4 +0,0 @@ -localhost -6667 -TheRaven-Test -somepass diff --git a/sample-conf/magic8.txt b/sample-conf/magic8.txt deleted file mode 100644 index da27101..0000000 --- a/sample-conf/magic8.txt +++ /dev/null @@ -1,20 +0,0 @@ -It is certain -It is decidedly so -Without a doubt -Yes definitely -You may rely on it -As I see it yes -Most likely -Outlook good -Yes -Signs point to yes -Reply hazy try again -Ask again later -Better not tell you now -Cannot predict now -Concentrate and ask again -Don't count on it -My reply is no -My sources say no -Outlook not so good -Very doubtful diff --git a/sample-conf/notifications.txt b/sample-conf/notifications.txt deleted file mode 100644 index 6c90591..0000000 --- a/sample-conf/notifications.txt +++ /dev/null @@ -1 +0,0 @@ -TheRaven|Surprise diff --git a/sample-conf/rooms.txt b/sample-conf/rooms.txt deleted file mode 100644 index cc42472..0000000 --- a/sample-conf/rooms.txt +++ /dev/null @@ -1,3 +0,0 @@ -TheRafters -lobby -#thisisacomment diff --git a/sample-conf/searches.txt b/sample-conf/searches.txt deleted file mode 100644 index 4c30287..0000000 --- a/sample-conf/searches.txt +++ /dev/null @@ -1,11 +0,0 @@ -r.google|http://google.com/search?q=|+ -r.images|http://images.google.com/search?tbm=isch&q=%s|+ -r.wiki|http://en.wikipedia.org/wiki/|_ -r.sound|http://www.soundcloud.com/search?q=|%20 -r.dict|http://www.merriam-webster.com/dictionary/|+ -r.tropes|http://tvtropes.org/pmwiki/search_result.php?cx=partner-pub-6610802604051523%3Aamzitfn8e7v&cof=FORID%3A10&ie=ISO-8859-1&siteurl=&ref=&ss=&siteurl=tvtropes.org%2F&ref=www.google.com%2F&ss=5135j1581675j28&q=|+ -r.yt|https://www.youtube.com/results?search_query=|+ -r.urban|http://www.urbandictionary.com/define.php?term=|+ -r.man|http://www.die.net/search/?q=|+ -r.hoogle|https://www.haskell.org/hoogle/?hoogle=|+ -r.so|http://stackoverflow.com/search?q=|+ diff --git a/sample-conf/whitelist.txt b/sample-conf/whitelist.txt deleted file mode 100644 index a35e183..0000000 --- a/sample-conf/whitelist.txt +++ /dev/null @@ -1 +0,0 @@ -DarkFeather diff --git a/sample.conf b/sample.conf new file mode 100644 index 0000000..ea919b6 --- /dev/null +++ b/sample.conf @@ -0,0 +1,30 @@ +[ Login ] +host=localhost +port=6667 +username=TheRaven +password=password + +[ Whitelist ] +Admin + +[ Blacklist ] + +[ Notifications ] + +[ Rooms ] +TheRaven + +[ Searches ] +r.google|http://google.com/search?q=|+|Google +r.image|http://images.google.com/search?tbm=isch&q=|+|Google Images +r.wiki|http://en.wikipedia.org/wiki/|_|Wikipedia +r.sound|http://www.soundcloud.com/search?q=|%20|Soundcloud +r.dict|http://www.merriam-webster.com/dictionary/|+|Dictionary +r.tropes|http://tvtropes.org/pmwiki/search_result.php?cx=partner-pub-6610802604051523%3Aamzitfn8e7v&cof=FORID%3A10&ie=ISO-8859-1&siteurl=&ref=&ss=&siteurl=tvtropes.org%2F&ref=www.google.com%2F&ss=5135j1581675j28&q=|+|Tropes +r.yt|https://www.youtube.com/results?search_query=|+|YouTube +r.urban|http://www.urbandictionary.com/define.php?term=|+|Urban Dictionary +r.man|http://www.die.net/search/?q=|+|Man-page +r.hoogle|https://www.haskell.org/hoogle/?hoogle=|+|Hoogle +r.so|http://stackoverflow.com/search?q=|+|Stack Overflow +r.aninix|https://aninix.net/|_|AniNIX +r.map|https://www.google.com/maps/search/|+|Google Maps