├── .gitignore ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── images └── screenshot.png ├── lib └── ruboty │ ├── cron.rb │ ├── cron │ ├── job.rb │ └── version.rb │ └── handlers │ └── cron.rb └── ruboty-cron.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | *.bundle 19 | *.so 20 | *.o 21 | *.a 22 | mkmf.log 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.1.0 2 | - Add suspend/resume features (#5) 3 | 4 | ## 1.0.2 5 | - Support multi lines message (#4) 6 | 7 | ## 1.0.1 8 | - Fix greedy match 9 | 10 | ## 1.0.0 11 | - Chnage job behavior to receive body as message in scheduled time 12 | 13 | ## 0.0.9 14 | - Rename: Ellen -> Ruboty 15 | 16 | ## 0.0.8 17 | - Fix loaded modules 18 | 19 | ## 0.0.7 20 | - Format message if adapter supports :code option 21 | 22 | ## 0.0.6 23 | - Store & Rebuild all message attributes 24 | 25 | ## 0.0.5 26 | - Says where job is registered on 27 | 28 | ## 0.0.4 29 | - Support Ruboty v0.2.0 30 | 31 | ## 0.0.3 32 | - Remember registered jobs after initialized 33 | - Stop running job before its deletion 34 | 35 | ## 0.0.2 36 | - Support the new Brain interface 37 | 38 | ## 0.0.1 39 | - 1st Release 40 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in ruboty-cron.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Ryo Nakamura 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ruboty::Cron 2 | Mount cron system to [Ruboty]("https://github.com/r7kamura/ruboty") to schedule messages on a specific time. 3 | 4 | ## Usage 5 | You can use any [Chrono](https://github.com/r7kamura/chrono/) compatible cron syntax to schedule messages. 6 | 7 | ``` 8 | @ruboty add job "" - Add a new cron job 9 | @ruboty delete job - Delete a cron job 10 | @ruboty suspend job - Suspend a cron job 11 | @ruboty resume job - Resume a cron job 12 | @ruboty list jobs - List all cron jobs 13 | ``` 14 | 15 | The scheduled message will be delivered to your bot itself (instead of posting to somewhere by the bot directly) on the time. If the message contains any valid command, your bot will try to respond to it. 16 | 17 | ### Example 18 | ``` 19 | $ bundle exec ruboty 20 | Type `exit` or `quit` to end the session. 21 | > @ruboty add job "* * * * *" @ruboty ping 22 | Job 3117 created 23 | pong 24 | pong 25 | pong 26 | pong 27 | pong 28 | > @ruboty list jobs 29 | 3117: "* * * * *" @ruboty ping 30 | > @ruboty delete job 3117 31 | Deleted 32 | > 33 | ``` 34 | 35 | ### Tips 36 | If you want to schedule Ruboty to say something, 37 | [ruboty-echo](https://github.com/taiki45/ruboty-echo) may help you. 38 | 39 | ``` 40 | > @ruboty add job "0 8 * * 1-5" @ruboty echo It's Time for School! 41 | ``` 42 | 43 | Note that you need to include `@ruboty` before the command name `echo`, because ruboty-echo responds only for any mention to the bot itself. Otherwise your bot will ignore the message as an invalid `echo` command. 44 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | -------------------------------------------------------------------------------- /images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r7kamura/ruboty-cron/5320a8d7651ef84a3eff36fe277556acf6fc525e/images/screenshot.png -------------------------------------------------------------------------------- /lib/ruboty/cron.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/hash/except" 2 | require "active_support/core_ext/hash/keys" 3 | require "chrono" 4 | require "json" 5 | 6 | require "ruboty" 7 | require "ruboty/handlers/cron" 8 | require "ruboty/cron/job" 9 | require "ruboty/cron/version" 10 | -------------------------------------------------------------------------------- /lib/ruboty/cron/job.rb: -------------------------------------------------------------------------------- 1 | module Ruboty 2 | module Cron 3 | class Job 4 | attr_reader :attributes, :thread 5 | 6 | def initialize(attributes) 7 | @attributes = attributes.stringify_keys 8 | end 9 | 10 | def start(robot) 11 | @thread = Thread.new do 12 | Chrono::Trigger.new(schedule) do 13 | robot.receive( 14 | attributes.symbolize_keys.except( 15 | :id, 16 | :schedule, 17 | ), 18 | ) 19 | end.run 20 | end 21 | end 22 | 23 | def to_hash 24 | attributes 25 | end 26 | 27 | def stop 28 | thread.kill 29 | end 30 | 31 | def suspend 32 | stop 33 | attributes["suspended"] = true 34 | end 35 | 36 | def resume(robot) 37 | start(robot) 38 | attributes.delete("suspended") if attributes.has_key?("suspended") 39 | end 40 | 41 | def description 42 | %<%5s: (%s) "%s" %s> % [id, suspended? ? "suspended" : "active", schedule, body] 43 | end 44 | 45 | def id 46 | attributes["id"] 47 | end 48 | 49 | def schedule 50 | attributes["schedule"] 51 | end 52 | 53 | def body 54 | attributes["body"] 55 | end 56 | 57 | def suspended? 58 | !!attributes["suspended"] 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/ruboty/cron/version.rb: -------------------------------------------------------------------------------- 1 | module Ruboty 2 | module Cron 3 | VERSION = "1.1.0" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/ruboty/handlers/cron.rb: -------------------------------------------------------------------------------- 1 | module Ruboty 2 | module Handlers 3 | class Cron < Base 4 | NAMESPACE = "cron" 5 | 6 | on(/add job "(?.+?)" (?.+)/m, name: "add", description: "Add a new cron job") 7 | 8 | on(/delete job (?\d+)/, name: "delete", description: "Delete a cron job") 9 | 10 | on(/list jobs\z/, name: "list", description: "List all cron jobs") 11 | 12 | on(/suspend job (?\d+)/, name: "suspend", description: "Suspend a cron job") 13 | 14 | on(/resume job (?\d+)/, name: "resume", description: "Resume a cron job") 15 | 16 | attr_writer :jobs 17 | 18 | def initialize(*args) 19 | super 20 | remember 21 | end 22 | 23 | def add(message) 24 | job = create(message) 25 | message.reply("Job #{job.id} created") 26 | end 27 | 28 | def delete(message) 29 | id = message[:id].to_i 30 | if jobs.has_key?(id) 31 | jobs.delete(id) 32 | running_jobs[id].stop 33 | running_jobs.delete(id) 34 | message.reply("Job #{id} deleted") 35 | else 36 | message.reply("Job #{id} does not exist") 37 | end 38 | end 39 | 40 | def list(message) 41 | message.reply(summary, code: true) 42 | end 43 | 44 | def suspend(message) 45 | id = message[:id].to_i 46 | if jobs.has_key?(id) 47 | if running_jobs[id] 48 | running_jobs[id].suspend 49 | jobs[id] = running_jobs[id].to_hash 50 | running_jobs.delete(id) 51 | message.reply("Job #{id} suspended") 52 | else 53 | message.reply("Job #{id} had suspended") 54 | end 55 | else 56 | message.reply("Job #{id} does not exist") 57 | end 58 | end 59 | 60 | def resume(message) 61 | id = message[:id].to_i 62 | if jobs.has_key?(id) 63 | job = Ruboty::Cron::Job.new(jobs[id]) 64 | if job.suspended? 65 | job.resume(robot) 66 | jobs[id] = job.to_hash 67 | running_jobs[id] = job 68 | message.reply("Job #{id} resumed") 69 | else 70 | message.reply("Job #{id} is running") 71 | end 72 | else 73 | message.reply("Job #{id} does not exist") 74 | end 75 | end 76 | 77 | private 78 | 79 | def remember 80 | jobs.each do |id, attributes| 81 | job = Ruboty::Cron::Job.new(attributes) 82 | unless job.suspended? 83 | running_jobs[id] = job 84 | job.start(robot) 85 | end 86 | end 87 | end 88 | 89 | def jobs 90 | robot.brain.data[NAMESPACE] ||= {} 91 | end 92 | 93 | def create(message) 94 | job = Ruboty::Cron::Job.new( 95 | message.original.except(:robot).merge( 96 | body: message[:body], 97 | id: generate_id, 98 | schedule: message[:schedule], 99 | ), 100 | ) 101 | jobs[job.id] = job.to_hash 102 | job.start(robot) 103 | running_jobs[job.id] = job 104 | job 105 | end 106 | 107 | def summary 108 | if jobs.empty? 109 | empty_message 110 | else 111 | job_descriptions 112 | end 113 | end 114 | 115 | def empty_message 116 | "Job not found" 117 | end 118 | 119 | def job_descriptions 120 | jobs.values.map do |attributes| 121 | Ruboty::Cron::Job.new(attributes).description 122 | end.join("\n") 123 | end 124 | 125 | def generate_id 126 | loop do 127 | id = rand(10000) 128 | break id unless jobs.has_key?(id) 129 | end 130 | end 131 | 132 | def running_jobs 133 | @running_jobs ||= {} 134 | end 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /ruboty-cron.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path("../lib", __FILE__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require "ruboty/cron/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "ruboty-cron" 7 | spec.version = Ruboty::Cron::VERSION 8 | spec.authors = ["Ryo Nakamura"] 9 | spec.email = ["r7kamura@gmail.com"] 10 | spec.summary = "Mount cron system to Ruboty to schedule messages on a specific time." 11 | spec.homepage = "https://github.com/r7kamura/ruboty-cron" 12 | spec.license = "MIT" 13 | 14 | spec.files = `git ls-files -z`.split("\x0") 15 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 16 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 17 | spec.require_paths = ["lib"] 18 | 19 | spec.add_dependency "activesupport" 20 | spec.add_dependency "chrono" 21 | spec.add_dependency "ruboty", ">= 0.2.0" 22 | spec.add_development_dependency "bundler", "~> 1.6" 23 | spec.add_development_dependency "rake" 24 | end 25 | --------------------------------------------------------------------------------