├── README.md ├── .gitignore ├── Gemfile ├── lib ├── invite.rb ├── adhocracia.rb ├── humanize.rb ├── empathy.rb ├── remember.rb └── url_title.rb ├── config.yml ├── daemonize ├── Gemfile.lock └── kropotkin.rb /README.md: -------------------------------------------------------------------------------- 1 | Daemonizar: ./daemonize 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/* 2 | .bundle/* 3 | *.db 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "cinch" 4 | gem "gdbm" 5 | gem "pry" 6 | gem "gdbm" 7 | gem "ffi" 8 | gem "opengraph" 9 | gem "htmlentities" 10 | gem "ffi" 11 | -------------------------------------------------------------------------------- /lib/invite.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # Acepta invitaciones a canales 4 | class AcceptInvite 5 | include Cinch::Plugin 6 | 7 | listen_to :invite 8 | 9 | def listen(m) 10 | m.channel.join 11 | m.reply "o/" 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | nick: kropotkin 3 | 4 | networks: 5 | - server: irc.hackcoop.com.ar 6 | bot: nil 7 | port: 6697 8 | ssl: true 9 | channels: 10 | - "#kropotest" 11 | # - server: bla.pirata 12 | # bot: nil 13 | # port: 6697 14 | # ssl: true 15 | # channels: 16 | # - "#kropotest" 17 | -------------------------------------------------------------------------------- /lib/adhocracia.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # Detecta construcciones comunes galponeras como "hay que hacer..." y 4 | # recomienda un curso de acción adhocrático 5 | class Adhocracia 6 | include Cinch::Plugin 7 | 8 | def initialize(*args) 9 | super 10 | end 11 | 12 | match /ha(y|bria|bría) que/, use_prefix: false, method: :adhocracia 13 | 14 | def adhocracia(m) 15 | m.reply '¡Che, qué buena idea! ¿Por qué no la hacés?', true 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /daemonize: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | mkdir -p tmp 5 | pushd $(dirname "$(realpath "$0")") 6 | 7 | test -f tmp/kropotkin.pid && pkill --pidfile tmp/kropotkin.pid || true 8 | 9 | pgrep tor &>/dev/null && which torify &>/dev/null && torify=torify 10 | pgrep tor &>/dev/null && which torsocks &>/dev/null && torify=torsocks 11 | 12 | test -z "$torify" && echo "Instala tor y torsocks para anonimizar" 13 | 14 | $torify bundle exec ruby kropotkin.rb & 15 | echo $! >tmp/kropotkin.pid 16 | -------------------------------------------------------------------------------- /lib/humanize.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'cinch/timer' 3 | # Agregar un temporizador para que las respuestas no sean inmediatas y 4 | # parezca más humano 5 | module HumanMessage 6 | def reply(text, prefix = false, now = false) 7 | if now 8 | super(text, prefix) 9 | else 10 | Thread.new do 11 | sleep rand(10) 12 | super(text, prefix) 13 | end 14 | end 15 | end 16 | end 17 | 18 | module Cinch 19 | class Message 20 | prepend HumanMessage 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/empathy.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | class Empathy 3 | include Cinch::Plugin 4 | 5 | match /!!/, use_prefix: false, method: :surprise 6 | def surprise(m) 7 | m.reply ":O" 8 | end 9 | 10 | match /:[c\(]/, use_prefix: false, method: :hug 11 | def hug(m) 12 | Timer(rand(10), shots: 1) { m.channel.action "abraza a #{m.user.nick} :)" } 13 | end 14 | 15 | match /\\o\//i, use_prefix: false, method: :cheer 16 | def cheer(m) 17 | m.reply '\o/' 18 | end 19 | 20 | match /(\A|\s)o\//i, use_prefix: false, method: :greet 21 | match /\\o(\Z|\s)/i, use_prefix: false, method: :greet 22 | # Saludar 23 | def greet(m) 24 | m.reply ['o/', '\o', 'ea'].sample, true 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | cinch (2.2.5) 5 | coderay (1.1.0) 6 | ffi (1.9.10) 7 | gdbm (1.3.1) 8 | hashie (3.4.2) 9 | htmlentities (4.3.3) 10 | method_source (0.8.2) 11 | mime-types (1.25.1) 12 | nokogiri (1.5.11) 13 | opengraph (0.0.4) 14 | hashie 15 | nokogiri (~> 1.5.0) 16 | rest-client (~> 1.6.0) 17 | pry (0.10.1) 18 | coderay (~> 1.1.0) 19 | method_source (~> 0.8.1) 20 | slop (~> 3.4) 21 | rest-client (1.6.9) 22 | mime-types (~> 1.16) 23 | slop (3.6.0) 24 | 25 | PLATFORMS 26 | ruby 27 | 28 | DEPENDENCIES 29 | cinch 30 | ffi 31 | gdbm 32 | htmlentities 33 | opengraph 34 | pry 35 | 36 | BUNDLED WITH 37 | 1.12.4 38 | -------------------------------------------------------------------------------- /lib/remember.rb: -------------------------------------------------------------------------------- 1 | require 'gdbm' 2 | 3 | # Recuerda quién estuvo en qué canal usando una base de datos de 4 | # llave-valor muy muy simple 5 | class Remember 6 | include Cinch::Plugin 7 | 8 | listen_to :join 9 | 10 | attr_reader :dbm 11 | 12 | def listen(m) 13 | return if m.user.nick == m.bot.config.nick 14 | 15 | # Inicia la base de datos para este servidor 16 | @dbm ||= GDBM.new("#{m.bot.config.server}.db", 0600, GDBM::SYNC) 17 | 18 | if channels(m.user.nick).include? m.channel.name 19 | # no hace falta saludar todo el tiempo :) 20 | if [ true, false, false, false, false ].sample 21 | m.reply ['o/','\o','ea'].sample, true 22 | end 23 | else 24 | # recordar donde lo vimos 25 | seen_in_channel(m.user.nick, m.channel.name) 26 | # darle la bienvenida 27 | m.reply ['bienvenidx!', 'qué tal?', ':)', 'hola', 'hola!', 'o/'].sample, true 28 | end 29 | end 30 | 31 | # Guarda la lista de canales en las que vimos un nick 32 | # #test,#test2,#etc 33 | def seen_in_channel(nick, chan) 34 | if @dbm.key? nick 35 | @dbm[nick] += ",#{chan}" 36 | else 37 | @dbm[nick] = chan 38 | end 39 | end 40 | 41 | # Devuelve todos los canales de un nick en un array 42 | # [ '#test', '#test2', '#etc' ] 43 | def channels(nick) 44 | return [] unless @dbm.key? nick || @dbm[nick].nil? 45 | 46 | @dbm[nick].split(',') 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /kropotkin.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'yaml' 3 | require 'cinch' 4 | require './lib/humanize' 5 | require './lib/url_title' 6 | require './lib/empathy' 7 | require './lib/invite' 8 | require './lib/adhocracia' 9 | require './lib/remember' 10 | 11 | # Carga de la configuracion 12 | config = YAML.load_file('config.yml') 13 | 14 | instances = [] 15 | 16 | # Por cada red generar una instancia del cyborg 17 | config['networks'].each do |n| 18 | n[:bot] = Cinch::Bot.new do 19 | configure do |c| 20 | c.nick = config['nick'] 21 | c.user = config['nick'] 22 | c.password = n['password'] if n['password'] 23 | c.server = n['server'] 24 | c.port = n['port'] 25 | c.channels = n['channels'] 26 | c.ssl.use = n['ssl'] unless n['ssl'].nil? 27 | c.plugins.plugins = [Empathy, UrlTitle, AcceptInvite, Adhocracia, Remember] 28 | end 29 | 30 | on :message, /\bbugs?\b/i do |m| 31 | m.reply 'patches welcome', true 32 | end 33 | 34 | # Corregir 35 | on :message, /open ?source/i do |m| 36 | m.reply 'no querrás decir software libre?', true 37 | end 38 | 39 | on :message, /^[!,]\w+/ do |m| 40 | m.reply ['a quién le habla?', 'hay un bot por acá? :O', '¬¬'].sample 41 | end 42 | end 43 | 44 | # Correr cada instancia en un thread nuevo 45 | instances << Thread.new { n[:bot].start } 46 | end 47 | 48 | # Esperar que terminen! 49 | instances.each(&:join) 50 | -------------------------------------------------------------------------------- /lib/url_title.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'net/https' 3 | require 'opengraph' 4 | require 'htmlentities' 5 | 6 | class UrlTitle 7 | include Cinch::Plugin 8 | 9 | def initialize(*args) 10 | super 11 | end 12 | 13 | match /https?:\/\/[^'"]+/, use_prefix: false, method: :fetch_title 14 | 15 | def fetch_title(m) 16 | m.message.scan(/https?:\/\/[^'" #]+/).each do |url| 17 | url = URI.encode(url) 18 | uri = URI(url) 19 | 20 | # icecast devuelve text/html para los los streams.. 21 | if uri.path.end_with?('ogg', 'mp3', 'ogv', 'webm', 'mp4') 22 | info 'es un archivo de medios' 23 | m.reply 'no puedo leer eso', false, true 24 | next 25 | end 26 | 27 | # Ignorar lo que no sea html 28 | Net::HTTP.start uri.host, uri.port, 29 | use_ssl: uri.scheme == 'https' do |http| 30 | info 'averiguando si es un html' 31 | unless http.head(uri.path)['content-type'] =~ /\Atext\/html\Z/ 32 | info 'no lo es' 33 | next 34 | end 35 | end 36 | 37 | info 'opengraph' 38 | resource = OpenGraph.fetch(url) 39 | 40 | if resource 41 | title = anti_blockflare(HTMLEntities.new.decode(resource.title)) 42 | 43 | m.reply(title, false, true) unless resource.title.empty? 44 | m.reply(resource.description[0..140], false, 45 | true) unless resource.description.empty? 46 | else 47 | title = Net::HTTP.get(uri).tr("\n", ' ').squeeze(' ').scan( 48 | /(.*?)<\/title>/i 49 | )[0][0] 50 | title = HTMLEntities.new.decode(title) 51 | title = anti_blockflare(title) 52 | m.reply(title, false, true) unless title.empty? 53 | end 54 | end 55 | end 56 | 57 | def anti_blockflare(title) 58 | if title =~ /cloudflare/i 59 | title.gsub! /cloudflare/i, 'BlockFlare' 60 | title + " - (ノಠ益ಠ)ノ彡┻━┻ " 61 | end 62 | title 63 | end 64 | end 65 | --------------------------------------------------------------------------------