├── version ├── plugins ├── points │ ├── migrations │ └── PointModel.rb ├── log_user.yml.example ├── iamalive │ ├── admin.rb │ ├── migrations │ │ ├── 02_create_admin.rb │ │ └── 01_create_entry.rb │ ├── entry.rb │ └── README.md ├── base │ ├── migrations │ │ ├── 06_create_eip.rb │ │ ├── 01_create_user.rb │ │ ├── 02_create_points.rb │ │ ├── 03_create_message.rb │ │ ├── 04_create_emails.rb │ │ └── 05_create_random_sequences.rb │ ├── UserModel.rb │ └── user.rb ├── dice │ ├── Warrior.rb │ ├── Character.rb │ ├── Dice.rb │ └── Weapon.rb ├── say_goodbye.rb ├── example.rb ├── poilo.rb ├── encrypt.rb ├── diceroller.rb ├── searchable.rb ├── taggle.rb ├── kick.rb ├── youtube.rb ├── eip.rb ├── root_me.rb ├── ops.rb ├── anecdote.rb ├── log.rb ├── cequetudisnaaucunsens.rb ├── base.rb ├── points.rb ├── proxy.rb ├── iamalive.rb ├── puppet.rb └── network.rb ├── .codeclimate.yml ├── .gitignore ├── contributors ├── RIGHTS_MANAGEMENT.md ├── Gemfile ├── database.rb ├── Rakefile ├── test └── test.rb ├── botpop_plugin_inclusion.rb ├── builtins.rb ├── Gemfile.lock ├── DATABASE_EXTENSION.md ├── botpop.rb ├── arguments.rb ├── modules_config.yml.example └── README.md /version: -------------------------------------------------------------------------------- 1 | v1.2-4 2 | -------------------------------------------------------------------------------- /plugins/points/migrations: -------------------------------------------------------------------------------- 1 | base/migrations/ -------------------------------------------------------------------------------- /plugins/log_user.yml.example: -------------------------------------------------------------------------------- 1 | list: 2 | - poulet_a 3 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | languages: 2 | Ruby: true 3 | exclude_paths: [] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | *.log 4 | *.sqlite3 5 | 6 | old/ 7 | 8 | modules_config.yml 9 | plugins/log_user.yml 10 | 11 | -------------------------------------------------------------------------------- /contributors: -------------------------------------------------------------------------------- 1 | pouleta -- main developer 2 | moul -- guest 3 | ernestjkaufman -- guest 4 | FolenScare -- guest 5 | JoycePF -- guest 6 | Deborah -- guest 7 | Shigugu -- guest 8 | -------------------------------------------------------------------------------- /plugins/iamalive/admin.rb: -------------------------------------------------------------------------------- 1 | require 'sequel' 2 | 3 | class IAmAlive 4 | 5 | class Admin < Sequel::Model 6 | set_dataset DB[:admins] 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /plugins/iamalive/migrations/02_create_admin.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | change do 3 | create_table(:admins) do 4 | primary_key :id 5 | String :user, null: false 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /plugins/base/migrations/06_create_eip.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | change do 3 | create_table(:eips) do 4 | primary_key :id 5 | String :author 6 | String :title 7 | DateTime :created_at 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /plugins/points/PointModel.rb: -------------------------------------------------------------------------------- 1 | require 'sequel' 2 | 3 | class Point < Sequel::Model 4 | 5 | def before_save 6 | return false if super == false 7 | self.created_at = Time.now 8 | end 9 | 10 | set_dataset Base::DB[:points] 11 | 12 | end 13 | -------------------------------------------------------------------------------- /plugins/dice/Warrior.rb: -------------------------------------------------------------------------------- 1 | require_relative 'Character' 2 | 3 | class Warrior < Character 4 | 5 | def initialize str, *arg 6 | super(str, 10, 10, 10, 10, 10, *arg) 7 | end 8 | 9 | def str; carac[:str]; end 10 | def bstr; (str - 10) / 2; end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /plugins/base/migrations/01_create_user.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | change do 3 | create_table(:users) do 4 | primary_key :id 5 | String :name, null: false, unique: true 6 | TrueClass :admin 7 | column :groups, "text[]" 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /plugins/base/migrations/02_create_points.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | change do 3 | create_table(:points) do 4 | # primary_key :id 5 | String :assigned_to 6 | String :assigned_by 7 | String :type 8 | DateTime :created_at 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /plugins/base/migrations/03_create_message.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | change do 3 | create_table(:messages) do 4 | primary_key :id 5 | String :author 6 | String :dest 7 | String :content 8 | DateTime :created_at 9 | DateTime :read_at 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /plugins/base/UserModel.rb: -------------------------------------------------------------------------------- 1 | require 'sequel' 2 | 3 | class User < Sequel::Model 4 | 5 | def is_admin? 6 | self.admin 7 | end 8 | 9 | def add_group 10 | end 11 | 12 | def del_group 13 | end 14 | 15 | def belongs_to? group 16 | self.groups.include? group 17 | end 18 | 19 | set_dataset Base::DB[:users] 20 | 21 | end 22 | -------------------------------------------------------------------------------- /plugins/base/migrations/04_create_emails.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | change do 3 | create_table(:emails) do 4 | primary_key :id 5 | String :authname 6 | String :address 7 | DateTime :created_at 8 | Integer :usage 9 | Bool :primary, default: false 10 | 11 | index [:address], unique: true 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /plugins/dice/Character.rb: -------------------------------------------------------------------------------- 1 | class Character 2 | 3 | attr_accessor :carac, :skills, :hp, :classes, :bab 4 | def initialize(str, dex, con, int, wiz, cha, opt={}) 5 | @carac = {str: str, dex: dex, con: con, int: int, wiz: wiz, cha: cha} 6 | @skills = {} 7 | @hp = opt[:hp] || nil 8 | @classes = opt[:classes] || {} 9 | @bab = opt[:bab] || [0] 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /plugins/iamalive/migrations/01_create_entry.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | change do 3 | create_table(:entries) do 4 | primary_key :id 5 | String :user, null: false 6 | String :channel, null: false 7 | String :message, null: false, text: true 8 | String :message_origin, null: false, text: true 9 | DateTime :created_at 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /RIGHTS_MANAGEMENT.md: -------------------------------------------------------------------------------- 1 | # How to use rights ? 2 | 3 | In your plugin, add the method ``cmd_allowed?`` and use it like in the following example: 4 | 5 | ```ruby 6 | class Plugin < Botpop::Plugin 7 | ... 8 | def cmd_allowed? m 9 | return if not Base.cmd_allowed? m, ["groupname"] 10 | end 11 | 12 | def exec_some_match m 13 | return Base.cmd_allowed? m, ["iaa"] 14 | end 15 | ``` 16 | -------------------------------------------------------------------------------- /plugins/base/migrations/05_create_random_sequences.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | change do 3 | create_table(:random_sentences) do 4 | primary_key :id 5 | String :author 6 | String :trigger 7 | String :content 8 | Bool :enabled, default: true 9 | DateTime :created_at 10 | 11 | index :trigger, unique: true 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | #irc 4 | gem 'cinch' 5 | 6 | # scrap yt 7 | gem 'mechanize' 8 | 9 | # debug 10 | gem 'pry' 11 | gem 'colorize' 12 | 13 | #network 14 | gem 'net-ping' 15 | 16 | #proxy 17 | gem 'htauth' 18 | 19 | #iamalive 20 | gem 'sequel' 21 | # gem 'sqlite3' 22 | gem 'pg' 23 | 24 | #encrypt 25 | gem 'tor257' 26 | 27 | #other 28 | gem 'nomorebeer' 29 | gem 'i18n' 30 | -------------------------------------------------------------------------------- /plugins/say_goodbye.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | 3 | class SayGoodBye < Botpop::Plugin 4 | include Cinch::Plugin 5 | 6 | match(/^!sg [\w\-\.].+/, use_prefix: false, method: :exec_sg) 7 | 8 | HELP = ["!sg src_name"] 9 | ENABLED = config['enable'].nil? ? true : config['enable'] 10 | 11 | def exec_sg m 12 | arg = m.message.split.last 13 | m.reply config[arg].sample 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /database.rb: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | 3 | class Botpop 4 | class Plugin 5 | module Database 6 | 7 | def self.append_features(b) 8 | b.extend self 9 | end 10 | 11 | attr_accessor :db_config 12 | attr_reader :db 13 | def db_connect! 14 | require 'sequel' 15 | @db = Sequel.connect(@db_config) 16 | @db 17 | end 18 | 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | 3 | require 'yaml' 4 | CONFIG = YAML.load_file("modules_config.yml") 5 | 6 | #require 'sequel' 7 | #DB_BASE = Sequel.connect(CONFIG['base']['database']) 8 | 9 | #task :default => ["x:x"] 10 | 11 | namespace :db do 12 | task :install do 13 | # TODO: use CONFIG['base'] 14 | `sequel -m plugins/base/migrations postgres://root:toor@localhost:5432/botpop_base` 15 | end 16 | end 17 | 18 | -------------------------------------------------------------------------------- /plugins/example.rb: -------------------------------------------------------------------------------- 1 | class MyFury < Botpop::Plugin 2 | include Cinch::Plugin 3 | 4 | match(/!whatkingofanimal.*/, use_prefix: false, method: :exec_whatkingofanimal) 5 | 6 | HELP = ["!whatkingofanimal", "!animallist", "!checkanimal [type]"] 7 | ENABLED = config['enable'].nil? ? false : config['enable'] 8 | CONFIG = config 9 | 10 | def exec_whatkingofanimal m 11 | m.reply "Die you son of a" + ["lion", "pig", "red panda"].sample + " !!" 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /plugins/poilo.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | class Poilo < Botpop::Plugin 4 | include Cinch::Plugin 5 | 6 | match(/^[^!].+/, use_prefix: false, method: :exec_poilo) 7 | 8 | ENABLED = config['enable'].nil? ? false : config['enable'] 9 | SYLLABE = %w(a i o u y oi eau au ou an eu) 10 | CONFIG = config 11 | 12 | def exec_poilo m 13 | word = m.message.split.last 14 | syl = word.split(/[^aeiouy]/).last 15 | m.reply "poil au " + CONFIG["list"][syl] if not syl.nil? 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /plugins/encrypt.rb: -------------------------------------------------------------------------------- 1 | require 'tor257/core' 2 | require 'base64' 3 | 4 | class Encrypt < Botpop::Plugin 5 | include Cinch::Plugin 6 | 7 | match(/^!tor257 (c|d) (\w+) (.+)/, use_prefix: false, method: :exec_tor257) 8 | 9 | HELP = ["!tor257 keyphrase data"] 10 | ENABLED = config['enable'].nil? ? false : config['enable'] 11 | CONFIG = config 12 | 13 | def exec_tor257 m, type, k, d 14 | d = Base64.decode64(d.strip) if type == 'd' 15 | e = Tor257::Message.new(d).encrypt(Tor257::Key.new(k)).to_s 16 | e = Base64.encode64(e) if type == 'c' 17 | m.reply e 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /plugins/diceroller.rb: -------------------------------------------------------------------------------- 1 | require_relative 'dice/Dice' 2 | require_relative 'dice/Weapon' 3 | require_relative 'dice/Character' 4 | require_relative 'dice/Warrior' 5 | 6 | class Diceroller < Botpop::Plugin 7 | include Cinch::Plugin 8 | 9 | match(/!roll (.+)/, use_prefix: false, method: :exec_roll) 10 | 11 | HELP = ["!roll (d20 ...)"] 12 | ENABLED = config['enable'].nil? ? false : config['enable'] 13 | CONFIG = config 14 | 15 | CHARACTER = Warrior.new 10, {bab: [0]} 16 | 17 | def exec_roll(m, roll) 18 | val = Weapon.new(CHARACTER, roll, {hands: 1}, 0).test 19 | m.reply "Roll ... '#{roll}' ... #{val}" 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /plugins/searchable.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | 3 | class Searchable < Botpop::Plugin 4 | include Cinch::Plugin 5 | 6 | ENABLED = config['enable'].nil? ? true : config['enable'] 7 | 8 | VALUES = config.values.map{|e|"!"+e}.join(', ') 9 | KEYS = config.keys.map{|e|"!"+e}.join(', ') 10 | HELP = config.keys.map{|e|"!"+e+" [search]"} 11 | CONFIG = config 12 | match(/\!(#{config.keys.join('|')}) .+/, use_prefix: false, method: :exec_search) 13 | 14 | def exec_search m 15 | msg = Botpop::Builtins.get_msg m 16 | url = CONFIG[m.params[1..-1].join(' ').gsub(/\!([^ ]+) .+/, '\1')] 17 | url = url.gsub('___MSG___', msg) 18 | m.reply url 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /test/test.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | 3 | require "test/unit" 4 | $botpop_include_verbose = false 5 | require_relative "../botpop" 6 | 7 | class TestBotbot < Test::Unit::TestCase 8 | 9 | def test_binding_pry_existence 10 | assert(`grep -R 'binding\\.pry' *.rb plugins/*.rb`.empty?) 11 | end 12 | 13 | def test_classes_constants 14 | assert(Botpop.class == Class) 15 | assert(Botpop::ARGUMENTS) 16 | assert(Botpop::VERSION) 17 | assert(Botpop::CONFIG) 18 | assert(Botpop::TARGET) 19 | assert(Botpop::PluginInclusion.class == Module) 20 | assert(Botpop::Builtins.class == Module) 21 | assert(Botpop::Plugin.class == Class) 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /plugins/iamalive/entry.rb: -------------------------------------------------------------------------------- 1 | require 'sequel' 2 | 3 | class IAmAlive 4 | 5 | class IAAMessage < String 6 | 7 | def initialize *arg 8 | super(*arg) 9 | self.strip! 10 | end 11 | 12 | end 13 | 14 | class Entry < Sequel::Model 15 | def before_create 16 | self.created_at ||= Time.now 17 | self.message.strip! 18 | self.message_origin = self.message 19 | self.message = IAAMessage.new(self.message).to_s 20 | super 21 | end 22 | set_dataset DB[:entries] 23 | 24 | def self.anwser(message) 25 | Entry.where('LOWER(message) = LOWER(?)', m.message.to_iaa_message). 26 | select(:id).all.map(&:id).map{|x| x+1} 27 | end 28 | 29 | end 30 | 31 | end 32 | -------------------------------------------------------------------------------- /plugins/taggle.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | 3 | class Taggle < Botpop::Plugin 4 | include Cinch::Plugin 5 | 6 | match(/!tg (.+)/, use_prefix: false, method: :exec_tg) 7 | 8 | HELP = ["!tg [nick]"] 9 | CONFIG = config(safe: true) || {} 10 | ENABLED = CONFIG['enable'].nil? ? true : CONFIG['enable'] 11 | NTIMES = CONFIG['ntimes'] || 10 12 | WAIT = CONFIG['wait'] || 0.3 13 | 14 | def cmd_allowed? m 15 | return Base.cmd_allowed? m, ["tg"] 16 | end 17 | 18 | def exec_tg m, who 19 | return if not cmd_allowed? m 20 | @@tg_lock ||= Mutex.new 21 | @@tg_lock.lock 22 | begin 23 | NTIMES.times do 24 | User(who).send("tg #{who}") 25 | sleep WAIT 26 | end 27 | ensure 28 | @@tg_lock.unlock 29 | end 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /plugins/kick.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | 3 | class Kick < Botpop::Plugin 4 | include Cinch::Plugin 5 | 6 | match(/!k (.+)/, use_prefix: false, method: :exec_kick) 7 | match(/!kick (.+)/, use_prefix: false, method: :exec_kick) 8 | match(/!k ([^|]+)\|(.+)/, use_prefix: false, method: :exec_kick_message) 9 | match(/!kick ([^|]+)\|(.+)/, use_prefix: false, method: :exec_kick_message) 10 | 11 | HELP = ["!kick nickname "] 12 | ENABLED = config['enable'].nil? ? true : config['enable'] 13 | CONFIG = config 14 | 15 | def exec_kick m, victim 16 | len = CONFIG["list"].length - 1 17 | msg = CONFIG["list"][rand(0..len)] 18 | m.channel.kick(victim, msg) 19 | m.reply "Bye bye " + victim 20 | end 21 | 22 | def exec_kick_message m, victim, reason 23 | m.channel.kick(victim, reason) 24 | m.reply "Bye bye " + victim 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /plugins/youtube.rb: -------------------------------------------------------------------------------- 1 | require 'mechanize' 2 | 3 | class Youtube < Botpop::Plugin 4 | include Cinch::Plugin 5 | 6 | match(/!yt (.+)/, use_prefix: false, method: :find_youtube_video) 7 | 8 | HELP = ["!yt title"] 9 | ENABLED = config['enable'].nil? ? false : config['enable'] 10 | CONFIG = config 11 | 12 | private 13 | def search_url(title) 14 | CONFIG['search_url'].gsub('___MSG___', title) 15 | end 16 | def reduce_url(url) 17 | CONFIG['reduce_url'].gsub('___ID___', url.gsub(/^(.+)(v=)(\w+)$/, '\3')) 18 | end 19 | def display(result) 20 | CONFIG['display'] 21 | .gsub('___TITLE___', result[:title]) 22 | .gsub('___URL___', reduce_url(result[:url])) 23 | end 24 | public 25 | 26 | def find_youtube_video m, title 27 | e = Mechanize.new 28 | e.get(search_url(title)) 29 | result = { 30 | title: e.page.at(".item-section li").at('h3').text, 31 | url: e.page.at(".item-section li").at('a')[:href], 32 | } 33 | m.reply display(result) 34 | end 35 | 36 | end 37 | -------------------------------------------------------------------------------- /botpop_plugin_inclusion.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #encoding: utf-8 3 | 4 | module PluginInclusion 5 | def self.plugin_error_failure! e, f 6 | STDERR.puts "Error during loading the file #{f}".red 7 | STDERR.puts "#{e.class}: #{e.message}".red.bold 8 | STDERR.puts "---- Trace ----" 9 | STDERR.puts e.backtrace.join("\n").black.bold 10 | exit 1 11 | end 12 | 13 | def self.plugin_include! f 14 | return false if File.basename(f) == "example.rb" 15 | begin 16 | if $botpop_include_verbose != false 17 | puts "Loading plugin file ... " + f.green + " ... " + require_relative(f).to_s 18 | else 19 | require_relative(f) 20 | end 21 | rescue => e 22 | plugin_error_failure! e, f 23 | end 24 | end 25 | 26 | # THEN INCLUDE THE PLUGINS (STATE MAY BE DEFINED BY THE PREVIOUS CONFIG) 27 | def self.plugins_include! arguments 28 | Dir[File.expand_path '*.rb', arguments.plugin_directory].each do |f| 29 | if !arguments.disable_plugins.include? f 30 | plugin_include! f 31 | end 32 | end 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /plugins/eip.rb: -------------------------------------------------------------------------------- 1 | class Eip < Botpop::Plugin 2 | include Cinch::Plugin 3 | 4 | match(/!eip add (.*)/, use_prefix: false, method: :exec_add) 5 | match(/!eip ls/, use_prefix: false, method: :exec_ls) 6 | match(/!eip (\d+)/, use_prefix: false, method: :exec_id) 7 | 8 | HELP = ["!eip add ...", "!eip ls", "!eip id"] 9 | ENABLED = config['enable'].nil? ? false : config['enable'] 10 | CONFIG = config 11 | 12 | def exec_add(m, title) 13 | begin 14 | Base::DB[:eips].insert(author: m.user.authname, 15 | title: title, 16 | created_at: Time.now) 17 | m.reply "Ok ! #{title} is a new eip" 18 | rescue 19 | m.reply "Err" 20 | end 21 | end 22 | 23 | def exec_id(m, id) 24 | all = Base::DB[:eips].where(id: Integer(id)).first 25 | m.reply all[:title] rescue m.reply("no such id") 26 | end 27 | 28 | def exec_ls(m) 29 | all = Base::DB[:eips].limit(3).reverse.all 30 | all.each{|e| m.reply e[:title]} 31 | n = Base::DB[:eips].count 32 | m.reply("There is #{n} elements") 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /builtins.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | class Botpop 4 | module Builtins 5 | 6 | def self.dos(ip, duration) 7 | `timeout #{duration} hping --flood '#{ip}' 2>&1` 8 | end 9 | 10 | def self.ping(ip) 11 | Net::Ping::External.new(ip).ping? 12 | end 13 | 14 | def self.intra_state 15 | Net::Ping::External.new("intra.epitech.eu").ping? ? "Intra ok" : "Intra down" 16 | end 17 | 18 | def self.trace ip 19 | `tracepath '#{ip}'`.to_s.split("\n") 20 | end 21 | 22 | def self.get_msg m 23 | URI.encode(m.params[1..-1].join(' ').gsub(/\![^ ]+ /, '')) 24 | end 25 | 26 | def self.get_ip m 27 | m.params[1..-1].join(' ').gsub(/\![^ ]+ /, '').gsub(/[^[:alnum:]\-\_\.]/, '') 28 | end 29 | 30 | def self.get_ip_from_nick m 31 | nick = get_ip m 32 | ip = m.target.users.keys.find{|u| u.nick == nick rescue nil}.host rescue nil 33 | return {nick: nick, ip: ip} 34 | end 35 | 36 | end 37 | end 38 | 39 | class MissingConfigurationZone < RuntimeError 40 | end 41 | 42 | class MissingConfigurationEntry < RuntimeError 43 | end 44 | -------------------------------------------------------------------------------- /plugins/iamalive/README.md: -------------------------------------------------------------------------------- 1 | # I Am Alive 2 | 3 | I am alive is a plugin, that allows to bot to answer to me :) 4 | 5 | ## Initialization 6 | 7 | ### Database 8 | 9 | Firstly, create the database and migrate it. To do this, use the following command. 10 | In the ``modules_config.yml`` file, configure it for your engine. 11 | As it use sequel engine, it is compatible with sqlite, mysql, postgres, etc. 12 | Checkout the [sequel documentaiton](http://sequel.jeremyevans.net/documentation.html) for more informations. 13 | Then, execute one of two: 14 | 15 | ```bash 16 | sequel -m plugins/iamalive/migrations sqlite://plugins/iamalive/db.sqlite3 17 | sequel -m plugins/iamalive/migrations postgres://root:toor@localhost:5432/botpop_iamalive 18 | ... 19 | ``` 20 | 21 | You can change the name of the database via the global configuration file (see the example). 22 | 23 | ### User / Admin 24 | 25 | Only authorized users have the rights to administrate the iaa plugin. 26 | Only when there is no administrator (by default), you can use the command "!iaa user add NICK" to add your NICK to the database. 27 | Be sure you have a protected identity. 28 | 29 | Then, only administrators can add / remove admin from the list. 30 | -------------------------------------------------------------------------------- /plugins/root_me.rb: -------------------------------------------------------------------------------- 1 | require "base64" 2 | 3 | class RootMe < Botpop::Plugin 4 | include Cinch::Plugin 5 | 6 | FloatRegexp = "\d+(\.\d+)?" 7 | match(/!ep1 (\w+)/, use_prefix: false, method: :start_ep1) 8 | match(/!ep2 (\w+)/, use_prefix: false, method: :start_ep2) 9 | match(/^(#{FloatRegexp}) ?\/ ?(#{FloatRegexp})$/, use_prefix: false, method: :play_ep1) 10 | match(/^(\d+) ?\/ ?(\d+)$/, use_prefix: false, method: :play_ep1) 11 | match(/^(\w+)$/, use_prefix: false, method: :play_ep2) 12 | 13 | ENABLED = config['enable'].nil? ? false : config['enable'] 14 | CONFIG = config 15 | 16 | def start_ep1 m, botname 17 | bot = User(botname) 18 | bot.send "!ep1" 19 | end 20 | 21 | def start_ep2 m, botname 22 | bot = User(botname) 23 | bot.send "!ep2" 24 | end 25 | 26 | def play_ep1 m, n1, n2 27 | r = n1.to_f**(0.5) * n2.to_f 28 | str = "!ep1 -rep #{r.round 2}" 29 | puts str 30 | m.reply str 31 | # response will be logged by the bot, check the log 32 | end 33 | 34 | def play_ep2 m, b64 35 | r = Base64.decode64(b64) 36 | str = "!ep2 -rep #{r}" 37 | puts str 38 | m.reply str 39 | # response will be logged by the bot, check the log 40 | end 41 | 42 | end 43 | -------------------------------------------------------------------------------- /plugins/ops.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | 3 | class Ops < Botpop::Plugin 4 | include Cinch::Plugin 5 | 6 | match(/!op/, use_prefix: false, method: :exec_op) 7 | match(/!op (.+)/, use_prefix: false, method: :exec_op_other) 8 | match(/!deop/, use_prefix: false, method: :exec_deop) 9 | match(/!deop (.+)/, use_prefix: false, method: :exec_deop_other) 10 | match(/!v/, use_prefix: false, method: :exec_voice) 11 | match(/!v (.+)/, use_prefix: false, method: :exec_voice_other) 12 | match(/!dv/, use_prefix: false, method: :exec_devoice) 13 | match(/!dv (.+)/, use_prefix: false, method: :exec_devoice_other) 14 | 15 | HELP = ["!op ", "!deop "] 16 | ENABLED = config['enable'].nil? ? true : config['enable'] 17 | CONFIG = config 18 | 19 | def exec_op m 20 | m.channel.op(m.user) 21 | end 22 | 23 | def exec_op_other m, other 24 | m.channel.op(other) 25 | end 26 | 27 | def exec_deop m 28 | m.channel.deop(m.user) 29 | end 30 | 31 | def exec_deop_other m, other 32 | m.channel.deop(other) 33 | end 34 | 35 | def exec_voice m 36 | m.channel.voice(m.user) 37 | end 38 | 39 | def exec_voice_other m, other 40 | m.channel.voice(other) 41 | end 42 | 43 | def exec_devoice m 44 | m.channel.devoice(m.user) 45 | end 46 | 47 | def exec_devoice_other m, other 48 | m.channel.devoice(other) 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /plugins/anecdote.rb: -------------------------------------------------------------------------------- 1 | require 'nokogiri' 2 | require 'net/http' 3 | 4 | class Anecdote < Botpop::Plugin 5 | include Cinch::Plugin 6 | 7 | match(/!a(necdote)? (.+)/, use_prefix: false, method: :exec_new) 8 | 9 | HELP = ["!anecdote <...>"] 10 | ENABLED = config['enable'].nil? ? false : config['enable'] 11 | CONFIG = config 12 | 13 | def exec_new m, _, s 14 | s.downcase! 15 | f = I18n.transliterate(s)[0] 16 | x = "Après je vous propose " 17 | x += (%w(a e i o u y).include?(f) ? "d'" : "de ") if not s.match(/^(d'|de ).+/) 18 | s = x + s 19 | url = URI.parse 'http://memegenerator.net/create/instance' 20 | post_data = { 21 | 'imageID' => 14185932, 22 | 'generatorID' => 5374051, 23 | 'watermark1' => 1, 24 | 'uploadtoImgur' => 'true', 25 | 'text0' => s, 26 | 'text1' => "Ca fera une petite anecdote !!", 27 | } 28 | meme = nil 29 | begin 30 | Net::HTTP.start url.host do |http| 31 | post = Net::HTTP::Post.new url.path 32 | post.set_form_data post_data 33 | res = http.request post 34 | location = res['Location'] 35 | redirect = url + location 36 | get = Net::HTTP::Get.new redirect.request_uri 37 | res = http.request get 38 | doc = Nokogiri.HTML res.body 39 | meme = doc.css("meta")[7]['content'] 40 | end 41 | rescue => _err 42 | end 43 | m.reply meme ? meme : "Achtung ! ACHTUUUUNG !!!" 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | cinch (2.2.7) 5 | coderay (1.1.0) 6 | colorize (0.7.7) 7 | domain_name (0.5.25) 8 | unf (>= 0.0.5, < 1.0.0) 9 | htauth (2.0.0) 10 | http-cookie (1.0.2) 11 | domain_name (~> 0.5) 12 | i18n (0.7.0) 13 | mechanize (2.7.3) 14 | domain_name (~> 0.5, >= 0.5.1) 15 | http-cookie (~> 1.0) 16 | mime-types (~> 2.0) 17 | net-http-digest_auth (~> 1.1, >= 1.1.1) 18 | net-http-persistent (~> 2.5, >= 2.5.2) 19 | nokogiri (~> 1.4) 20 | ntlm-http (~> 0.1, >= 0.1.1) 21 | webrobots (>= 0.0.9, < 0.2) 22 | method_source (0.8.2) 23 | mime-types (2.6.2) 24 | mini_portile2 (2.8.0) 25 | net-http-digest_auth (1.4) 26 | net-http-persistent (2.9.4) 27 | net-ping (1.7.8) 28 | nokogiri (1.13.9) 29 | mini_portile2 (~> 2.8.0) 30 | racc (~> 1.4) 31 | nomorebeer (1.1) 32 | ntlm-http (0.1.1) 33 | pg (0.18.3) 34 | pry (0.10.3) 35 | coderay (~> 1.1.0) 36 | method_source (~> 0.8.1) 37 | slop (~> 3.4) 38 | racc (1.6.0) 39 | sequel (4.27.0) 40 | slop (3.6.0) 41 | tor257 (0.2) 42 | nomorebeer (~> 1.1) 43 | unf (0.1.4) 44 | unf_ext 45 | unf_ext (0.0.7.1) 46 | webrobots (0.1.1) 47 | 48 | PLATFORMS 49 | ruby 50 | 51 | DEPENDENCIES 52 | cinch 53 | colorize 54 | htauth 55 | i18n 56 | mechanize 57 | net-ping 58 | nomorebeer 59 | pg 60 | pry 61 | sequel 62 | tor257 63 | 64 | BUNDLED WITH 65 | 1.11.2 66 | -------------------------------------------------------------------------------- /plugins/dice/Dice.rb: -------------------------------------------------------------------------------- 1 | class FrozenDice 2 | attr_reader :min, :max, :values, :nb, :faces 3 | 4 | def initialize arg 5 | if arg.is_a? String 6 | v = arg.match(/^(?\d+)d(?\d+)$/i) 7 | if v 8 | set_rolldice v 9 | else 10 | raise ArgumentError unless arg.match(/^\d+$/) 11 | set_value arg.to_i 12 | end 13 | elsif arg.is_a? Integer 14 | set_value arg 15 | else 16 | raise ArgumentError 17 | end 18 | end 19 | 20 | def throw 21 | @nb.times.map{ rand(@values) } 22 | end 23 | 24 | def test 25 | self.throw.inject(&:+) 26 | end 27 | 28 | def mean 29 | v = values.to_a 30 | if v.size % 2 == 0 31 | (v[v.size / 2 - 1] + v[v.size / 2]) * 0.5 32 | else 33 | v[v.size / 2] 34 | end 35 | end 36 | 37 | private 38 | def set_rolldice v 39 | @nb, @faces = v[:nb].to_i, v[:faces].to_i 40 | @max = @faces 41 | @min = 1 42 | @values = @min..@max 43 | end 44 | 45 | def set_value v 46 | @nb = 1 47 | @faces = v 48 | @min = @faces 49 | @max = @faces 50 | @values = @min..@max 51 | end 52 | 53 | end 54 | 55 | class Dice 56 | attr_accessor :bonus, :dices 57 | 58 | def initialize *arg 59 | @dices = [] 60 | arg.each do |a1| 61 | a1.gsub!(" ", "") 62 | a1.split(/[+ ]/).each do |a2| 63 | @dices << FrozenDice.new(a2) 64 | end 65 | end 66 | end 67 | 68 | def min 69 | @dices.map do |dice| 70 | dice.min 71 | end 72 | end 73 | 74 | def mean 75 | @dices.map do |dice| 76 | dice.mean 77 | end 78 | end 79 | 80 | def max 81 | @dices.map do |dice| 82 | dice.max 83 | end 84 | end 85 | 86 | def throw 87 | @dices.map do |dice| 88 | dice.throw 89 | end 90 | end 91 | 92 | def test 93 | @dices.map do |dice| 94 | dice.test 95 | end.inject(&:+) 96 | end 97 | 98 | end 99 | -------------------------------------------------------------------------------- /plugins/base/user.rb: -------------------------------------------------------------------------------- 1 | class Base 2 | 3 | def find_and_exec(m, name) 4 | u = User.where(name: name).first 5 | if u 6 | yield u 7 | else 8 | m.reply "No such user '#{name}'" 9 | end 10 | end 11 | 12 | def self.cmd_allowed? m, groups=["admin"], verbose=true 13 | user = User.where(name: m.user.authname).where("groups @> '{#{groups.join(',')}}'").first 14 | if user.nil? 15 | m.reply "No authorized" if verbose 16 | return false 17 | else 18 | return true 19 | end 20 | end 21 | 22 | def cmd_allowed? m, groups=["admin"], verbose=true 23 | Base.cmd_allowed?(m, groups, verbose) 24 | end 25 | 26 | def user_register m 27 | return m.reply "You are not connected" if m.user.authname.nil? 28 | begin 29 | admin = (User.count == 0) 30 | u = User.create(name: m.user.authname, 31 | admin: admin, 32 | groups: [admin ? 'admin' : 'default']) 33 | m.reply "Welcome ##{u.id} #{u.name}" 34 | rescue => _ 35 | m.reply "Cannot register #{m.user.authname}" 36 | end 37 | end 38 | 39 | def user_ls m 40 | c = User.count 41 | m.reply User.limit(20).all.map(&:name).join(', ') 42 | if c > 20 43 | m.reply "And #{c-20} more" 44 | end 45 | end 46 | 47 | def user_group_ls m, name 48 | cmd_allowed? m 49 | find_and_exec(m, name) do |u| 50 | m.reply u.groups.join(', ') 51 | end 52 | end 53 | 54 | def user_group_add m, name, group 55 | cmd_allowed? m 56 | find_and_exec(m, name) do |u| 57 | u.update(groups: (u.groups + [group])) 58 | m.reply "group #{group} added to #{u.name}" 59 | end 60 | end 61 | 62 | def user_group_rm m, name, group 63 | cmd_allowed? m 64 | find_and_exec(m, name) do |u| 65 | u.update(groups: (u.groups - [group])) 66 | m.reply "group #{group} removed from #{u.name}" 67 | end 68 | end 69 | 70 | end 71 | -------------------------------------------------------------------------------- /DATABASE_EXTENSION.md: -------------------------------------------------------------------------------- 1 | # Database Extension 2 | 3 | ## Plugin Database Extension 4 | 5 | You can configure a database to store a large amount of volatiles informations, 6 | like the users rights, etc. 7 | To do it, there is an extension, ready to be used. 8 | 9 | ### 1. configure the database access 10 | 11 | for exemple, in the ``modules_config.yml``: 12 | ```yaml 13 | plugin: 14 | database: 15 | adapter: postgres 16 | host: localhost 17 | port: 5432 18 | user: root 19 | password: toor 20 | database: botpop_db 21 | ``` 22 | 23 | *note: you can also configure it in a specific database file. 24 | In these case, adapt the following code.* 25 | 26 | Then, in you plugin, add the following code: 27 | 28 | ```ruby 29 | class Plugin < Botpop::Plugin 30 | include Cinch::Plugin 31 | include Botpop::Plugin::Database # include the extension 32 | 33 | ... 34 | if ENABLED 35 | DB_CONFIG = self.db_config = config(safe: true)['database'] 36 | DB = self.db_connect! 37 | require_relative 'plugin/model' # if you have a model, include it now 38 | end 39 | 40 | end 41 | ``` 42 | 43 | ### 2. create the database and tables 44 | It can be done via 2 ways: 45 | 46 | - migrations: **recommanded**. 47 | This is safer and more reliable. 48 | There is an example in the plugin [iamalive](plugins/iamalive/). 49 | Checkout the documentation of the orm: 50 | [sequel migrations](http://sequel.jeremyevans.net/rdoc/files/doc/migration_rdoc.html). 51 | - manual: **NOT recommanded**. 52 | Create the database and tables manually. 53 | 54 | ### 3. use it 55 | 56 | You can access to the database via the constant ``DB`` 57 | 58 | ```ruby 59 | class Plugin ... 60 | ... 61 | def search_word m, word 62 | found = DB[:words].where(word: word).first 63 | m.reply found ? found[:id] : 'no such word' 64 | end 65 | end 66 | ``` 67 | 68 | ### 4. models 69 | 70 | If you want to use models, don't forget to set the "dataset" 71 | (association with the right database / table) 72 | to avoid conflicts: 73 | 74 | ```ruby 75 | class Model < Sequel::Model 76 | set_dataset DB[:admins] 77 | end 78 | ``` 79 | -------------------------------------------------------------------------------- /plugins/log.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | 3 | class Log < Botpop::Plugin 4 | include Cinch::Plugin 5 | 6 | match /users$/, use_prefix: true, method: :exec_list_user 7 | match /remove .+$/, use_prefix: true, method: :exec_remove_user 8 | match /add .+$/, use_prefix: true, method: :exec_add_user 9 | match /clean$/, use_prefix: true, method: :exec_clean 10 | match /status$/, use_prefix: true, method: :exec_status 11 | match /enable$/, use_prefix: true, method: :exec_log_enable 12 | match /.+/, use_prefix: false, method: :exec_log 13 | 14 | HELP = ["!log enable", "!log add", "!log remove", "!log users", "!log clean", "!log status"] 15 | CONFIG = config 16 | ENABLED = CONFIG['enable'].nil? ? false : CONFIG['enable'] 17 | USER_CONFIG = "plugins/log_user.yml" 18 | USERS = YAML.load_file(USER_CONFIG) || raise(MissingConfigurationZone, USER_CONFIG) 19 | 20 | @@log_user_list = USERS["list"] 21 | @@log_enabled = CONFIG["default_started"] 22 | 23 | def exec_list_user m 24 | m.reply @@log_user_list.join(", ") 25 | m.reply "no log admin" if @@log_user_list.empty? 26 | end 27 | 28 | def exec_remove_user m 29 | return unless is_admin? m 30 | m.message.gsub("!log add ", "").split(" ").each do |name| 31 | @@log_user_list.delete name unless USERS["list"].include?(name) 32 | end 33 | end 34 | 35 | def exec_add_user m 36 | return unless is_admin? m 37 | @@log_user_list += m.message.gsub("!log add ", "").split(" ") 38 | @@log_user_list.uniq! 39 | end 40 | 41 | def exec_log_enable m 42 | @@log_enabled = !@@log_enabled 43 | exec_status m 44 | end 45 | 46 | def exec_status m 47 | m.reply "Log #{@@log_enabled ? :enabled : :disabled}" 48 | end 49 | 50 | def exec_clean m 51 | return unless is_admin? m 52 | File.delete(CONFIG["file"]) rescue nil 53 | end 54 | 55 | def exec_log m 56 | log(m) if @@log_enabled 57 | end 58 | 59 | private 60 | def log m 61 | File.open(CONFIG["file"], 'a') {|f| f << (m.user.to_s + ": " + m.message + "\n")} 62 | end 63 | 64 | def is_admin? m 65 | @@log_user_list.include? m.user.to_s 66 | end 67 | 68 | end 69 | -------------------------------------------------------------------------------- /plugins/cequetudisnaaucunsens.rb: -------------------------------------------------------------------------------- 1 | class CeQueTuDisNAAucunSens < Botpop::Plugin 2 | include Cinch::Plugin 3 | 4 | match(/^[^!].+$/, use_prefix: false, method: :say_random_sentence) 5 | match(/^!random_sentence register ([^|]+)\|(.+)/, use_prefix: false, method: :register_trigger) 6 | match(/^!random_sentence remove (.+)/, use_prefix: false, method: :remove_trigger) 7 | 8 | HELP = ["!random_sentence register trigger | content", 9 | "!random_sentence remove trigger" ] 10 | ENABLED = config['enable'].nil? ? false : config['enable'] 11 | CONFIG = config 12 | 13 | def cmd_allowed? m 14 | return Base.cmd_allowed? m, ["random_sentence"] 15 | end 16 | 17 | def say_random_sentence m 18 | trigger = I18n.transliterate(m.message).strip 19 | r = Base::DB[:random_sentences].where(enabled: true).where('? ~* "trigger"', trigger).select(:content).first 20 | return if r.nil? 21 | m.reply r[:content].split(' ').shuffle.join(' ') 22 | end 23 | 24 | def say_random m 25 | m.reply %w(ce que tu dis n'a aucun sens).shuffle.join(' ') 26 | end 27 | 28 | def register_trigger m, t, c 29 | return if not cmd_allowed? m 30 | t = t.triggerize 31 | begin 32 | Base::DB[:random_sentences].insert(trigger: t, 33 | content: c.strip, 34 | author: m.user.authname, 35 | created_at: Time.now.utc) 36 | m.reply "The trigger \"#{t.strip}\" will raise \"#{c.strip}\"" 37 | rescue => _err 38 | m.reply "Error. Cannot register this trigger" 39 | m.reply _err 40 | end 41 | end 42 | 43 | def remove_trigger m, t 44 | return if not cmd_allowed? m 45 | t = t.triggerize 46 | n = Base::DB[:random_sentences].where(trigger: t).delete 47 | m.reply "Deleted #{n} trigger" 48 | end 49 | 50 | end 51 | 52 | class String 53 | def triggerize 54 | t = self.dup 55 | t = I18n.transliterate(t).strip 56 | t = Regexp.quote(t) 57 | t.gsub!(/((a)a+)/i, '\2') # ... i know :( 58 | t.gsub!(/((e)e+)/i, '\2') 59 | t.gsub!(/((i)i+)/i, '\2') 60 | t.gsub!(/((o)o+)/i, '\2') 61 | t.gsub!(/((u)u+)/i, '\2') 62 | t.gsub!(/((y)y+)/i, '\2') 63 | t.gsub!(/([aeiouy])/, '\1+') 64 | # TODO: not only " " but also ponctuation etc. 65 | t = "^(.* )?#{t}( .*)?$" 66 | return t 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /plugins/dice/Weapon.rb: -------------------------------------------------------------------------------- 1 | require_relative 'Dice' 2 | require_relative 'Warrior' 3 | 4 | class Weapon 5 | attr_reader :from, :degats, :opt, :bonus 6 | 7 | def initialize from, degats, opt, bonus, attack_opt={} 8 | @from = from 9 | @bonus = from.bab.map{|e| e + bonus} 10 | @degats = Dice.new(degats + "+#{(from.bstr * 1.5).ceil}") 11 | @hands = opt[:hands] || 1 12 | @max = attack_opt[:max] || Float::INFINITY 13 | end 14 | 15 | def min 16 | @degats.min 17 | end 18 | 19 | def max 20 | @degats.max 21 | end 22 | 23 | def mean 24 | @degats.mean 25 | end 26 | 27 | def test 28 | @degats.test 29 | end 30 | 31 | def mean_p(ca=20.0) 32 | d = @degats.mean.inject(&:+) 33 | p(ca).map do |b| 34 | (b * d).round(4) 35 | end 36 | end 37 | 38 | def p(ca=20.0) 39 | @bonus.map do |b| 40 | ((b + from.bstr) / ca.to_f).round(4) 41 | end 42 | end 43 | 44 | def mean_p_total(ca=20.0) 45 | mean_p(ca).inject(&:+).round(4) 46 | end 47 | 48 | def to_s(ca=20) 49 | "mean: #{mean} * #{p(ca)} => #{mean_p(ca)} = #{mean_p_total(ca)}" 50 | end 51 | 52 | end 53 | 54 | if __FILE__ == $0 55 | alteration = 2 56 | taille = -2 57 | bonus = alteration + taille 58 | 59 | epees = [] 60 | normal = Warrior.new 18, {bab: [7, 1]} 61 | epees << ["normal", Weapon.new(normal, "4d6+2", {hands: 2}, bonus)] 62 | 63 | rage = Warrior.new 19+4, {bab: [7, 1]} 64 | epees << ["rage", Weapon.new(rage, "4d6+2", {hands: 2}, bonus)] 65 | 66 | fren = Warrior.new 19+6, {bab: [7, 1]} 67 | epees << ["frenesie", Weapon.new(fren, "4d6+2", {hands: 2}, bonus)] 68 | 69 | ra_fr = Warrior.new 19+4+6, {bab: [7, 7, 1]} 70 | epees << ["rage+frenesie", Weapon.new(ra_fr, "4d6+2", {hands: 2}, bonus)] 71 | 72 | ra_fr_so = Warrior.new 19+6+4, {bab: [7, 7, 1]} 73 | epees << ["rage+frenesie+sorciere", Weapon.new(ra_fr_so, "4d6+2+5+1d6", {hands: 2}, bonus)] 74 | 75 | ra_fr_so_buff = Warrior.new 19+6+4+4, {bab: [7, 7, 1]} 76 | epees << ["rage+frenesie+sorciere+taureau+benediction", Weapon.new(ra_fr_so, "4d6+2+5+1d6", {hands: 2}, bonus+1)] 77 | 78 | ra_fr_so_buff_char = Warrior.new 19+6+4+4, {bab: [7, 7]} 79 | epees << ["rage+frenesie+sorciere+taureau+benediction+charge", Weapon.new(ra_fr_so, "4d6+2+5+1d6", {hands: 2}, bonus+1+2, {max: 2})] 80 | 81 | epees = Hash[epees] 82 | require 'pry' 83 | binding.pry 84 | end 85 | -------------------------------------------------------------------------------- /botpop.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #encoding: utf-8 3 | 4 | if RUBY_VERSION.split('.').first.to_i == 1 5 | raise RuntimeError, "#{__FILE__} is not compatible with Ruby 1.X." 6 | end 7 | 8 | require 'cinch' 9 | require 'uri' 10 | require 'net/ping' 11 | require 'pry' 12 | require 'yaml' 13 | require 'colorize' 14 | 15 | require_relative 'arguments' 16 | require_relative 'botpop_plugin_inclusion' 17 | require_relative 'builtins' 18 | require_relative 'database' 19 | 20 | class Botpop 21 | 22 | class Plugin 23 | def self.config(infos={}) 24 | name = (infos[:name] || self.to_s.downcase).to_s 25 | config = Botpop::CONFIG[name] 26 | return config || (raise(MissingConfigurationZone, self.to_s) unless infos[:safe]) 27 | end 28 | end 29 | 30 | def self.load_version 31 | begin 32 | return IO.read('version') 33 | rescue Errno::ENOENT 34 | puts "No version specified".red 35 | return "???" 36 | end 37 | end 38 | 39 | def self.include_plugins 40 | PluginInclusion.plugins_include! ARGUMENTS 41 | end 42 | 43 | def self.load_plugins 44 | Module.constants.select{ |m| 45 | (m = Module.const_get(m) rescue false) and 46 | (m.is_a?(Class)) and 47 | (m.ancestors.include?(Plugin)) and 48 | (m.included_modules.include?(Cinch::Plugin)) 49 | }. 50 | select{|m| not ARGUMENTS.disable_plugins.include? m.to_s 51 | }. 52 | map{|m| Module.const_get(m) 53 | }. 54 | select{|m| m::ENABLED} 55 | end 56 | 57 | # FIRST LOAD THE CONFIGURATION 58 | ARGUMENTS = Arguments.new(ARGV) 59 | VERSION = load_version() 60 | CONFIG = YAML.load_file(ARGUMENTS.config_file) 61 | TARGET = /[[:alnum:]_\-\.]+/ 62 | include_plugins() 63 | PLUGINS = load_plugins() 64 | 65 | def start 66 | @engine.start 67 | end 68 | 69 | def initialize 70 | @engine = Cinch::Bot.new do 71 | configure do |c| 72 | c.server = ARGUMENTS.server 73 | c.channels = ARGUMENTS.channels 74 | c.ssl.use = ARGUMENTS.ssl 75 | c.port = ARGUMENTS.port 76 | c.user = ARGUMENTS.user 77 | c.nick = ARGUMENTS.nick 78 | c.plugins.plugins = PLUGINS 79 | end 80 | end 81 | end 82 | 83 | end 84 | 85 | if __FILE__ == $0 86 | $bot = Botpop.new 87 | trap("SIGINT") do 88 | puts "\b" 89 | puts "SIGINT Catched" 90 | exit 91 | end 92 | $bot.start 93 | end 94 | -------------------------------------------------------------------------------- /arguments.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | class Arguments 3 | 4 | # @param name [String] the option to search 5 | # @param name [Array] the options to search (multiples keys avaliables) 6 | def get_one_argument(name, default_value) 7 | if name.is_a? String 8 | i = @argv.index(name) 9 | elsif name.is_a? Array 10 | i = nil 11 | name.each{|n| i ||= @argv.index(n) } 12 | else 13 | raise ArgumentError, "name must be an Array or a String, not #{name.class}" 14 | end 15 | return default_value if i.nil? 16 | value = @argv[i + 1] 17 | return value.empty? ? default_value : value 18 | end 19 | 20 | def initialize argv 21 | @argv = argv 22 | i = 0 23 | debugvars = [] 24 | argv = @argv.dup 25 | while i 26 | i1 = argv.index('--debug') 27 | i2 = argv.index('-d') 28 | i = i1 ? i2 ? i1 < i2 ? i1 : i2 : i1 : i2 29 | if i 30 | debugvars << argv[i + 1] 31 | argv = argv[(i+2)..(-1)] 32 | end 33 | end 34 | debugvars.each{|dv| eval("$debug_#{dv}=true")} 35 | end 36 | 37 | DEFAULT_SERVER = 'irc.freenode.org' 38 | def server 39 | get_one_argument ['--ip', '-s'], DEFAULT_SERVER 40 | end 41 | 42 | def channels 43 | i = @argv.index('--channels') || @argv.index('-c') 44 | return ['#equilibre'] if i.nil? 45 | chans = @argv[(i+1)..-1] 46 | i = chans.index{|c| c[0] == '-'} 47 | i = i.nil? ? -1 : i - 1 48 | chans = chans[0..i] 49 | return chans.map{|c| c[0] == '#' ? c : "##{c}"} 50 | end 51 | 52 | def port 53 | if ssl and not @argv.index('--port') and not @argv.index('-p') 54 | return 7000 55 | else 56 | i = @argv.index('--port') || @argv.index('-p') 57 | return 6667 if i.nil? 58 | return @argv[i + 1].to_i 59 | end 60 | end 61 | 62 | def ssl 63 | return !@argv.include?('--no-ssl') 64 | end 65 | 66 | DEFAULT_NICK = 'cotcot' 67 | def nick 68 | get_one_argument ['--nick', '-n', '--user', '-u'], DEFAULT_NICK 69 | end 70 | 71 | def user 72 | get_one_argument ['--user', '-u', '--nick', '-n'], DEFAULT_NICK 73 | end 74 | 75 | DEFAULT_CONFIG = "modules_config.yml" 76 | def config_file 77 | get_one_argument ['--config'], DEFAULT_CONFIG 78 | end 79 | 80 | DEFAULT_PLUGIN_DIR = 'plugins' 81 | def plugin_directory 82 | get_one_argument ['--plugin_directory'], DEFAULT_PLUGIN_DIR 83 | end 84 | 85 | def disable_plugins 86 | plugins = [] 87 | argv = @argv.dup 88 | loop do 89 | i = argv.index('--plugin-disable') 90 | break unless i 91 | plugins << @argv[i + 1] 92 | argv = argv[(i+2)..(-1)] 93 | end 94 | return plugins 95 | end 96 | 97 | end 98 | -------------------------------------------------------------------------------- /plugins/base.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | 3 | require "i18n" 4 | I18n.config.available_locales = [:en, :fr] 5 | 6 | class Base < Botpop::Plugin 7 | include Cinch::Plugin 8 | include Botpop::Plugin::Database 9 | 10 | match(/^!troll .+/ , use_prefix: false, method: :exec_troll) 11 | match "!version" , use_prefix: false, method: :exec_version 12 | match "!code" , use_prefix: false, method: :exec_code 13 | match "!cmds" , use_prefix: false, method: :exec_help 14 | match "!help" , use_prefix: false, method: :exec_help 15 | match(/^!help \w+/ , use_prefix: false, method: :exec_help_plugin) 16 | 17 | match("!register", use_prefix: false, method: :user_register) 18 | match("!user ls", use_prefix: false, method: :user_ls) 19 | match(/^!user (\w+) group ls$/, use_prefix: false, method: :user_group_ls) 20 | match(/^!user (\w+) group add (\w+)/, use_prefix: false, method: :user_group_add) 21 | match(/^!user (\w+) group rm (\w+)/, use_prefix: false, method: :user_group_rm) 22 | 23 | HELP = ["!troll [msg]", "!version", "!code", "!help [plugin]", "!cmds", 24 | "!user ls", "!user group [group]"] 25 | ENABLED = config['enable'].nil? ? true : config['enable'] 26 | CONFIG = config 27 | 28 | if ENABLED 29 | DB_CONFIG = self.db_config = CONFIG['database'] 30 | DB = self.db_connect! 31 | DB.extension :pg_array 32 | require_relative 'base/UserModel' 33 | require_relative 'base/user' 34 | end 35 | 36 | def help_wait_before_quit 37 | HELP_WAIT_DURATION.times do 38 | sleep 1 39 | @@help_time += 1 40 | end 41 | end 42 | 43 | def help_get_plugins_str 44 | ["Plugins found : " + Botpop::PLUGINS.size.to_s] + 45 | Botpop::PLUGINS.map do |plugin| 46 | plugin.to_s.split(':').last 47 | end.compact 48 | end 49 | 50 | HELP_WAIT_DURATION = config['help_wait_duration'] || 120 51 | def help m 52 | m.reply help_get_plugins_str.join(', ') 53 | end 54 | 55 | def exec_version m 56 | m.reply Botpop::VERSION 57 | end 58 | 59 | def exec_code m 60 | m.reply "https://github.com/Nephos/botpop" 61 | end 62 | 63 | def exec_help m 64 | help m 65 | end 66 | 67 | def exec_help_plugin m 68 | module_name = m.message.split(" ").last.downcase 69 | i = Botpop::PLUGINS.map{|e| e.to_s.split(":").last.downcase}.index(module_name) 70 | if i.nil? 71 | m.reply "No plugin #{module_name}" 72 | return 73 | end 74 | plugin = Botpop::PLUGINS[i] 75 | m.reply plugin::HELP.join(', ') 76 | end 77 | 78 | def exec_troll m 79 | # hours = (Time.now.to_i - Time.gm(2015, 04, 27, 9).to_i) / 60 / 60 80 | s = Botpop::Builtins.get_msg m 81 | url = "http://www.fuck-you-internet.com/delivery.php?text=#{s}" 82 | m.reply url 83 | end 84 | 85 | end 86 | -------------------------------------------------------------------------------- /plugins/points.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | 3 | class Points < Botpop::Plugin 4 | include Cinch::Plugin 5 | include Botpop::Plugin::Database 6 | 7 | match(/.*/, use_prefix: false, method: :save_last_user) 8 | match(/^!pstats?$/, use_prefix: false, method: :statistics) 9 | match(/^!pstats?u (\w+)$/, use_prefix: false, method: :statistics_for_user) 10 | match(/^!pstats?p (\w+)$/, use_prefix: false, method: :statistics_for_point) 11 | match(/^!p +(\w+)$/, use_prefix: false, method: :add_point_to_last) 12 | match(/^!p +(\w+) +(\w+)$/, use_prefix: false, method: :add_point_to_user) 13 | match(/hei(l|i)/i, use_prefix: false, method: :point_nazi) 14 | 15 | HELP = ["!p [to]", "!pstats", "!pstatsu ", "!pstatsp "] 16 | ENABLED = config['enable'].nil? ? true : config['enable'] 17 | CONFIG = config 18 | 19 | @@users = {} 20 | @@lock = Mutex.new 21 | 22 | def statistics m 23 | ret = Base::DB.fetch("SELECT points.type, COUNT(*) AS nb FROM points GROUP BY points.type ORDER BY nb DESC LIMIT 10;").all.map{|e| e[:type] + "(#{e[:nb]})"}.join(", ") 24 | # data = Base::DB.fetch("SELECT points.type, COUNT(points.*) AS nb FROM points GROUP BY points.type ORDER BY nb DESC LIMIT 10;").all 25 | # data.map!{|e| Base::DB.fetch("SELECT assigned_to FROM points GROUP BY type, assigned_to HAVING type = ? ORDER BY COUNT(*) DESC;", e[:type]).first.merge(e)} 26 | # ret = data.map{|e| e[:type] + "(#{e[:nb]}x #{e[:assigned_to]})"}.join(", ") 27 | m.reply "Top used: #{ret}" 28 | end 29 | 30 | def statistics_for_user m, u 31 | ret = Base::DB.fetch("SELECT points.type, COUNT(*) AS nb FROM points WHERE assigned_to = ? GROUP BY points.type ORDER BY COUNT(*) DESC LIMIT 10;", u.downcase).all.map{|e| e[:type] + "(#{e[:nb]})"}.join(", ") 32 | m.reply "User #{u} has: #{ret}" 33 | end 34 | 35 | def statistics_for_point m, p 36 | data = Base::DB.fetch("SELECT assigned_to, COUNT(*) AS nb FROM points GROUP BY type, assigned_to HAVING type = ? ORDER BY COUNT(*) DESC LIMIT 10;", p).all 37 | ret = data.map{|e| e[:assigned_to] + "(#{e[:nb]})"}.join(", ") 38 | m.reply "Point #{p}: #{ret}" 39 | end 40 | 41 | def save_last_user m 42 | return if m.message.match(/^!p .+$/) 43 | @@lock.lock 44 | @@users[m.channel.to_s] = m.user.nick 45 | @@lock.unlock 46 | end 47 | 48 | def add_point_to_last m, type 49 | return if @@users[m.channel.to_s].nil? 50 | nick = @@users[m.channel.to_s] 51 | count = add_point(m.user.nick, nick, type) 52 | m.reply "#{nick} has now #{count} points #{type} !" 53 | end 54 | 55 | def add_point_to_user m, type, nick 56 | count = add_point(m.user.nick, nick, type) 57 | m.reply "#{nick} has now #{count} points #{type} !" 58 | end 59 | 60 | def point_nazi m 61 | nick = m.user.nick 62 | count = add_point("self", nick, "nazi") 63 | m.reply "#{nick} has now #{count} points nazi !" if count % 10 == 0 64 | end 65 | 66 | private 67 | def add_point(by, to, type) 68 | to.downcase! 69 | Base::DB[:points].insert({assigned_by: by, 70 | assigned_to: to, 71 | type: type, 72 | created_at: Time.now}) 73 | Base::DB[:points].where(assigned_to: to, type: type).count 74 | end 75 | 76 | end 77 | -------------------------------------------------------------------------------- /plugins/proxy.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | 3 | trap('SIGINT') { 4 | Botpop::Plugins::Proxy.database_users_write({}) 5 | exit 6 | } 7 | 8 | class Proxy < Botpop::Plugin 9 | include Cinch::Plugin 10 | 11 | match("!proxy list", use_prefix: false, method: :exec_proxy_list) 12 | match("!proxy ip", use_prefix: false, method: :exec_proxy_ip) 13 | match("!proxy get", use_prefix: false, method: :exec_proxy_get) 14 | match("!proxy drop", use_prefix: false, method: :exec_proxy_drop) 15 | 16 | HELP = ["!proxy list", "!proxy ip", "!proxy get", "!proxy drop"] 17 | ENABLED = config['enable'].nil? ? true : config['enable'] 18 | 19 | if ENABLED 20 | require 'htauth' 21 | require 'digest' 22 | end 23 | 24 | LIMIT_USERS = config['limit_users'] || 1 25 | PASSWD_FILE = config['passwd_file'] || '/etc/squid3/passwords' 26 | IP = config['ip_addr'] || raise(MissingConfigurationEntry, 'ip_addr') 27 | PORT = config['ip_port'] || raise(MissingConfigurationEntry, 'ip_port') 28 | 29 | File.open(PASSWD_FILE, 'a') {} 30 | LOCKED_USERS = File.readlines(PASSWD_FILE) 31 | 32 | @@proxy_users = [] 33 | 34 | def username m 35 | Digest::SHA256.hexdigest m.user.nick 36 | end 37 | 38 | def password_rand 39 | File.readlines('/proc/sys/kernel/random/uuid').first.split('-').last.chomp 40 | end 41 | 42 | def database_users_reset 43 | File.write(PASSWD_FILE, '') 44 | end 45 | 46 | def database_users_read 47 | begin 48 | return File.readlines(PASSWD_FILE) 49 | rescue 50 | database_users_reset 51 | sleep 1 52 | retry 53 | end 54 | end 55 | 56 | def database_users_write users 57 | database_users_reset 58 | contents = (LOCKED_USERS + users.map{|u,p| "#{u}:#{p}"}).join("\n").chomp 59 | contents += "\n" if not contents.empty? 60 | File.write(PASSWD_FILE, contents) 61 | end 62 | 63 | def users 64 | users = database_users_read 65 | users.map!{|l| l.chomp.split(':')} 66 | users.map!{|u| {u[0] => u[1]}} 67 | users = users.reduce({}) {|h,pairs| pairs.each {|k,v| h[k] = v}; h} 68 | users 69 | end 70 | 71 | def remove_user nick 72 | users = database_users_read 73 | users.delete_if {|line| line =~ /\A#{nick}:.+/ } 74 | database_users_write users 75 | end 76 | 77 | def add_user username, password 78 | p = HTAuth::PasswdFile.new(PASSWD_FILE) 79 | p.add(username, password) 80 | p.save! 81 | end 82 | 83 | def user_exists? m 84 | users[username m] 85 | end 86 | 87 | def exec_proxy_list m 88 | users.keys.each_with_index do |username, i| 89 | m.reply "Proxy list [#{i+1}/#{users.size}/#{LIMIT_USERS}] : #{username}" 90 | sleep 0.1 91 | end 92 | end 93 | 94 | def exec_proxy_ip m 95 | m.reply "Proxy connexion on http://#{IP}:#{PORT}" 96 | end 97 | 98 | def exec_proxy_get m 99 | if not user_exists? m 100 | password = password_rand 101 | @@proxy_users << m.user.nick 102 | add_user(username(m), password) 103 | m.reply "User : #{username(m)} created, password : #{password}" 104 | else 105 | if @@proxy_users.include? m.user.nick 106 | m.reply "You already have a proxy. Drop it before creating a new one." 107 | else 108 | m.reply "Locked nick #{m.user.nick} out of the my jurisdiction. Use an other." 109 | end 110 | end 111 | end 112 | 113 | def exec_proxy_drop m 114 | if @@proxy_users.include?(m.user.nick) and user_exists? m 115 | @@proxy_users.delete(m.user.nick) 116 | remove_user(username(m)) 117 | m.reply "Removed." 118 | else 119 | m.reply "No proxy registered with your nick on my jurisdiction." 120 | end 121 | end 122 | 123 | end 124 | -------------------------------------------------------------------------------- /plugins/iamalive.rb: -------------------------------------------------------------------------------- 1 | class IAmAlive < Botpop::Plugin 2 | include Cinch::Plugin 3 | include Botpop::Plugin::Database 4 | 5 | match(/^[^!].*/, use_prefix: false, method: :register_entry) 6 | match(/^[^!].*/, use_prefix: false, method: :react_on_entry) 7 | match(/^!iaa reac(tivity)?$/, use_prefix: false, method: :get_reactivity) 8 | match(/^!iaa reac(tivity)? \d{1,3}$/, use_prefix: false, method: :set_reactivity) 9 | match(/^!iaa learn$/, use_prefix: false, method: :set_mode_learn) 10 | match(/^!iaa live$/, use_prefix: false, method: :set_mode_live) 11 | match(/^!iaa mode$/, use_prefix: false, method: :get_mode) 12 | match(/^!iaa stats?$/, use_prefix: false, method: :get_stats) 13 | match(/^!iaa forget( (\d+ )?(.+))?/, use_prefix: false, method: :forget) 14 | match(/^!iaa last( \w+)?$/, use_prefix: false, method: :get_last) 15 | 16 | CONFIG = config(:safe => true) 17 | ENABLED = CONFIG['enable'] || false 18 | HELP = ["!iaa reac", "!iaa reac P", "!iaa learn", "!iaa live", "!iaa mode", 19 | "!iaa stats", "!iaa forget (Nx SENTENCE)", "!iaa last (nick)", 20 | "!iaa user [add/remove/list]"] 21 | 22 | @@mode = config['default_mode'].to_sym 23 | @@reactivity = config['reactivity'] || 50 24 | 25 | def cmd_allowed? m 26 | return Base.cmd_allowed? m, ["iaa"] 27 | end 28 | 29 | if ENABLED 30 | DB_CONFIG = self.db_config = CONFIG['database'] 31 | DB = self.db_connect! 32 | require_relative 'iamalive/entry' 33 | end 34 | 35 | def register_entry m 36 | Entry.create(user: (m.user.authname || m.user.nick), message: m.message, channel: m.channel.to_s) 37 | forget_older! if rand(1..100) == 100 38 | end 39 | 40 | def react_on_entry m 41 | return if @@mode != :live 42 | e = Entry.where('LOWER(message) = LOWER(?)', m.message).select(:id).all.map(&:id).map{|x| x+1} 43 | if @@reactivity > rand(1..100) 44 | answer_to(m, e) 45 | end 46 | end 47 | 48 | private 49 | def answer_to m, e 50 | a = Entry.where(id: e).to_a.sample 51 | if not a.nil? 52 | sleep(a.message.split.size.to_f / 10) 53 | m.reply a.message 54 | Entry.create(user: "self", message: a.message, channel: m.channel.to_s) 55 | end 56 | end 57 | 58 | def forget_older! 59 | log "Forget the older entry" 60 | Entry.first.delete 61 | end 62 | public 63 | 64 | def get_reactivity m 65 | m.reply "Current reactivity: #{@@reactivity}" 66 | end 67 | 68 | def set_reactivity m 69 | return if not cmd_allowed? m 70 | @@reactivity = m.message.split[2].to_i 71 | end 72 | 73 | def set_mode_learn m 74 | return if not cmd_allowed? m 75 | @@mode = :learn 76 | end 77 | 78 | def set_mode_live m 79 | return if not cmd_allowed? m 80 | @@mode = :live 81 | end 82 | 83 | def get_mode m 84 | m.reply "Current mode: #{@@mode}" 85 | end 86 | 87 | def get_stats m 88 | m.reply "Registered sentences: #{Entry.count}" 89 | end 90 | 91 | def forget m, arguments, nb, what 92 | return if not cmd_allowed? m 93 | if arguments.nil? 94 | last = Entry.where(channel: m.channel.to_s, user: "self").last 95 | m.reply last ? "\"#{last.message}\" Forgotten" : "Nop" 96 | last.delete 97 | else 98 | nb = nb.to_i if not nb.nil? 99 | nb ||= Entry.where(message: what).count 100 | n = Entry.where(message: what).order_by(:id).reverse.limit(nb).map(&:delete).size rescue 0 101 | m.reply "(#{n}x) \"#{what}\" Forgotten" 102 | end 103 | end 104 | 105 | def get_last m, user 106 | user.strip! if user 107 | last = Entry.where(channel: m.channel.to_s, user: (user || "self")).last 108 | m.reply "#{user}: #{last ? last.message : 'no message found'}" 109 | end 110 | 111 | end 112 | -------------------------------------------------------------------------------- /plugins/puppet.rb: -------------------------------------------------------------------------------- 1 | require 'date' 2 | 3 | class Puppet < Botpop::Plugin 4 | include Cinch::Plugin 5 | 6 | match(/^!pm (\#*\w+) (.*)/, use_prefix: false, method: :send_privmsg) 7 | match(/^!join (\#\w+)/, use_prefix: false, method: :join) 8 | match(/^!part (\#\w+)/, use_prefix: false, method: :part) 9 | 10 | # Email handlement 11 | EMAIL = '[[:alnum:]\.\-_]{1,64}@[[:alnum:]\.\-_]{1,64}' 12 | NICK = '\w+' 13 | # Registration of an email address - association with authname 14 | match(/^!mail register (#{EMAIL})$/, use_prefix: false, method: :register) 15 | match(/^!mail primary (#{EMAIL})$/, use_prefix: false, method: :make_primary) 16 | # Send email to user through its nickname (not safe) 17 | match(/^!(mail )?(let|send) (#{NICK}) (.+)/, use_prefix: false, method: :let) 18 | # Send email to user through one of its emails (safe) 19 | match(/^!(mail )?(let|send) (#{EMAIL}) (.+)/, use_prefix: false, method: :let) 20 | # Read email (based on nickname, authname, and emails) 21 | match(/^!(mail )?r(ead)?$/, use_prefix: false, method: :read) 22 | 23 | HELP = ["!pm <#chat/nick> ", "!join <#chan>", "!part <#chan>", "!let msg", "!read"] + 24 | ["!mail read", "!mail send/let <...>", "!mail register address"] 25 | ENABLED = config['enable'].nil? ? false : config['enable'] 26 | CONFIG = config 27 | 28 | def send_privmsg m, what, message 29 | if what.match(/^\#.+/) 30 | send_privmsg_to_channel(what, message) 31 | else 32 | send_privmsg_to_user(what, message) 33 | end 34 | end 35 | 36 | def join m, chan 37 | Channel(chan).join 38 | end 39 | 40 | def part m, chan 41 | Channel(chan).part 42 | end 43 | 44 | def register m, email 45 | begin 46 | Base::DB[:emails].insert(authname: m.user.authname, 47 | address: email, 48 | created_at: Time.now.utc, 49 | usage: 0) 50 | rescue => _ 51 | return m.reply "Error, cannot register this email !" 52 | end 53 | return m.reply "Email #{email} registered for you, #{m.user.authname}" 54 | end 55 | 56 | def make_primary m, email 57 | a = get_addresses(m.user).where(address: email) 58 | return m.reply "No your email #{email}" if a.first.nil? 59 | get_addresses(m.user).update(primary: false) 60 | a.update(primary: true) 61 | m.reply "Your primary email #{m.user.nick} is now #{email}" 62 | end 63 | 64 | def let m, _, _, to, msg 65 | log "New message addressed to #{to} to send" 66 | # insert new message in database 67 | from = Base::DB[:emails].where(authname: m.user.authname, primary: true).select(:address).first 68 | from = from && from[:address] || m.user.nick 69 | Base::DB[:messages].insert(author: from, 70 | dest: to, 71 | content: msg.strip, 72 | created_at: Time.now, 73 | read_at: nil) 74 | Base::DB[:emails].where(address: to).update('usage = usage+1') 75 | end 76 | 77 | def read m, _ 78 | msg = get_messages(m.user).first 79 | if msg.nil? 80 | send_privmsg_to_user m.user, "No message." 81 | return 82 | end 83 | Base::DB[:messages].where(id: msg[:id]).update(read_at: Time.now) 84 | date = msg[:created_at] 85 | if Date.parse(Time.now.to_s) == Date.parse(date.to_s) 86 | date = date.strftime("%H:%M:%S") 87 | else 88 | date = date.strftime("%B, %d at %H:%M:%S") 89 | end 90 | send_privmsg_to_user m.user, "##{msg[:id]}# #{date} -- from #{msg[:author]}" 91 | send_privmsg_to_user m.user, msg[:content] 92 | end 93 | 94 | listen_to :join, method: :bip_on_join 95 | 96 | def bip_on_join m 97 | nb = Base::DB[:messages].where(dest: m.user.nick, read_at: nil).count 98 | send_privmsg_to_user m.user, "#{m.user.nick}: You have #{nb} message unread." unless nb.zero? 99 | end 100 | 101 | private 102 | def send_privmsg_to_channel chan, msg 103 | Channel(chan).send(msg.strip) 104 | end 105 | 106 | def send_privmsg_to_user user, msg 107 | User(user).send(msg.strip) 108 | end 109 | 110 | def get_messages user 111 | emails = Base::DB[:emails].where(authname: user.authname).select(:address).all.map(&:values).flatten 112 | Base::DB[:messages].where(dest: [user.nick, user.authname] + emails).where(read_at: nil) 113 | end 114 | 115 | def get_addresses user 116 | Base::DB[:emails].where(authname: user.authname) 117 | end 118 | 119 | end 120 | -------------------------------------------------------------------------------- /modules_config.yml.example: -------------------------------------------------------------------------------- 1 | base: 2 | enable: true 3 | help_wait_duration: 120 4 | database: 5 | adapter: postgres 6 | host: localhost 7 | port: 5432 8 | user: root 9 | password: toor 10 | database: botpop_base 11 | 12 | dice: 13 | enable: true 14 | 15 | eip: 16 | enable: true 17 | 18 | rootme: 19 | enable: false 20 | 21 | points: 22 | enable: true 23 | 24 | anecdote: 25 | enable: true 26 | 27 | log: 28 | enable: true 29 | default_started: false 30 | 31 | encrypt: 32 | enable: true 33 | 34 | puppet: 35 | enable: true 36 | 37 | taggle: 38 | enable: true 39 | ntimes: 10 40 | wait: 0.3 41 | 42 | youtube: 43 | enable: true 44 | display: "Youtube: ___TITLE___ ___URL___" 45 | reduce_url: https://youtu.be/___ID___ 46 | search_url: https://www.youtube.com/results?search_query=___MSG___ 47 | 48 | poilo: 49 | enable: false 50 | list: 51 | u: cul 52 | a: bras 53 | o: dos 54 | i: kiki 55 | eu: lepreux 56 | y: nazi 57 | oi: petit-pois 58 | eau: bébé oiseau 59 | au: marmot 60 | ou: doudou 61 | an: manant 62 | 63 | searchable: 64 | #yt: https://www.youtube.com/results?search_query=___MSG___ # use the plugin youtube instead 65 | xv: http://www.xvideos.com/?k=___MSG___ 66 | yp: https://www.youporn.com/search/?query=___MSG___ 67 | 68 | paste: https://pastebin.com/search?ie=UTF-8&q=___MSG___ 69 | gh: https://github.com/search?q=___MSG___&type=Code&utf8=%E2%9C%93 70 | gl: https://gitlab.com/search?utf8=%E2%9C%93&search=___MSG___ 71 | code: http://rosettacode.org/mw/index.php?search=___MSG___&title=Special%3ASearch 72 | cpp: http://www.cplusplus.com/search.do?q=___MSG___ 73 | rb: https://ruby-doc.com/search.html?q=___MSG___&sa=Search 74 | ror: https://github.com/rails?utf8=%E2%9C%93&query=___MSG___ 75 | php: https://php.net/manual-lookup.php?pattern=___MSG___ 76 | python: https://www.python.org/search/?q=___MSG___ 77 | 78 | archfr: https://wiki.archlinux.fr/index.php?title=Sp%C3%A9cial%3ARecherche&search=___MSG___ 79 | arch: https://wiki.archlinux.org/index.php?title=Special%3ASearch&search=___MSG___&go=Go 80 | 81 | map: https://www.google.fr/maps/search/___MSG___ 82 | 83 | actu: https://www.google.fr/search?hl=fr&gl=fr&tbm=nws&authuser=0&q=___MSG___ 84 | news: https://www.google.fr/search?hl=fr&gl=fr&tbm=nws&authuser=0&q=___MSG___ 85 | tw: https://twitter.com/search?q=___MSG___ 86 | 87 | dicofr: https://fr.wiktionary.org/wiki/___MSG___ 88 | w: https://en.wikipedia.org/wiki/Special:Search?&go=Go&search=___MSG___ 89 | wfr: https://fr.wikipedia.org/wiki/Special:Search?search=___MSG___&go=Go 90 | ddg: https://duckduckgo.com/?q=___MSG___ 91 | q: https://prev.qwant.com/?q=___MSG___ 92 | g: https://encrypted.google.com/search?hl=en&q=___MSG___ 93 | lmsfy: http://lmgtfy.com/?q=___MSG___ 94 | search: http://lmgtfy.com/?q=___MSG___ 95 | s: http://lmgtfy.com/?q=___MSG___ 96 | buy: http://www.ebay.com/sch/i.html?_from=R40&_trksid=m570.l1313&_nkw=___MSG___&_sacat=0 97 | amazon: http://www.amazon.fr/s?url=search-alias=aps&field-keywords=___MSG___&tag=a2appw-21 98 | 99 | pkmn: http://pokemondb.net/search?q=___MSG___ 100 | tek: https://intra.epitech.eu/user/___MSG___ 101 | 102 | network: 103 | enable: true 104 | dos_wait: 10s 105 | dos_duration: 3s 106 | trace_duration_init: 0.3s 107 | trace_duration_incr: 0.1s 108 | 109 | proxy: 110 | enable: false 111 | ip_addr: localhost 112 | ip_port: 0 113 | limit_users: 4 114 | passwd_file: /tmp/squid3_passwords 115 | 116 | epitech: 117 | enable: false 118 | 119 | iamalive: 120 | enable: true 121 | default_mode: live 122 | database: 123 | adapter: postgres 124 | host: localhost 125 | port: 5432 126 | user: root 127 | password: toor 128 | database: botpop_iamalive 129 | 130 | cequetudisnaaucunsens: 131 | enabled: true 132 | 133 | saygoodbye: 134 | chapui_s: 135 | - "@chapui_s t'es là ?" 136 | - "chapui_s !!! T'es où ?" 137 | - "Chapuis ?" 138 | - "Hey chapuis !!" 139 | - "Chapui, viens ici !" 140 | - "chapui_s, j'ai mis register const int i en global ! :)" 141 | - "Chapuis, j'ai trouvé une faille dans gdb" 142 | - "Hey seb ! J'ai remis le t_server en global" 143 | - "Chapuis ! J'ai recodé printf en asm" 144 | - "Chapuis, mon pc il est en 32 ou 64 bits ?" 145 | - "Chapui, tu me fais mon corewar stp ?" 146 | - "Sebastien, comment on swap 2 int en version opti ?" 147 | - "Seb, comment tu te fais respecter ?" 148 | - "@chapui_s, viens on a besoin de toi" 149 | - "@chapui_s t'as soutenance dans 15 minutes au fait" 150 | - "Chapuis, comment on fait un const cast" 151 | asteks: 152 | - "Faut surtout pas lock les mutexes !!! oO Après tu peux deadlock !" 153 | - "J'ai ubuntu pourquoi ?" 154 | - "C'est l'histoire d'un astek qui voulait installer Archlinux..." 155 | - "Ouais nan mais système unix avancé c'est chiant, ça sert à rien en fait..." 156 | - "Nan mais, on peut pas faire le 42sh et le Raytracer en même temps, c'est trop dur." 157 | - "T'as triché. T'as regardé du code un jour en salle, et tu l'as imprimé dans ta tête" 158 | jacob_f: 159 | - "No râge de mon gradÂge !" 160 | poulet_a: 161 | - "Nan mais c'est un scandale ! Pourquoi j'ai grade A ?" 162 | -------------------------------------------------------------------------------- /plugins/network.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | 3 | class Network < Botpop::Plugin 4 | include Cinch::Plugin 5 | 6 | match("!ping", use_prefix: false, method: :exec_ping) 7 | match(/!ping #{Botpop::TARGET}\Z/, use_prefix: false, method: :exec_ping_target) 8 | match(/!httping #{Botpop::TARGET}\Z/, use_prefix: false, method: :exec_ping_http) 9 | match(/!dos #{Botpop::TARGET}\Z/, use_prefix: false, method: :exec_dos) 10 | match(/!fok #{Botpop::TARGET}\Z/, use_prefix: false, method: :exec_fok) 11 | match(/!trace #{Botpop::TARGET}\Z/, use_prefix: false, method: :exec_trace) 12 | match(/!poke #{Botpop::TARGET}\Z/, use_prefix: false, method: :exec_poke) 13 | 14 | HELP = ["!ping", "!ping [ip]", "!httping [ip]", 15 | "!dos [ip]", "!fok [nick]", "!trace [ip]", "!poke [nick]"] 16 | ENABLED = config['enable'].nil? ? true : config['enable'] 17 | CONFIG = config 18 | 19 | private 20 | # Conversion of the string to value in ms 21 | def self.config_string_to_time(value) 22 | value.match(/\d+ms\Z/) ? value.to_f / 100.0 : value.to_f 23 | end 24 | public 25 | 26 | DOS_DURATION = config['dos_duration'] || '2s' 27 | DOS_WAIT_DURATION_STRING = config['dos_wait'] || '5s' 28 | DOS_WAIT_DURATION = config_string_to_time DOS_WAIT_DURATION_STRING 29 | 30 | # Trace is complexe. 3 functions used exec_trace_display_lines, exec_trace_with_time, exec_trace 31 | TRACE_DURATION_INIT_STRING_DEFAULT = "0.3s" 32 | TRACE_DURATION_INIT_STRING = config['trace_duration_init'] || TRACE_DURATION_INIT_STRING_DEFAULT 33 | TRACE_DURATION_INCR_STRING_DEFAULT = "0.1s" 34 | TRACE_DURATION_INCR_STRING = config['trace_duration_incr'] || TRACE_DURATION_INCR_STRING_DEFAULT 35 | TRACE_DURATION_INIT = config_string_to_time TRACE_DURATION_INIT_STRING 36 | TRACE_DURATION_INCR = config_string_to_time TRACE_DURATION_INCR_STRING 37 | 38 | # @param what [Net::Ping::External] 39 | # @param what [Net::Ping::HTTP] 40 | def ping_with m, what 41 | ip = Botpop::Builtins.get_ip m 42 | p = what.new ip 43 | str = p.ping(ip) ? "#{(p.duration*100.0).round 2}ms (#{p.host})" : 'failed' 44 | m.reply "#{ip} > #{what.to_s.split(':').last} ping > #{str}" 45 | end 46 | 47 | def exec_ping m 48 | m.reply "#{m.user} > pong" 49 | end 50 | 51 | def exec_ping_target m 52 | ping_with m, Net::Ping::External 53 | end 54 | 55 | def exec_ping_http m 56 | ping_with m, Net::Ping::HTTP 57 | end 58 | 59 | def exec_poke m 60 | nick = Botpop::Builtins.get_ip_from_nick(m)[:nick] 61 | ip = Botpop::Builtins.get_ip_from_nick(m)[:ip] 62 | return m.reply "User '#{nick}' doesn't exists" if ip.nil? 63 | # Display 64 | response = Botpop::Builtins.ping(ip) ? "#{(p.duration*100.0).round 2}ms (#{p.host})" : "failed" 65 | m.reply "#{nick} > poke > #{response}" 66 | end 67 | 68 | 69 | def dos_check_ip(m, ip) 70 | return true if Botpop::Builtins.ping(ip) 71 | m.reply "Cannot reach the host '#{ip}'" 72 | return false 73 | end 74 | 75 | def dos_replier m, ip, s 76 | if s.nil? 77 | m.reply "The dos has failed" 78 | elsif Botpop::Builtins.ping(ip) 79 | m.reply "Sorry, the target is still up !" 80 | else 81 | m.reply "Target down ! --- #{s}" 82 | end 83 | end 84 | 85 | # This function avoid overusage of the resources by using mutex locking. 86 | # It execute the lamdba function passed as 2sd parameter if resources are ok 87 | # At the end of the attack, it wait few seconds (configuration) before 88 | # releasing the resources and permit a new attack. 89 | # 90 | # @arg lambda [Lambda] lambda with one argument (m). It wil be executed 91 | def dos_execution(m, lambda) 92 | @dos ||= Mutex.new 93 | if @dos.try_lock 94 | lambda.call(m) 95 | sleep DOS_WAIT_DURATION 96 | @dos.unlock 97 | else 98 | m.reply "Wait for the end of the last dos" 99 | end 100 | end 101 | 102 | def dos_ip(ip) 103 | return Botpop::Builtins.dos(ip, DOS_DURATION).split("\n")[3].to_s rescue nil 104 | end 105 | 106 | def exec_dos m 107 | dos_execution m, lambda {|m| 108 | ip = Botpop::Builtins.get_ip m 109 | return if not dos_check_ip(m, ip) 110 | m.reply "Begin attack against #{ip}" 111 | s = dos_ip(ip) 112 | dos_replier m, ip, s 113 | } 114 | end 115 | 116 | def exec_fok m 117 | dos_execution m, lambda {|m| 118 | nick = Botpop::Builtins.get_ip_from_nick(m)[:nick] 119 | ip = Botpop::Builtins.get_ip_from_nick(m)[:ip] 120 | return m.reply "User '#{nick}' doesn't exists" if ip.nil? 121 | return m.reply "Cannot reach the host '#{ip}'" if not Botpop::Builtins.ping(ip) 122 | s = dos_ip(ip) 123 | r = Botpop::Builtins.ping(ip) ? "failed :(" : "down !!!" 124 | m.reply "#{nick} : #{r} #{s}" 125 | } 126 | end 127 | 128 | def trace_display_lines m, lines 129 | lines.select!{|e| not e.include? "no reply" and e =~ /\A \d+: .+/} 130 | duration = TRACE_DURATION_INIT 131 | lines.each do |l| 132 | m.reply l 133 | sleep duration 134 | duration += TRACE_DURATION_INCR 135 | end 136 | m.reply 'finished' 137 | end 138 | 139 | def trace_with_time ip 140 | t1 = Time.now 141 | s = Botpop::Builtins.trace ip 142 | t2 = Time.now 143 | return [s, t1, t2] 144 | end 145 | 146 | # see {trace_execution}. Seem system without sleep 147 | # 148 | # @arg lambda [Lambda] lambda with one argument (m). It wil be executed 149 | def trace_execution(m, lambda) 150 | @trace ||= Mutex.new 151 | if @trace.try_lock 152 | lambda.call(m) rescue nil 153 | @trace.unlock 154 | else 155 | m.reply "A trace is still running" 156 | end 157 | end 158 | 159 | def exec_trace m 160 | trace_execution m, lambda {|m| 161 | ip = Botpop::Builtins.get_ip m 162 | m.reply "It can take time" 163 | begin 164 | # Calculations 165 | s, t1, t2 = trace_with_time ip 166 | m.reply "Trace executed in #{(t2 - t1).round(3)} seconds" 167 | rescue => e 168 | m.reply "Sorry, but the last author of this plugin is so stupid his mother is a tomato" 169 | end 170 | # Display 171 | trace_display_lines m, s 172 | } 173 | end 174 | 175 | end 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # botpop 2 | [![Code Climate](https://codeclimate.com/github/Nephos/botpop/badges/gpa.svg)](https://codeclimate.com/github/Nephos/botpop) 3 | [![GitHub version](https://badge.fury.io/gh/Nephos%2Fbotpop.svg)](http://badge.fury.io/gh/Nephos%2Fbotpop) 4 | 5 | ## Requirements 6 | 7 | - Ruby 2.0 or greater 8 | - Postgresql 9.3 or greater 9 | 10 | 11 | 12 | ## Installation 13 | Ruby 2 or greater is required. To be compatible with Ruby 1.9, you can try : 14 | ```bash 15 | sed 's/prepend/include/g' -i botpop.rb 16 | ``` 17 | but i did never try... You better update ruby ! ;) 18 | 19 | Too install the stuff, just do : 20 | ```bash 21 | bundle install # install the required gems 22 | cp modules_config.yml.example modules_config.yml 23 | editor modules_config.yml # set the database settings, etc. 24 | # create your database 25 | rake db:install # migrate the base plugin 26 | ``` 27 | 28 | 29 | ## Arguments 30 | By default, only the first occurence of the argument will be used, unless specified. 31 | - --channels, -c _OPTION_ : list of channels (default __equilibre__) 32 | - --ip, -s _OPTION_ : server ip (default to __freenode__) 33 | - --port, -p _OPTION_ : port (default __7000__ or __6667__ if no ssl) 34 | - --no-ssl : disable ssl (__enabled__ by default) 35 | - --nick, -n _OPTION_ : change the __nickname__ 36 | - --user, -u _OPTION_ : change the __username__ 37 | - --config _OPTION_ : change the plugin configuration file (default to ``modules_config.yml``) 38 | - --plugin-directory _OPTION_ : change the directory where the plugins are installed (default ``plugins/``) 39 | - --plugin-disable _OPTION_ : disable a plugin (can be specified many times) 40 | - --debug, -d _OPTION_ : enable the debug mod. It et a global __$debug_OPTION__ to true. (can be specified many times) 41 | 42 | 43 | ### Debugging easier 44 | You can specify the --debug OPT option at program start. 45 | It will define as many __$debug_OPT__ globals to enable debug on the plugins. 46 | 47 | As example: 48 | ```ruby 49 | # If debug enabled for this options and error occured 50 | if $debug_plugin and variable == :failed 51 | binding.pry # user hand here 52 | # Obsiously, it is usefull to trylock a mutex before because the bot use 53 | # Threads and can call many times this binding.pry 54 | end 55 | ``` 56 | 57 | 58 | 59 | # Plugins 60 | Some official plugins are developped. You can propose your own creation by pull request, or add snippets link to the wiki. 61 | 62 | ## List 63 | - [Base](https://github.com/Nephos/botpop/blob/master/plugins/base.rb) : this is a basic plugin, providing __version, code, help, and troll__. It also provide a full groups's system. 64 | - [Network](https://github.com/Nephos/botpop/blob/master/plugins/network.rb) : an usefull plugin with commands __ping, ping ip, ping http, traceroute, dos attack and poke__ 65 | - [Searchable](https://github.com/Nephos/botpop/blob/master/plugins/searchable.rb) : a little plugin providing irc research with engines like __google, wikipedia, ruby-doc, etc...__ 66 | - [Proxy](https://github.com/Nephos/botpop/blob/master/plugins/proxy.rb) : an audacious plugin to create user access to a local proxy 67 | - [Log](https://github.com/Nephos/botpop/blob/master/plugins/log.rb) : simple logger 68 | - [IAmAlive](https://github.com/Nephos/botpop/tree/master/plugins/iamalive) : a plugin to learn how to respond to the users. Fucking machine learning, oh yearh. 69 | - [CeQueTuDisNaAucunSens](https://github.com/Nephos/botpop/tree/master/plugins/cequetudisnaaucunsens.rb) : a funny plugin to say "ce que tu dis n'a aucun sens" without any meaning. 70 | - [Points](https://github.com/Nephos/botpop/tree/master/plugins/points.rb) : a gem to add points to an user. ``!p noob for_you`` 71 | - [Anecdote](https://github.com/Nephos/botpop/blob/master/plugins/anecdote.rb) : a cool meme generator plugin with nazi and youtuber. French meme. 72 | 73 | ### In version 0.X, not upgraded to v1 74 | - [Coupon](https://github.com/Nephos/botpop/blob/master/plugins/coupons.rb) : the original aim of the bot. It get coupons for the challenge __pathwar__ 75 | - [Intranet](https://github.com/Nephos/botpop/blob/master/plugins/intranet.rb) : an useless plugin to check the intranet of epitech 76 | 77 | 78 | ## Create your own 79 | You can easy create your own plugins. 80 | 81 | The bot is based on [Cinch framework](https://github.com/cinchrb/cinch/). 82 | You should take the time to read the documentation before developping anything. 83 | 84 | 85 | ### Example of new plugin 86 | A full example of plugin code is provided in the commented file : [Example of Fury Plugin](https://github.com/Nephos/botpop/blob/master/plugins/example.rb) 87 | 88 | First, put your ruby code file in ``plugins/``, and put your code in the scope : 89 | ```ruby 90 | class MyFuryPlugin < Botpop::Plugin 91 | include Cinch::Plugin 92 | 93 | def exec_whatkingofanimal m 94 | m.reply "Die you son of a" + ["lion", "pig", "red panda"].sample + " !!" 95 | end 96 | ...code... 97 | end 98 | ``` 99 | 100 | 101 | ### Matching messages 102 | To create a matching to respond to a message, you have to specifie in your plugin : 103 | ```ruby 104 | class MyFuryPlugin < Botpop::Plugin 105 | include Cinch::Plugin 106 | match(/!whatkingofanimal.*/, use_prefix: false, method: :exec_whatkingofanimal) 107 | ...code... 108 | end 109 | ``` 110 | 111 | 112 | ### Add entry to the !help command 113 | The __official plugin__ [Base](https://github.com/Nephos/botpop/blob/master/plugins/base.rb) provides the command __!help__ and __!help plugin__. 114 | 115 | It list the avaliable commands of the plugins. You can add your help to your plugin by providing a __HELP__ constant. 116 | __The strings should be as short as possible.__ 117 | You should write it like the following: 118 | ```ruby 119 | class MyFuryPlugin < Botpop::Plugin 120 | HELP = ["!whatkingofanimal", "!animallist", "!checkanimal [type]"] 121 | ...code... 122 | end 123 | ``` 124 | 125 | 126 | ### Enable and disable plugin 127 | You can enable or disable plugin by using the constant __ENABLED__. 128 | The constant must be defined by the developper of the plugin. 129 | For example, you can implement it like : 130 | ```ruby 131 | class MyFuryPlugin < Botpop::Plugin 132 | ENABLED = config['enable'].nil? ? true : config['enable'] 133 | end 134 | ``` 135 | 136 | Then, a simple line in the ``modules_configuration.yml`` file should be enough. 137 | 138 | 139 | ### Plugin Configuration 140 | You can configure your plugins via the file ``modules_configuration.yml``. 141 | If you considere that your plugin needs a particular configuration file, then create a new one il the ``plugins`` directory. 142 | 143 | To use the configuration loaded by ``modules_configuration.yml``, use the method ``config``. 144 | 145 | ``config`` takes an optionnal Hash as argument. It can take: 146 | 147 | - ``:safe => (true or false)`` 148 | - ``:name => (string or symbol)`` 149 | 150 | This method returns a Hash with configuration. 151 | 152 | By default, the method raise a ``MissingConfigurationZone`` error if no entry in the ``modules_configuration.yml`` file. 153 | 154 | The configuration file ``modules_configuration.yml`` must seems like : 155 | ```yaml 156 | name: 157 | entry: "string" 158 | entry2: 159 | - 1 160 | - 2.2 161 | - "ohoh" 162 | - nextelement: 163 | - oh oh ! 164 | ``` 165 | 166 | By default, the ``modules_configuration.yml`` file is configured for default plugins. 167 | 168 | ### Plugin Database 169 | 170 | Check this specified [README FOR DATABASE IN PLUGINS](DATABASE_EXTENSION.md) 171 | 172 | ### Rights managements (users, groups) 173 | 174 | Requires postgresql, because it uses the pg_array extension. 175 | 176 | Check this specified [README FOR RIGHTS MANAGEMENT](RIGHTS_MANAGEMENT.md) 177 | --------------------------------------------------------------------------------