├── .gitignore ├── .merlin ├── .ocamlinit ├── LICENSE ├── Makefile ├── README.md ├── dune-project ├── ocabot.opam ├── src ├── dune ├── hello.ml └── ocabot.ml ├── start.sh └── tools ├── load_dump.ml ├── repair_utf8.ml └── save.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | .*.swo 3 | _build 4 | *.native 5 | *.byte 6 | .session 7 | TAGS 8 | *.docdir 9 | setup.* 10 | qtest* 11 | *.html 12 | *_j.* 13 | *_t.* 14 | *.install 15 | .merlin 16 | *.json 17 | .env 18 | -------------------------------------------------------------------------------- /.merlin: -------------------------------------------------------------------------------- 1 | S src 2 | B _build/src 3 | PKG lwt 4 | PKG calculon 5 | FLG -w +a-4-44-48 6 | -------------------------------------------------------------------------------- /.ocamlinit: -------------------------------------------------------------------------------- 1 | #require "lwt.unix";; 2 | #require "irc-client.lwt";; 3 | #require "str";; 4 | #require "yojson";; 5 | #require "containers";; 6 | #require "sequence";; 7 | #require "cohttp.lwt";; 8 | #require "atdgen";; 9 | #require "lambdasoup";; 10 | #directory "_build/src";; 11 | #load "ocabotlib.cma";; 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 chamo armael c-cube 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: build test 2 | 3 | build: 4 | @dune build @install 5 | 6 | test: build 7 | @dune runtest --no-buffer --force 8 | 9 | clean: 10 | @dune clean 11 | 12 | doc: 13 | @dune build @doc 14 | 15 | backups: 16 | #@echo "doing backups of all .json files…" 17 | ./tools/save.sh *.json 18 | 19 | .PHONY: backups 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OCabot 2 | 3 | IRC bot for "#OCaml" on freenode 4 | 5 | It can be interacted with using `!`-prefixed commands. `!help` lists these. 6 | 7 | - New factoids can be added using `!foo = bar`, which will cause `!foo` to 8 | answer `bar` in the future. 9 | - `!search ` and `!search_all ` perform basic search among factoids. 10 | - `!help` or `!help ` provide help. 11 | - `!history` lists a few recent messages on `#ocaml`. 12 | - `!seen ` will report last time the given nickname was seen speaking on the channel. 13 | 14 | ## Build 15 | 16 | - `opam pin add calculon https://github.com/c-cube/calculon.git` 17 | - `make` 18 | - `./ocabot.native` 19 | 20 | ## License 21 | 22 | MIT, see `LICENSE` 23 | 24 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.0) 2 | -------------------------------------------------------------------------------- /ocabot.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | name: "ocabot" 3 | version: "dev" 4 | synopsis: "IRC bot for #OCaml on freenode" 5 | authors: ["Armael" "c-cube"] 6 | maintainer: ["Armael" "c-cube"] 7 | build: [ 8 | ["dune" "build" "-p" name "-j" jobs] 9 | ] 10 | depends: [ 11 | "dune" {build} 12 | "base-bytes" 13 | "base-unix" 14 | "lwt" 15 | "calculon" { >= "0.5" & < "0.6" } 16 | ] 17 | tags: [ "irc" "bot" ] 18 | homepage: "https://github.com/c-cube/ocabot" 19 | bug-reports: "https://github.com/c-cube/ocabot/issues" 20 | dev-repo: "git+https://github.com/c-cube/ocabot" 21 | 22 | -------------------------------------------------------------------------------- /src/dune: -------------------------------------------------------------------------------- 1 | 2 | (executable 3 | (name ocabot) 4 | (public_name ocabot) 5 | (flags :standard -w +a-4-44-48-29) 6 | (libraries calculon) 7 | ) 8 | -------------------------------------------------------------------------------- /src/hello.ml: -------------------------------------------------------------------------------- 1 | 2 | open Calculon 3 | 4 | let cmd_hello : Command.t = 5 | Command.make_simple 6 | ~descr:"politeness core interface" ~cmd:"hello" ~prio:10 7 | (fun (input_msg:Core.privmsg) _ -> 8 | let who = input_msg.Core.nick in 9 | Lwt.return (Some ("hello " ^ who)) 10 | ) 11 | 12 | let plugin = Plugin.of_cmd cmd_hello 13 | -------------------------------------------------------------------------------- /src/ocabot.ml: -------------------------------------------------------------------------------- 1 | 2 | module C = Calculon 3 | 4 | let all_ : C.Plugin.t list = [ 5 | C.Plugin_social.plugin; 6 | C.Plugin_factoids.plugin; 7 | C.Plugin_state.plugin; 8 | C.Plugin_history.plugin ~n:100 (); 9 | Hello.plugin; 10 | ] 11 | 12 | let config = { 13 | C.Config.default with 14 | C.Config. 15 | log_level = Logs.Info; 16 | server = "irc.libera.chat"; 17 | port = 6697; 18 | username = "ocabot"; 19 | realname = "ocabot"; 20 | nick = "ocabot"; 21 | channel = "#ocaml"; 22 | tls = true; 23 | sasl = true; 24 | } 25 | 26 | let () = 27 | Logs.set_reporter (Logs.format_reporter ()); 28 | try 29 | (* update with CLI parameters *) 30 | let config = C.Config.parse config Sys.argv in 31 | Logs.set_level ~all:true (Some config.C.Config.log_level); 32 | Logs.info (fun k->k"start ocabot"); 33 | C.Run_main.main config all_ |> Lwt_main.run 34 | with 35 | | Arg.Help msg -> print_endline msg 36 | | Arg.Bad msg -> prerr_endline msg; exit 1 37 | 38 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source .env && ./_build/default/src/ocabot.exe $@ 3 | -------------------------------------------------------------------------------- /tools/load_dump.ml: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ocaml 2 | 3 | #use "topfind";; 4 | #require "lwt.unix";; 5 | #require "irc-client.lwt";; 6 | #require "str";; 7 | #require "yojson";; 8 | #require "containers";; 9 | #directory "_build/src";; 10 | #load "ocaobtlib.cma";; 11 | 12 | let s = CCIO.with_in "old_dump" CCIO.read_lines_l;; 13 | let l = CCList.filter_map Factoids.parse_op s;; 14 | let fs = List.fold_left (fun acc (Factoids.Set f) -> Factoids.append f acc) Factoids.empty l;; 15 | Printf.printf "%d records\n" (Prelude.StrMap.cardinal fs);; 16 | Factoids.write_file ~file:"factoids.json" fs |> Lwt_main.run;; 17 | 18 | -------------------------------------------------------------------------------- /tools/repair_utf8.ml: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ocaml 2 | 3 | #use "topfind";; 4 | #require "uutf";; 5 | #require "sequence";; 6 | #require "containers";; 7 | 8 | open Sequence.Infix;; 9 | 10 | (* repair unicode text that was broken by {!String.lowercase} *) 11 | 12 | (* is [s] a single unicode char *) 13 | let is_utf8_char s = 14 | let d = Uutf.decoder ~encoding:`UTF_8 (`String s) in 15 | match Uutf.decode d with 16 | | `Uchar c -> Uutf.decode d = `End 17 | (* Uutf.cp_to_string c = s *) 18 | | `Malformed _ -> false 19 | | `Await -> assert false 20 | | `End -> false 21 | 22 | let input = CCIO.with_in "factoids.json" CCIO.read_all ;; 23 | 24 | let l = Uutf.String.fold_utf_8 (fun acc i c -> (i,c)::acc) [] input |> List.rev;; 25 | 26 | Printf.printf "there are %d broken chunks\n%!" 27 | (l |> List.filter (function (_,`Malformed _) -> true | _ -> false) |> List.length);; 28 | 29 | (* function that repairs a single malformed string *) 30 | let repair1 s: string option = 31 | let rec aux l = match l with 32 | | [] -> Sequence.return [] 33 | | c :: tail -> 34 | Sequence.of_list [c; Char.uppercase c] >>= fun c -> 35 | aux tail >|= fun tl -> 36 | c::tl 37 | in 38 | CCString.to_list s 39 | |> aux 40 | |> Sequence.map CCString.of_list 41 | |> Sequence.filter is_utf8_char 42 | |> Sequence.head 43 | ;; 44 | 45 | let is_ascii c = Char.code c < 128 46 | 47 | (* repair a malformed chunk. First tries to repair it fully, then tries 48 | to split and repair it recursively *) 49 | let rec repair s: string option = 50 | let len = String.length s in 51 | 2 -- len 52 | |> Sequence.filter_map 53 | (fun i -> 54 | let s1 = String.sub s 0 i in 55 | let s2 = String.sub s i (len-i) in 56 | match repair1 s1 with 57 | | Some u1 when s2="" -> Some u1 58 | | Some u1 when CCString.for_all is_ascii s2 -> Some (u1 ^ s2) 59 | | Some u1 -> 60 | begin match repair s2 with 61 | | Some u2 -> Some (u1 ^ u2) 62 | | None -> None 63 | end 64 | | None -> None) 65 | |> Sequence.head 66 | ;; 67 | 68 | let res = 69 | let buf = Buffer.create (String.length input) in 70 | let n_repair = ref 0 in 71 | let n_fail = ref 0 in 72 | List.iter 73 | (function 74 | | (_, `Uchar c) -> Uutf.Buffer.add_utf_8 buf c 75 | | (_, `Malformed s) -> 76 | match repair s with 77 | | Some s' -> 78 | Printf.printf "repaired %S into %S\n%!" s s'; 79 | incr n_repair; 80 | Buffer.add_string buf s' 81 | | None -> 82 | Printf.printf "could not repair %S\n%!" s; 83 | incr n_fail; 84 | Buffer.add_string buf s 85 | ) 86 | l; 87 | Printf.printf "num repair: %d, num fail: %d\n%!" !n_repair !n_fail; 88 | Buffer.contents buf ;; 89 | 90 | CCIO.with_out "factoids.new.json" (fun oc -> output_string oc res);; 91 | -------------------------------------------------------------------------------- /tools/save.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | mkdir -p backups/ 4 | for i in $@ ; do 5 | TARGET="backups/$i-`date -I`" 6 | cp "$i" "$TARGET"; 7 | gzip "$TARGET"; 8 | done 9 | 10 | --------------------------------------------------------------------------------