From 124fd62fb6c5095aba442725e3dba08015675ab9 Mon Sep 17 00:00:00 2001 From: dim Date: Sat, 20 Jul 2002 14:08:20 +0000 Subject: [PATCH] Adding all the projects file. --- Makefile | 21 + README | 12 + TODO | 14 + config.xml | 982 +++++++++++++++++++++++++++++++++++++++++ ebin/manderlbot.app | 15 + inc/config.hrl | 35 ++ inc/mdb.hrl | 74 ++++ inc/mdb_macros.hrl | 13 + inc/xmerl.hrl | 211 +++++++++ inc/xmerl_xlink.hrl | 26 ++ src/bloto.erl | 110 +++++ src/config.erl | 133 ++++++ src/config_srv.erl | 214 +++++++++ src/google.erl | 48 ++ src/irc_lib.erl | 123 ++++++ src/manderlbot.erl | 71 +++ src/manderlbot_sup.erl | 52 +++ src/mdb_behaviours.erl | 203 +++++++++ src/mdb_bot.erl | 267 +++++++++++ src/mdb_bot_sup.erl | 49 ++ src/mdb_connection.erl | 169 +++++++ src/mdb_dispatch.erl | 279 ++++++++++++ src/misc_tools.erl | 164 +++++++ 23 files changed, 3285 insertions(+) create mode 100644 Makefile create mode 100644 README create mode 100644 TODO create mode 100644 config.xml create mode 100644 ebin/manderlbot.app create mode 100644 inc/config.hrl create mode 100644 inc/mdb.hrl create mode 100644 inc/mdb_macros.hrl create mode 100644 inc/xmerl.hrl create mode 100644 inc/xmerl_xlink.hrl create mode 100644 src/bloto.erl create mode 100644 src/config.erl create mode 100644 src/config_srv.erl create mode 100644 src/google.erl create mode 100644 src/irc_lib.erl create mode 100644 src/manderlbot.erl create mode 100644 src/manderlbot_sup.erl create mode 100644 src/mdb_behaviours.erl create mode 100644 src/mdb_bot.erl create mode 100644 src/mdb_bot_sup.erl create mode 100644 src/mdb_connection.erl create mode 100644 src/mdb_dispatch.erl create mode 100644 src/misc_tools.erl diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5213e39 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +# Make the html files from the chords one +OPT = -W +INC = ./inc +CC = erlc + +SRC=$(wildcard src/*.erl) +TMP=$(wildcard src/*~) $(wildcard inc/*~) +TARGET=$(addsuffix .beam, $(basename $(addprefix ebin/, $(notdir $(SRC))))) +EMAKE = $(addsuffix \'., $(addprefix \'../, $(SRC))) + +all: $(TARGET) + +# used to generate the erlang Emakefile +emake: + @echo $(EMAKE) | tr -s ' ' '\n' > ebin/Emakefile + +clean: + -rm -f $(TARGET) $(TMP) + +ebin/%.beam: src/%.erl + $(CC) $(OPT) -I $(INC) -o ebin $< diff --git a/README b/README new file mode 100644 index 0000000..4e45f19 --- /dev/null +++ b/README @@ -0,0 +1,12 @@ +This is manderlbot, an attempt to write an irc bot in erlang, with a XML +based configuration. + +To use it, install erlang, then xmerl-0.15 + http://www.erlang.org/user.html#xmerl-0.15 + +On my system this means install the compiled files in + /usr/lib/erlang/lib/xmerl-0.15 + +To start manderlbot, simply launch erlang shell +erl +> application:start(manderlbot). diff --git a/TODO b/TODO new file mode 100644 index 0000000..b3fc001 --- /dev/null +++ b/TODO @@ -0,0 +1,14 @@ +Behaviours + Fortune + On logue quelques ligne et sur demande on fortune les dernieres + n lignes demandés... + + Rumeur + Avec un nick en argument, raconte des choses + +Config + DTD + Behaviours lists, inheritance + +Others + Do regexp matchings case independant (Lower/Upper case) diff --git a/config.xml b/config.xml new file mode 100644 index 0000000..2033c2d --- /dev/null +++ b/config.xml @@ -0,0 +1,982 @@ + + + + + + + + + + + + + + + + + + + + won Business Loto - http://www.isty-info.uvsq.fr/~fontaine/images/autres/corpo/business-loto.jpg + + + + google + + + + Salut les loutres ! + + + +%tic... tac... +%DANS TON CUL + + + + P.A.M.I.E. + + + + Halte au tourisme sexuel (DANS TON CUL) ! + + + +%DC1 Les dictons chinois sont toujours vrais +%DC2 Je préfère tout de même les proverbes chinois +%DC3 La lose est une constante de l'Univers +%DC4 Si tu veux des amis, ne leur dis pas qu'ils sont lourds +%DC5 Si tu tiens à tes bourses, ne t'approche pas de l'ours +%DC6 Si tu tiens a tes couilles, n'approche pas de l'eau qui bouille +%DC7 Le travail ne tue pas l'ennui. +%DC8 Demain est un autre jour chiant. +%DC9 Quand tu ne sais pas quoi dire, t'as qu'à sortir un proverbe chinois. +%DC10 Arrête de te faire chier, ça m'ennuie +%DC11 Je code, donc je lose +%DC12 Si t'es pas pédé, va te faire enculer +%DC13 Je pense, donc je fais un autre métier très vite +%DC14 Quand elle a la tête dans l'oreiller, tu n'entends pas la cochonne roter +%DC15 Deux paires de couilles au cul, ça rend nerveux +%DC16 Quand t'as plus de place, sauvegarde dans /dev/null, personne ne lira ton rapport de toute façon +%DC17 L'ours se lasse qu'on se le leche, c'est la lose. +%DC18 Les dictons chinois, même à base de plantigrade, ne sont JAMAIS pourraves. +%DC19 La proqvo au boulto, ça fait mal au bas du dos ! +%DC20 Le mariage, c'est le meilleur moyen de divorcer. +%DC21 Le SOAP c'est comme le vin rouge, ça fait mal au cul. Surtout dans les douches. + + + +%JCVD 1 Les plantes qui n'ont pas de mains et pas d'oreilles, elles sentent les choses, les vibrations. Elles sont plus aware que les autres species +%JCVD 2 Si on enlevait l'air, tous les oiseaux tomberaient par terre et les avions aussi +%JCVD 3 Le ciment est composé de protons et de neutrons +%JCVD 4 Manger des cacahuètes, it's a strong feeling ! +%JCVD 5 Les cacahuètes, c'est le mouvement perpétuel à portée de l'homme +%JCVD 6 Entre toi et moi, il y a un produit qui s'apelle l'oxygène, alors tu vis. Mais si je tue l'oxygène comme sur la lune, tu meurs +%JCVD 7 EST-CE QU'IL Y A UNE LOI POUR L'AMOUR ? +%JCVD 8 La plus belle religion qu'on puisse avoir, c'est de rentrer en soi-même et de digérer l'essence de la vie, se digérer soi-même et produire à partir de ça sa propre religion : l'instinct. +%JCVD 9 Tout seul avec des gens ou bien tout seul ? +%JCVD 10 Même en français, on dit « feeling » ? +%JCVD 11 Les animaux parlent avec du feeling, mais ils n'ont pas de langages pour nos trouilles à nous +%JCVD 12 Là j'ai un chien en ce moment à côté de moi et je le caresse. +%JCVD 13 Le meilleur modèle qu'on peut avoir dans la vie c'est soi-même. +%JCVD 14 C'est très très beau d'avoir son propre moi-même +%JCVD 15 Parce qu'en vérité, la vérité, il n'y a pas de vérité. Hé hé, il faut me comprendre... +%JCVD 16 J'espère que c'est pas trop fort, mais c'est très profond ce que je vais dire : il y a deux vies. +%JCVD 17 On vit dans une réalité qu'on a créée et que j'appelle « illusion ». +%JCVD 18 La mort n'existe pas. La mort, c'est la seconde dimension ; la vraie dimension de la vie, c'est l'univers ! +%JCVD 19 Et je sais que même si tu comprends pas ce que je dis, tu le comprends. +%JCVD 20 Ça veut dire quoi « devise » ? Je m'excuse, je suis un peu illétré pour ça. +%JCVD 21 De toutes façons, je suis éternel. +%JCVD 22 Il faut se recréer.. pour recréer... a better you. +%JCVD 23 T'es sympa comme mec. Au passage, il n'y a rien de concret. +%JCVD 24 Mais en vérité, les choses concrètes, oui, elles sont concrètes si on croit qu'elles sont concrètes ! +%JCVD 25 L'eau, c'est quelque chose de concret mais pas concret. +%JCVD 26 C'est quelque chose qui a beacoup de dimensions, l'eau. +%JCVD 27 Et en vérité, elle est simple si elle est simple +%JCVD 28 Je le sens, tu peux dire ce que tu veux. +%JCVD 29 Une personne s'appelle « Dieu », et l'autre s'appelle « être humain ». Et on est tous les deux la même chose au même niveau... +%JCVD 30 La femme sait pondre, c'est pour c,a qu'elle est supérieure à nous + + + +%Je vais te décalotter la glotte +%Je vais te mettre du plomb dans les molaires +%Je vais te détartrer le larynx +%Je vais te repeindre l'oeusophage +%Je vais te faire gober un oeuf dur +%Je vais te bénir l'étouffe chretien +%Je vais te faire saliver la brosse a dents + + + +%Hé mais va t'épiler le menhir +%Hé mais va te pignoler la gaufrette +%Hé mais va t'éffeuiller le baobab +%Hé mais va te dégoupiller la bombinette +%Hé mais va te dessouder le chalumeau +%Hé mais va te gourmander le capricieux +%Hé mais va te raboter le gourdin +%Hé mais va te faire loucher le cyclope +%Hé mais va dégager en touche +%Hé mais va te barbapaper le battonet +%Hé mais va te sucer la friandise +%Hé mais va te faire revenir la gousse a l'huile de coude +%Hé mais va border l'insomniaque +%Hé mais va zapper sur manuel + + + +%Je vais m'faire chocolater le kinder +%Je vais baguer le pigeon +%Je vais taper dans le tas de charbon +%Je vais me faire vernir le doigt sans ongle +%Je vais m'empapaouter son moule a cake +%Je vais me carameliser le p'tit sucre +%Je vais faire un swing au deuxieme trou +%Je vais terrasser la fosse septique +%Je vais balancer le bébé dans l'egout +%Je vais rouler a contre sens +%Je vais emmener bronzer le speleo +%Je vais embourber le 4x4 +%Je vais mazouter le pingouin +%Je vais shouter dans la boite d'ovomaltine +%Je vais téléphoner aux congolais +%Je vais te défoncer la boite a cachous +%Je vais polir le petit chauve +%Je vais te bourrer le sac a dos + + + +%Je vais te zygomater le rigoleur +%Je vais te glisser l'accordeon dans le valseur +%Je vais te ratisser le bunker +%Je vais te brouter le green +%Je vais t'incendier la chambre froide +%Je vais te faisander le dindon +%Je vais te poncer le fagot +%Je vais te mettre mon petit lardon dans sa grosse frisee +%Je vais te gominer la touffe +%Je vais te glisser l'zakouski dans le blinis +%Je vais t'exploser le terrier +%Je vais bivouaquer dans la crevasse +%Je vais te faire mariner le mousse +%Je vais te faire crapahuter le flemmard +%Je vais t'apostropher le bavard +%Je vais te dégivrer le congelo +%Je vais te faire baver le mollusque +%Je vais te mettre la quenelle dans le shaker +%Je vais te mettre la piece dans le tronc +%Je vais te recurer la marmite +%Je vais te planter le javelot dans la moquette +%Je vais te cotontiger l'oreille sourde +%Je vais te meringuer la tumeur +%Je vais te regaler le petit +%Je vais m'desenclaver la peninsule +%Je vais te parmesaner les lasagnes +%Je vais me rechauffer les abats dans l'four a quenelles +%Je vais t'enguimauver la ruche +%Je vais te gloutonner la cressoniere +%Je vais te débroussailler la tranchee +%Je vais me faire une ligne de coquine +%Je vais te palucher le mou de veau +%Je vais faire sprinter l'unijambiste +%Je vais débarbouiller le pygmée +%Je vais nourrir la bouche sans dents +%Je vais écraser mon megot dans le gigot +%Je vais Masser le p'tit bossu +%Je vais te massicoter le feuillete +%Je vais cravacher le pur sang +%Je vais mettre le traineau dans le fjord +%Je vais nraquer le vigile +%Je vais t'ébouriffer le chignon +%Je vais débourrer le mammouth + + + + . o O ( jamais othar ne saura programmer en C++ ! ) + + + + vous conseille http://maleloria.org/pictures/dim_seduce.jpg + + + + . o O ( jamais dim ne saura faire un paquet debian ! ) + + + + . o O ( dim va encore prendre une branlée sur + http://www.jippii.fr/fr/games/pasiworld/index-green.html ! ) + + + + . o O ( anon va encore se faire rootkiter sa Sparc/64... ) + + + + . o O (zope: Grosse daube) + + + + http://www.chezmoicamarche.org/ + + + + http://dekpower.free.fr/stuff/funpics/whyPlayq3.jpg + + + + . o O ( jamais nmi ne saura jouer ! ) + + + + . o O (mais quel boulet !) + + + + « Les réunions, le dernier endroit à la mode pour pas bosser » + + + + La Goudale, ça emballe ! + + + +%Tu vas pas aimer le vendredi... +%Le vendredi, c'est SODOMIE ! + + + + FOUTAISES + + + +%COUCOU ! +%MOUAAAAAAAAAAAAAAAAAAAAAA + + + +%Il a dit coin là ? +%TOUS DESSUS ! + + + + poh + + + + rana + + + +%burp +%Pfiou (oops) +%roooooh +%BRRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHH +%BRRAAAaaaaAAAAAAaaaaAAAAAAAAAAAAAAAH +%BRRRRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHHHHHHHHHHHHH +%*BROOOOOOOOAAAAAAAAAAAAAAAAAAAAAAAAARP* + + + +http://www.isty-info.uvsq.fr/~fontaine/gli/sort.gif + + + + http://maleloria.org/pictures/dim_book.jpg + + + + http://maleloria.org/pictures/nmi_book.jpg + + + + plic plic plic + + + + Ah non, Pluto c'est l'ami de Mickey ! + + + + Grosse Loutre + + + + DANS TON CUL + + + +%Presse: Après l'autopsie du cadavre de Jean M.. on reste un peu sur sa faim. (France-Soir) +%Presse: Le rapport de la gendarmerie révèle que Alain P... se serait suicidé lui-même. (Nord Éclair) +%Presse: Le grand inventeur Louis Lumière s'est éteint. (L'Aurore) +%Presse: Le mystère de la femme coupée en morceaux reste entier. (Est-Éclair) +%Presse: Quand Honoré Gall s'est-il suicidé ? S'est-il donné la mort avant de se jeter à l'eau ? (Le Progrès) +%Presse: Très gravement brûlée, elle s'est éteinte pendant son transport à l'hôpital. (Dauphiné Actualité) + + + +%Presse: Il lui bottait le derrière à tour de bras. +%Presse: Il abusait de la puissance de son sexe pour frapper son ex-épouse. +%Presse: C'est avant votre crime qu'il fallait éprouver des remords. +%Presse: À la lumière du doute qui obscurcit cette affaire, nous trancherons. +%Presse: C'est l'immobilisme qui conduit notre région au gouffre. +%Presse: Un tas de briques avait amorti sa chute. +%Presse: Des trous dans sa culotte laissaient entrevoir une famille pauvre. +%Presse: En raison de la chaleur, les musiciens ne porteront que la casquette de l'uniforme. +%Presse: L'individu n'était pas à prendre avec du pain sec. +%Presse: Les pieds de Damoclès. +%Presse: M. Jean C. remercie chaleureusement les personnes qui ont pris part au décès de son épouse. +%Presse: Elle venait d'enterrer son regrettable époux. +%Presse: Elle est décédée mortellement. +%Presse: Ses dernières paroles furent un silence farouche. +%Presse: L'église étant en travaux, ses obsèques ont été célébrées à la salle des fêtes. +%Presse: Tous portaient une crêpe à la boutonnière. +%Presse: Vers 18h30, la brigade canine arrive. Tout le monde est sur les dents. (La Voix du Nord, 25/02/1995) +%Presse: Le monte-en-l'air a finalement été appréhendé par une patrouille de voltigeurs. +%Presse: L'arrestation s'est opérée sans infusion de sang. +%Presse: Comme il s'agissait d'un sourd, la police dut pour l'interroger avoir recours à l'alphabet braille. +%Presse: Cet ancien haltérophile est accusé de vols à l'arraché. +%Presse: Il naquit dans la voiture que transportait sa mère à l'hopital. +%Presse: Issu de la France profonde, il fut quelque temps mineur. Puis il pustula à de plus hautes fonctions. +%Presse: Ayant debuté comme simple fossoyeur, il a, depuis, fait son trou. +%Presse: Depuis quelques mois, il emplit une secrétaire. +%Presse: Plus solide et moins infalsifiable, le nouveau permis de conduire est arrivé. (l'Yonne Republicaine, 25/11/1994) +%Presse: Deux conducteurs étaient interpellés par les gendarmes en état d'ivresse. (Var Matin, 13/07/1994) +%Presse: Détail navrant, cette personne avait déjà été victime l'an dernier d'un accident mortel. +%Presse: Il a été superficiellement égorgé au bras. +%Presse: Quand vous doublez un cycliste, laissez lui toujours la place de tomber. (Le Republicain Lorrain, 14/08/1954) +%Presse: On faisait la queue, hier, en face du trou de la place de la Motte. (La Montagne, 01/10/1995) +%Presse: Là-bas, la main de l'homme n'a jamais mis les pieds. +%Presse: Le climat et les eaux sont très humides. +%Presse: Les mosquées sont très nombreuses car les musulmans sont très chretiens. +%Presse: La conférence sur la constipation sera suivie d'un pot amical. (Ouest-France, 12/08/1995) +%Presse: Ses hémorroïdes l'empêchait de fermer l'oeil. +%Presse: Il y aura un appareil de réanimation dernier cri. +%Presse: Chasse: Moins de cerfs, mais plus nombreux. (Dernières nouvelles d'Alsace, 14/03/1993) +%Presse: Il remue la queue en cadence comme un soldat à la parade. +%Presse: C'est un chasseur, qui ne voulant pas rentrer bredouille, s'est tué... +%Presse: Tombola de la Société Bayonnaise des Amis des Oiseaux: le numéro 5963 gagne un fusil de chasse. (Sud-Ouest 24/11/1956) +%Presse: A aucun moment le Christ n'a baissé les bras. +%Presse: Il s'agit de financer les réparations du presbytère qui a brûlé le jour de la fête des Cendres. +%Presse: Le syndicat des inséminateurs fait appel à la vigueur de ses membres. +%Presse: Les brasseurs sont sous pression. +%Presse: Les kinés se sont massés contre les grilles de la préfecture. +%Presse: Visiblement, la victime a été étranglée à coups de couteau. +%Presse: A Montaigu, la fête du 1er mai aura lieu le 1er mai. +%Presse: C'est la foire des veaux et des porcs: venez nombreux ! (La Vie Correzienne, 09/05/1954) +%Presse: Il a applaudit a pleins poumons. +%Presse: Robinson harcèle son adversaire par de durs gauches des deux mains. (La Resistance de l'Ouest, 11/12/1950) +%Presse: Journée du sang: s'inscrire à la boucherie. +%Presse: Parmi les nombreux lots: un chariot elevateur, un cric hydraulique, 500 kg de briques, une portée de porcelets, une paire de draps pour un lit a deux places,... +%Presse: Veritable Pub anglais : specialité couscous. +%Presse: Chien perdu: policier a poil ras et queue noire +%Presse: Suite à ce succès, les acteurs se reproduiront devant vous. +%Presse: Une bicyclette ne peut avancer que mise en mouvement. (L'Intransigeant,14/12/1906) +%Presse: Cette attaque frappe les hommes politiques mais aussi les honnêtes gens. + + + +%http://www.partyvibes.nl/wallpapers/02/klum.jpg +%http://10home.de/wsxq/star/heidi/heidi115.jpg +%http://10home.de/wsxq/star/heidi/heidi125.jpg +%http://10home.de/wsxq/star/heidi/heidi81.jpg +%http://www.fifty-five.com/pictures/swimsuit/HK200026.JPG +%http://www.fifty-five.com/pictures/swimsuit/HK200030.JPG +%http://www.fifty-five.com/pictures/swimsuit/HK200032.JPG +%http://www.fifty-five.com/pictures/swimsuit/HKART02.JPG +%http://www.fifty-five.com/pictures/swimsuit/HK2000W1.JPG +%http://www.fifty-five.com/pictures/swimsuit/HK200043.JPG +%http://www.fifty-five.com/pictures/swimsuit/HK2000W6.JPG +%http://www.fifty-five.com/pictures/swimsuit/HK200021.JPG +%http://www.fifty-five.com/pictures/swimsuit/HK200019.JPG +%http://www.fifty-five.com/pictures/swimsuit/HKGQ9909.JPG +%http://www.fifty-five.com/pictures/swimsuit/HKSI2018.JPG +%http://www.fifty-five.com/pictures/swimsuit/HKSI2019.JPG +%http://www.fifty-five.com/pictures/swimsuit/HKSI2003.JPG +%http://www.fifty-five.com/pictures/swimsuit/Beachscannew.jpg +%http://www.fifty-five.com/pictures/swimsuit/h1.jpg +%http://www.fifty-five.com/pictures/swimsuit/heidi_klum02_si99.jpg +%http://www.fifty-five.com/pictures/swimsuit/heidi4_big.jpg +%http://www.fifty-five.com/pictures/swimsuit/HKART06.JPG +%http://www.fifty-five.com/pictures/swimsuit/HKART07.JPG +%http://www.fifty-five.com/pictures/fashion/HKCAND38.JPG +%http://www.fifty-five.com/pictures/other/klum41.jpg +%http://www.fifty-five.com/pictures/other/HKSI2004.jpg +%http://www.fifty-five.com/pictures/other/HKSI2002.jpg +%http://www.fifty-five.com/pictures/sexy/hk19.jpg +%http://www.fifty-five.com/pictures/sexy/hk17.jpg +%http://www.fifty-five.com/pictures/sexy/hk212.jpg +%http://www.fifty-five.com/pictures/sexy/HK200016.JPG +%http://www.fifty-five.com/pictures/sexy/HK200020.JPG +%http://www.fifty-five.com/pictures/sexy/HK200038.JPG +%http://www.fifty-five.com/pictures/sexy/HKSTUF13.JPG +%http://www.fifty-five.com/pictures/sexy/HKGQ9910.JPG +%http://www.fifty-five.com/pictures/sexy/hk213.jpg +%http://www.fifty-five.com/pictures/sexy/hk84.jpg +%http://www.fifty-five.com/pictures/sexy/hk77.jpg +%http://www.fifty-five.com/pictures/sexy/hk78.jpg +%http://www.fifty-five.com/pictures/sexy/hk88.jpg +%http://www.fifty-five.com/pictures/sexy/HEIDILIF_jt1.JPG +%http://www.fifty-five.com/pictures/sexy/HK200013.JPG +%http://www.fifty-five.com/pictures/sexy/HK200010-tb.JPG + + + +%http://www.bullz-eye.com/models/ +%http://bullz-eye.com/gnd/default.htm +http://krazor.com/images/j-l/Klum,_Heidi/80.jpg +%http://krazor.com/images/G-I/Hill,_Faith/02.jpg +%http://www.bullz-eye.com/Models/200201kathie/kathie-03.jpg +%http://www.bullz-eye.com/models/200105michelle/fmichelle-09.jpg +%http://www.bullz-eye.com/models/200105michelle/fmichelle-18.jpg +%http://www.bullz-eye.com/models/200011lisa1/lisa-03.jpg +%http://bullz-eye.com/gnd/200202annika/annika-05.jpg +%http://bullz-eye.com/gnd/200202annika/annika-30.jpg +%http://bullz-eye.com/gnd/200201karina/karina-10.jpg +%http://bullz-eye.com/gnd/200201karina/karina-16.jpg +%http://bullz-eye.com/gnd/200112esther/esther-17.jpg +%http://bullz-eye.com/gnd/200111marsha/marsha-04.jpg +%http://bullz-eye.com/gnd/200111lisa/lisa-05.jpg +%http://bullz-eye.com/gnd/200103michelle/michelle-04.jpg +%http://bullz-eye.com/gnd/200103michelle/michelle-27.jpg +%http://bullz-eye.com/gnd/200103michelle/michelle-33.jpg +%http://bullz-eye.com/gnd/200103michelle/michelle-34.jpg +%http://bullz-eye.com/gnd/200008alisa1/alisa1-13.jpg +%http://www.alisonarmitage.com/pictures/gilles/07.jpg +%http://www.sexyvictoria.com/samplegal/silvstedt01.jpg +%http://www.sexyvictoria.com/samplegal/silvstedt02.jpg +%http://www.sexyvictoria.com/samplegal/silvstedt03.jpg +%http://www.sexyvictoria.com/samplegal/silvstedt04.jpg +%http://www.sexyvictoria.com/samplegal/silvstedt05.jpg +%http://www.sexyvictoria.com/samplegal/silvstedt06.jpg +%http://www.sexyvictoria.com/samplegal/silvstedt07.jpg +%http://www.sexyvictoria.com/samplegal/silvstedt08.jpg +%http://www.sexyvictoria.com/samplegal/silvstedt09.jpg +%http://www.sexyvictoria.com/samplegal/silvstedt10.jpg +%http://www.sexyvictoria.com/samplegal/silvstedt11.jpg +%http://www.sexyvictoria.com/samplegal/silvstedt12.jpg +%http://www.mxdpi.com/gallery/ACTRESSES/Jaime_Pressly/004.jpg +%http://www.mxdpi.com/gallery/ACTRESSES/Jaime_Pressly/008.jpg +%http://www.mxdpi.com/gallery/ACTRESSES/Jaime_Pressly/013.jpg +%http://www.mxdpi.com/gallery/ACTRESSES/Jaime_Pressly/036.jpg +%http://www.mxdpi.com/gallery/ACTRESSES/Jaime_Pressly/044.jpg +%http://www.mxdpi.com/gallery/ACTRESSES/Jaime_Pressly/050.jpg +%http://www.mxdpi.com/gallery/ACTRESSES/Jaime_Pressly/064.jpg +%http://www.mxdpi.com/gallery/ACTRESSES/Jaime_Pressly/073.jpg +%http://www.mxdpi.com/gallery/ACTRESSES/Jaime_Pressly/133.jpg +%http://www.mxdpi.com/gallery/ACTRESSES/Jennifer_Lopez/002.jpg +%http://www.mxdpi.com/gallery/ACTRESSES/Jennifer_Lopez/008.jpg +%http://www.mxdpi.com/gallery/ACTRESSES/Rachael_Leigh_Cook/018.jpg +%http://www.mxdpi.com/gallery/ACTRESSES/Rachael_Leigh_Cook/063.jpg +%http://www.mxdpi.com/gallery/ACTRESSES/Neve_Campbell/002.jpg +%http://www.mxdpi.com/gallery/ACTRESSES/Neve_Campbell/014.jpg +%http://www.mxdpi.com/gallery/ACTRESSES/Neve_Campbell/029.jpg +%http://www.mxdpi.com/gallery/ACTRESSES/Neve_Campbell/049.jpg +%http://www.mxdpi.com/gallery/ACTRESSES/Neve_Campbell/054.jpg +%http://www.mxdpi.com/gallery/ACTRESSES/Neve_Campbell/063.jpg +%http://www.partyvibes.nl/wallpapers/02/klum.jpg +%http://10home.de/wsxq/star/heidi/heidi115.jpg +%http://10home.de/wsxq/star/heidi/heidi125.jpg +%http://10home.de/wsxq/star/heidi/heidi81.jpg +%http://www.partyvibes.nl/wallpapers/01/Kim_Smith.jpg +%http://www.artbyal.com/supermodelwalls/abaf1024/VeronicaVarekova2_1024x768.jpg +%http://www.allcelebrity.it/De_Grenet/08.jpg +%http://www.allcelebrity.it/Falchi/01.jpg +%http://www.allcelebrity.it/Fontana/13.jpg +%http://www.allcelebrity.it/Fontana/146.jpg +%http://www.allcelebrity.it/Galassi/101.jpg +%http://www.allcelebrity.it/Galassi/143.jpg +%http://www.allcelebrity.it/Marcuzzi/05.jpg +%http://www.allcelebrity.it/Massola/04.jpg +%http://www.allcelebrity.it/Merz/17.jpg +%http://www.allcelebrity.it/Miconi/09.jpg +%http://www.allcelebrity.it/Modigliani/03.jpg + + + +%En essayant continuellement on finit par réussir. Donc plus ça rate, plus on a de chances que ça marche. +%Il vaut mieux pomper même s'il ne se passe rien que risquer qu'il se passe quelque chose de pire en ne pompant pas. +%Pour qu'il y ait le moins de mécontents possible il faut toujours taper sur les mêmes. +%Si ça fait mal c'est que ça fait du bien. +%Quand on ne sait pas où l'on va, il faut y aller !... et le plus vite possible. +%La plus grave maladie du cerveau, c'est de réfléchir. +%Pourquoi faire simple quand on peut faire compliqué ? +%S'il n'y a pas de solution c'est qu'il n'y a pas de problème. +%La vérite, c'est qu'il n'y a pas de vérité. Y compris celle-ci. +%Le bon sens est la chose du monde la mieux partagée... La connerie aussi. +%Il vaut mieux mobiliser son intelligence sur des conneries que mobiliser sa connerie sur des choses intelligentes. +%Avec un escalier prévu pour la montée, on réussit souvent à monter plus bas qu'on ne serait descendu avec un escalier prévu pour la descente. +%Je dis des choses tellement intelligentes que le plus souvent je comprends pas ce que je dis. +%Dans la marine, on fait pas grand chose, mais on le fait de bonne heure. +%Excusez-moi, j'ai oublié que j'étais amnésique ! +%Je pompe donc je suis. +%Dans la Marine on salue tout ce qui bouge et on peint le reste. + + + +%clock speed +%solar flares +%electromagnetic radiation from satellite debris +%static from nylon underwear +%static from plastic slide rules +%global warming +%poor power conditioning +%static buildup +%doppler effect +%hardware stress fractures +%magnetic interferance from money/credit cards +%dry joints on cable plug +%we're waiting for [the phone company] to fix that line +%sounds like a Windows problem, try calling Microsoft support +%temporary routing anomoly +%somebody was calculating pi on the server +%fat electrons in the lines +%excess surge protection +%floating point processor overflow +%divide-by-zero error +%POSIX complience problem +%monitor resolution too high +%improperly oriented keyboard +%network packets travelling uphill (use a carrier pigeon) +%Decreasing electron flux +%first Saturday after first full moon in Winter +%radiosity depletion +%CPU radiator broken +%It works the way the Wang did, what's the problem +%positron router malfunction +%cellular telephone interference +%techtonic stress +%pizeo-electric interference +%(l)user error +%working as designed +%dynamic software linking table corrupted +%heavy gravity fluctuation, move computer to floor rapidly +%secretary plugged hairdryer into UPS +%terrorist activities +%not enough memory, go get system upgrade +%interrupt configuration error +%spaghetti cable cause packet failure +%boss forgot system password +%bank holiday - system operating credits not recharged +%virus attack, luser responsible +%waste water tank overflowed onto computer +%Complete Transient Lockout +%bad ether in the cables +%Bogon emissions +%Change in Earth's rotational speed +%Cosmic ray particles crashed through the hard disk platter +%Smell from unhygenic janitorial staff wrecked the tape heads +%Evil dogs hypnotized the night shift +%Plumber mistook routing panel for decorative wall fixture +%Electricians made popcorn in the power supply +%Groundskeepers stole the root password +%high pressure system failure +%failed trials, system needs redesigned +%system has been recalled +%not approved by the FCC +%need to wrap system in aluminum foil to fix problem +%not properly grounded, please bury computer +%CPU needs recalibration +%bit bucket overflow +%descramble code needed from software company +%only available on a need to know basis +%knot in cables caused data stream to become twisted and kinked +%nesting roaches shorted out the ether cable +%The file system is full of it +%Satan did it +%Daemons did it +%You're out of memory +%There isn't any problem +%Unoptimized hard drive +%Typo in the code +%Yes, yes, its called a desgin limitation +%Look, buddy: Windows 3.1 IS A General Protection Fault. +%Support staff hung over, send aspirin and come back LATER. +%Someone is standing on the ethernet cable, causeing a kink in the cable +%Windows 95 undocumented "feature" +%Runt packets +%Password is too complex to decrypt +%Boss' kid fucked up the machine +%Electromagnetic energy loss +%Budget cuts +%Mouse chewed through power cable +%Stale file handle (next time use Tupperware(tm)!) +%Feature not yet implimented +%Internet outage +%Pentium FDIV bug +%Vendor no longer supports the product +%Small animal kamikaze attack on power supplies +%The vendor put the bug there. +%SIMM crosstalk. +%IRQ dropout +%Collapsed Backbone +%Power company testing new voltage spike (creation) equipment +%operators on strike due to broken coffee machine +%backup tape overwritten with copy of system manager's favourite CD +%UPS interrupted the server's power +%The electrician didn't know what the yellow cable was so he yanked the ethernet out. +%The keyboard isn't plugged in +%The air conditioning water supply pipe ruptured over the machine room +%The electricity substation in the car park blew up. +%The rolling stones concert down the road caused a brown out +%The salesman drove over the CPU board. +%Root nameservers are out of sync +%electro-magnetic pulses from French above ground nuke testing. +%your keyboard's space bar is generating spurious keycodes. +%the real ttys became pseudo ttys and vice-versa. +%the printer thinks its a router. +%the router thinks its a printer. +%we just switched to FDDI. +%halon system went off and killed the operators. +%user to computer ratio too high. +%user to computer ration too low. +%Sticky bits on disk. +%Power Company having EMP problems with their reactor +%The ring needs another token +%SCSI Chain overterminated +%because of network lag due to too many people playing deathmatch +%Daemons loose in system. +%BNC (brain not (user brain not connected) +%UBNC (user brain not connected) +%LBNC (luser brain not connected) +%disks spinning backwards - toggle the hemisphere jumper. +%new guy cross-connected phone lines with ac power bus. +%had to use hammer to free stuck disk drive heads. +%Too few computrons available. +%Flat tire on station wagon with tapes. ("Never underestimate the bandwidth of a station wagon full of tapes hurling down the highway" Andrew S. Tanenbaum) +%Communications satellite used by the military for star wars. +%Party-bug in the Aloha protocol. +%Insert coin for new game +%Dew on the telephone lines. +%Arcserve crashed the server again. +%Some one needed the powerstrip, so they pulled the switch plug. +%My pony-tail hit the on/off switch on the power strip. +%Big to little endian conversion error +%You can tune a file system, but you can't tune a fish (from most tunefs man pages) +%Dumb terminal +%Zombie processes haunting the computer +%Incorrect time syncronization +%Defunct processes +%Stubborn processes +%non-redundant fan failure +%monitor VLF leakage +%bugs in the RAID +%no "any" key on keyboard +%root rot +%Backbone Scoliosis +%mv /pub/lunch +%excessive collisions and not enough packet ambulances +%le0: no carrier: transceiver cable problem? +%broadcast packets on wrong frequency +%popper unable to process jumbo kernel +%NOTICE: alloc: /dev/null: filesystem full +%pseudo-user on a pseudo-terminal +%Recursive traversal of loopback mount points +%Backbone adjustment +%OS swapped to disk +%vapors from evaporating sticky-note adhesives +%sticktion +%short leg on process table +%multicasts on broken packets +%ether leak +%Atilla the Hub +%endothermal recalibration +%filesystem not big enough for Jumbo Kernel Patch +%loop found in loop in redundant loopback +%system consumed all the paper for paging +%permission denied +%Reformatting Page. Wait... +%SCSI's too wide. +%Proprietary Information. +%Just type 'mv * /dev/null'. +%runaway cat on system. +%We only support a 1200 bps connection. +%Me no internet, only janitor, me just wax floors. +%I'm sorry a pentium won't do, you need an SGI to connect with us. +%Post-it Note Sludge leaked into the monitor. +%the curls in your keyboard cord are losing electricity. +%The monitor needs another box of pixels. +%RPC_PMAP_FAILURE +%kernel panic: write-only-memory (/dev/wom0) capacity exceeded. +%Write-only-memory subsystem too slow for this machine. Contact your local dealer. +%Quantum dynamics are affecting the transistors +%Police are examining all internet packets in the search for a narco-net-traficer +%We are currently trying a new concept of using a live mouse. Unfortuantely, one has yet to survive being hooked up to the computer.....please bear with us. +%We didn't pay the Internet bill and it's been cut off. +%Lightning strikes. +%Of course it doesn't work. We've performed a software upgrade. +%Change your language to Finnish. +%Flourescent lights are generating negative ions. If turning them off doesn't work, take them out and put tin foil on the ends. +%High nuclear activity in your area. +%The MGs ran out of gas. +%The UPS doesn't have a battery backup. +%Recursivity. Call back if it happens again. +%Someone thought The Big Red Button was a light switch. +%The mainframe needs to rest. +%Try calling the Internet's head office -- it's in the book. +%The lines are all busy (busied out, that is -- why let them in to begin with?). +%A star wars satellite accidently blew up the WAN. +%Fatal error right in front of screen +%wrong polarity of neutron flow +%Lusers learning curve appears to be fractal +%We had to turn off that service to comply with the CDA Bill. +%Ionisation from the air-conditioning +%TCP/IP UDP alarm threshold is set too low. +%Someone is broadcasting pigmy packets and the router dosn't know how to deal with them. +%The new frame relay network hasn't bedded down the software loop transmitter yet. +%Fanout dropping voltage too much, try cutting some of those little traces +%Plate voltage too low on demodulator tube +%CPU needs bearings repacked +%Too many little pins on CPU confusing it, bend back and forth until 10-20% are neatly removed. Do _not_ leave metal bits visible! +%Software uses US measurements, but the OS is in metric... +%The computer fletely, mouse and all. +%Too much radiation coming from the soil. +%Program load too heavy for processor to lift. +%Processes running slowly due to weak power supply +%Our ISP is having {switching,routing,SMDS,frame relay} problems +%We've run out of licenses +%Interference from lunar radiation +%Standing room only on the bus. +%You need to install an RTFM interface. +%That would be because the software doesn't work. +%That's easy to fix, but I can't be bothered. +%Someone's tie is caught in the printer, and if anything else gets printed, he'll be in it too. +%We're upgrading /dev/null +%The Usenet news is out of date +%Our POP server was kidnapped by a weasel. +%It's stuck in the Web. +%Your modem doesn't speak English. +%The mouse escaped. +%All of the packets are empty. +%The UPS is on strike. +%Neutrino overload on the nameserver +%Melting hard drives +%Someone has messed up the kernel pointers +%The kernel license has expired +%The cord jumped over and hit the power switch. +%Bit rot +%The Dilithium Cyrstals need to be rotated. +%The static electricity routing is acting up... +%Traceroute says that there is a routing problem in the backbone. It's not our problem. +%The co-locator cannot verify the frame-relay gateway to the ISDN server. +%Lawn mower blade in your fan need sharpening +%Electrons on a bender +%Telecommunications is upgrading. +%Telecommunications is downgrading. +%Telecommunications is downshifting. +%Hard drive sleeping. Let it wake up on it's own... +%Interference between the keyboard and the chair. +%The CPU has shifted, and become decentralized. +%Due to the CDA, we no longer have a root account. +%We ran out of dial tone and we're and waiting for the phone company to deliver another bottle. +%You must've hit the wrong anykey. +%PCMCIA slave driver +%The Token fell out of the ring. Call us when you find it. +%The hardware bus needs a new token. +%Too many interrupts +%Not enough interrupts +%The data on your hard drive is out of balance. +%Digital Manipulator exceeding velocity parameters +%appears to be a Slow/Narrow SCSI-0 Interface problem +%microelectronic Riemannian curved-space fault in write-only file system +%fractal radiation jamming the backbone +%routing problems on the neural net +%IRQ-problems with the Un-Interruptable-Power-Supply +%CPU-angle has to be adjusted because of vibrations coming from the nearby road +%emissions from GSM-phones +%CD-ROM server needs recalibration +%firewall needs cooling +%asynchronous inode failure +%transient bus protocol violation +%incompatible bit-registration operators +%your process is not ISO 9000 compliant +%You need to upgrade your VESA local bus to a MasterCard local bus. +%The recent proliferation of Nuclear Testing +%Elves on strike. (Why do they call EMAG Elf Magic) +%Internet exceeded Luser level, please wait until a luser logs off before attempting to log back on. +%Your EMAIL is now being delivered by the USPS. +%Your computer hasn't been returning all the bits it gets from the Internet. +%You've been infected by the Telescoping Hubble virus. +%Scheduled global CPU outage +%processor has processed too many intructions. +%packets were eaten by the terminator +%The POP server is out of Coke +%Fiber optics caused gas main leak +%Server depressed, needs Prozak +%quatnum decoherence +%those damn racoons! +%suboptimal routing experience +%A plumber is needed, the network drain is clogged +%50% of the manual is in .pdf readme files +%the AA battery in the wallclock sends magnetic interference +%the xy axis in the trackball is coordinated with the summer soltice +%the butane lighter causes the pincushioning +%old inkjet cartridges emanate barium-based fumes +%manager in the cable duct +%HTTPD Error 666 : BOFH was here +%HTTPD Error 4004 : very old Intel cpu - insufficient processing power +%Network failure - call NBC +%Having to manually track the satellite. +%The rubber band broke +%We're on Token Ring, and it looks like the token got loose. +%Stray Alpha Particles from memory packaging caused Hard Memory Error on Server. +%paradigm shift...without a clutch +%PEBKAC (Problem Exists Between Keyboard And Chair) +%The cables are not the same length. +%Second-sytem effect. +%Chewing gum on /dev/sd3c +%Boredom in the Kernel. +%struck by the Good Times virus +%YOU HAVE AN I/O ERROR -> Incompetent Operator error +%Your parity check is overdrawn and you're out of cache. +%Plasma conduit breach +%Out of cards on drive D: +%Sand fleas eating the Internet cables +%parallel processors running perpendicular today +%ATM cell has no roaming feature turned on, notebooks can't connect +%Webmasters kidnapped by evil cult. +%Failure to adjust for daylight savings time. +%Virus transmitted from computer to sysadmins. +%Virus due to computers having unsafe sex. +%Incorrectly configured static routes on the corerouters. +%Forced to support NT servers; sysadmins quit. +%Suspicious pointer corrupted virtual machine +%Its the InterNIC's fault. +%Root name servers corrupted. +%Budget cuts forced us to sell all the power cords for the servers. +%Someone hooked the twisted pair wires into the answering machine. +%Operators killed by year 2000 bug bite. +%We've picked COBOL as the language of choice. +%Operators killed when huge stack of backup tapes fell over. +%Robotic tape changer mistook operator's tie for a backup tape. +%t's an ID-10-T error +%Dyslexics retyping hosts file on servers +%The Internet is being scanned for viruses. +%Your computer's union contract is set to expire at midnight. +%Bad user karma. +%/dev/clue was linked to /dev/null +%Increased sunspot activity. +%It's union rules. There's nothing we can do about it. Sorry. +%Interferance from the Van Allen Belt. +%Jupiter is aligned with Mars. +%Redundant ACLs. +%Mail server hit by UniSpammer. +%T-1's congested due to porn traffic to the news server. +%Data for intranet got routed through the extranet and landed on the internet. +%What you are experiencing is not a problem; it is an undocumented feature. +%Secretary sent chain letter to all 5000 employees. +%Sysadmin accidentally destroyed pager with a large hammer. +%Bad cafeteria food landed all the sysadmins in the hospital. +%Route flapping at the NAP. +%Computers under water due to SYN flooding. +%The vulcan-death-grip ping has been applied. +%Electrical conduits in machine room are melting. +%Traffic jam on the Information Superhighway. +%Radial Telemetry Infiltration +%Cow-tippers tipped a cow onto the server. +%tachyon emissions overloading the system +%Maintence window broken +%Computer room being moved. Our systems are down for the weekend. +%Sysadmins busy fighting SPAM. +%Repeated reboots of the system failed to solve problem +%Domain controler not responding +%Someone stole your IP address, call the Internet detectives. +%operation failed because: there is no message for this error (#1014) +%stop bit received +%internet is needed to catch the etherbunny +%network down, IP packets delivered via UPS +%Firmware update in the coffee machine +%Mouse has out-of-cheese-error +%Borg implants are failing +%Borg nanites have infested the server +%error: one bad user found in front of screen +%Please state the nature of the technical emergency +%Internet shut down due to maintainance +%Daemon escaped from pentagram +%crop circles in the corn shell +%sticky bit has come loose +%Hot Java has gone cold +%Cache miss - please take better aim next time +%Hash table has woodworm +%Trojan horse ran out of hay +%Zombie processess detected, machine is haunted. +%overflow error in /dev/null +%Browser's cookie is corrupted -- someone's been nibbling on it. +%Mailer-daemon is busy burning your message in hell. +%vi needs to be upgraded to vii + + + +%San Antonio: L'échec, c'est la réussite du con. +%San Antonio: Prenez l'habitude de bien baiser votre femme, vous lui éviterez le dérangement d'aller se faire baiser par vos copains. +%San Antonio: Les hommes sont empêtrés dans leurs fantasmes comme des spaghettis dans du parmesan fondu. +%San Antonio: L'existence, c'est comme ça : tu fais des gosses et tu attends qu'ils s'en aillent. Et puis, quand ils sont partis, tu attends qu'ils reviennent. +%San Antonio: Nous autres, poètes, quand nous avons de la peine, au lieu de la chasser, nous lui cherchons un titre. +%San Antonio: Une femme aux cheveux châtains est une blonde modeste. +%San Antonio: La science est un train que le mécanicien ne peut arrêter. +%San Antonio: Pour l'homme, le mensonge est un outil ; pour la femme, une parure. +%San Antonio: C'est à la culotte de ses filles qu'on juge un pays. +%San Antonio: Les Français qui croisent une étrangère lui regardent successivement les jambes, les yeux et l'annulaire gauche. +%San Antonio: Le passé est un oeuf sans germe : tout ce qu'on peut en tirer c'est une omelette. +%San Antonio: Parler est le plus moche moyen de communication. L'homme ne s'exprime pleinement que par ses silences. +%San Antonio: La seule satisfaction réelle de l'homme, c'est de provoquer la jouissance de la femme. +%San Antonio: Il faut beaucoup de talent pour faire rire avec des mots. Mais il faut du génie pour amuser avec des points de suspension... +%San Antonio: Les hommes s'imaginent faire des enfants, alors qu'ils ne font que d'autres hommes. +%San Antonio: Y a des tas d'endroits sur la planète, je comprends pas que des gens les habitent, quand bien même ils y sont nés. +%San Antonio: Le merveilleux, on s'y habitue plus vite qu'à des godasses trop courtes. +%San Antonio: Ce qui console de la mort des amis, c'est qu'ils laissent des veuves. +%San Antonio: Il ne faut jamais se foutre de la gueule des riches car on ne sait pas ce qu'on peut devenir. +%San Antonio: J'aime profondément qui j'aime sans détester pour autant qui je n'aime pas. +%San Antonio: On cesse toujours d'être le numéro 1 mais on ne cesse jamais d'avoir été le premier. +%San Antonio: Moi, quand j'ai un message à expédier, je n'écris pas un livre : je vais à la poste. +%San Antonio: Les femmes, c'est comme les artichauts : le coeur est sous les poils. +%San Antonio: Je suis un obsédé de l'obsession sexuelle. Mais il est inutile de me féliciter : j'ai également des défauts. + + + + diff --git a/ebin/manderlbot.app b/ebin/manderlbot.app new file mode 100644 index 0000000..a35eed8 --- /dev/null +++ b/ebin/manderlbot.app @@ -0,0 +1,15 @@ +%% Manderlbot Application Configuration File +%% ----- +%% +{application, manderlbot, + [ + {description, "Manderlbot"}, + {vsn, "0.6"}, + {id, "Manderlbot"}, + {modules, []}, + {registered, []}, + {applications, [kernel, stdlib]}, + {env, []}, + {mod, {manderlbot, []}} + ] +}. diff --git a/inc/config.hrl b/inc/config.hrl new file mode 100644 index 0000000..c5c2953 --- /dev/null +++ b/inc/config.hrl @@ -0,0 +1,35 @@ +%%%---------------------------------------------------------------------- +%%% File : config.hrl +%%% Author : Dimitri Fontaine +%%% Purpose : +%%% Created : 19 Feb 2002 by Dimitri Fontaine +%%%---------------------------------------------------------------------- + +-author('tapoueh@free.fr'). + + +-record(config, {name, % the name of the bot + controler, % the nick of the one wich + % controls the bot from irc + servers=[], + behaviours=[] + }). + +-record(server, {host, + port, + channels = [] + }). + +-record(channel, {name, + botname, + behaviours = [] + }). + + +-record(cfg_behaviour, {name, + action, + pattern, + data = [] + }). + + diff --git a/inc/mdb.hrl b/inc/mdb.hrl new file mode 100644 index 0000000..7f63e0a --- /dev/null +++ b/inc/mdb.hrl @@ -0,0 +1,74 @@ +%% -------------------------- +%% Parameters to start a bot +-record(params, {server="", + port=0, + password="", + channel="", + nickserv_password="", + nickname="", + realname=""}). + +%% ---------------------- +%% Used to keep track of +%% who is connecting to +%% the channel +-record(spy, {nickname="", + user="", + last_phrase_date={}, + last_phrase="", + join_date={}, + quit_date={}, + quit_reason=""}). + +%% ---------------------- +%% Used by mdb_bot.erl + +%% Parsed incoming IRC data +-record(data, {body = '_', + header_from = '_', + header_op = '_', + header_to = '_', + header_options = '_'}). + +%% Bot process state +-record(state, {bot_pid = "", + channel = "", + nickname = "", + controler = "", + socket = "", + buffer = <<>>, + behaviours = [], + bot_state={}, + date={}}). + +%% Behaviour description +-record(behaviour, {id="", + pattern, + function, + data}). + +%% ---------------------- +%% Used by mdb_srv.erl + +%% Use to keep trace of the networks that we can connect to +-record(network, {network_id=0, + network_name="", + server="", + ip_port=0}). + +%% Use to keep track of running bots +-record(bot, {bot_id, + network_id, + password, + channel, + nickserv_password, + nickname, + real_name, + botpid, + %% socket, + date}). + +%% State of the server +-record(srv_state, {networks=[], + bots=[]}). + diff --git a/inc/mdb_macros.hrl b/inc/mdb_macros.hrl new file mode 100644 index 0000000..ccea55f --- /dev/null +++ b/inc/mdb_macros.hrl @@ -0,0 +1,13 @@ +%% Debugging macro +%% If the macro debug is defined before including this file, +%% the debug print-outs are enabled. + +-define(debug, true). + +-ifdef(debug). +-define(dbg(Fmt, Args), ok=io:format("~p: " ++ Fmt ++ "~n", [?LINE|Args])). +-define(trace, ok=io:format("<<~p:~p>>~n", [?MODULE, ?LINE])). +-else. +-define(dbg(Fmt, Args), no_debug). +-define(trace, no_debug). +-endif. diff --git a/inc/xmerl.hrl b/inc/xmerl.hrl new file mode 100644 index 0000000..4be135a --- /dev/null +++ b/inc/xmerl.hrl @@ -0,0 +1,211 @@ +%%% The contents of this file are subject to the Erlang Public License, +%%% Version 1.0, (the "License"); you may not use this file except in +%%% compliance with the License. You may obtain a copy of the License at +%%% http://www.erlang.org/license/EPL1_0.txt +%%% +%%% Software distributed under the License is distributed on an "AS IS" +%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%%% the License for the specific language governing rights and limitations +%%% under the License. +%%% +%%% The Original Code is xmerl-0.13 +%%% +%%% The Initial Developer of the Original Code is Ericsson Telecom +%%% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson +%%% Telecom AB. All Rights Reserved. +%%% +%%% Contributor(s): +%%% : suggested #xmlDocument{} +%%% +%%%---------------------------------------------------------------------- +%%% #0. BASIC INFORMATION +%%%---------------------------------------------------------------------- +%%% File: xmerl.hrl +%%% Author : Ulf Wiger +%%% Date : 00-09-22 +%%% Description : Record and macro definitions for xmerl +%%%---------------------------------------------------------------------- + + + +%% records generated by the scanner +%% -------------------------------- + +%% XML declaration +-record(xmlDecl, { + vsn, + encoding, + attributes + }). + +%% Attribute +-record(xmlAttribute, { + name, + parents = [], + pos, + language = [], % inherits the element's language + expanded_name = [], + nsinfo = [], % {Prefix, Local} | [] + namespace = [], % inherits the element's namespace + value + }). + +%% namespace record +-record(xmlNamespace, { + default = [], + nodes = [] + }). + +%% namespace node - i.e. a {Prefix, URI} pair +-record(xmlNsNode, { + prefix, + uri = [] + }). + +%% XML Element +-record(xmlElement, { + name, + parents = [], + pos, + attributes = [], + content = [], + language = [], + expanded_name = [], + nsinfo = [], % {Prefix, Local} | [] + namespace = #xmlNamespace{} + }). + +%% plain text +-record(xmlText, { + parents = [], + pos, + language = [], % inherits the element's language + value + }). + +%% plain text +-record(xmlComment, { + parents = [], + pos, + language = [], % inherits the element's language + value + }). + +%% processing instruction +-record(xmlPI, { + name, + pos, + value + }). + +-record(xmlDocument, { + content + }). + + +%% XPATH (xmerl_xpath, xmerl_pred_funcs) records + +-record(xmlContext, { + axis_type = forward, + context_node, + context_position = 1, + nodeset = [], + bindings = [], + functions = [], + namespace = [], + whole_document + }). + +-record(xmlNode, { + type = element, + node, + parents = [], + pos = 1 + }). + +-record(xmlObj, { + type, + value + }). + +-record(xmerl_fun_states, {event, + hook, + rules, + fetch, + cont}). + + +%% scanner state record +-record(xmerl_scanner, { + encoding = "ISO-8859-1", + declarations = [], % [{Name, Attrs}] + doctype_name, + doctype_DTD = internal, % internal | DTDId + rules, + keep_rules = false, % delete (ets) tab if false + namespace_conformant = false, % true | false + event_fun, + hook_fun, + acc_fun, + fetch_fun, + close_fun, + continuation_fun, + rules_read_fun, + rules_write_fun, + directory, + fetch_path = [], + user_state, + fun_states = #xmerl_fun_states{}, + col = 1, + line = 1 + }). + + + + +%% scanner events + +%% event : start | end +-record(xmerl_event, { + event, + line, + col, + pos, + data + }). + + + +%% useful scanner macros +%% --------------------- + +-ifdef(debug). +-define(dbg(Fmt, Args), ok=io:format("~p: " ++ Fmt, [?LINE|Args])). +-define(DBG, ok=io:format("<<~p:~p>>~n", [?MODULE, ?LINE])). +-else. +-define(dbg(Fmt, Args), no_debug). +-define(DBG, no_debug). +-endif. + +-define(space, 16#20). +-define(cr, 16#9). +-define(lf, 16#D). +-define(tab, 16#A). + +%% whitespace consists of 'space', 'carriage return', 'line feed' or 'tab' +-define(whitespace(H), H==?space ; H==?cr ; H==?lf ; H==?tab). + +-define(strip1, {_, T1, S1} = strip(T, S)). +-define(strip2, {_, T2, S2} = strip(T1, S1)). +-define(strip3, {_, T3, S3} = strip(T2, S2)). +-define(strip4, {_, T4, S4} = strip(T3, S3)). +-define(strip5, {_, T5, S5} = strip(T4, S4)). +-define(strip6, {_, T6, S6} = strip(T5, S5)). +-define(strip7, {_, T7, S7} = strip(T6, S6)). +-define(strip8, {_, T8, S8} = strip(T7, S7)). +-define(strip9, {_, T9, S9} = strip(T8, S8)). +-define(strip10, {_, T10, S10} = strip(T9, S9)). + +-define(bump_col(N), + ?dbg("bump_col(~p), US = ~p~n", [N, S0#xmerl_scanner.user_state]), + S = S0#xmerl_scanner{col = S0#xmerl_scanner.col + N}). diff --git a/inc/xmerl_xlink.hrl b/inc/xmerl_xlink.hrl new file mode 100644 index 0000000..375e244 --- /dev/null +++ b/inc/xmerl_xlink.hrl @@ -0,0 +1,26 @@ + + + +%% The following is a brief summary of the element types (columns) on +%% which the global attributes are allowed: +%% +%% simple extended locator arc resource title +%% type X X X X X X +%% href X X +%% role X X X X +%% title X X X X +%% show X X X +%% actuate X X X +%% from X +%% to X +%% +-record(xlink, { + type, % simple | extended | locator | arc | resource | title + href, + role + title, + show, + actuate, + from, + to + }). diff --git a/src/bloto.erl b/src/bloto.erl new file mode 100644 index 0000000..bff9963 --- /dev/null +++ b/src/bloto.erl @@ -0,0 +1,110 @@ +%%%---------------------------------------------------------------------- +%%% File : bloto.erl +%%% Author : Dimitri Fontaine +%%% Purpose : Count the buzzwords and give a winner +%%% Created : 6 Mar 2002 by Dimitri Fontaine +%%%---------------------------------------------------------------------- + +-module(bloto). +-author('tapoueh@free.fr'). + +%%-compile(export_all). +%%-export([Function/Arity, ...]). + +-behaviour(gen_server). + +%% External exports +-export([start_link/0, add/1]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-define(timeout, 5000). +-define(MAX, 4). %% put here MAX-1 + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +add(Nick) -> + gen_server:call(?MODULE, {add, Nick}, ?timeout). + +%%%---------------------------------------------------------------------- +%%% Callback functions from gen_server +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%%---------------------------------------------------------------------- +init([]) -> + {ok, []}. + +%%---------------------------------------------------------------------- +%% Func: handle_call/3 +%% Returns: {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | (terminate/2 is called) +%% {stop, Reason, State} (terminate/2 is called) +%%---------------------------------------------------------------------- +handle_call({add, Nick}, From, List) -> + case lists:keysearch(Nick, 1, List) of + {value, {Nick, ?MAX}} -> + {reply, {winner, Nick}, []}; + + {value, {Nick, N}} -> + NewList = lists:keyreplace(Nick, 1, List, {Nick, N+1}), + {reply, ok, NewList}; + + false -> + {reply, ok, [{Nick, 1}|List]} + end; + +handle_call(Request, From, State) -> + Reply = ok, + {reply, Reply, State}. + +%%---------------------------------------------------------------------- +%% Func: handle_cast/2 +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%---------------------------------------------------------------------- +handle_cast(Msg, State) -> + {noreply, State}. + +%%---------------------------------------------------------------------- +%% Func: handle_info/2 +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%---------------------------------------------------------------------- +handle_info(Info, State) -> + {noreply, State}. + +%%---------------------------------------------------------------------- +%% Func: terminate/2 +%% Purpose: Shutdown the server +%% Returns: any (ignored by gen_server) +%%---------------------------------------------------------------------- +terminate(Reason, State) -> + ok. + +%%---------------------------------------------------------------------- +%% Func: code_change/3 +%%---------------------------------------------------------------------- +code_change(OldVsn, State, Extra) -> + {ok, State}. + + +%%%---------------------------------------------------------------------- +%%% Internal functions +%%%---------------------------------------------------------------------- diff --git a/src/config.erl b/src/config.erl new file mode 100644 index 0000000..fc4a084 --- /dev/null +++ b/src/config.erl @@ -0,0 +1,133 @@ +%%%---------------------------------------------------------------------- +%%% File : config.erl +%%% Author : Dimitri Fontaine +%%% Purpose : Read the manderlbot XML xonfig file +%%% Created : 19 Feb 2002 by Dimitri Fontaine +%%%---------------------------------------------------------------------- + +-module(config). +-author('tapoueh@free.fr'). + +-include("config.hrl"). +-include("xmerl.hrl"). + +-export([read/1]). + +%%%---------------------------------------------------------------------- +%%% Function: read/1 +%%% Purpose: read the xml config file +%%%---------------------------------------------------------------------- +read(Filename) -> + case xmerl_scan:file(Filename) of + {ok, Root = #xmlElement{}} -> + %% io:format("root: ~p~n~p~n", [Root#xmlElement.name, + %% Root#xmlElement.content]), + {ok, parse(Root, #config{})}; + + _Error -> + {error, config} + end. + + +%%%---------------------------------------------------------------------- +%%% Function: parse/2 +%%% Purpose: parse the xmerl structure +%%%---------------------------------------------------------------------- +parse(Element = #xmlElement{parents = []}, #config{}) -> + Name = getAttr(Element#xmlElement.attributes, name), + Controler = getAttr(Element#xmlElement.attributes, controler), + + lists:foldl(fun parse/2, + #config{name = Name, controler = Controler}, + Element#xmlElement.content); + + +%% parsing the Server elements +parse(Element = #xmlElement{name=server}, Conf = #config{servers=SList}) -> + Server = getAttr(Element#xmlElement.attributes, host), + Port = getAttr(Element#xmlElement.attributes, port), + + {ok, [{integer,1,IPort}],1} = erl_scan:string(Port), + + lists:foldl(fun parse/2, + Conf#config{servers = [#server{host=Server, + port=IPort + }|SList]}, + Element#xmlElement.content); + + +%% Parsing the Channel element +parse(Element = #xmlElement{name=channel}, + Conf = #config{servers=[CurServ|SList]}) -> + + ChanList = CurServ#server.channels, + Chan = getAttr(Element#xmlElement.attributes, name), + Bot = getAttr(Element#xmlElement.attributes, botname), + B = string:tokens( + getAttr(Element#xmlElement.attributes, behaviours), ", "), + + lists:foldl(fun parse/2, + Conf#config{servers = [CurServ#server{channels = + [#channel{name=Chan, + botname=Bot, + behaviours=B} + |ChanList]} + |SList]}, + Element#xmlElement.content); + + +%% Parsing the behaviour element +parse(Element = #xmlElement{name=behaviour}, + Conf = #config{servers=[CurServ|SList], behaviours=BList}) -> + + [CurChan|ChanList] = CurServ#server.channels, + + Name = getAttr(Element#xmlElement.attributes, name), + Pattern = getAttr(Element#xmlElement.attributes, pattern), + Action = getAttr(Element#xmlElement.attributes, action), + Data = getText(Element#xmlElement.content), + + lists:foldl(fun parse/2, + Conf#config{behaviours = + [#cfg_behaviour{name=Name, pattern = Pattern, + action=Action, data=Data} + | BList]}, + Element#xmlElement.content); + + +%% Parsing other elements +parse(Element = #xmlElement{}, Conf = #config{}) -> + lists:foldl(fun parse/2, Conf, Element#xmlElement.content); + +%% Parsing non #xmlElement elements +parse(Element, Conf = #config{}) -> + Conf. + + +%%%---------------------------------------------------------------------- +%%% Function: getAttr/2 +%%% Purpose: search the attibute list for the given one +%%%---------------------------------------------------------------------- +getAttr([Attr = #xmlAttribute{name=Name}|Tail], Name) -> + Attr#xmlAttribute.value; + +getAttr([H|T], Name) -> + getAttr(T, Name); + +getAttr([], Name) -> + "". + + +%%%---------------------------------------------------------------------- +%%% Function: getText/1 +%%% Purpose: get the text of the XML node +%%%---------------------------------------------------------------------- +getText([Text = #xmlText{value=Value}|Tail]) -> build_list( + string:strip(Value, both)); +getText(_Other) -> "". + + + +build_list(String) -> + %% Separator is '%' + string:tokens(String, "%"). diff --git a/src/config_srv.erl b/src/config_srv.erl new file mode 100644 index 0000000..bbf4b78 --- /dev/null +++ b/src/config_srv.erl @@ -0,0 +1,214 @@ +%%%---------------------------------------------------------------------- +%%% File : mdb_behaviours_srv.erl +%%% Author : Dimitri Fontaine +%%% Purpose : Manage the manderlbot config +%%% Created : 2 Mar 2002 by Dimitri Fontaine +%%%---------------------------------------------------------------------- + +-module(config_srv). +-author('fontaine@whitestar'). + +%%-compile(export_all). +%%-export([Function/Arity, ...]). + +-behaviour(gen_server). + +%% External exports +-export([start_link/1, getConf/0, reconf/2, getBList/1, getBehaviours/1]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-include("config.hrl"). +-include("mdb.hrl"). + +-define(timeout, 5000). + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- +start_link(ConfigFile) -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [ConfigFile], []). + +getConf() -> + gen_server:call(?MODULE, {getConf}, ?timeout). + +reconf(Chan, ConfigFile) -> + gen_server:call(?MODULE, {reconf, Chan, ConfigFile}, ?timeout). + +getBList(Channel) -> + gen_server:call(?MODULE, {getlist, Channel}, ?timeout). + +getBehaviours(BNames) -> + gen_server:call(?MODULE, {getBehaviours, BNames}, ?timeout). + + +%%%---------------------------------------------------------------------- +%%% Callback functions from gen_server +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%%---------------------------------------------------------------------- +init([ConfigFile]) -> + case config:read(ConfigFile) of + {ok, Config} -> {ok, Config}; + {error, Reason} -> {stop, Reason} + end. + +%%---------------------------------------------------------------------- +%% Func: handle_call/3 +%% Returns: {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | (terminate/2 is called) +%% {stop, Reason, State} (terminate/2 is called) +%%---------------------------------------------------------------------- +handle_call({getConf}, From, Config) -> + {reply, {ok, Config}, Config}; + +handle_call({reconf, Channel, ConfigFile}, From, Config) -> + case config:read(ConfigFile) of + {ok, NewConfig = #config{}} -> + {reply, {ok, getBehaviours(NewConfig, Channel)}, NewConfig}; + + Error -> + {reply, Error, Config} + end; + +handle_call({getlist, Channel}, From, Conf) -> + {reply, {ok, getBehaviours(Conf, Channel)}, Conf}; + + +handle_call({getBehaviours, BNames}, From, Conf = #config{behaviours=BList}) -> + {reply, + {ok, lists:filter(fun(Behaviour=#behaviour{id=Id}) -> + lists:member(Id, BNames) + end, + build_behaviours_list(BList, []))}, + Conf}; + +handle_call(Request, From, State) -> + Reply = ok, + {reply, Reply, State}. + +%%---------------------------------------------------------------------- +%% Func: handle_cast/2 +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%---------------------------------------------------------------------- +handle_cast(Msg, State) -> + {noreply, State}. + +%%---------------------------------------------------------------------- +%% Func: handle_info/2 +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%---------------------------------------------------------------------- +handle_info(Info, State) -> + {noreply, State}. + +%%---------------------------------------------------------------------- +%% Func: terminate/2 +%% Purpose: Shutdown the server +%% Returns: any (ignored by gen_server) +%%---------------------------------------------------------------------- +terminate(Reason, State) -> + ok. + +%%---------------------------------------------------------------------- +%% Func: code_change/3 +%%---------------------------------------------------------------------- +code_change(OldVsn, State, Extra) -> + {ok, State}. + + +%%%---------------------------------------------------------------------- +%%% Internal functions +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% build_behaviours_list/2 +%% Build a behaviour list from the behaviours found in the +%% config file +%%---------------------------------------------------------------------- +build_behaviours_list([], Acc) -> + %% Auto add the rejoin-on-kick ability + Rejoin = #behaviour{id ="rejoin", + pattern = #data{header_op="KICK"}, + function = {mdb_behaviours, rejoin}, + data = [] + }, + + Reconf = #behaviour{id ="reconf", + pattern = #data{body={regexp, "^%BOTNAME.*reconf"}}, + function = {mdb_behaviours, reconf}, + data = "../config.xml" + }, + + [Rejoin, Reconf | Acc]; + +build_behaviours_list([BC=#cfg_behaviour{name=Id, + data=Data, + action="login"}|BClist], Acc) -> + Login = #behaviour{id = Id, + pattern = #data{header_op = "JOIN", + header_from = "%BOTNAME.*"}, + function = {mdb_behaviours, say}, + data = Data + }, + build_behaviours_list(BClist, [Login|Acc]); + +build_behaviours_list([BC=#cfg_behaviour{}|BClist], Acc) -> + %% Here we map the actions defined in the config file + %% With the code to use in order to make the action + Fun = case BC#cfg_behaviour.action of + "say" -> {mdb_behaviours, say}; + "answer" -> {mdb_behaviours, answer}; + "random" -> {mdb_behaviours, random}; + "timer" -> {mdb_behaviours, timer}; + "think" -> {mdb_behaviours, action}; + "bloto" -> {mdb_behaviours, bloto}; + "google" -> {mdb_behaviours, google}; + Other -> {mdb_behaviours, say} + end, + + Behaviour = #behaviour{id = BC#cfg_behaviour.name, + pattern = #data{body={regexp, + BC#cfg_behaviour.pattern}}, + function = Fun, + data = BC#cfg_behaviour.data}, + + build_behaviours_list(BClist, [Behaviour|Acc]). + + + +%%---------------------------------------------------------------------- +%% getBehaviours/2 +%% Read the config and find on it our behaviours +%%---------------------------------------------------------------------- +getBehaviours(#config{servers=SList}, Chan) -> + getBehaviours(SList, Chan); + +getBehaviours([#server{channels=CList}|STail], Chan) -> + case getBehaviours(CList, Chan) of + notfound -> getBehaviours(STail, Chan); + BList -> BList + end; + +getBehaviours([#channel{name=Chan, behaviours=BList}|CTail], Chan) -> + ["reconf", "rejoin" | BList]; + +getBehaviours([#channel{name=Name}|CTail], Chan) -> + getBehaviours(CTail, Chan); + +getBehaviours([], Chan) -> + notfound. diff --git a/src/google.erl b/src/google.erl new file mode 100644 index 0000000..a5b60bd --- /dev/null +++ b/src/google.erl @@ -0,0 +1,48 @@ +%%% File : google.erl +%%% Author : Nicolas Niclausse +%%% Purpose : ask google for the first match of a given keyword +%%% Created : 16 Jul 2002 by Nicolas Niclausse + +-module(google). +-author('nniclausse@idealx.com'). + +-export([search/1]). + +search(Keyword) -> + init({"www.google.com", 80, Keyword}). + +do_recv(Socket) -> + do_recv(Socket, [], []). + +do_recv(Socket, Bs, URL) -> + case gen_tcp:recv(Socket, 0) of + {ok, B} -> + %% search for redirected url + case regexp:first_match(B, "here") of + {match,Start,Length} -> % ok, found + do_recv(Socket, [Bs, B], string:substr(B, 10, length(B)-22)); + _ -> + do_recv(Socket, [Bs, B], URL) + end; + {error, einval} -> % non fatal error, continue + do_recv(Socket, Bs, URL); + {error, closed} -> + {ok, URL} + end. + +init({Server, Port, Keyword}) -> + case gen_tcp:connect(Server, Port, + [list, + {packet, line}, + {active, false}]) of + {ok, Socket} -> + Request = google_request(Keyword), + gen_tcp:send(Socket, Request), + {ok, URL} = do_recv(Socket), + URL; + {error, Reason} -> + {stop, connfailed} + end. + +google_request(Keyword) -> + "GET /search?q=" ++ Keyword ++"&hl=fr&btnI=J%27ai+de+la+chance HTTP/1.0" ++ io_lib:nl() ++ io_lib:nl(). diff --git a/src/irc_lib.erl b/src/irc_lib.erl new file mode 100644 index 0000000..0cbb27d --- /dev/null +++ b/src/irc_lib.erl @@ -0,0 +1,123 @@ +%%%---------------------------------------------------------------------- +%%% File : irc_lib.erl +%%% Author : Mickaël Rémond +%%% Purpose : This library gathers all functions to format and send +%%% IRC commands. +%%% It manage IRC server connexion, automatically answer to +%%% server pings (necessary to stay online) and behaviour +%%% management. +%%% Created : 11 Sep 2001, Mickaël Rémond +%%%---------------------------------------------------------------------- +%%% +%%% 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. +%%% +%%%---------------------------------------------------------------------- +%%% +%%% See COPYING for detailled license +%%% +%%%---------------------------------------------------------------------- + +-module(irc_lib). + +-author('mickael.remond@erlang-fr.org'). +-created('Date: 20010911'). +-revision(' $Id$ '). +-vsn(' $Revision$ '). + +%% IRC operations +-export([pong/2, join/2, say/3, action/3]). + +%% IRC helper functions +-export([is_chanop/1, + nickname/1, + split_nick_user/1]). + +%%---------------------------------------------------------------------- +%% Function: pong/2 +%% Purpose: Send a pong answer with the right id +%% Args: Sock = socket +%% Id = ping id to send back to the server +%% Returns: ok +%% or {error, Reason} (if the process is dead) +%%---------------------------------------------------------------------- +pong(Sock, Id)-> + Pong = lists:append("PONG ", Id), + gen_tcp:send(Sock, Pong). + +%%---------------------------------------------------------------------- +%% join/2 +%% Format an IRC join command: Used to join a discussion channel +%%---------------------------------------------------------------------- +join(Sock, Channel) -> + Command = lists:append("JOIN ", Channel), + command(Sock, Command). + +%%---------------------------------------------------------------------- +%% say/3 +%% Say something in the given channel +%%---------------------------------------------------------------------- +say(Sock, Channel, Message) -> + Command = lists:concat(["PRIVMSG ", Channel, " :", Message]), + command(Sock, Command). + +%%---------------------------------------------------------------------- +%% say/3 +%% Say something in the given channel +%%---------------------------------------------------------------------- +action(Sock, Channel, Message) -> + Command = "PRIVMSG " ++ Channel ++ " :" + ++ [1] ++ "ACTION " ++ Message ++ [1], + command(Sock, Command). + +%%---------------------------------------------------------------------- +%% command/2 +%% Send a command to the IRC server +%%---------------------------------------------------------------------- +command(Sock, Command) -> + CompleteCmd = [Command, "\r\n"], + gen_tcp:send(Sock, "\r\n"), % FIXME: Workaround: The first message of a + % sequence does not seem to be received by the + % server ... + % Sending a blank line to awake the line... + gen_tcp:send(Sock, CompleteCmd). + +%%---------------------------------------------------------------------- +%% is_chanop/1 +%% Returns true if the given nick is a chanop or false otherwise +%% A chanop as an '@' before its nickname. +%%---------------------------------------------------------------------- +is_chanop([$@ | Nickname]) -> + true; +is_chanop(Nickname) -> + false. + +%%---------------------------------------------------------------------- +%% nickname/1 +%% Return the nickname (removing '@' to chanop) +%% A chanop as an '@' before its nickname. +%%---------------------------------------------------------------------- +nickname([$@ | Nickname]) -> + Nickname; +nickname(Nickname) -> + Nickname. + +%%---------------------------------------------------------------------- +%% split_nick_user/1 +%% Return the nickname in lower case and +%% the user +%%---------------------------------------------------------------------- +split_nick_user(HeaderFrom) -> + %% Split the string between Nick and User (separated by !) + [Nick, User] = string:tokens(HeaderFrom, "!"), + + %% Remove chanop indicator from nick + Nick2 = nickname(Nick), + + %% convert Nickname to lowercase + Nick3 = misc_tools:lower_string(Nick2), + + %% Return a list: Nick, User + [Nick3, User]. diff --git a/src/manderlbot.erl b/src/manderlbot.erl new file mode 100644 index 0000000..e8a4220 --- /dev/null +++ b/src/manderlbot.erl @@ -0,0 +1,71 @@ +%%%---------------------------------------------------------------------- +%%% File : manderlbot.erl +%%% Author : Dimitri Fontaine +%%% Purpose : This app is an IRC bot +%%% Created : 19 Feb 2002 by Dimitri Fontaine +%%%---------------------------------------------------------------------- + +-module(manderlbot). +-author('tapoueh@free.fr'). + +-include("config.hrl"). +-define(CONFIG_FILE, "../config.xml"). + +-behaviour(application). + +%% application callbacks +-export([start/2, stop/1]). + +%%%---------------------------------------------------------------------- +%%% Callback functions from application +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Func: start/2 +%% Returns: {ok, Pid} | +%% {ok, Pid, State} | +%% {error, Reason} +%%---------------------------------------------------------------------- +start(Type, StartArgs) -> + %%case mdb_sup:start_link(StartArgs) of + case manderlbot_sup:start_link() of + {ok, Pid} -> + %% Do our stuff + init(), + {ok, Pid}; + Error -> + Error + end. + +%%---------------------------------------------------------------------- +%% Func: stop/1 +%% Returns: any +%%---------------------------------------------------------------------- +stop(State) -> + ok. + +%%%---------------------------------------------------------------------- +%%% Internal functions +%%%---------------------------------------------------------------------- +init() -> + %% Start a bot per chan + {ok, Conf} = config_srv:getConf(), + + Name = Conf#config.name, + Controler = Conf#config.controler, + + lists:foreach(fun(Server = #server{host = Host, + port = Port, + channels = ChanList}) -> + + StartBot = fun(Chan = #channel{}) -> + mdb_bot_sup:start_child(Name, + Controler, + Host, + Port, + Chan) + end, + lists:foreach(StartBot, ChanList) + end, + Conf#config.servers), + ok. diff --git a/src/manderlbot_sup.erl b/src/manderlbot_sup.erl new file mode 100644 index 0000000..53ab32b --- /dev/null +++ b/src/manderlbot_sup.erl @@ -0,0 +1,52 @@ +%%%---------------------------------------------------------------------- +%%% File : mdb_sup.erl +%%% Author : Dimitri Fontaine +%%% Purpose : Supervise all the bot instances (dynamic) +%%% Created : 19 Feb 2002 by Dimitri Fontaine +%%%---------------------------------------------------------------------- + +-module(manderlbot_sup). +-author('tapoueh@free.fr'). + +-include("mdb.hrl"). +-include("config.hrl"). + +-behaviour(supervisor). + +%% External exports +-export([start_link/0]). + +%% supervisor callbacks +-export([init/1]). + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%%%---------------------------------------------------------------------- +%%% Callback functions from supervisor +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, {SupFlags, [ChildSpec]}} | +%% ignore | +%% {error, Reason} +%%---------------------------------------------------------------------- +init([]) -> + BotSup = {mdb_bot_sup, {mdb_bot_sup, start_link, []}, + permanent, 2000, supervisor, [mdb_bot_sup]}, + + BServ = {config_srv, {config_srv, start_link, ["../config.xml"]}, + permanent, 2000, worker, [config_srv]}, + + BLoto = {bloto, {bloto, start_link, []}, + permanent, 2000, worker, [bloto]}, + + {ok, {{one_for_all, 3, 60}, [BotSup, BServ, BLoto]}}. + +%%%---------------------------------------------------------------------- +%%% Internal functions +%%%---------------------------------------------------------------------- diff --git a/src/mdb_behaviours.erl b/src/mdb_behaviours.erl new file mode 100644 index 0000000..2ca094d --- /dev/null +++ b/src/mdb_behaviours.erl @@ -0,0 +1,203 @@ +%%%---------------------------------------------------------------------- +%%% File : mdb_behaviours_new.erl +%%% Author : Dimitri Fontaine +%%% Purpose : +%%% Created : 27 Feb 2002 by Dimitri Fontaine +%%%---------------------------------------------------------------------- + +-module(mdb_behaviours). +-author('tapoueh@free.fr'). + +%% Exported behaviours +-export([say/4, action/4, answer/4, random/4, timer/4, bloto/4, + google/4, rejoin/4, reconf/4]). + +-include("mdb.hrl"). +-define(TIME, 2000). +-define(RNDTIME, 3000). + +%% A plugin function for behaviour is of the form: +%% Fun(Input, BotName, Data, BotPid) + + + +%%%---------------------------------------------------------------------- +%%% Function: say/4 +%%% Purpose: Say the data in the channel or to the speaker +%%%---------------------------------------------------------------------- +say(Input = #data{header_to=BotName}, BotName, Data, BotPid) -> + [NickFrom|IpFrom] = string:tokens(Input#data.header_from, "!"), + + lists:map(fun(String) -> + mdb_bot:say(NickFrom, BotPid, String) + end, + Data); + +say(Input, BotName, Data, BotPid) -> + lists:map(fun(String) -> + mdb_bot:say(BotPid, String) + end, + Data). + + +%%%---------------------------------------------------------------------- +%%% Function: action/4 +%%% Purpose: Answer with Action IRC usage - /me +%%%---------------------------------------------------------------------- +action(Input = #data{header_to=BotName}, BotName, Data, BotPid) -> + [NickFrom|IpFrom] = string:tokens(Input#data.header_from, "!"), + + lists:map(fun(String) -> + mdb_bot:action(NickFrom, BotPid, String) + end, + Data); + +action(Input, BotName, Data, BotPid) -> + lists:map(fun(String) -> + mdb_bot:action(BotPid, String) + end, + Data). + + +%%%---------------------------------------------------------------------- +%%% Function: answer/4 +%%% Purpose: Answer the given (config) data to the one who talk +%%%---------------------------------------------------------------------- +answer(Input = #data{header_to=BotName}, BotName, Data, BotPid) -> + [NickFrom|IpFrom] = string:tokens(Input#data.header_from, "!"), + + lists:map(fun(String) -> + mdb_bot:say(NickFrom, BotPid, String) + end, + Data); + +answer(Input, BotName, Data, BotPid) -> + [NickFrom|IpFrom] = string:tokens(Input#data.header_from, "!"), + lists:map(fun(String) -> + mdb_bot:say(BotPid, NickFrom ++ ": " ++ String) + end, + Data). + + +%%%---------------------------------------------------------------------- +%%% Function: random/4 +%%% Purpose: Choose at random a line in Data and answer it. +%%%---------------------------------------------------------------------- +random(Input = #data{header_to=BotName}, BotName, Data, BotPid) -> + [NickFrom|IpFrom] = string:tokens(Input#data.header_from, "!"), + + {A, B, C} = now(), + random:seed(A, B, C), + mdb_bot:say(NickFrom, BotPid, + lists:nth(random:uniform(length(Data)), Data)); + +random(Input, BotName, Data, BotPid) -> + {A, B, C} = now(), + random:seed(A, B, C), + mdb_bot:say(BotPid, lists:nth(random:uniform(length(Data)), Data)). + + +%%%---------------------------------------------------------------------- +%%% Function: timer/4 +%%% Purpose: Answer the given data, but waiting for random time +%%% between the 2 lines to say. +%%%---------------------------------------------------------------------- +timer(Input = #data{header_to=BotName}, BotName, Data, BotPid) -> + [NickFrom|IpFrom] = string:tokens(Input#data.header_from, "!"), + case length(Data) of + 1 -> + [String] = Data, + mdb_bot:say(NickFrom, BotPid, String); + + 2 -> + [H|T] = Data, + mdb_bot:say(NickFrom, BotPid, H), + %% we sleep for a random time + {A, B, C} = now(), + random:seed(A, B, C), + timer:sleep(random:uniform(?RNDTIME) + ?TIME), + mdb_bot:say(NickFrom, BotPid, T); + + More -> + say(Input, BotName, Data, BotPid) + end; + +timer(Input, BotName, Data, BotPid) -> + case length(Data) of + 1 -> + [String] = Data, + mdb_bot:say(BotPid, String); + + 2 -> + [H|T] = Data, + mdb_bot:say(BotPid, H), + %% we sleep for a random time + {A, B, C} = now(), + random:seed(A, B, C), + timer:sleep(random:uniform(?RNDTIME) + ?TIME), + mdb_bot:say(BotPid, T); + + More -> + say(Input, BotName, Data, BotPid) + end. + + +%%%---------------------------------------------------------------------- +%%% Function: bloto/4 +%%% Purpose: Play to business loto... +%%%---------------------------------------------------------------------- +bloto(Input, BotName, Data, BotPid) -> + [NickFrom|IpFrom] = string:tokens(Input#data.header_from, "!"), + + case bloto:add(NickFrom) of + {winner, Nick} -> + mdb_bot:say(BotPid, Nick ++ " " ++ Data); + + Other -> + ok + end. + + +%%%---------------------------------------------------------------------- +%%% Function: rejoin/4 +%%% Purpose: When kicked, rejoin the chan +%%%---------------------------------------------------------------------- +rejoin(Input, BotName, Data, BotPid) -> + mdb_bot:rejoin(BotPid). + + +%%%---------------------------------------------------------------------- +%%% Function: reconf/4 +%%% Purpose: Re-read now the config file +%%%---------------------------------------------------------------------- +reconf(Input, BotName, ConfigFile, BotPid) -> + [NickFrom|IpFrom] = string:tokens(Input#data.header_from, "!"), + mdb_bot:reconf(BotPid, NickFrom, ConfigFile). + + +%%%---------------------------------------------------------------------- +%%% Function: google/4 +%%% Purpose: ask google and give the first response +%%%---------------------------------------------------------------------- +google(Input, BotName, ConfigFile, BotPid) -> + io:format("GOOGLE input: ~p~n", [Input#data.body]), + + Criteria = lists:foldl( + fun(Str, "") -> + Str; + (Str, Acc) -> + Acc ++ "+" ++ Str + end, + "", + string:tokens( + string:strip(string:substr(Input#data.body, + string:len("google") + 1)), + " ")), + + io:format("GOOGLE criteria: ~p~n", [Criteria]), + + Result = google:search(Criteria), + + io:format("GOOGLE result: ~p~n", [Result]), + + mdb_bot:say(BotPid, Result). diff --git a/src/mdb_bot.erl b/src/mdb_bot.erl new file mode 100644 index 0000000..f4b4445 --- /dev/null +++ b/src/mdb_bot.erl @@ -0,0 +1,267 @@ +%%%---------------------------------------------------------------------- +%%% File : mdb_bot.erl +%%% Author : Mickaël Rémond +%%% Purpose : This is the behaviour for a generic bot. +%%% It manage IRC server connexion, automatically answer to +%%% server pings (necessary to stay online) and behaviour +%%% management. +%%% Created : 8 Sep 2001 by Mickaël Rémond +%%%---------------------------------------------------------------------- +%%% +%%% 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. +%%% +%%%---------------------------------------------------------------------- +%%% +%%% See COPYING for detailled license +%%% +%%%---------------------------------------------------------------------- + +-module(mdb_bot). + +-author('mickael.remond@erlang-fr.org'). +-created('Date: 20010908'). +-revision(' $Id$ '). +-vsn(' $Revision$ '). + +%% External exports (API) +-export([start/1, start_link/1, say/2, say/3, action/2, rejoin/1, reconf/3]). + +%% special process callbacks +-export([init/3, + system_code_change/4, + system_continue/3, + system_terminate/4]). + +%% Internal exports +-export([loop/3]). + +%% Configure debugging mode: +-include("mdb_macros.hrl"). + +%% Include record description +-include("mdb.hrl"). +-include("config.hrl"). + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- + +start(Params) -> + start([], Params). + +start_link(Params) -> + start_link([],Params). + +%% Controling the bot environment +%% Send a message a the channel the bot is connected to +say(BotPid, Message) -> + BotPid ! {self(), {say, Message}}, + get_answer(self(), 10000). + +say(To, BotPid, Message) -> + BotPid ! {self(), {say, Message, To}}, + get_answer(self(), 10000). + +action(BotPid, Message) -> + BotPid ! {self(), {action, Message}}, + get_answer(self(), 10000). + +%% Rejoin the channel (Use it when you have been kicked) +rejoin(BotPid) -> + BotPid ! {self(), {rejoin}}, + get_answer(self(), 10000). + +reconf(BotPid, NickName, ConfigFile) -> + BotPid ! {self(), {reconf, NickName, ConfigFile}}, + get_answer(self(), 10000). + + +%%%---------------------------------------------------------------------- +%%% Specific process callbacks +%%%---------------------------------------------------------------------- + +start(Options, Params) -> + proc_lib:start(?MODULE, init, [self(), Options, Params]). + +start_link(Options, Params) -> + proc_lib:start_link(?MODULE, init, [self(), Options, Params]). + +init(Parent, Options, [RealName, Controler, Host, Port, Channel]) -> + process_flag(trap_exit, true), + Deb = sys:debug_options(Options), + proc_lib:init_ack(Parent, {ok, self()}), + + {ok, Sock} = mdb_connection:connect(Host, Port), + + ok = mdb_connection:log(Sock, Channel, RealName), + io:format("~p joined ~p (~p)~n", [Channel#channel.botname, + Channel#channel.name, Sock]), + + {ok, BList} = config_srv:getBList(Channel#channel.name), + + State = #state{bot_pid=self(), + channel = Channel#channel.name, + controler = Controler, + socket = Sock, + nickname = Channel#channel.botname, + date = calendar:local_time(), + behaviours = BList + }, + + loop(State, Parent, Deb). + +%% The loop is too big: +%% I should strip it. +loop(State, Parent, Deb) -> + receive + {system, From, Request} -> + sys:handle_system_msg(Request, From, Parent, ?MODULE, Deb, State); + + {'EXIT', Parent, Reason} -> + cleanup(State), + exit(Reason); + + %% Socket management + %% Receiving data... + {tcp, Socket, Data} -> + Buffer = State#state.buffer, + List = lists:append(binary_to_list(Buffer), binary_to_list(Data)), + + Rest = mdb_dispatch:process_data(Socket, List, State), + + NewState = State#state{buffer=list_to_binary(Rest)}, + ?MODULE:loop(NewState, Parent, Deb); + + %% Close/error => reconnect + {tcp_closed, Socket} -> + ?dbg("Socket closed", []), + mdb_connection:manage_reconnect(State), + loop(State, Parent, Deb); + + {tcp_error, Socket, Reason} -> + ?dbg("Socket error: ~p", [Reason]), + mdb_connection:manage_reconnect(State), + loop(State, Parent, Deb); + + %% Callback management -> handle_call + {From, OurMsgs} -> + %% io:format("API: ~p - ~p~n", [From, OurMsgs]), + NewDeb = sys:handle_debug(Deb, {?MODULE, write_debug}, + ?MODULE, {in, OurMsgs, From}), + + {Answer, NewState} = handle_msg(OurMsgs, State), + From ! {From, Answer}, + + NewerDeb = sys:handle_debug(NewDeb, + {?MODULE, write_debug}, ?MODULE, + {out, {self(), Answer}, From}), + ?MODULE:loop(NewState, Parent, NewerDeb); + + %% Unknown message + %% In this case we also need to launch a reconnect + %% "ERROR :Closing Link: Manderlbot[bas1-71.idf2-1.club-internet.fr] by forward.openprojects.net (Ping timeout for Manderlbot[bas1-71.idf2-1.club-internet.fr]" = When the irc client did not answer to a ping + What -> + io:format("~p~n", [What]), + NewDeb = sys:handle_debug(Deb, {?MODULE, write_debug}, + ?MODULE, {in, What}), + %% mdb_connection:manage_reconnect(State), + ?MODULE:loop(State, Parent, NewDeb) + + %% Message timeout: give the opportunity to handle system messages + after 100 -> + ?MODULE:loop(State, Parent, Deb) + end. + +cleanup(State) -> + ok. + +%%---------------------------------------------------------------------- +%% Function: handle_msg/2 +%% Purpose: Internal callback funtion +%%---------------------------------------------------------------------- +handle_msg({say, Message}, State) -> + Channel = State#state.channel, + Sock = State#state.socket, + + irc_lib:say(Sock, Channel, Message), + {ok, State}; + +handle_msg({say, Message, To}, State) -> + Channel = State#state.channel, + Sock = State#state.socket, + + io:format("DANS TON CUL: ~p~n", [To]), + + irc_lib:say(Sock, To, Message), + {ok, State}; + +handle_msg({action, Message}, State) -> + Channel = State#state.channel, + Sock = State#state.socket, + + irc_lib:action(Sock, Channel, Message), + + {ok, State}; + +handle_msg({rejoin}, State) -> + Channel = State#state.channel, + Sock = State#state.socket, + + irc_lib:join(Sock, Channel), + + {ok, State}; + +handle_msg({reconf, NickName, ConfigFile}, State) -> + %% First read the conf file given + %% Then get our behaviours list, and replace it in the State + case State#state.controler of + NickName -> + case config_srv:reconf(State#state.channel, ConfigFile) of + {ok, BList} -> {ok, State#state{behaviours=BList}}; + _Error -> {ok, State} + end; + + Other -> + irc_lib:say(State#state.socket, State#state.channel, + NickName ++ ": " ++ + "Who do you think you are to 'reconf' me ?"), + {ok, State} + end; + +handle_msg(Other, State) -> + {ok, State}. + + +%% Here are the sys call back functions +system_continue(Parent, Deb, State) -> + loop(State, Parent, Deb). + +system_terminate(Reason, Parent, Deb, State) -> + cleanup(State), + exit(Reason). + +system_code_change(Data, OldVsn, Module, Extra) -> {ok, Data}. + + + +%%%---------------------------------------------------------------------- +%%% Internal functions +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% get_answer/2 +%% Used in synchronous calls +%% Get back the result of a command sent to the server and returns +%% the result +%%---------------------------------------------------------------------- +get_answer(Pid, Timeout) -> + receive + {Pid, Result} -> + Result + after + Timeout -> + timeout + end. diff --git a/src/mdb_bot_sup.erl b/src/mdb_bot_sup.erl new file mode 100644 index 0000000..e7d559d --- /dev/null +++ b/src/mdb_bot_sup.erl @@ -0,0 +1,49 @@ +%%%---------------------------------------------------------------------- +%%% File : mdb_sup.erl +%%% Author : Dimitri Fontaine +%%% Purpose : Supervise all the bot instances (dynamic) +%%% Created : 19 Feb 2002 by Dimitri Fontaine +%%%---------------------------------------------------------------------- + +-module(mdb_bot_sup). +-author('tapoueh@free.fr'). + +-include("mdb.hrl"). +-include("config.hrl"). + +-behaviour(supervisor). + +%% External exports +-export([start_link/0, start_child/5]). + +%% supervisor callbacks +-export([init/1]). + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +start_child(Name, Controler, Host, Port, Chan) -> + supervisor:start_child(?MODULE, [[Name, Controler, Host, Port, Chan]]). + +%%%---------------------------------------------------------------------- +%%% Callback functions from supervisor +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, {SupFlags, [ChildSpec]}} | +%% ignore | +%% {error, Reason} +%%---------------------------------------------------------------------- +init([]) -> + Bot = {manderlbot, {mdb_bot, start_link, []}, + transient, 2000, worker, [mdb_bot]}, + + {ok, {{simple_one_for_one, 3, 60}, [Bot]}}. + +%%%---------------------------------------------------------------------- +%%% Internal functions +%%%---------------------------------------------------------------------- diff --git a/src/mdb_connection.erl b/src/mdb_connection.erl new file mode 100644 index 0000000..aad6257 --- /dev/null +++ b/src/mdb_connection.erl @@ -0,0 +1,169 @@ +%%%---------------------------------------------------------------------- +%%% File : mdb_connection.erl +%%% Author : Mickaël Rémond +%%% Purpose : Connection management library. +%%% Used by mdb_bot.erl +%%% Created : 16 Sep 2001, Mickaël Rémond +%%%---------------------------------------------------------------------- +%%% +%%% 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. +%%% +%%%---------------------------------------------------------------------- +%%% +%%% See COPYING for detailled license +%%% +%%%---------------------------------------------------------------------- + +-module(mdb_connection). + +-author('mickael.remond@erlang-fr.org'). +-created('Date: 20010916'). +-revision(' $Id$ '). +-vsn(' $Revision$ '). + +%% External exports (API) +-export([connect/2, log/3, manage_reconnect/1]). + +%% Configure debugging mode: +-include("mdb_macros.hrl"). +-include("config.hrl"). + +%%---------------------------------------------------------------------- +%% log/2 +%% Start an IRC bot and connect it to a given channel +%%---------------------------------------------------------------------- +log(Sock, Channel = #channel{}, RealName) -> + %% Logging in + _Motd = log_in(Sock, Channel#channel.botname, RealName), + + %% Join the given channel + irc_lib:join(Sock, Channel#channel.name), + ok. + +%%---------------------------------------------------------------------- +%% connect/2 +%% Physically connects to the IRC server +%%---------------------------------------------------------------------- +connect(Server, Ip_port) -> + %% TCP connection to the IRC server + Connect = fun() -> gen_tcp:connect(Server, Ip_port, + [binary, + {packet, 0}, + {nodelay, true}, + {keepalive, true}, + {active, true}, + {reuseaddr, true}]) + end, + + case Connect() of + %% If everything went ok + {ok, Sock} -> {ok, Sock}; + %% If there is an error, wait 30 secondes and try to reconnect + {error, Reason} -> + ?dbg("Server connection error: ~p", [Reason]), + timer:sleep(30000), + connect(Server, Ip_port) + end. + +%%---------------------------------------------------------------------- +%% log_in/3 +%% Logging in: Give nick and realname to the server +%%---------------------------------------------------------------------- +%%log_in(Sock, Nickname, RealName, Password) -> +log_in(Sock, Nickname, RealName) -> + log_in_nick(Sock, Nickname), + log_in_pong(Sock), + log_in_pass(Sock, "Password"), + log_in_user(Sock, Nickname, RealName), + Motd = wait_for_motd(5000). + + %% TODO: Add an event notification: logged in as Realname aka Nick + +%%---------------------------------------------------------------------- +%% log_in_nick/2 +%% Send nickname during logging +%%---------------------------------------------------------------------- +log_in_nick(Sock, Nickname) -> + NickCommand = ["NICK ", Nickname, "\r\n"], + gen_tcp:send(Sock, NickCommand). + +%%---------------------------------------------------------------------- +%% log_in_pong/1 +%% Some server send an initial ping after the nickname to check the +%% connection +%% Handle this ping/pong session correctly +%%---------------------------------------------------------------------- +log_in_pong(Sock) -> + Result = receive + {tcp, Sock, Data} -> + binary_to_list(Data) + after 120000 -> + binary_to_list(<<>>) + end, + %% Test is the first part is a connection string + TokenizedResult= string:tokens(Result, "\r\n"), + IsPingString = misc_tools:nth(1, TokenizedResult), + testPingPong(Sock, IsPingString). + +%%---------------------------------------------------------------------- +%% log_in_pass/2 +%% If the IRC server is password protected, this function is supposed +%% send the needed password +%%---------------------------------------------------------------------- +log_in_pass(Sock, Password) -> + PassCommand = ["PASS ", Password, "\r\n"], + gen_tcp:send(Sock, PassCommand). + +%%---------------------------------------------------------------------- +%% log_in_nick/3 +%% Send the user information to terminate the log in phase +%%---------------------------------------------------------------------- +log_in_user(Sock, Nickname, Realname) -> + UserCommand = lists:concat(["USER ", Nickname, + " dummy dummy :", Realname, "\r\n"]), + gen_tcp:send(Sock, UserCommand). + +%%---------------------------------------------------------------------- +%% wait_for_motd/1 +%% This function gets the Message Of The Day that IRC servers usually +%% send after the connection (usage rules of the server) +%%---------------------------------------------------------------------- +wait_for_motd(Timeout) -> + wait_for_motd(Timeout, []). +wait_for_motd(Timeout, Acc) -> + receive + {tcp, _Sock, Data} -> + wait_for_motd(Timeout, Acc ++ binary_to_list(Data)) + after Timeout -> + Acc + end. + + +%%---------------------------------------------------------------------- +%% testPingPong/2 +%% Check if the incoming data is a server ping +%% If so, answer it and thus maintains the connection +%%---------------------------------------------------------------------- +testPingPong(Sock, Data) -> + case string:substr(Data, 1, 4) of + "PING" -> + Id = string:substr(Data, 6), + irc_lib:pong(Sock, Id); + Other -> + ok + end. + +%%---------------------------------------------------------------------- +%% manage_reconnect/1 +%% When something fails, automatically reconnects the bot +%%---------------------------------------------------------------------- +manage_reconnect(State) -> + %% TODO: Implement this function. + %% When I am not connected, the connection/reconnection process is + %% already handled by the irc server (irc_srv) + ok. + + diff --git a/src/mdb_dispatch.erl b/src/mdb_dispatch.erl new file mode 100644 index 0000000..6d9e3fd --- /dev/null +++ b/src/mdb_dispatch.erl @@ -0,0 +1,279 @@ +%%%---------------------------------------------------------------------- +%%% File : mdb_dispatch.erl +%%% Author : Mickaël Rémond +%%% Purpose : Library gather the process of IRC event and the execution +%%% of "behaviours" (event handling code). +%%% Created : 16 Sep 2001, Mickaël Rémond +%%%---------------------------------------------------------------------- +%%% +%%% 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. +%%% +%%%---------------------------------------------------------------------- +%%% +%%% See COPYING for detailled license +%%% +%%%---------------------------------------------------------------------- + +-module(mdb_dispatch). + +-author('mickael.remond@erlang-fr.org'). +-created('Date: 20010916'). +-revision(' $Id$ '). +-vsn(' $Revision$ '). + +%% External exports (API) +-export([process_data/3, treat_recv/3]). + +%% -- Includes -- +%% Configure debugging mode: +-include("mdb_macros.hrl"). + +%% Include record description +-include("mdb.hrl"). + +-define(BOTNAME, "%BOTNAME"). + +%%---------------------------------------------------------------------- +%% process_data/3 +%% Parse the incoming data into lines, +%% and spawn a treat_recv process on each one +%%---------------------------------------------------------------------- +process_data(Sock, Data, State=#state{}) -> + Pos = string:str(Data, "\r\n"), + case Pos of + 0 -> Data ; + _ -> + Line = string:substr( Data, 1, Pos-1 ), + Rest = string:substr( Data, Pos+2, string:len(Data) - (Pos-1) ), + + %% treat_recv(Sock, list_to_binary(Line), State), + proc_lib:spawn(?MODULE, treat_recv, + [Sock, list_to_binary(Line), State]), + process_data(Sock, Rest, State) + end; + +process_data(Sock, Data, State) -> + io:format("process_data: ~p ~p ~p ~n", [Sock, Data, State]). + +%%---------------------------------------------------------------------- +%% treat_recv/5 +%% If this is a PING from the server: +%%---------------------------------------------------------------------- +treat_recv(Sock, <<$P, $I, $N, $G, $ , Rest/binary>>, State) -> + irc_lib:pong(Sock, binary_to_list(Rest)); + +%%---------------------------------------------------------------------- +%% treat_recv/5 +%% Otherwise: +%%---------------------------------------------------------------------- +treat_recv(Sock, Data, State=#state{}) -> + Result = binary_to_list(Data), + + %% The Parsed_Result is a list of data records. + Parsed_result = input_to_record(Result), + + %% Get the list of behaviours that match to the current IRC line + %% And for which the corresponding fun will be executed + lists:map(fun(X) -> + {ok, BList} = + config_srv:getBehaviours(State#state.behaviours), + MatchingList = match_event(X, BList, + State#state.nickname), + dispatch_message(MatchingList, X, State) + end, + Parsed_result), + + %%Print what is received + lists:map(fun(Res) -> ?dbg("HEADER: [~s/~s/~s/~s] - BODY: [~s]", + [Res#data.header_from, + Res#data.header_op, + Res#data.header_to, + Res#data.header_options, + Res#data.body]) end, + Parsed_result). + +%%---------------------------------------------------------------------- +%% dispatch_message/3 +%% We are executing the behaviour whose pattern is matching with +%% the input from the IRC server +%%---------------------------------------------------------------------- +dispatch_message(Behaviours, Input, State) -> + ?dbg( "Match= ~p", [ Behaviours ]), + + lists:map(fun(Behaviour) -> + {M, F} = Behaviour#behaviour.function, + apply(M, F, [Input, + State#state.nickname, + Behaviour#behaviour.data, + State#state.bot_pid]) + end, + Behaviours). + +%%---------------------------------------------------------------------- +%% input_to_record/1 +%% Convert a given input to a list of preparsed data records +%%---------------------------------------------------------------------- +input_to_record(ServerData) -> + Lines = string:tokens(ServerData, "\r\n"), + parse_lines(Lines, []). + +%%---------------------------------------------------------------------- +%% parse_lines/2 +%% Each input line will be a data record +%%---------------------------------------------------------------------- +parse_lines([], Result) -> + lists:reverse(Result); +parse_lines([Line|Lines], Result) -> + parse_lines(Lines, [parse_line(Line) | Result]). + +%%---------------------------------------------------------------------- +%% parse_line/1 +%% Each line is split between the data record fields +%%---------------------------------------------------------------------- +parse_line([$: | ServerData]) -> + BodyPos = string:chr(ServerData, $:), + + case BodyPos > 0 of + true -> + Header = string:substr(ServerData, 1, BodyPos - 1), + Body = string:substr(ServerData, BodyPos + 1), + Result = string:tokens(Header, " "), + + case length(Result) of + 1 -> + Header_from = lists:nth(1, Result), + #data{header_from = Header_from, + body = Body}; + + 2 -> + Header_from = lists:nth(1, Result), + Header_op = lists:nth(2,Result), + #data{header_from = Header_from, + header_op = Header_op, + body = Body}; + + Other -> + Header_from = lists:nth(1, Result), + Header_op = lists:nth(2,Result), + Header_to = lists:nth(3, Result), + Header_options = lists:flatten(lists:nthtail(3, Result)), + + #data{header_from = Header_from, + header_op = Header_op, + header_to = Header_to, + header_options = Header_options, + body = Body} + end; + + false -> + [Header_from, Header_op, Header_to | _Rest] = + string:tokens(ServerData, " "), + + #data{header_from = Header_from, + header_op = Header_op, + header_to = Header_to, + body = ""} + end; + +%% I think that missing a ping generate a message that fall in this case and +%% crash the process +parse_line(ServerData) -> + ?dbg("In ParseLine: unidentified: ~p", [ServerData]), + %% Ignore. + #data{}. + + +%%---------------------------------------------------------------------- +%% match_event/3 +%% Returns the list of behaviour that should be executed on an irc input +%%---------------------------------------------------------------------- +match_event(Data, Behaviours, Nickname) -> + %% io:format("Nickname: ~p~n", [Nickname]), + match_event(Data, Behaviours, Nickname, []). + +match_event(Data, [], Nickname, Acc) -> + lists:reverse(Acc); + +match_event(Data, [Behaviour|Behaviours], Nickname, Acc) -> + DataList = data_as_list(Data), + MatchCritList = append_botname(data_as_list(Behaviour#behaviour.pattern), + Nickname), + + case is_matching(DataList, MatchCritList) of + true -> + match_event(Data, Behaviours, Nickname, [Behaviour|Acc]); + false -> + match_event(Data, Behaviours, Nickname, Acc) + end. + +%%---------------------------------------------------------------------- +%% data_as_list/1 +%% Convert the data record to a list of values +%%---------------------------------------------------------------------- +data_as_list(Data) -> + DataList = tuple_to_list(Data), + [RecordName | Rest] = DataList, + Rest. + +%%---------------------------------------------------------------------- +%% append_botname/2 +%% Convert '%BOTNAME' wherever in the list by its real name +%%---------------------------------------------------------------------- +append_botname(List, Botname) -> + lists:map(fun({regexp, String}) -> + {ok, NewString, _C} = + regexp:sub(String, ?BOTNAME, Botname), + {regexp, NewString}; + (Other) -> + Other + end, + List). + +%%---------------------------------------------------------------------- +%% is_matching/2 +%% Check if the first list (data record field values) match the +%% Criterium +%%---------------------------------------------------------------------- +is_matching(Data, Criterium) -> + is_matching(Data, Criterium, true). + +is_matching(_Data, _Criterium, false) -> + false; + +is_matching([],[], Result) -> + Result; + +is_matching([Element|Elements], [Criterium|Criteria], Result) -> + %% io:format("is_matching: ~p ~p~n", [Criterium, Element]), + case Criterium of + '_' -> + is_matching(Elements, Criteria, true); + + {regexp, []} -> + is_matching(Elements, Criteria, false); + + {regexp, Expr} -> + is_matching(Elements, Criteria, is_matching_regexp(Element, Expr)); + + %% Should tag the Criterium value as {exact, Criterium} + Element -> + is_matching(Elements, Criteria, true); + + _Other -> + is_matching(Elements, Criteria, false) + end. + +%%---------------------------------------------------------------------- +%% is_matching_regexp/2 +%% Check the match based on a regexp expression +%%---------------------------------------------------------------------- +is_matching_regexp(String, Regexp) -> + %% io:format("is_matching_regexp: ~p ~p~n", [String, Regexp]), + case regexp:match(String, Regexp) of + {match, _Start, _Length} -> true; + nomatch -> false; + {error, Error} -> false + end. diff --git a/src/misc_tools.erl b/src/misc_tools.erl new file mode 100644 index 0000000..54c5048 --- /dev/null +++ b/src/misc_tools.erl @@ -0,0 +1,164 @@ +%%%---------------------------------------------------------------------- +%%% File : misc_tools.erl +%%% Author : Mickael Remond +%%% Purpose : This module gather various generic functions +%%% we used for Manderlbot developpment +%%% Created : 16 Nov 2000, Mickael Remond +%%% +%%%---------------------------------------------------------------------- +%%% +%%% 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. +%%% +%%%---------------------------------------------------------------------- +%%% +%%% See COPYING for detailled license +%%% +%%%---------------------------------------------------------------------- + +-module(misc_tools). +-author('mickael.remond@erlang-fr.org'). +-created('Date: 20001116'). +-revision(' $Revision$ '). +-vsn(' $Id$ '). + +-export([nth/2, + regexpize/1, + date_to_integer/1, + date_to_string/2, + last_event/1, + lower_string/1, + upper_string/1]). + +%%---------------------------------------------------------------------- +%% Function: nth/2 +%% Purpose: Returns the element of a list according to its position +%% Args: N = element position +%% List = list to extract the element from +%% Returns: The requested element +%% or "" if the requested element does not exist +%%---------------------------------------------------------------------- +nth(N,List) when N > length(List) -> + ""; +nth(N, List) when N =< length(List) -> + lists:nth(N, List). + +%%--------------------------------------------------------------------- +%% Utility fonction : regexpize +%% arg : a token in caps (ex DATE) +%% return : a regexp allowing any combination of uppercase/lowercase +%% for example : DATE gives [Dd][Aa][Tt][Ee] +%% -------------------------------------------------------------------- +regexpize(Token) when list(Token) -> + %% This fonction transform the element "E" for example to + %% "[Ee]" + F = fun(Element) -> + "["++ + [Element] ++ + [lower_char(Element)] ++ + "]" + end, + + %% Convert all the token using this function + lists:flatmap(F, Token). + +%%-------------------------------------------------------------------- +%% lower_char/1 +%% It seems there isn't a lower/upper function in stdlib, so here's a +%% quick hack, using specific ASCII charset +%% FIXME : maybe not portable +%%-------------------------------------------------------------------- +lower_char(Char) when Char >= $A, Char =< $Z -> + Char + 32 ; +lower_char(OtherChar) -> + OtherChar. + +%%---------------------------------------------------------------------- +%% Function: lower_string/1 +%% Purpose: Convert a string to lower case. +%% It seems there isn't a lower/upper function in stdlib, so here it is +%% Args: String is an erlang string. +%% Returns: A string +%%---------------------------------------------------------------------- +lower_string(String) -> + lower_string(String, []). +lower_string([], Acc) -> + lists:reverse(Acc); +lower_string([H|T], Acc) when H >= $A, H =< $Z -> + LowerChar = H + 32, + lower_string(T, [LowerChar|Acc]); +lower_string([H|T], Acc) -> + lower_string(T, [H|Acc]). + +%%---------------------------------------------------------------------- +%% Function: upper_string/1 +%% Purpose: Convert a string to upper case. +%% Args: String is an erlang string. +%% Returns: A string +%%---------------------------------------------------------------------- +upper_string(String) -> + upper_string(String, []). +upper_string([], Acc) -> + lists:reverse(Acc); +upper_string([H|T], Acc) when H >= $a, H =< $z -> + UpperChar = H - 32, + upper_string(T, [UpperChar|Acc]); +upper_string([H|T], Acc) -> + upper_string(T, [H|Acc]). + +%%-------------------------------------------------------------------- +%% date_to_integer/1 +%% Converts a date to an Integer +%%-------------------------------------------------------------------- +date_to_integer({}) -> + 0; +date_to_integer({Date,Time}) -> + {Year, Month, Day} = Date, + {Hour, Minute, Second} = Time, + Second + (Minute * 100) + (Hour * 10000) + + (Day * 1000000) + (Month * 100000000) + (Year * 10000000000). + +%%-------------------------------------------------------------------- +%% date_to_string/2 +%% Converts a date into an English string +%%-------------------------------------------------------------------- +date_to_string(_Language, {}) -> + ""; +date_to_string(en, {Date, Time}) -> + {Year, Month, Day} = Date, + {Hour, Minute, Second} = Time, + integer_to_list(Year) ++ "-" ++ + integer_to_list(Month) ++ "-" ++ + integer_to_list(Day) ++ " " ++ + integer_to_list(Hour) ++ ":" ++ + integer_to_list(Minute) ++ ":" ++ + integer_to_list(Second). + +%%-------------------------------------------------------------------- +%% last_event/1 +%% TODO: Rewrite it to make it more generic and pass it to misc_tools. +%% Return the last event from a given list of events: +%% Events are of the form: +%% {eventname, Date} +%% Return the eventname of the latest event. +%%-------------------------------------------------------------------- +last_event(Events) -> + last_event(Events, {none, {{0,0,0}, {0,0,0}}}). +last_event([], LastEvent)-> + LastEvent; +last_event([Event|Events], LastEvent) -> + {EventName, Date} = Event, + {LastEventName, LastDate} = LastEvent, + + DateInt = date_to_integer(Date), + LastDateInt = date_to_integer(LastDate), + + case DateInt > LastDateInt of + true -> + last_event(Events, Event); + false -> + last_event(Events, LastEvent) + end. +