atig-0.6.1/0000755000175000017500000000000012661500130012270 5ustar uwabamiuwabamiatig-0.6.1/atig.gemspec0000644000175000017500000000232312661500130014561 0ustar uwabamiuwabami# -*- encoding: utf-8 -*- require File.expand_path('../lib/atig/version', __FILE__) Gem::Specification.new do |spec| spec.name = "atig" spec.version = Atig::VERSION spec.authors = ["MIZUNO Hiroki", "SHIBATA Hiroshi", ] spec.email = ["mzp@ocaml.jp", "shibata.hiroshi@gmail.com"] spec.summary = %q{Atig.rb is forked from cho45's tig.rb. We improve some features of tig.rb.} spec.description = %q{Atig.rb is Twitter Irc Gateway.} spec.homepage = "https://github.com/atig/atig" spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") spec.required_ruby_version = Gem::Requirement.new(">= 1.9.3") spec.add_dependency 'sqlite3', '>= 1.3.2' spec.add_dependency 'net-irc' spec.add_dependency 'oauth' spec.add_dependency 'twitter-text' spec.add_development_dependency 'bundler' spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec' spec.add_development_dependency 'coveralls' end atig-0.6.1/.rspec0000644000175000017500000000005012661500130013400 0ustar uwabamiuwabami--require spec_helper --color --profile atig-0.6.1/.gitignore0000644000175000017500000000031712661500130014261 0ustar uwabamiuwabami*~ *.omc .omakedb .omakedb.lock _build *.db .DS_Store .gem *.rbc .bundle .config .yardoc InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp Gemfile.lock atig-0.6.1/bin/0000755000175000017500000000000012661500130013040 5ustar uwabamiuwabamiatig-0.6.1/bin/setup0000755000175000017500000000016312661500130014126 0ustar uwabamiuwabami#!/bin/bash set -euo pipefail IFS=$'\n\t' bundle install # Do any other automated setup that you need to do here atig-0.6.1/bin/console0000755000175000017500000000051112661500130014425 0ustar uwabamiuwabami#!/usr/bin/env ruby require "bundler/setup" require "atig" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. # (If you use this, don't forget to add pry to your Gemfile!) # require "pry" # Pry.start require "irb" IRB.start atig-0.6.1/lib/0000755000175000017500000000000012661500130013036 5ustar uwabamiuwabamiatig-0.6.1/lib/memory_profiler.rb0000644000175000017500000000422212661500130016575 0ustar uwabamiuwabami# This is a memory profiler for Ruby. Once started, it runs in a thread in the # background, periodically inspecting Ruby's ObjectSpace to look for new # objects and printing a count of objects added and removed since the previous # cycle. # # To use the profiler, do something like this: # # require 'memory_profiler' # # MemoryProfiler.start # # The profiler will write logs to ./log/memory_profiler.log. # # If you start MemoryProfiler with the ':string_debug => true' option, then it # will dump a list of all strings in the app into the log/ directory after # each cycle. You can then use 'diff' to spot which strings were added # between runs. class MemoryProfiler DEFAULTS = {delay: 10, string_debug: false} def self.start(opt={}) opt = DEFAULTS.dup.merge(opt) Thread.new do prev = Hash.new(0) curr = Hash.new(0) curr_strings = [] delta = Hash.new(0) file = File.open("log/memory_profiler.#{Time.now.to_i}.log",'w') loop do begin GC.start curr.clear curr_strings = [] if opt[:string_debug] ObjectSpace.each_object do |o| curr[o.class] += 1 #Marshal.dump(o).size rescue 1 if opt[:string_debug] and o.class == String curr_strings.push o end end if opt[:string_debug] File.open("log/memory_profiler_strings.log.#{Time.now.to_i}",'w') do |f| curr_strings.sort.each do |s| f.puts s end end curr_strings.clear end delta.clear (curr.keys + delta.keys).uniq.each do |k,v| delta[k] = curr[k]-prev[k] end file.puts "Top 20: #{Time.now}" delta.sort_by { |k,v| -v.abs }[0..19].sort_by { |k,v| -v}.each do |k,v| file.printf "%+5d: %s (%d)\n", v, k.name, curr[k] unless v == 0 end file.flush delta.clear prev.clear prev.update curr GC.start rescue Exception => err STDERR.puts "** memory_profiler error: #{err}" end sleep opt[:delay] end end end end atig-0.6.1/lib/atig.rb0000644000175000017500000000045312661500130014311 0ustar uwabamiuwabami# -*- coding: utf-8 -*- require 'atig/version' require 'atig/monkey' require 'atig/optparse' require 'atig/client' require 'atig/twitter' require 'atig/scheduler' require 'atig/agent' require 'atig/ifilter' require 'atig/ofilter' require 'atig/command' require 'atig/channel' require 'atig/gateway' atig-0.6.1/lib/atig/0000755000175000017500000000000012661500130013762 5ustar uwabamiuwabamiatig-0.6.1/lib/atig/search.rb0000644000175000017500000000106112661500130015552 0ustar uwabamiuwabamirequire 'json' require 'atig/http' require 'atig/url_escape' module Atig class Search def search(query, options = {}) options[:q] = query search = URI("https://search.twitter.com") search.path = "/search.json" search.query = options.to_query_str http = Http.new nil req = http.req(:get, search) res = http.http(search, 5, 10).request(req) JSON.parse(res.body) rescue Errno::ETIMEDOUT, JSON::ParserError, IOError, Timeout::Error, Errno::ECONNRESET => e @log.error e text end end end atig-0.6.1/lib/atig/optparse.rb0000644000175000017500000000367112661500130016153 0ustar uwabamiuwabamirequire "optparse" require 'tmpdir' module Atig module OptParser class << self def parse!(argv) opts = { port: 16668, host: "localhost", log: nil, debug: false, foreground: false, tmpdir: ::Dir.tmpdir, conf: '~/.atig/config', } OptionParser.new do |parser| parser.version = Atig::VERSION parser.instance_eval do self.banner = < 3 end sleep (3*60*60) end if UpdateChecker.git_repos? db.statuses.listen do|entry| if db.followings.include?(entry.user) or entry.source == :timeline or entry.source == :user_stream or entry.source == :me then @channel.message entry end end @channel.send :join, db.followings.users db.followings.listen do|kind, users| @channel.send(kind, users) if @channel.respond_to?(kind) end end def on_invite(api, nick) api.post("friendships/create/#{nick}") @db.followings.invalidate end def on_kick(api, nick) api.post("friendships/destroy/#{nick}") @db.followings.invalidate end def on_who(&f) return unless f @db.followings.users.each(&f) end def channel_name; "#twitter" end end end end atig-0.6.1/lib/atig/channel/retweet.rb0000644000175000017500000000105512661500130017377 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/channel/channel' module Atig module Channel class Retweet < Atig::Channel::Channel def initialize(context, gateway, db) super db.statuses.find_all(limit:50).reverse_each {|entry| message entry } db.statuses.listen {|entry| message entry } end def channel_name; "#retweet" end def message(entry) if entry.status.retweeted_status then @channel.message entry end end end end end atig-0.6.1/lib/atig/oauth.rb0000644000175000017500000000244312661500130015432 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'oauth' require 'atig/oauth-patch' module Atig class OAuth CONSUMER_KEY = 'TO9wbD379qmFSJp6pFs5w' CONSUMER_SECRET = 'Gap8ishP3J3JrjH4JEspcii4poiZgMowHRazWGM1cYg' @@profiles = {} class << self def dump @@profiles end def load(profiles) @@profiles = profiles end end attr_reader :access attr_reader :oauth def initialize(context, nick) uri = URI(context.opts.api_base) site = "https://#{uri.host}" @nick = nick @oauth = ::OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET, { site: site, proxy: ENV["HTTP_PROXY"] || ENV["http_proxy"] }) if @@profiles.key? @nick token,secret = @@profiles[@nick] @access = ::OAuth::AccessToken.new(@oauth, token, secret) end end def verified? @access != nil end def url @request = @oauth.get_request_token @request.authorize_url end def verify(code) @access = @request.get_access_token(oauth_verifier: code) if @access then @@profiles[@nick] = [ @access.token , @access.secret ] end rescue false end end end atig-0.6.1/lib/atig/levenshtein.rb0000644000175000017500000000206612661500130016637 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- # http://subtech.g.hatena.ne.jp/cho45/20091008/1254934083 module Atig module Levenshtein def levenshtein(a, b) PureRuby.levenshtein(a, b) end module_function :levenshtein module PureRuby def levenshtein(a, b) case when a.empty? b.length when b.empty? a.length else d = Array.new(a.length + 1) { |s| Array.new(b.length + 1, 0) } (0..a.length).each do |i| d[i][0] = i end (0..b.length).each do |j| d[0][j] = j end (1..a.length).each do |i| (1..b.length).each do |j| cost = (a[i - 1] == b[j - 1]) ? 0 : 1 d[i][j] = [ d[i-1][j ] + 1, d[i ][j-1] + 1, d[i-1][j-1] + cost ].min end end d[a.length][b.length] end end module_function :levenshtein end end end atig-0.6.1/lib/atig/command/0000755000175000017500000000000012661500130015400 5ustar uwabamiuwabamiatig-0.6.1/lib/atig/command/search.rb0000644000175000017500000000304112661500130017170 0ustar uwabamiuwabami#!/usr/bin/env ruby # -*- mode:ruby; coding:utf-8 -*- require 'atig/command/command' require 'atig/search' # originally developped by xeres # http://blog.xeres.jp/2010/06/04/atig_rb-tweet-search/ module Atig module Command class Search < Atig::Command::Command def command_name; %w(search s) end def action(target, mesg, command, args) if args.empty? yield "/me #{command} [option...] blah blah" return end q = mesg.sub(/^#{command}\s+/, '') opts = { q: q } while /^:(?:(lang)=(\w+))/ =~ args.first opts[$1] = $2 q.sub!(/^#{args.first}\W+/, "") args.shift end statuses = api.search.get('search', opts).results if statuses.empty? yield "\"#{q}\": not found. options=#{opts.inspect} (#{res['completed_in']} sec.)" return end statuses.reverse_each do|status| db.statuses.transaction do|d| user = TwitterStruct.make('id' => status.from_user_id, 'screen_name' => status.from_user) d.add status: status, user: user, source: :user end end statuses.reverse_each do|status| entry = db.statuses.find_by_status_id(status.id) entry.status = entry.status.merge('text' => "#{entry.status.text} (#{entry.status.created_at})") gateway[target].message entry, Net::IRC::Constants::NOTICE end end end end end atig-0.6.1/lib/atig/command/info.rb0000644000175000017500000000243512661500130016664 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- module Atig module Command module Info def user(db, api, name, &f) if user = db.followings.find_by_screen_name(name) then f.call user else api.delay(0) do|t| user = t.get "users/show",:screen_name=>name f.call user end end end def status(db, api, id, &f) if status = db.statuses.find_by_status_id(id) then f.call status else api.delay(0) do|t| status = t.get "statuses/show/#{id}" db.statuses.transaction do|d| d.add status: status, user: status.user, source: :thread f.call d.find_by_status_id(id) end end end end def find_status(db, tid_or_screen_name) find = lambda do|x| xs = db.statuses.find_by_screen_name(x, limit:1) unless xs.empty? then xs.first else nil end end (db.statuses.find_by_tid(tid_or_screen_name) || db.statuses.find_by_sid(tid_or_screen_name) || find.call(tid_or_screen_name) || find.call(tid_or_screen_name.sub(/\A@/,''))) end module_function :user,:status, :find_status end end end atig-0.6.1/lib/atig/command/status.rb0000644000175000017500000000232012661500130017245 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/command' require 'twitter-text' module Atig module Command class Status < Atig::Command::Command include ::Twitter::Validation def command_name; %w(status) end def action(target, mesg, command, args) if args.empty? yield "/me #{command} blah blah" return end text = mesg.split(" ", 2)[1] previous,*_ = db.statuses.find_by_user( db.me, limit: 1) if previous and ((::Time.now - ::Time.parse(previous.status.created_at)).to_i < 60*60*24 rescue true) and text.strip == previous.status.text.strip yield "You can't submit the same status twice in a row." return end q = gateway.output_message(status: text) case tweet_invalid? q[:status] when :too_long yield "You can't submit the status over 140 chars" return when :invalid_characters yield "You can't submit the status invalid chars" return end api.delay(0, retry:3) do|t| ret = t.post("statuses/update", q) gateway.update_status ret,target end end end end end atig-0.6.1/lib/atig/command/limit.rb0000644000175000017500000000056312661500130017047 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/command' module Atig module Command class Limit < Atig::Command::Command def command_name; %w(rls limit limits) end def action(target, mesg, command, args) yield "#{api.remain} / #{api.limit} (reset at #{::Time.at(api.reset).strftime('%Y-%m-%d %H:%M:%S')})" end end end end atig-0.6.1/lib/atig/command/thread.rb0000644000175000017500000000177112661500130017202 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/command' require 'atig/command/info' module Atig module Command class Thread < Atig::Command::Command def initialize(*args) super end def command_name; %w(thread) end def action(target, mesg, command, args) if args.empty? yield "/me #{command} []" return end tid, num = args count = 10 unless (1..20).include?(count = num.to_i) if entry = Info.find_status(db, tid) then chain(entry,count){|x| gateway[target].message x, Net::IRC::Constants::NOTICE } else yield "No such ID : #{tid}" end end def chain(entry,count, &f) if count <= 0 then return elsif id = entry.status.in_reply_to_status_id then Info.status(db, api, id){|next_| chain(next_, count - 1, &f) } end f.call entry end end end end atig-0.6.1/lib/atig/command/spam.rb0000644000175000017500000000124512661500130016667 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/command' require 'atig/command/info' module Atig module Command class Spam < Atig::Command::Command def initialize(*args); super end def command_name; %w(spam SPAM) end def action(target, mesg, command, args) if args.empty? yield "/me #{command} ..." return else args.each do|screen_name| api.delay(0) do|t| res = t.post("report_spam",:screen_name => screen_name) yield "Report #{res.screen_name} as SPAMMER" end end end end end end end atig-0.6.1/lib/atig/command/reply.rb0000644000175000017500000000204012661500130017054 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/command' require 'atig/command/info' module Atig module Command class Reply < Atig::Command::Command def initialize(*args); super end def command_name; %w(mention re reply rp) end def action(target, mesg, command, args) if args.empty? yield "/me #{command} blah blah" return end tid = args.first if entry = Info.find_status(db,tid) then text = mesg.split(" ", 3)[2] name = entry.user.screen_name text = "@#{name} #{text}" if text.nil? or not text.include?("@#{name}") q = gateway.output_message(status: text, in_reply_to_status_id: entry.status.id) api.delay(0) do|t| ret = t.post("statuses/update", q) gateway.update_status ret, target, "In reply to #{name}: #{entry.status.text}" end else yield "No such ID : #{tid}" end end end end end atig-0.6.1/lib/atig/command/destroy.rb0000644000175000017500000000240712661500130017421 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/command' require 'atig/command/info' module Atig module Command class Destroy < Atig::Command::Command def initialize(*args); super end def command_name; %w(destroy remove rm) end def action(target, mesg, command, args) if args.empty? yield "/me #{command} ..." return end args.each do|tid| if entry = Info.find_status(db, tid) if entry.user.id == db.me.id api.delay(0) do|t| t.post("statuses/destroy/#{entry.status.id}") yield "Destroyed: #{entry.status.text}" db.statuses.transaction do|d| xs = d.find_by_screen_name db.me.screen_name,:limit=>1 d.remove_by_id entry.id ys = d.find_by_screen_name db.me.screen_name,:limit=>1 unless xs.map{|x| x.id} == ys.map{|y| y.id} then gateway.topic ys.first end end end else yield "The status you specified by the ID tid is not yours." end else yield "No such ID tid" end end end end end end atig-0.6.1/lib/atig/command/command.rb0000644000175000017500000000113012661500130017336 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- module Atig module Command class Command attr_reader :gateway, :api, :db, :opts def initialize(context, gateway, api, db) @log = context.log @opts = context.opts @gateway = gateway @api = api @db = db @gateway.ctcp_action(*command_name) do |target, mesg, command, args| action(target, mesg, command, args){|m| gateway[target].notify m } end end def find_by_tid(tid) @db.statuses.find_by_tid tid end end end end atig-0.6.1/lib/atig/command/user.rb0000644000175000017500000000217612661500130016711 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'time' require 'atig/command/command' module Atig module Command class User < Atig::Command::Command def initialize(*args); super end def command_name; %w(user u) end def action(target, mesg, command, args) if args.empty? yield "/me #{command} []" return end nick, num,*_ = args count = 20 unless (1..200).include?(count = num.to_i) api.delay(0) do|t| begin statuses = t.get("statuses/user_timeline", { count: count, screen_name: nick}) statuses.reverse_each do|status| db.statuses.transaction do|d| d.add status: status, user: status.user, source: :user end end db.statuses. find_by_screen_name(nick, limit:count). reverse_each do|entry| gateway[target].message entry, Net::IRC::Constants::NOTICE end rescue Twitter::APIFailed => e yield e.to_s end end end end end end atig-0.6.1/lib/atig/command/location.rb0000644000175000017500000000106612661500130017540 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/command' module Atig module Command class Location < Atig::Command::Command def command_name; %w(in location loc) end def action(target, mesg, command, args) api.delay(0) do|t| location = mesg.split(" ", 2)[1] || "" t.post('account/update_profile',:location=>location) if location.empty? then yield "You are nowhere now." else yield "You are in #{location} now." end end end end end end atig-0.6.1/lib/atig/command/time.rb0000644000175000017500000000177512661500130016675 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'time' require 'atig/command/command' module Atig module Command class Time < Atig::Command::Command def command_name; %w(time) end def action(target, mesg, command, args) if args.empty? yield "/me #{command} " return end nick, *_ = args Info.user(db, api, nick){|user| offset = user.utc_offset time = "TIME :%s%s (%s)" % [ (::Time.now + offset).utc.iso8601[0, 19], "%+.2d:%.2d" % (offset/60).divmod(60), user.time_zone ] entry = TwitterStruct.make('user' => user, 'status' => { 'text' => Net::IRC.ctcp_encode(time) }) gateway[target].message entry, Net::IRC::Constants::NOTICE } end end end end atig-0.6.1/lib/atig/command/user_info.rb0000644000175000017500000000133312661500130017716 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/command' module Atig module Command class UserInfo < Atig::Command::Command def initialize(*args); super end def command_name; %w(bio userinfo) end def action(target, mesg, command,args) if args.empty? yield "/me #{command} " return end nick,*_ = args Info.user(db, api, nick)do|user| entry = TwitterStruct.make('user' => user, 'status' => { 'text' => Net::IRC.ctcp_encode(user.description) }) gateway[target].message entry, Net::IRC::Constants::NOTICE end end end end end atig-0.6.1/lib/atig/command/version.rb0000644000175000017500000000311012661500130017405 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/twitter' require 'atig/command/command' module Atig module Command class Version < Atig::Command::Command def command_name; %w(version) end def action(target, mesg, command,args) if args.empty? yield "/me #{command} " return end nick,*_ = args entries = db.statuses.find_by_screen_name(nick, limit: 1) if entries && !entries.empty? then entry = TwitterStruct.make('user' => entries.first.user, 'status' => { 'text' => format(entries.first.status.source) }) gateway[target].message entry, Net::IRC::Constants::NOTICE else api.delay(0) do|t| begin user = t.get("users/show", { screen_name: nick}) db.statuses.transaction do|d| d.add user: user, status: user.status, source: :version entry = TwitterStruct.make('user' => user, 'status' => { 'text' => format(user.status.source) }) gateway[target].message entry, Net::IRC::Constants::NOTICE end rescue Twitter::APIFailed => e yield e.to_s end end end end def format(source) version = source.gsub(/<[^>]*>/, "").strip version << " <#{$1}>" if source =~ / href="([^"]+)/ Net::IRC.ctcp_encode version end end end end atig-0.6.1/lib/atig/command/refresh.rb0000644000175000017500000000063012661500130017362 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- # -*- mode:ruby; coding:utf-8 -*- require 'atig/command/info' require 'time' module Atig module Command class Refresh < Atig::Command::Command def command_name; %w(refresh) end def action(target, mesg, command,args) db.followings.invalidate db.lists.invalidate :all yield "refresh followings/lists..." end end end end atig-0.6.1/lib/atig/command/name.rb0000644000175000017500000000065312661500130016651 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/command' module Atig module Command class Name < Atig::Command::Command def command_name; %w(name) end def action(target, mesg, command, args) api.delay(0) do|t| name = mesg.split(" ", 2)[1] || "" t.post('account/update_profile',:name=>name) yield "You are named #{name}." end end end end end atig-0.6.1/lib/atig/command/whois.rb0000644000175000017500000000274512661500130017066 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/info' require 'time' module Atig module Command class Whois < Atig::Command::Command def command_name; %w(whois) end def action(target, mesg, command,args) if args.empty? yield "/me #{command} " return end nick,*_ = args Atig::Command::Info::user(db, api, nick) do|user| id = "id=#{user.id}" host = "twitter.com" host += "/protected" if user.protected desc = user.name desc = "#{desc} / #{user.description}".gsub(/\s+/, " ") if user.description and not user.description.empty? signon_at = ::Time.parse(user.created_at).to_i rescue 0 idle_sec = (::Time.now - (user.status ? ::Time.parse(user.status.created_at) : signon_at)).to_i rescue 0 location = user.location location = "SoMa neighborhood of San Francisco, CA" if location.nil? or location.empty? send Net::IRC::Constants::RPL_WHOISUSER, nick, id, host, "*", desc send Net::IRC::Constants::RPL_WHOISSERVER, nick, host, location send Net::IRC::Constants::RPL_WHOISIDLE, nick, "#{idle_sec}", "#{signon_at}", "seconds idle, signon time" send Net::IRC::Constants::RPL_ENDOFWHOIS, nick, "End of WHOIS list" end end def send(command,nick,*params) gateway.post gateway.server_name, command, db.me.screen_name, nick, *params end end end end atig-0.6.1/lib/atig/command/option.rb0000644000175000017500000000161712661500130017242 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/command' require 'atig/option' module Atig module Command class Option < Atig::Command::Command def initialize(*args) super @methods = OpenStruct.instance_methods end def command_name; %w(opt opts option options) end def action(target, mesg, command, args) if args.empty? @opts.fields. map{|x| x.to_s }. sort.each do|name| yield "#{name} => #{@opts[name]}" end else _,name,value = mesg.split ' ', 3 unless value then # show the value yield "#{name} => #{@opts.send name}" else # set the value @opts.send "#{name}=",::Atig::Option.parse_value(value) yield "#{name} => #{@opts.send name}" end end end end end end atig-0.6.1/lib/atig/command/dm.rb0000644000175000017500000000132412661500130016325 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/command' begin require 'jcode' rescue LoadError end module Atig module Command class Dm < Atig::Command::Command def initialize(*args); super end def command_name; %w(d dm dms) end def action(target, mesg, command, args) if args.empty? yield "/me #{command} blah blah" return end user = args.first text = mesg.split(" ", 3)[2] api.delay(0) do|t| t.post("direct_messages/new",{ screen_name: user, text: text }) yield "Sent message to #{user}: #{text}" end end end end end atig-0.6.1/lib/atig/command/autofix.rb0000644000175000017500000000311012661500130017377 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/command' require 'atig/levenshtein' begin require 'jcode' rescue LoadError end module Atig module Command class Autofix < Atig::Command::Command def initialize(*args); super end def command_name; /(?:autofix|topic|overwwrite)!?/ end def distance(s1, s2) c1 = s1.split(//) c2 = s2.split(//) distance = Atig::Levenshtein.levenshtein c1, c2 distance.to_f / [ c1.size, c2.size ].max end def fix?(command, text, prev) command[-1,1] == '!' or distance(text, prev.status.text) < 0.5 end def action(target, mesg, command, args) if args.empty? yield "/me #{command} blah blah" return end text = mesg.split(" ", 2)[1] q = gateway.output_message(status: text) prev,*_ = db.statuses.find_by_user( db.me, limit: 1) unless fix?(command, q[:status], prev) then api.delay(0, retry:3) do|t| ret = t.post("statuses/update", q) gateway.update_status ret, target end else api.delay(0, retry:3) do|t| yield "Similar update in previous. Conclude that it has error." yield "And overwrite previous as new status: #{q[:status]}" ret = t.post("statuses/update", q) gateway.update_status ret, target t.post("statuses/destroy/#{prev.status.id}") db.statuses.transaction{|d| d.remove_by_id prev.id } end end end end end end atig-0.6.1/lib/atig/command/favorite.rb0000644000175000017500000000135512661500130017550 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/command' require 'atig/command/info' module Atig module Command class Favorite < Atig::Command::Command def initialize(*args); super end def command_name; %w(fav unfav) end def action(target, mesg, command, args) method = { 'fav' => 'create', 'unfav' => 'destroy' }[command] args.each do|tid| if entry = Info.find_status(db, tid) api.delay(0){|t| t.post("favorites/#{method}", {id: entry.status.id}) yield "#{command.upcase}: #{entry.user.screen_name}: #{entry.status.text}" } else yield "No such ID : #{tid}" end end end end end end atig-0.6.1/lib/atig/command/retweet.rb0000644000175000017500000000341612661500130017410 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/bitly' require 'atig/command/command' begin require 'jcode' rescue LoadError end module Atig module Command class Retweet < Atig::Command::Command def initialize(*args) super @bitly = Bitly.no_login @log end def command_name; %w(ort rt retweet qt) end def rt_with_comment(target, comment, entry) screen_name = "@#{entry.user.screen_name}" text = "#{comment.strip} RT #{screen_name}: #{entry.status.text}" chars = text.each_char.to_a if chars.size > 140 then url = @bitly.shorten "https://twitter.com/#{entry.user.screen_name}/status/#{entry.status.id}" text = chars[0,140-url.size-1].join('') + ' ' + url end q = gateway.output_message(status: text) api.delay(0) do|t| ret = t.post("statuses/update", q) gateway.update_status ret,target, "RT to #{entry.user.screen_name}: #{entry.status.text}" end end def rt_with_no_comment(target, entry) api.delay(0) do|t| ret = t.post("statuses/retweet/#{entry.status.id}") gateway.update_status ret,target, "RT to #{entry.user.screen_name}: #{entry.status.text}" end end def action(target, mesg, command, args) if args.empty? yield "/me #{command} blah blah" return end tid = args.first if status = Info.find_status(db, tid) then if args.size >= 2 comment = mesg.split(" ", 3)[2] + " " rt_with_comment(target, comment, status) else rt_with_no_comment(target, status) end else yield "No such ID : #{tid}" end end end end end atig-0.6.1/lib/atig/command/uptime.rb0000644000175000017500000000121412661500130017226 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/command' module Atig module Command class Uptime < Atig::Command::Command def initialize(*args) super @time = ::Time.now end def command_name; "uptime" end def action(target, mesg, command, args) yield format(::Time.now - @time) end def format(x) day , y = x.divmod(60*60*24) hour, z = y.divmod(60*60) min , sec = z.divmod(60) s = "" s += "#{day} days " if day > 0 s += "%02d:" % hour if hour > 0 s += "%02d:%02d" % [min,sec] s end end end end atig-0.6.1/lib/atig/gateway.rb0000644000175000017500000000601212661500130015747 0ustar uwabamiuwabamirequire 'atig/gateway/session' Atig::Gateway::Session.agents = [ Atig::Agent::FullList, Atig::Agent::Following, Atig::Agent::ListStatus, Atig::Agent::Mention, Atig::Agent::Dm, Atig::Agent::Timeline, Atig::Agent::Cleanup, Atig::Agent::UserStream, Atig::Agent::Noretweets, ] Atig::Gateway::Session.ifilters = [ Atig::IFilter::Retweet, Atig::IFilter::RetweetTime, Atig::IFilter::Sanitize, Atig::IFilter::ExpandUrl, Atig::IFilter::Strip.new(%w{ *tw* *Sh*}), Atig::IFilter::Tid, Atig::IFilter::Sid ] Atig::Gateway::Session.ofilters = [ Atig::OFilter::EscapeUrl, Atig::OFilter::ShortUrl, Atig::OFilter::Geo, Atig::OFilter::Footer, ] Atig::Gateway::Session.commands = [ Atig::Command::Retweet, Atig::Command::Reply, Atig::Command::User, Atig::Command::Favorite, Atig::Command::Uptime, Atig::Command::Destroy, Atig::Command::Status, Atig::Command::Thread, Atig::Command::Time, Atig::Command::Version, Atig::Command::UserInfo, Atig::Command::Whois, Atig::Command::Option, Atig::Command::Location, Atig::Command::Name, Atig::Command::Autofix, Atig::Command::Limit, Atig::Command::Search, Atig::Command::Refresh, Atig::Command::Spam, Atig::Command::Dm, ] Atig::Gateway::Session.channels = [ Atig::Channel::Timeline, Atig::Channel::Mention, Atig::Channel::Dm, Atig::Channel::List, Atig::Channel::Retweet, ] atig-0.6.1/lib/atig/db/0000755000175000017500000000000012661500130014347 5ustar uwabamiuwabamiatig-0.6.1/lib/atig/db/sized_uniq_array.rb0000644000175000017500000000234012661500130020243 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'forwardable' module Atig module Db class SizedUniqArray extend Forwardable include Enumerable def_delegators :@xs,:[] attr_reader :size def initialize(capacity) @size = 0 @index = 0 @capacity = capacity @xs = Array.new(capacity, nil) end def each(&f) if @size < @capacity then 0.upto(@size - 1) {|i| f.call @xs[i] } else 0.upto(@size - 1){|i| f.call @xs[ (i + @index) % @capacity ] } end end def reverse_each(&f) if @size < @capacity then (@size - 1).downto(0) {|i| f.call @xs[i] } else (@size - 1).downto(0){|i| f.call @xs[ (i + @index) % @capacity ] } end end def include?(item) self.any?{|x| x.id == item.id } end def push(item) return nil if include? item i = @index @xs[i] = item @size = [ @size + 1, @capacity ].min @index = ( @index + 1 ) % @capacity i end alias_method :<<, :push end end end atig-0.6.1/lib/atig/db/lists.rb0000644000175000017500000000275512661500130016043 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/db/listenable' require 'atig/db/transaction' module Atig module Db class Lists include Listenable include Transaction def initialize(name) @name = name @lists = {} @on_invalidated = lambda{|*_| } @members = nil end def update(lists) @members = Hash.new{|hash,key| hash[key] = [] } (lists.keys - @lists.keys).each do|name| list = Followings.new(@name % name) list.listen{|kind,users| notify kind,name,users } @lists[name] = list notify :new, name end (@lists.keys - lists.keys).each do|x| @lists.delete x notify :del,x end lists.each do|name,users| @lists[name].update users end lists.each do|list, users| users.each do|user| @members[user.screen_name] << list end end end def [](name) @lists[name] end def invalidate(name) @on_invalidated.call name end def on_invalidated(&f) @on_invalidated = f end def find_by_screen_name(name) return [] unless @members @members[name] end def find_by_list_name(name) @lists[name].users end def each(&f) @lists.each do|name,users| f.call name,users.users end end end end end atig-0.6.1/lib/atig/db/transaction.rb0000644000175000017500000000173012661500130017222 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'timeout' require 'atig/util' module Atig module Db module Transaction include Util def init return if @queue debug "transaction thread start" @queue = SizedQueue.new 10 daemon do f,src = @queue.pop debug "transaction is poped at #{src}" if respond_to?(:timeout_interval) && timeout_interval > 0 then begin Timeout.timeout(timeout_interval){ f.call self } rescue TimeoutError debug "transaction is timeout at #{src}" end else f.call self end debug "transaction is finished at #{src}" end end def transaction(&f) init debug "transaction is registered" @queue.push [ f, caller.first ] end def debug(s) if respond_to? :log log :debug, s end end end end end atig-0.6.1/lib/atig/db/roman.rb0000644000175000017500000000147412661500130016016 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- module Atig module Db class Roman Seq = %w[ a i u e o ka ki ku ke ko sa shi su se so ta chi tsu te to na ni nu ne no ha hi fu he ho ma mi mu me mo ya yu yo ra ri ru re ro wa wo n ga gi gu ge go za ji zu ze zo da de do ba bi bu be bo pa pi pu pe po kya kyu kyo sha shu sho cha chu cho nya nyu nyo hya hyu hyo mya myu myo rya ryu ryo gya gyu gyo ja ju jo bya byu byo pya pyu pyo ].freeze def make(n) ret = [] begin n, r = n.divmod(Seq.size) ret << Seq[r] end while n > 0 ret.reverse.join end end end end atig-0.6.1/lib/atig/db/sql.rb0000644000175000017500000000105312661500130015472 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'sqlite3' module Atig module Db class Sql def initialize(name) @name = name end def dump(obj) [Marshal.dump(obj)].pack('m') end def load(text) if text == nil then nil else Marshal.load(text.unpack('m').first) end end def execute(&f) db = SQLite3::Database.new @name begin res = f.call db ensure db.close end res end end end end atig-0.6.1/lib/atig/db/db.rb0000644000175000017500000000257012661500130015265 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/db/followings' require 'atig/db/statuses' require 'atig/db/lists' require 'atig/util' require 'thread' require 'set' require 'fileutils' module Atig module Db class Db include Util attr_reader :followings, :statuses, :dms, :lists, :noretweets attr_accessor :me VERSION = 4 def initialize(context, opt={}) @log = context.log @me = opt[:me] @tmpdir = opt[:tmpdir] @followings = Followings.new dir('following') @statuses = Statuses.new dir('status') @dms = Statuses.new dir('dm') @lists = Lists.new dir('lists.%s') @noretweets = Array.new log :info, "initialize" end def dir(id) dir = File.expand_path "atig/#{@me.screen_name}/", @tmpdir log :debug, "db(#{id}) = #{dir}" FileUtils.mkdir_p dir File.expand_path "#{id}.#{VERSION}.db", dir end def transaction(&f) @followings.transaction do|_| @statuses.transaction do|_| @dms.transaction do|_| @lists.transaction do|_| f.call self end end end end end def cleanup transaction do @statuses.cleanup @dms.cleanup end end end end end atig-0.6.1/lib/atig/db/followings.rb0000644000175000017500000000723612661500130017067 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/db/listenable' require 'atig/db/transaction' require 'atig/db/sql' module Atig module Db class Followings include Listenable include Transaction def initialize(name) @db = Sql.new name unless File.exist? name then @db.execute do|db| db.execute %{create table users ( id integer primary key, screen_name text, user_id text, protected bool, only bool, data blob);} db.execute %{ create index users_screen on users (screen_name); } end end @users = [] @on_invalidated = lambda{} end def size @db.execute do|db| db.get_first_value('SELECT COUNT(*) FROM users').to_i end end def empty? @db.execute do|db| db.get_first_value('SELECT * FROM users LIMIT 1') == nil end end def invalidate @on_invalidated.call end def on_invalidated(&f) @on_invalidated = f end def users @db.execute{|db| db.execute("SELECT data FROM users").map{|data| @db.load data[0] } } end def exists?(db, templ, *args) db.get_first_value("SELECT * FROM users WHERE #{templ} LIMIT 1",*args) != nil end def may_notify(mode, xs) unless xs.empty? then notify mode, xs end end def bool(b) if b then 1 else 0 end end def update(users) @db.execute do|db| may_notify :join, users.select{|u| not exists?(db, "screen_name = ?", u.screen_name) } names = users.map{|u| u.screen_name.inspect }.join(",") may_notify :part, db.execute(%{SELECT screen_name,data FROM users WHERE screen_name NOT IN (#{names})}).map{|_,data| @db.load(data) } db.execute(%{DELETE FROM users WHERE screen_name NOT IN (#{names})}) may_notify :mode, users.select{|u| exists?(db, "screen_name = ? AND (protected != ? OR only != ?)", u.screen_name, bool(u.protected), bool(u.only)) } users.each do|user| id = db.get_first_value('SELECT id FROM users WHERE user_id = ? LIMIT 1', user.id) if id then db.execute("UPDATE users SET screen_name = ?, protected = ?, only = ?, data = ? WHERE id = ?", user.screen_name, bool(user.protected), bool(user.only), @db.dump(user), id) else db.execute("INSERT INTO users VALUES(NULL, :screen_name, :user_id, :protected, :only, :data)", screen_name: user.screen_name, user_id: user.id, protected: bool(user.protected), only: bool(user.only), data: @db.dump(user)) end end end end def find_by_screen_name(name) @db.execute do|db| @db.load db.get_first_value('SELECT data FROM users WHERE screen_name = ? LIMIT 1', name) end end def include?(user) @db.execute do|db| exists? db,'user_id = ?', user.id end end end end end atig-0.6.1/lib/atig/db/statuses.rb0000644000175000017500000001051712661500130016553 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/db/listenable' require 'atig/db/transaction' require 'sqlite3' require 'atig/db/roman' require 'atig/db/sql' require 'base64' class OpenStruct def id; method_missing(:id) end end module Atig module Db class Statuses include Listenable include Transaction Size = 400 def initialize(name) @db = Sql.new name @roman = Roman.new unless File.exist? name then @db.execute do|db| db.execute %{create table status ( id integer primary key, status_id text, tid text, sid text, screen_name text, user_id text, created_at integer, data blob);} db.execute %{create table id ( id integer primary key, screen_name text, count integer);} # thx to @L_star # http://d.hatena.ne.jp/mzp/20100407#c db.execute_batch %{ create index status_createdat on status(created_at); create index status_sid on status(sid); create index status_statusid on status(status_id); create index status_tid on status(tid); create index status_userid on status(user_id); create index status_id on status(id); create index id_id on id(id); } end end end def add(opt) @db.execute do|db| id = opt[:status].id return unless db.execute(%{SELECT id FROM status WHERE status_id = ?}, id).empty? screen_name = opt[:user].screen_name sum = db.get_first_value("SELECT sum(count) FROM id").to_i count = db.get_first_value("SELECT count FROM id WHERE screen_name = ?", screen_name).to_i entry = OpenStruct.new opt.merge(tid: @roman.make(sum), sid: "#{screen_name}:#{@roman.make(count)}") db.execute(%{INSERT INTO status VALUES(NULL, :id, :tid, :sid, :screen_name, :user_id, :created_at, :data)}, id: id, tid: entry.tid, sid: entry.sid, screen_name: screen_name, user_id: opt[:user].id, created_at: Time.parse(opt[:status].created_at).to_i, data: @db.dump(entry)) if count == 0 then db.execute("INSERT INTO id VALUES(NULL,?,?)", screen_name, 1) else db.execute("UPDATE id SET count = ? WHERE screen_name = ?", count + 1, screen_name) end notify entry end end def find_all(opt={}) find '1', 1, opt end def find_by_screen_name(name, opt={}) find 'screen_name',name, opt end def find_by_user(user, opt={}) find 'user_id', user.id, opt end def find_by_tid(tid) find('tid', tid).first end def find_by_sid(tid) find('sid', tid).first end def find_by_status_id(id) find('status_id', id).first end def find_by_id(id) find('id', id).first end def remove_by_id(id) @db.execute do|db| db.execute "DELETE FROM status WHERE id = ?",id end end def cleanup @db.execute do|db| created_at = db.execute("SELECT created_at FROM status ORDER BY created_at DESC LIMIT 1 OFFSET ?", Size-1) unless created_at.empty? then db.execute "DELETE FROM status WHERE created_at < ?", created_at.first end db.execute "VACUUM status" end end private def find(lhs,rhs, opt={},&f) rhs.encoding!("UTF-8") if rhs.respond_to? :encoding! query = "SELECT id,data FROM status WHERE #{lhs} = :rhs ORDER BY created_at DESC LIMIT :limit" params = { rhs: rhs, limit: opt.fetch(:limit,20) } res = [] @db.execute do|db| db.execute(query,params) do|id,data,*_| e = @db.load(data) e.id = id.to_i res << e end end res end end end end atig-0.6.1/lib/atig/db/listenable.rb0000644000175000017500000000056012661500130017017 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'forwardable' module Atig module Db module Listenable SingleThread = false def listen(&f) @listeners ||= [] @listeners.push f end private def notify(*args) @listeners ||= [] @listeners.each{|f| f.call(*args) } end end end end atig-0.6.1/lib/atig/ofilter.rb0000644000175000017500000000017412661500130015755 0ustar uwabamiuwabamirequire 'atig/ofilter/escape_url' require 'atig/ofilter/short_url' require 'atig/ofilter/geo' require 'atig/ofilter/footer' atig-0.6.1/lib/atig/http.rb0000644000175000017500000000576212661500130015300 0ustar uwabamiuwabamirequire "net/https" require 'atig/util' module Atig class Http include Util @@proxy = nil def self.proxy=(proxy) if proxy =~ /\A(?:([^:@]+)(?::([^@]+))?@)?([^:]+)(?::(\d+))?\z/ then @@proxy = OpenStruct.new({ user: $1, password: $2, address: $3, port: $4.to_i, }) end end def initialize(logger=nil) @log = logger @cert_store = OpenSSL::X509::Store.new @cert_store.set_default_paths end def server_name "twittergw" end def server_version @server_version ||= instance_eval { head = `git rev-parse HEAD 2>/dev/null`.chomp head.empty?? "unknown" : head } end def http(uri, open_timeout = nil, read_timeout = 60) http = case when @@proxy Net::HTTP.new(uri.host, uri.port, @@proxy.address, @@proxy.port, @@proxy.user, @@proxy.password) when ENV["HTTP_PROXY"], ENV["http_proxy"] proxy = URI(ENV["HTTP_PROXY"] || ENV["http_proxy"]) Net::HTTP.new(uri.host, uri.port, proxy.host, proxy.port, proxy.user, proxy.password) else Net::HTTP.new(uri.host, uri.port) end http.open_timeout = open_timeout if open_timeout # nil by default http.read_timeout = read_timeout if read_timeout # 60 by default if uri.is_a? URI::HTTPS http.use_ssl = true http.cert_store = @cert_store end http rescue => e log(:error, e) if @log end def req(method, uri, header = {}, credentials = nil) accepts = ["*/*"] types = { "json" => "application/json", "txt" => "text/plain" } ext = uri.path[/[^.]+\z/] accepts.unshift types[ext] if types.key?(ext) user_agent = "#{self.class}/#{server_version} (#{File.basename(__FILE__)}; net-irc) Ruby/#{RUBY_VERSION} (#{RUBY_PLATFORM})" header["User-Agent"] ||= user_agent header["Accept"] ||= accepts.join(",") header["Accept-Charset"] ||= "UTF-8,*;q=0.0" if ext != "json" req = case method.to_s.downcase.to_sym when :get Net::HTTP::Get.new uri.request_uri, header when :head Net::HTTP::Head.new uri.request_uri, header when :post Net::HTTP::Post.new uri.path, header when :put Net::HTTP::Put.new uri.path, header when :delete Net::HTTP::Delete.new uri.request_uri, header else # raise "" end if req.request_body_permitted? req["Content-Type"] ||= "application/x-www-form-urlencoded" req.body = uri.query end req.basic_auth(*credentials) if credentials req rescue => e log(:error, e) if @log end end end atig-0.6.1/lib/atig/command.rb0000644000175000017500000000116312661500130015726 0ustar uwabamiuwabamirequire 'atig/command/retweet' require 'atig/command/reply' require 'atig/command/user' require 'atig/command/favorite' require 'atig/command/uptime' require 'atig/command/destroy' require 'atig/command/status' require 'atig/command/thread' require 'atig/command/time' require 'atig/command/version' require 'atig/command/user_info' require 'atig/command/whois' require 'atig/command/option' require 'atig/command/location' require 'atig/command/name' require 'atig/command/autofix' require 'atig/command/limit' require 'atig/command/search' require 'atig/command/refresh' require 'atig/command/spam' require 'atig/command/dm' atig-0.6.1/lib/atig/bitly.rb0000644000175000017500000000223212661500130015431 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'json' require 'atig/http' module Atig class Bitly class << self def no_login(logger) self.new logger, nil, nil end def login(logger, login, key) self.new logger, login, key end end def initialize(logger, login, key) @log = logger @login = login @key = key @http = Http.new logger end def shorten(url) return url if url =~ /bit\.ly/ bitly = URI("http://api.bit.ly/v3/shorten") if @login and @key bitly.path = "/shorten" bitly.query = { format: "json", longUrl: url, login: @login, apiKey: @key, }.to_query_str(";") req = @http.req(:get, bitly, {}) res = @http.http(bitly, 5, 10).request(req) res = JSON.parse(res.body) if res['statusCode'] == "ERROR" then @log.error res['errorMessage'] url else res["results"][url]['shortUrl'] end else url end rescue Errno::ETIMEDOUT, JSON::ParserError, IOError, Timeout::Error, Errno::ECONNRESET => e @log.error e url end end end atig-0.6.1/lib/atig/stream.rb0000644000175000017500000000304612661500130015605 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'json' require 'uri' require 'logger' require 'atig/twitter_struct' require 'atig/util' require 'atig/url_escape' module Atig class Stream include Util attr_reader :channel class APIFailed < StandardError; end def initialize(context, channel, access) @log = context.log @opts = context.opts @channel = channel @access = access end def watch(path, query={}, &f) path.sub!(%r{\A/+}, "") uri = api_base uri.path += path uri.path += ".json" uri.query = query.to_query_str unless query.empty? @log.debug [uri.to_s] http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri.request_uri, {"Accept-Encoding" => "identity"}) request.oauth!(http, @access.consumer, @access) http.request(request) do |response| unless response.code == '200' then raise APIFailed,"#{response.code} #{response.message}" end begin buffer = '' response.read_body do |chunk| next if chunk.chomp.empty? buffer << chunk.to_s if buffer =~ /\A(.*)\n/ then text = $1 unless text.strip.empty? f.call TwitterStruct.make(JSON.parse(text)) end buffer = '' end end rescue => e raise APIFailed,e.to_s end end end def api_base URI(@opts.stream_api_base) end end end atig-0.6.1/lib/atig/client.rb0000644000175000017500000000102312661500130015561 0ustar uwabamiuwabamirequire 'logger' module Atig module Client class << self def run opts = Atig::OptParser.parse!(ARGV) opts[:logger] = Logger.new(opts[:log], "weekly") opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO conf = File.expand_path opts[:conf] if File.exist? conf then opts[:logger].info "Loading #{conf}" load conf end Net::IRC::Server.new(opts[:host], opts[:port], Atig::Gateway::Session, opts).start end end end end atig-0.6.1/lib/atig/search_twitter.rb0000644000175000017500000000072712661500130017344 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/basic_twitter' require 'atig/http' module Atig # from tig.rb class SearchTwitter < BasicTwitter def initialize(context) super context, :search_api_base @http = Atig::Http.new @log end protected def request(uri, opts) header = {} req = @http.req :get, uri, header @log.debug [req.method, uri.to_s] @http.http(uri, 30, 30).request req end end end atig-0.6.1/lib/atig/basic_twitter.rb0000755000175000017500000000603712661500130017163 0ustar uwabamiuwabami#!/usr/bin/env ruby # -*- coding: utf-8 -*- require "json" require 'atig/twitter_struct' require 'atig/http' module Atig # from tig.rb class BasicTwitter attr_reader :limit, :remain, :reset class APIFailed < StandardError; end def initialize(context, api_base) @log = context.log @opts = context.opts @api_base = api_base @http = Atig::Http.new @log @limit = @remain = 150 end def api(path, query = {}, opts = {}) path.sub!(%r{\A/+}, "") uri = api_base uri.path += path uri.path += ".json" if path != "users/username_available" uri.query = query.to_query_str unless query.empty? begin ret = request(uri, opts) rescue OpenSSL::SSL::SSLError => e @log.error e.inspect raise e.inspect end if ret["X-RateLimit-Limit"] then hourly_limit = ret["X-RateLimit-Limit"].to_i unless hourly_limit.zero? if @limit != hourly_limit msg = "The rate limit per hour was changed: #{@limit} to #{hourly_limit}" @log.info msg @limit = hourly_limit end end end if ret["X-RateLimit-Remaining"] then @remain = ret["X-RateLimit-Remaining"].to_i @log.debug "IP based limit: #{@remain}" end if ret["X-RateLimit-Reset"] then @reset = ret["X-RateLimit-Reset"].to_i @log.debug "RateLimit Reset: #{@reset}" end case ret when Net::HTTPOK # 200 # Avoid Twitter's invalid JSON json = ret.body.strip.sub(/\A(?:false|true)\z/, "[\\&]") res = JSON.parse(json) if res.is_a?(Hash) && res["error"] # and not res["response"] if @error != res["error"] @error = res["error"] log @error end raise APIFailed, res["error"] end TwitterStruct.make(res) when Net::HTTPNoContent, # 204 Net::HTTPNotModified # 304 [] when Net::HTTPBadRequest # 400: exceeded the rate limitation if ret.key?("X-RateLimit-Reset") @log.info "waiting for rate limit reset" s = ret["X-RateLimit-Reset"].to_i - Time.now.to_i if s > 0 sleep (s > 60 * 10) ? 60 * 10 : s # 10 分に一回はとってくるように end end raise APIFailed, "#{ret.code}: #{ret.message}" when Net::HTTPUnauthorized # 401 raise APIFailed, "#{ret.code}: #{ret.message}" else raise APIFailed, "Server Returned #{ret.code} #{ret.message}" end rescue Errno::ETIMEDOUT, JSON::ParserError, IOError, Timeout::Error, Errno::ECONNRESET => e raise APIFailed, e.inspect end def self.http_methods(*methods) methods.each do |m| self.module_eval < e @log.error e.inspect ret end def escape_http_urls(text) original_text = text.encoding!("UTF-8").dup if defined? ::Punycode # TODO: Nameprep text.gsub!(%r{(https?://)([^\x00-\x2C\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+)}) do domain = $2 # Dots: # * U+002E (full stop) * U+3002 (ideographic full stop) # * U+FF0E (fullwidth full stop) * U+FF61 (halfwidth ideographic full stop) # => /[.\u3002\uFF0E\uFF61] # Ruby 1.9 /x $1 + domain.split(/\.|\343\200\202|\357\274\216|\357\275\241/).map do |label| break [domain] if /\A-|[\x00-\x2C\x2E\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]|-\z/ === label next label unless /[^-A-Za-z0-9]/ === label punycode = Punycode.encode(label) break [domain] if punycode.size > 59 "xn--#{punycode}" end.join(".") end if text != original_text log :info, "Punycode encoded: #{text}" original_text = text.dup end end urls = [] text.split(/[\s<>]+/).each do |str| next if /%[0-9A-Fa-f]{2}/ === str # URI::UNSAFE + "#" escaped_str = URI.escape(str, %r{[^-_.!~*'()a-zA-Z0-9;/?:@&=+$,\[\]#]}) #' URI.extract(escaped_str, %w[http https]).each do |url| uri = URI(URI.rstrip(url)) if not urls.include?(uri.to_s) and self.exist_uri?(uri) urls << uri.to_s end end if escaped_str != str end urls.each do |url| unescaped_url = URI.unescape(url).encoding!("UTF-8") text.gsub!(unescaped_url, url) end log :info, "Percent encoded: #{text}" if text != original_text text.encoding!("UTF-8") rescue => e log :error, e text end end end end atig-0.6.1/lib/atig/ofilter/geo.rb0000644000175000017500000000047512661500130016533 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- module Atig module OFilter class Geo def initialize(context) @opts = context.opts end def call(q) return q unless @opts.ll lat, long = @opts.ll.split(",", 2) q.merge lat: lat.to_f, long: long.to_f end end end end atig-0.6.1/lib/atig/util.rb0000644000175000017500000000042512661500130015265 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/exception_util' module Atig module Util private include ExceptionUtil def log(type, s) if @log then @log.send type, "[#{self.class}] #{s}" else STDERR.puts s end end end end atig-0.6.1/lib/atig/exception_util.rb0000644000175000017500000000063712661500130017350 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- module Atig module ExceptionUtil def safe(&f) begin f.call rescue Exception => e s = e.inspect + "\n" e.backtrace.each do |l| s += "\t#{l}\n" end log :error,s end end def daemon(&f) Thread.new do loop{ safe { f.call }} end end module_function :safe,:daemon end end atig-0.6.1/lib/atig/url_escape.rb0000644000175000017500000000256212661500130016436 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- class Hash # { f: "v" } #=> "f=v" # { "f" => [1, 2] } #=> "f=1&f=2" # { "f" => "" } #=> "f=" # { "f" => nil } #=> "f" def to_query_str separator = "&" inject([]) do |r, (k, v)| k = URI.encode_component k.to_s (v.is_a?(Array) ? v : [v]).each do |i| if i.nil? r << k else r << "#{k}=#{URI.encode_component i.to_s}" end end r end.join separator end end class String def ch? /\A[&#+!][^ \007,]{1,50}\z/ === self end def screen_name? /\A[A-Za-z0-9_]{1,15}\z/ === self end def encoding! enc return self unless respond_to? :force_encoding force_encoding enc end end module URI::Escape alias :_orig_escape :escape if defined? ::RUBY_REVISION and RUBY_REVISION < 24544 # URI.escape("あ1") #=> "%E3%81%82\xEF\xBC\x91" # URI("file:///4") #=> # # "\\d" -> "[0-9]" for Ruby 1.9 def escape str, unsafe = %r{[^-_.!~*'()a-zA-Z0-9;/?:@&=+$,\[\]]} #' _orig_escape(str, unsafe) end alias :encode :escape end def encode_component(str, unsafe = ::OAuth::RESERVED_CHARACTERS) _orig_escape(str, unsafe).tr(" ", "+") end def rstrip str str.sub(%r{ (?: ( / [^/?#()]* (?: \( [^/?#()]* \) [^/?#()]* )* ) \) [^/?#()]* | \. ) \z }x, "\\1") end end atig-0.6.1/lib/atig/version.rb0000644000175000017500000000004412661500130015772 0ustar uwabamiuwabamimodule Atig VERSION = "0.6.1" end atig-0.6.1/lib/atig/gateway/0000755000175000017500000000000012661500130015423 5ustar uwabamiuwabamiatig-0.6.1/lib/atig/gateway/channel.rb0000644000175000017500000000465012661500130017365 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- module Atig module Gateway class Channel class << self def delegate(*ids) ids.each do|id| module_eval < and try again later. END finish end @prefix = prefix me @user = @prefix.user @host = @prefix.host post server_name, MODE, @nick, "+o" @db = Atig::Db::Db.new context, me:me, size: 100, tmpdir: @tmpdir run_new @@commands, context, self, @api, @db run_new @@agents , context, @api, @db run_new @@channels, context, self, @db @db.statuses.add user: me, source: :me, status: me.status end end def on_disconnected (@thread_group.list - [Thread.current]).each {|t| t.kill } end def on_privmsg(m) target, mesg = *m.params m.ctcps.each {|ctcp| on_ctcp(target, ctcp) } if m.ctcp? case when mesg.empty? return when mesg.sub!(/\A +/, "") on_ctcp_action(target, mesg) when target[0] != ?# channel target on_ctcp_action(target, "dm #{target} #{mesg}") when (@opts.old_style_reply and mesg =~ /\A@(?>([A-Za-z0-9_]{1,15}))[^A-Za-z0-9_]/) on_ctcp_action(target, "reply #{$1} #{mesg}") else on_ctcp_action(target, "status #{mesg}") end end def on_ctcp(target, mesg) if mesg.respond_to? :encoding! mesg.encoding! "UTF-8" end type, mesg = mesg.split(" ", 2) method = "on_ctcp_#{type.downcase}".to_sym send(method, target, mesg) if respond_to? method, true end def on_ctcp_action(target, mesg) command, *args = mesg.split(" ") last_match = nil command = command.to_s.downcase _, action = @ctcp_actions.find{|define, f| r = (define === command) last_match = Regexp.last_match r } if action then safe { action.call(target, mesg, last_match || command, args) } else self[target].notify "[atig.rb] CTCP ACTION COMMANDS:" @ctcp_actions.keys.each do |c| self[target].notify c.to_s end end end def on_invite(m) nick, channel = *m.params if not nick.screen_name? or @db.me.screen_name.casecmp(nick).zero? post server_name, ERR_NOSUCHNICK, nick, "No such nick: #{nick}" # or yourself return end unless @channels.key? channel post server_name, ERR_NOSUCHNICK, nick, "No such channel: #{channel}" return end @api.delay(0){|api| @channels[channel].on_invite(api, nick) } end def on_kick(m) channel, nick, _ = *m.params if not nick.screen_name? or @db.me.screen_name.casecmp(nick).zero? post server_name, ERR_NOSUCHNICK, nick, "No such nick: #{nick}" # or yourself return end unless @channels.key? channel post server_name, ERR_NOSUCHNICK, nick, "No such channel: #{channel}" return end @api.delay(0){|api| @channels[channel].on_kick(api, nick) } end def on_whois(m) nick = m.params[0] unless nick.screen_name? post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel" return end on_ctcp_action(nil, "whois #{nick}") end def on_topic(m) channel,topic = *m.params on_ctcp_action(channel, "topic #{topic}") end def on_who(m) channel = m.params[0] unless @channels.key? channel post server_name, ERR_NOSUCHNICK, nick, "No such channel: #{channel}" return end @channels[channel].on_who do|user| # " # ( "H" / "G" > ["*"] [ ( "@" / "+" ) ] # : " prefix = prefix(user) server = 'twitter.com' mode = case prefix.nick when @nick then "~" else "+" end real = user.name post server_name, RPL_WHOREPLY, @nick, channel, prefix.user, prefix.host, server, prefix.nick, "H*#{mode}", "1 #{real}" end post server_name, RPL_ENDOFWHO, @nick, channel end protected def run_new(klasses,*args) (klasses || []).map do|klass| if klass.respond_to?(:new) klass.new(*args) else klass end end end CONFIG_FILE = File.expand_path("~/.atig/oauth") def save_config FileUtils.mkdir_p File.dirname(CONFIG_FILE) File.open(CONFIG_FILE, "w") {|io| YAML.dump(OAuth.dump,io) } FileUtils.chmod 0600, CONFIG_FILE end def load_config FileUtils.mkdir_p File.dirname(CONFIG_FILE) OAuth.load(YAML.load_file(CONFIG_FILE)) rescue nil end def available_user_modes "o" end def available_channel_modes "mntiovah" end end end end atig-0.6.1/lib/atig/monkey.rb0000644000175000017500000000014012661500130015604 0ustar uwabamiuwabamirequire 'net/irc' # monkey hack module Net::IRC module_function :low_quote, :low_dequote end atig-0.6.1/lib/atig/sized_hash.rb0000644000175000017500000000100112661500130016420 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'forwardable' module Atig class SizedHash extend Forwardable def_delegators :@xs, :size def initialize(size) @size = size @xs = [] end def [](key) kv = @xs.assoc(key) if kv then @xs.delete kv @xs.push kv kv[1] end end def []=(key,value) @xs.push [key, value] @xs.shift if @xs.size > @size end def key?(key) @xs.any?{|k,_| key == k } end end end atig-0.6.1/lib/atig/agent/0000755000175000017500000000000012661500130015060 5ustar uwabamiuwabamiatig-0.6.1/lib/atig/agent/following.rb0000644000175000017500000000221412661500130017404 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/util' module Atig module Agent class Following include Util def initialize(context, api, db) @opts = context.opts @log = context.log @db = db log :info, "initialize" api.repeat(3600){|t| update t } @db.followings.on_invalidated{ log :info, "invalidated followings" api.delay(0){|t| update t } } end def update(api) if @db.followings.empty? friends = api.page("friends/list", :users, {user_id: @db.me.id, count: 100}) else @db.me = api.post("account/update_profile") return if @db.me.friends_count == @db.followings.size friends = api.page("friends/list", :users, {user_id: @db.me.id, count: 100}) end if @opts.only followers = api.page("friends/ids", :ids, {user_id: @db.me.id, count: 2500}) friends.each do|friend| friend[:only] = !followers.include?(friend.id) end end @db.followings.transaction do|d| d.update friends end end end end end atig-0.6.1/lib/atig/agent/mention.rb0000644000175000017500000000054412661500130017061 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/agent/agent' module Atig module Agent class Mention < Atig::Agent::Agent def initialize(context, api, db) return if context.opts.stream super end def interval; 180 end def path; '/statuses/mentions_timeline' end def source; :mention end end end end atig-0.6.1/lib/atig/agent/noretweets.rb0000644000175000017500000000070312661500130017604 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/util' module Atig module Agent class Noretweets include Util def initialize(context, api, db) @opts = context.opts @log = context.log @db = db log :info, "initialize" api.repeat(3600){|t| update t } end def update(api) @db.noretweets.clear.concat( api.get("friendships/no_retweets/ids") ) end end end end atig-0.6.1/lib/atig/agent/clenup.rb0000644000175000017500000000061112661500130016671 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/util' module Atig module Agent class Cleanup include Util def initialize(context, api, db) @log = context.log daemon do db.transaction do|t| log :info, "cleanup" t.cleanup end # once a day sleep 60*60*24 end end end end end atig-0.6.1/lib/atig/agent/list.rb0000644000175000017500000000261312661500130016362 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/util' module Atig module Agent class List include Util def initialize(context, api, db) @log = context.log @db = db log :info, "initialize" @db.lists.on_invalidated do |name| log :info, "invalidated #{name}" api.delay(0) do |t| if name == :all then full_update t else @db.lists[name].update t.page("lists/members", :users, {owner_screen_name: @db.me.screen_name, slug: name}) end end end api.repeat( interval ) do|t| self.full_update t end end def full_update(t) lists = entry_points.map{|entry| t.get(entry) }.flatten.compact users = {} lists.map do |list| name = if list.user.screen_name == @db.me.screen_name then "#{list.slug}" else "#{list.user.screen_name}^#{list.slug}" end begin users[name] = t.page("lists/members", :users, {owner_screen_name: list.user.screen_name, slug: list.slug}) rescue => e log :error, e.inspect users[name] = @db.lists.find_by_list_name(list.slug) end end @db.lists.update users end end end end atig-0.6.1/lib/atig/agent/stream_follow.rb0000644000175000017500000000154312661500130020265 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/util' module Atig module Agent class StreamFollow include Util def initialize(context, api, db) @log = context.log @api = api @prev = nil return unless context.opts.stream log :info, "initialize" @api.delay(0)do|t| @follows = context.opts.follow.split(',').map{|user| t.get("users/show",:screen_name=>user).id }.join(',') end @api.stream do|t| Thread.pass until @follows t.watch('statuses/filter', follow: @follows) do |status| if status and status.user db.transaction do|d| d.statuses.add status: status, user: status.user, source: :stream_follow end end end end end end end end atig-0.6.1/lib/atig/agent/agent.rb0000644000175000017500000000136212661500130016505 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/util' module Atig module Agent class Agent include Util def initialize(context, api, db) @log = context.log @api = api @prev = nil log :info, "initialize" @api.repeat( interval ) do|t| q = { count: 200 } if @prev q.update since_id: @prev else q.update count: 20 end sources = t.get( path, q) sources.reverse_each do|s| db.statuses.transaction do|d| d.add source: source, status: s, user: s.user end end @prev = sources.first.id if sources && !sources.empty? end end end end end atig-0.6.1/lib/atig/agent/dm.rb0000644000175000017500000000135612661500130016012 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/util' module Atig module Agent class Dm include Util def initialize(context, api, db) return if context.opts.stream @log = context.log @api = api @prev = nil log :info, "initialize" @api.repeat(600) do|t| q = { count: 200 } if @prev q.update since_id: @prev else q.update count: 1 end dms = t.get("direct_messages", q) log :debug, "You have #{dms.size} dm." dms.reverse_each do|dm| db.dms.transaction do|d| d.add status: dm, user: dm.sender end end end end end end end atig-0.6.1/lib/atig/agent/timeline.rb0000644000175000017500000000075312661500130017220 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/agent/agent' module Atig module Agent class Timeline < Atig::Agent::Agent DEFAULT_INTERVAL = 60 def initialize(context, api, db) @opts = context.opts return if @opts.stream super end def interval @interval ||= @opts.interval.nil? ? DEFAULT_INTERVAL : @opts.interval.to_i end def path; '/statuses/home_timeline' end def source; :timeline end end end end atig-0.6.1/lib/atig/agent/full_list.rb0000644000175000017500000000042112661500130017377 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/util' require 'atig/agent/list' module Atig module Agent class FullList < List def entry_points [ "lists/list", ] end def interval 3600 end end end end atig-0.6.1/lib/atig/agent/user_stream.rb0000644000175000017500000000403312661500130017736 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/util' module Atig module Agent class UserStream include Util def initialize(context, api, db) @log = context.log @api = api @prev = nil return unless context.opts.stream log :info, "initialize" @api.stream do|t| options = context.opts.allreplies ? {replies: :all} : {} t.watch('user', options) do |status| # @log.debug status.inspect next if status.retweeted_status and db.noretweets.include?(status.user.id) if status.direct_message dm = status.direct_message db.dms.transaction do|d| d.add status: dm, user: dm.sender end elsif status and status.user db.statuses.transaction do|d| d.add status: status, user: status.user, source: :user_stream end elsif status and status.event case status.event when 'list_member_added' t.channel.notify "list member \00311added\017 : @#{status.target.screen_name} into #{status.target_object.full_name} [ http://twitter.com#{status.target_object.uri} ]" when 'list_member_removed' t.channel.notify "list member \00305removed\017 : @#{status.target.screen_name} from #{status.target_object.full_name} [ http://twitter.com#{status.target_object.uri} ]" when 'follow' t.channel.notify "#{status.source.screen_name} \00311follows\017 @#{status.target.screen_name}" when 'favorite' t.channel.notify "#{status.source.screen_name} \00311favorites\017 => @#{status.target_object.user.screen_name} : #{status.target_object.text}" when 'unfavorite' t.channel.notify "#{status.source.screen_name} \00305unfavorites\017 => @#{status.target_object.user.screen_name} : #{status.target_object.text}" end end end end end end end end atig-0.6.1/lib/atig/agent/list_status.rb0000644000175000017500000000206712661500130017770 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/util' module Atig; end module Atig::Agent; end class Atig::Agent::ListStatus include Atig::Util def initialize(context, api, db) @log = context.log @db = db log :info, "initialize" @prev = {} api.repeat(60*5) do|t| db.lists.each do |name, _| log :debug, "retrieve #{name} statuses" q = {} q.update(since_id: @prev[name]) if @prev.key?(name) screen_name, slug = parse name q.update(owner_screen_name: screen_name, slug: slug) statuses = t.get("lists/statuses", q) statuses.reverse_each do|status| db.statuses.transaction do|d| d.add(status: status, user: status.user, source: :list, list: name) end end @prev[name] = statuses[0].id if statuses && statuses.size > 0 end end def parse(name) if name.include? '^' then name.split("^",2) else [@db.me.screen_name, name] end end end end atig-0.6.1/lib/atig/agent.rb0000644000175000017500000000041712661500130015407 0ustar uwabamiuwabamirequire 'atig/agent/full_list' require 'atig/agent/following' require 'atig/agent/list_status' require 'atig/agent/mention' require 'atig/agent/dm' require 'atig/agent/timeline' require 'atig/agent/clenup' require 'atig/agent/user_stream' require 'atig/agent/noretweets' atig-0.6.1/lib/atig/scheduler.rb0000644000175000017500000000270012661500130016264 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/util' module Atig class Scheduler include Util attr_reader :search def initialize(context, api, search, stream) @log = context.log @api = api @search = search @stream = stream @agents = [] @queue = SizedQueue.new 10 daemon do f = @queue.pop f.call end end def repeat(interval,opts={}, &f) @queue.push(lambda{ safe { f.call @api } }) t = daemon do sleep interval log :debug, "agent #{t.inspect} is invoked" @queue.push(lambda{ safe { f.call @api } }) end log :info, "repeat agent #{t.inspect} is registered" @agents << t t end def delay(interval, opts={}, &f) sleep interval @queue.push(lambda{ safe { f.call @api } }) end def stream(&f) return nil unless @stream @stream_thread.kill if @stream_thread @stream_thread = daemon { f.call @stream } end def re_try(count, &f) begin f.call rescue => e log :error, [count, e.inspect].inspect if count > 0 count -= 1 sleep 1 log :debug, "retry" retry end log :error, "Some Error Happened: #{e}" raise e end end def limit @api.limit end def remain @api.remain end def reset @api.reset end end end atig-0.6.1/lib/atig/option.rb0000644000175000017500000000450612661500130015624 0ustar uwabamiuwabami# -*- coding: utf-8 -*- module Atig class Option class << self def default_value(name, value) @default ||= {} @default[name.to_sym] = value end def parse(str) @default ||= {} opts = str.split(" ") opts = opts.inject({}) do |r, i| key, value = i.split("=", 2) r.update key.to_sym => parse_value(value) end self.new(@default.merge(opts)) end def parse_value(value) case value when nil, /\Atrue\z/ then true when /\Afalse\z/ then false when /\A\d+\z/ then value.to_i when /\A(?:\d+\.\d*|\.\d+)\z/ then value.to_f else value end end end default_value :api_base, 'https://api.twitter.com/1.1/' default_value :stream_api_base, 'https://userstream.twitter.com/1.1/' default_value :search_api_base, 'https://search.twitter.com/' def initialize(table) @table = {} table.each do|key,value| key = key.to_sym @table[key] = value new_ostruct_member key end # Ruby 1.8だとidというフィールドが作れないので作っておく new_ostruct_member :id, true end def marshal_dump; @table end def fields; @table.keys end def [](name) @table[name.to_sym] end def []=(name, value) @table[name.to_sym] = value end def new_ostruct_member(name, force=false) if force or (not self.respond_to?(name)) class << self; self; end.class_eval do define_method(name) { @table[name] } define_method(:"#{name}=") { |x| @table[name] = x } end end end def method_missing(mid, *args) # :nodoc: mname = mid.id2name len = args.length if mname =~ /=$/ if len != 1 raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1) end if self.frozen? raise TypeError, "can't modify frozen #{self.class}", caller(1) end name = mname.chop.to_sym @table[name] = args[0] self.new_ostruct_member(name) elsif len == 0 @table[mid] else raise NoMethodError, "undefined method `#{mname}' for #{self}", caller(1) end end end end atig-0.6.1/lib/atig/channel.rb0000644000175000017500000000022412661500130015715 0ustar uwabamiuwabamirequire 'atig/channel/timeline' require 'atig/channel/mention' require 'atig/channel/dm' require 'atig/channel/list' require 'atig/channel/retweet' atig-0.6.1/lib/atig/ifilter.rb0000644000175000017500000000027512661500130015751 0ustar uwabamiuwabamirequire 'atig/ifilter/retweet' require 'atig/ifilter/retweet_time' require 'atig/ifilter/sanitize' require 'atig/ifilter/expand_url' require 'atig/ifilter/strip' require 'atig/ifilter/xid' atig-0.6.1/lib/atig/twitter.rb0000755000175000017500000000374512661500130016025 0ustar uwabamiuwabami# -*- coding: utf-8 -*- require 'timeout' require 'atig/basic_twitter' require 'atig/http' module Atig # from tig.rb class Twitter < BasicTwitter def initialize(context, oauth) super context, :api_base @oauth = oauth @http = Atig::Http.new @log end def page(path, name, opts = {}, &block) limit = 0.98 * @remain # 98% of IP based rate limit r = [] cursor = -1 1.upto(limit) do |num| options = {cursor: cursor}.merge(opts) ret = api(path, options, { authenticate: true }) r.concat ret[name] cursor = ret[:next_cursor] break if cursor.zero? end r end def self.http_methods(*methods) methods.each do |m| self.module_eval </dev/null`.chomp head.empty?? "unknown" : head } end def local_repos?(rev) system("git show #{rev} > /dev/null 2>&1") end def git_repos? File.exists? File.expand_path('../../../.git', __FILE__) end def git? system('which git > /dev/null 2>&1') end def latest unless git? then [] else cs = commits latest = cs.first['sha'][/^[0-9a-z]{40}$/] raise "github API changed?" unless latest if local_repos?(latest) then [] else current = cs.map {|i| i['sha'] }.index(server_version) if current then cs[0...current] else cs end.map {|i| i['commit']['message'] } end end rescue TypeError, Errno::ECONNREFUSED, Timeout::Error [] end module_function :latest, :commits, :server_version, :local_repos?, :git?, :git_repos? end end atig-0.6.1/lib/atig/ifilter/0000755000175000017500000000000012661500130015420 5ustar uwabamiuwabamiatig-0.6.1/lib/atig/ifilter/xid.rb0000644000175000017500000000145212661500130016533 0ustar uwabamiuwabami module Atig module IFilter class Xid def initialize(context) @opts = context.opts c = @opts.send name # expect: 0..15, true, "0,1" b = nil c, b = c.split(",", 2).map {|i| i.to_i } if c.respond_to? :split c = 10 unless (0 .. 15).include? c # 10: teal if (0 .. 15).include?(b) @format = "\003%.2d,%.2d[%%s]\017" % [c, b] else @format = "\003%.2d[%%s]\017" % c end end def call(status) xid = status.send name unless xid and @opts.send(name) status else status.merge text: "#{status.text} #{@format % xid}" end end end class Sid < Xid def name; :sid end end class Tid < Xid def name; :tid end end end end atig-0.6.1/lib/atig/ifilter/retweet_time.rb0000644000175000017500000000061212661500130020441 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- module Atig module IFilter module RetweetTime def self.call(status) unless status.retweeted_status then status else t = Time.gm(*Time.parse(status.retweeted_status.created_at).to_a) status.merge text: "#{status.text} \x0310[#{t.strftime "%Y-%m-%d %H:%M"}]\x0F" end end end end end atig-0.6.1/lib/atig/ifilter/sanitize.rb0000644000175000017500000000075212661500130017577 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- module Atig module IFilter WSP_REGEX = Regexp.new("\\r\\n|[\\r\\n\\t#{"\\u00A0\\u1680\\u180E\\u2002-\\u200D\\u202F\\u205F\\u2060\\uFEFF" if "\u0000" == "\000"}]") Sanitize = lambda{|status| text = status.text. delete("\000\001"). gsub(">", ">"). gsub(""", '"'). gsub("<", "<"). gsub("&", "&"). gsub(WSP_REGEX, " ") status.merge text: text } end end atig-0.6.1/lib/atig/ifilter/retweet.rb0000644000175000017500000000052212661500130017423 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- module Atig module IFilter module Retweet Prefix = "\00310♺ \017" def self.call(status) return status unless status.retweeted_status rt = status.retweeted_status status.merge text: "#{Prefix}RT @#{rt.user.screen_name}: #{rt.text}" end end end end atig-0.6.1/lib/atig/ifilter/strip.rb0000644000175000017500000000043512661500130017110 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- module Atig module IFilter class Strip def initialize(suffix=[]) @rsuffix = /#{Regexp.union(*suffix)}\z/ end def call(status) status.merge text: status.text.sub(@rsuffix, "").strip end end end end atig-0.6.1/lib/atig/ifilter/expand_url.rb0000644000175000017500000000525412661500130020114 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/util' require 'atig/http' require 'atig/sized_hash' module Atig module IFilter class ExpandUrl include Util def initialize(context) @log = context.log @opts = context.opts @http = Atig::Http.new @log @cache = Atig::SizedHash.new 100 end def call(status) target = short_url_regexp entities = (entities = status.entities).nil? ? [] : entities.urls status.merge text: status.text.gsub(target) {|url| unless entities.nil? or entities.empty? @cache[url] ||= search_url_from_entities(url, entities) url = @cache[url] if @cache[url] =~ target end @cache[url] ||= resolve_http_redirect(URI(url)).to_s || url } end private def short_url_regexp return URI.regexp(%w[http https]) if @opts.untiny_whole_urls %r{ https?:// (?: (?: t (?: mblr )? \.co | (?: bitly | bkite | digg | tumblr | (?: tin | rub ) yurl ) \.com | (?: is | pic ) \.gd | goo\.gl | cli\.gs | (?: ff | sn | tr ) \.im | bit\.ly | j\.mp | nico\.ms | airme\.us | twurl\.nl | htn\.to) / [0-9a-z=-]+ | blip\.fm/~ (?> [0-9a-z]+) (?! /) | flic\.kr/[a-z0-9/]+ ) }ix end def search_url_from_entities(url, entities) expanded_url = nil entities.reject! do |entity| entity.url == url && (expanded_url = entity.expanded_url) end expanded_url end def resolve_http_redirect(uri, limit = 3) return uri if limit.zero? or uri.nil? log :debug, uri.inspect req = @http.req :head, uri @http.http(uri, 3, 2).request(req) do |res| break if not res.is_a?(Net::HTTPRedirection) or not res.key?("Location") begin location = URI(res["Location"]) rescue URI::InvalidURIError end unless location.is_a? URI::HTTP begin location = URI.join(uri.to_s, res["Location"]) rescue URI::InvalidURIError, URI::BadURIError # FIXME end end uri = resolve_http_redirect(location, limit - 1) end uri rescue Errno::ETIMEDOUT, IOError, Timeout::Error, Errno::ECONNRESET => e log :error, e.inspect uri end end end end atig-0.6.1/lib/atig/twitter_struct.rb0000644000175000017500000000172612661500130017423 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- # module Atig # from tig.rb class TwitterStruct def self.make(obj) case obj when Hash obj = obj.dup obj.each do |k, v| obj[k] = TwitterStruct.make(v) end TwitterStruct.new(obj) when Array obj.map {|i| TwitterStruct.make(i) } else obj end end def initialize(obj) @obj = obj end def id @obj["id"] end def [](name) @obj[name.to_s] end def []=(name,val) @obj[name.to_s] = val end def merge(hash) obj = @obj.dup hash.each do|key,value| obj[key.to_s] = value end TwitterStruct.make obj end def hash self.id ? self.id.hash : super end def eql?(other) self.hash == other.hash end def ==(other) self.hash == other.hash end def method_missing(sym, *args) # XXX @obj[sym.to_s] end end end atig-0.6.1/lib/atig/oauth-patch.rb0000644000175000017500000000236712661500130016534 0ustar uwabamiuwabami# -*- coding: utf-8 -*- if defined? ::Encoding # エンコーディングの違いのせいで、 # 日本語の文字列をpostパラメータに含めようとするとエラーが出ます。 # 無理矢理エンコーディングをUTF-8に変えて再試行することで回避。 module OAuth module Helper def escape(value) begin URI::escape(value.to_s, OAuth::RESERVED_CHARACTERS) rescue ArgumentError URI::escape( value.to_s.force_encoding(Encoding::UTF_8), OAuth::RESERVED_CHARACTERS ) end end end end # 1.9から文字列がEnumerableでなくなりましたので、 # その対応をしています。 module HMAC class Base def set_key(key) key = @algorithm.digest(key) if key.size > @block_size key_xor_ipad = Array.new(@block_size, 0x36) key_xor_opad = Array.new(@block_size, 0x5c) key.bytes.each_with_index do |value, index| key_xor_ipad[index] ^= value key_xor_opad[index] ^= value end @key_xor_ipad = key_xor_ipad.pack('c*') @key_xor_opad = key_xor_opad.pack('c*') @md = @algorithm.new @initialized = true end end end end atig-0.6.1/.travis.yml0000644000175000017500000000026712661500130014406 0ustar uwabamiuwabamisudo: false language: ruby rvm: - 2.1.8 - 2.2.4 - 2.3.0 - ruby-head before_install: - gem update --system - gem update bundler --no-document script: bundle exec rake spec atig-0.6.1/Rakefile0000644000175000017500000000044512661500130013740 0ustar uwabamiuwabami# -*- mode:ruby -*- require 'rake' require 'rake/clean' require 'rspec/core/rake_task' require "bundler/gem_tasks" CLEAN.include( "**/*.db" ) CLOBBER.include( "pkg", "coverage" ) RSpec::Core::RakeTask.new do |t| t.pattern = 'spec/**/*_spec.rb' end task :default => [:spec, :clean] atig-0.6.1/spec/0000755000175000017500000000000012661500130013222 5ustar uwabamiuwabamiatig-0.6.1/spec/command_helper.rb0000644000175000017500000000356712661500130016537 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- class FakeGateway attr_reader :names,:action,:filtered,:updated, :notified def initialize(channel) @channel = channel end def ctcp_action(*names, &action) @names = names @action = action end def output_message(m); @filtered = m end def update_status(*args); @updated = args end def [](name) @notified = name @channel end def server_name; "server-name" end end class FakeScheduler def initialize(api) @api = api end def delay(interval,opt={},&f) f.call @api end def limit; @api.limit end def remain; @api.remain end def reset; @api.reset end end class FakeDb attr_reader :statuses, :followings,:lists, :me def initialize(statuses, followings,lists, me) @statuses = statuses @followings = followings @lists = lists @me = me end def transaction(&f) f.call self end end class FakeDbEntry def initialize(name) @name = name end def transaction(&f) f.call self end end module CommandHelper def init(klass) @log = double 'log' @opts = Atig::Option.new({}) context = OpenStruct.new log:@log, opts:@opts @channel = double 'channel' @gateway = FakeGateway.new @channel @api = double 'api' @statuses = FakeDbEntry.new 'status DB' @followings = FakeDbEntry.new 'followings DB' @lists = { "A" => FakeDbEntry.new('list A'), "B" => FakeDbEntry.new('list B') } @me = user 1,'me' @db = FakeDb.new @statuses, @followings, @lists, @me @command = klass.new context, @gateway, FakeScheduler.new(@api), @db end def call(channel, command, args) @gateway.action.call channel, "#{command} #{args.join(' ')}", command, args end def stub_status(key, hash) allow(@statuses).to receive(key){|arg,*_| hash.fetch(arg, hash[:default]) } end end atig-0.6.1/spec/sized_hash_spec.rb0000644000175000017500000000153512661500130016706 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/sized_hash' describe Atig::SizedHash do before do @hash = Atig::SizedHash.new 3 end it "はkeyとvalueでアクセスできる" do @hash[:foo] = :bar expect(@hash[:foo]).to eq(:bar) end it "はサイズが取得できる" do @hash.size == 0 @hash[:foo] = :bar @hash.size == 1 @hash[:foo] = :baz @hash.size == 1 @hash[:xyzzy] = :hoge @hash.size == 2 end it "は古いのが消える" do ('a'..'c').each{|c| @hash[c] = 42 } expect(@hash.key?('a')).to be_truthy @hash['d'] = 42 expect(@hash.key?('a')).to be_falsey end it "は使うたびに寿命が伸びる" do ('a'..'c').each{|c| @hash[c] = 42 } @hash['a'] @hash['d'] = 42 expect(@hash.key?('a')).to be_truthy expect(@hash.key?('b')).to be_falsey end end atig-0.6.1/spec/spec_helper.rb0000644000175000017500000000136412661500130016044 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'bundler/setup' require 'atig/monkey' require 'command_helper' ENV['TZ'] = 'UTC' RSpec::Matchers.define :be_text do |text| match do |status| expect(status.text).to eq(text) end end def status(text, opt = {}) Atig::TwitterStruct.make(opt.merge('text' => text)) end def user(id, name) user = double("User-#{name}") allow(user).to receive(:id).and_return(id) allow(user).to receive(:screen_name).and_return(name) user end def entry(user, status, name = 'entry', id = 0) entry = double name allow(entry).to receive('id').and_return(id) allow(entry).to receive('user').and_return(user) allow(entry).to receive('status').and_return(status) entry end require 'coveralls' Coveralls.wear! atig-0.6.1/spec/levenshtein_spec.rb0000644000175000017500000000111712661500130017105 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/levenshtein' target = [Atig::Levenshtein, Atig::Levenshtein::PureRuby] target.each do |m| describe m do it "should return correct levenshtein distance" do [ ["kitten", "sitting", 3], ["foo", "foo", 0], ["", "", 0], ["foO", "foo", 1], ["", "foo", 3], ["あああ", "ああい", 1], ].each do |a, b, expected| expect(m.levenshtein(a.split(//), b.split(//))).to eq(expected) expect(m.levenshtein(b.split(//), a.split(//))).to eq(expected) end end end end atig-0.6.1/spec/update_checker_spec.rb0000644000175000017500000000323412661500130017531 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/update_checker' describe Atig::UpdateChecker,'when use git version' do def rev(c) c * 40 end def commit(c, mesg) {'sha' => rev(c), 'commit' => {'message' => mesg}} end before do allow(Atig::UpdateChecker).to receive(:git?).and_return(true) allow(Atig::UpdateChecker).to receive(:commits). and_return [ commit('a', 'foo'), commit('b', 'bar'), commit('c', 'baz'), commit('d', 'xyzzy'), commit('e', 'fuga'), ] end it "should not do anything when use HEAD version" do allow(Atig::UpdateChecker).to receive(:local_repos?).and_return true allow(Atig::UpdateChecker).to receive(:server_version).and_return rev('a') expect(Atig::UpdateChecker.latest).to eq([]) end it "should notify when not use HEAD version" do allow(Atig::UpdateChecker).to receive(:local_repos?).and_return false allow(Atig::UpdateChecker).to receive(:server_version).and_return rev('b') expect(Atig::UpdateChecker.latest).to eq([ 'foo' ]) end it "should notify many changes" do allow(Atig::UpdateChecker).to receive(:local_repos?).and_return false allow(Atig::UpdateChecker).to receive(:server_version).and_return rev('d') expect(Atig::UpdateChecker.latest).to eq([ 'foo', 'bar', 'baz' ]) end it "should notify all changes" do allow(Atig::UpdateChecker).to receive(:local_repos?).and_return false allow(Atig::UpdateChecker).to receive(:server_version).and_return rev('z') expect(Atig::UpdateChecker.latest).to eq([ 'foo', 'bar', 'baz', 'xyzzy', 'fuga' ]) end end atig-0.6.1/spec/command/0000755000175000017500000000000012661500130014640 5ustar uwabamiuwabamiatig-0.6.1/spec/command/user_info_spec.rb0000644000175000017500000000220412661500130020166 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/user_info' describe Atig::Command::UserInfo do include CommandHelper before do @command = init Atig::Command::UserInfo @status = double "status" @user = double "user" allow(@user).to receive(:description).and_return('hogehoge') allow(@user).to receive(:status).and_return(@status) end it "should show the source via DB" do expect(@followings).to receive(:find_by_screen_name).with('mzp').and_return(@user) expect(@channel). to receive(:message). with(anything, Net::IRC::Constants::NOTICE){|s,_| expect(s.status.text).to eq("\x01hogehoge\x01") } call '#twitter','userinfo',%w(mzp) end it "should show the source via API" do expect(@followings).to receive(:find_by_screen_name).with('mzp').and_return(nil) expect(@api).to receive(:get).with('users/show',:screen_name=>'mzp').and_return(@user) expect(@channel). to receive(:message). with(anything, Net::IRC::Constants::NOTICE){|s,_| expect(s.status.text).to eq("\x01hogehoge\x01") } call '#twitter','userinfo',%w(mzp) end end atig-0.6.1/spec/command/user_spec.rb0000644000175000017500000000303012661500130017151 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/user' describe Atig::Command::User do include CommandHelper before do @command = init Atig::Command::User end it "should have '/me status' name" do expect(@gateway.names).to eq(['user', 'u']) end it "should" do foo = entry user(1,'mzp'),status('foo') bar = entry user(1,'mzp'),status('bar') baz = entry user(1,'mzp'),status('baz') expect(@api). to receive(:get). with('statuses/user_timeline',:count=>20,:screen_name=>'mzp'). and_return([foo, bar, baz]) expect(@statuses).to receive(:add).with(any_args).at_least(3) expect(@statuses). to receive(:find_by_screen_name). with('mzp',:limit=>20). and_return([foo, bar, baz]) expect(@channel).to receive(:message).with(anything, Net::IRC::Constants::NOTICE).at_least(3) call "#twitter","user",%w(mzp) end it "should get specified statuses" do foo = entry user(1,'mzp'),status('foo') bar = entry user(1,'mzp'),status('bar') baz = entry user(1,'mzp'),status('baz') expect(@api). to receive(:get). with('statuses/user_timeline',:count=>200,:screen_name=>'mzp'). and_return([foo, bar, baz]) expect(@statuses).to receive(:add).with(any_args).at_least(3) expect(@statuses). to receive(:find_by_screen_name). with('mzp',:limit=>200). and_return([foo, bar, baz]) expect(@channel).to receive(:message).with(anything, Net::IRC::Constants::NOTICE).at_least(3) call "#twitter","user",%w(mzp 200) end end atig-0.6.1/spec/command/spam_spec.rb0000644000175000017500000000117412661500130017142 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/spam' describe Atig::Command::Spam do include CommandHelper before do @command = init Atig::Command::Spam end it "はspamコマンドを提供する" do expect(@gateway.names).to eq(%w(spam SPAM)) end it "は指定されたscreen_nameを通報する" do user = user(1,'examplespammer') expect(@api). to receive(:post). with('report_spam',:screen_name=> 'examplespammer'). and_return(user) expect(@channel).to receive(:notify).with("Report examplespammer as SPAMMER") call "#twitter", 'spam', %w(examplespammer) end end atig-0.6.1/spec/command/uptime_spec.rb0000644000175000017500000000307412661500130017506 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/uptime' describe Atig::Command::Uptime do include CommandHelper before do expect(::Time).to receive(:now).and_return(::Time.at(0)) @command = init Atig::Command::Uptime end it "should register uptime command" do expect(@gateway.names).to eq(['uptime']) end it "should return mm:ss(min)" do expect(::Time).to receive(:now).and_return(::Time.at(0)) expect(@channel).to receive(:notify).with("00:00") call '#twitter', 'uptime', [] expect(@gateway.notified).to eq('#twitter') end it "should return mm:ss(max)" do expect(::Time).to receive(:now).and_return(::Time.at(60*60-1)) expect(@channel).to receive(:notify).with("59:59") call '#twitter', 'uptime', [] expect(@gateway.notified).to eq('#twitter') end it "should return hh:mm:ss(min)" do expect(::Time).to receive(:now).and_return(::Time.at(60*60)) expect(@channel).to receive(:notify).with("01:00:00") call '#twitter', 'uptime', [] expect(@gateway.notified).to eq('#twitter') end it "should return hh:mm:ss(max)" do expect(::Time).to receive(:now).and_return(::Time.at(24*60*60-1)) expect(@channel).to receive(:notify).with("23:59:59") call '#twitter', 'uptime', [] expect(@gateway.notified).to eq('#twitter') end it "should return dd days hh:mm:ss" do expect(::Time).to receive(:now).and_return(::Time.at(24*60*60)) expect(@channel).to receive(:notify).with("1 days 00:00") call '#twitter', 'uptime', [] expect(@gateway.notified).to eq('#twitter') end end atig-0.6.1/spec/command/favorite_spec.rb0000644000175000017500000000303312661500130020015 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/favorite' describe Atig::Command::Favorite do include CommandHelper before do @command = init Atig::Command::Favorite target = status 'blah blah', 'id'=>'1' entry = entry user(1,'mzp'), target @res = double 'res' stub_status(:find_by_tid,'a' => entry) stub_status(:find_by_sid,'mzp:a' => entry) stub_status(:find_by_screen_name,'mzp' => [ entry ], default:[]) end it "should post fav by tid" do expect(@api).to receive(:post).with("favorites/create", {id: "1"}) expect(@channel).to receive(:notify).with("FAV: mzp: blah blah") call "#twitter","fav",%w(a) end it "should post fav by sid" do expect(@api).to receive(:post).with("favorites/create", {id: "1"}) expect(@channel).to receive(:notify).with("FAV: mzp: blah blah") call "#twitter","fav",%w(mzp:a) end it "should post fav by screen name" do expect(@api).to receive(:post).with("favorites/create", {id: "1"}) expect(@channel).to receive(:notify).with("FAV: mzp: blah blah") call "#twitter","fav",%w(mzp) end it "should post fav by screen name with at" do expect(@api).to receive(:post).with("favorites/create", {id: "1"}) expect(@channel).to receive(:notify).with("FAV: mzp: blah blah") call "#twitter","fav",%w(@mzp) end it "should post unfav" do expect(@api).to receive(:post).with("favorites/destroy", {id: "1"}) expect(@channel).to receive(:notify).with("UNFAV: mzp: blah blah") call "#twitter","unfav",%w(a) end end atig-0.6.1/spec/command/version_spec.rb0000644000175000017500000000375512661500130017676 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/version' describe Atig::Command::Version do include CommandHelper before do @command = init Atig::Command::Version @status = double "status" allow(@status).to receive(:source).and_return('Echofon') @user = double "user" allow(@user).to receive(:status).and_return(@status) end it "should provide version command" do expect(@gateway.names).to eq(['version']) end it "should show the source via DB" do expect(@statuses). to receive(:find_by_screen_name). with('mzp',:limit => 1). and_return([ entry(@user,@status) ]) expect(@channel). to receive(:message). with(anything, Net::IRC::Constants::NOTICE){|s,_| expect(s.status.text).to eq("\x01Echofon \x01") } call '#twitter','version',%w(mzp) end it "should show the source of web" do status = double "status" allow(status).to receive(:source).and_return('web') expect(@statuses). to receive(:find_by_screen_name). with('mzp',:limit => 1). and_return([ entry(@user,status) ]) expect(@channel). to receive(:message). with(anything, Net::IRC::Constants::NOTICE){|s,_| expect(s.status.text).to eq("\x01web\x01") } call '#twitter','version',%w(mzp) end it "should show the source via API" do allow(@statuses).to receive(:find_by_screen_name).and_return(@status) expect(@statuses).to receive(:find_by_screen_name).with('mzp',:limit => 1).and_return(nil) expect(@statuses).to receive(:add).with(status: @status, user: @user, source: :version) expect(@api).to receive(:get).with('users/show',:screen_name=>'mzp').and_return(@user) expect(@channel). to receive(:message). with(anything, Net::IRC::Constants::NOTICE){|s,_| expect(s.status.text).to eq("\x01Echofon \x01") } call '#twitter','version',%w(mzp) end end atig-0.6.1/spec/command/autofix_spec.rb0000644000175000017500000000217012661500130017656 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/autofix' describe Atig::Command::Autofix do include CommandHelper before do @command = init Atig::Command::Autofix @opts.autofix = true target = status 'hello', 'id'=>'42' entry = entry user(1,'mzp'), target, "entry", 1 expect(@statuses).to receive(:find_by_user).with(@me,:limit=>1).and_return([ entry ]) end it "should post normal tweet" do res = status('blah blah') expect(@api).to receive(:post).with('statuses/update', {status:'blah blah'}).and_return(res) call '#twitter', "autofix", %w(blah blah) end it "should delete old similar tweet" do res = status('hillo') expect(@api).to receive(:post).with('statuses/update', {status:'hillo'}).and_return(res) expect(@api).to receive(:post).with("statuses/destroy/42") expect(@statuses).to receive(:remove_by_id).with(1) expect(@channel).to receive(:notify).with("Similar update in previous. Conclude that it has error.") expect(@channel).to receive(:notify).with("And overwrite previous as new status: hillo") call '#twitter', "autofix", %w(hillo) end end atig-0.6.1/spec/command/status_spec.rb0000644000175000017500000000572612661500130017534 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/status' describe Atig::Command::Status do include CommandHelper before do @command = init Atig::Command::Status end it "should have '/me status' name" do expect(@gateway.names).to eq(['status']) end it "should post the status by API" do res = status('blah blah') expect(@statuses).to receive(:find_by_user).with(@me,:limit=>1).and_return(nil) expect(@api).to receive(:post).with('statuses/update', {status:'blah blah'}).and_return(res) call '#twitter', "status", %w(blah blah) expect(@gateway.updated).to eq([ res, '#twitter' ]) expect(@gateway.filtered).to eq({ status: 'blah blah' }) end it "should post with japanese language" do res = status("あ"*140) expect(@statuses).to receive(:find_by_user).with(@me,:limit=>1).and_return(nil) expect(@api).to receive(:post).with('statuses/update', {status:"あ"*140}).and_return(res) call '#twitter', "status", ["あ" * 140] expect(@gateway.updated).to eq([ res, '#twitter' ]) expect(@gateway.filtered).to eq({ status: "あ" * 140 }) end it "should post the status even if has long URL" do res = status("https://www.google.co.jp/search?q=%E3%83%AB%E3%83%93%E3%83%BC%E3%82%92%E7%94%A8%E3%81%84%E3%81%9F%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E9%96%8B%E7%99%BA&safe=off") expect(@statuses).to receive(:find_by_user).with(@me,:limit=>1).and_return(nil) expect(@api).to receive(:post).with('statuses/update', {status:'https://www.google.co.jp/search?q=%E3%83%AB%E3%83%93%E3%83%BC%E3%82%92%E7%94%A8%E3%81%84%E3%81%9F%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E9%96%8B%E7%99%BA&safe=off'}).and_return(res) call '#twitter', "status", ['https://www.google.co.jp/search?q=%E3%83%AB%E3%83%93%E3%83%BC%E3%82%92%E7%94%A8%E3%81%84%E3%81%9F%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E9%96%8B%E7%99%BA&safe=off'] expect(@gateway.updated).to eq([ res, '#twitter' ]) expect(@gateway.filtered).to eq({ status: 'https://www.google.co.jp/search?q=%E3%83%AB%E3%83%93%E3%83%BC%E3%82%92%E7%94%A8%E3%81%84%E3%81%9F%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E9%96%8B%E7%99%BA&safe=off'}) end it "should not post same post" do e = entry user(1,'mzp'), status('blah blah') expect(@statuses).to receive(:find_by_user).with(@me,:limit=>1).and_return([ e ] ) expect(@channel).to receive(:notify).with("You can't submit the same status twice in a row.") call '#twitter', "status", %w(blah blah) expect(@gateway.notified).to eq('#twitter') end it "should not post over 140" do expect(@statuses).to receive(:find_by_user).with(@me,:limit=>1).and_return(nil) expect(@channel).to receive(:notify).with("You can't submit the status over 140 chars") call '#twitter', "status", [ 'a' * 141 ] expect(@gateway.notified).to eq('#twitter') end end atig-0.6.1/spec/command/destroy_spec.rb0000644000175000017500000000473412661500130017700 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/destroy' describe Atig::Command::Destroy,"when status is not removed" do include CommandHelper before do @command = init Atig::Command::Destroy end it "should specified other's status" do entry = entry user(2,'other'), status('blah blah', 'id'=>'1') allow(@statuses).to receive(:find_by_tid).with('b').and_return(entry) expect(@channel).to receive(:notify).with("The status you specified by the ID tid is not yours.") call "#twitter","destory",%w(b) end end describe Atig::Command::Destroy,"when remove recently tweet" do include CommandHelper before do @command = init Atig::Command::Destroy target = status 'blah blah', 'id'=>'1' entry = entry @me, target,'entry',1 @res = double 'res' stub_status(:find_by_tid,'a' => entry) stub_status(:find_by_sid,'mzp:a' => entry) stub_status(:find_by_screen_name,'mzp' => [ entry ], default:[]) # api expect(@api).to receive(:post).with("statuses/destroy/1") # notice expect(@channel).to receive(:notify).with("Destroyed: blah blah") # update topics new_entry = entry @me, status('foo', 'id'=>'2') expect(@gateway).to receive(:topic).with(new_entry) expect(@statuses).to receive(:remove_by_id).with(1){ expect(@statuses).to receive(:find_by_screen_name).with(@me.screen_name,:limit=>1){ [ new_entry ] } } end it "should specified by tid" do call "#twitter","destory",%w(a) end it "should remove status by user" do call "#twitter","destory",%w(mzp) end it "should remove status by sid" do call "#twitter","destory",%w(mzp:a) end end describe Atig::Command::Destroy,"when remove old tweet" do include CommandHelper before do @command = init Atig::Command::Destroy target = status 'blah blah', 'id'=>'1' entry = entry @me, target,'entry',1 @res = double 'res' stub_status(:find_by_tid,'a' => entry) stub_status(:find_by_sid,'mzp:a' => entry) stub_status(:find_by_screen_name, @db.me.screen_name => [ entry ], default:[]) # api expect(@api).to receive(:post).with("statuses/destroy/1") # notice expect(@channel).to receive(:notify).with("Destroyed: blah blah") # update topics expect(@statuses).to receive(:remove_by_id).with(1) end it "should specified by tid" do call "#twitter","destory",%w(a) end it "should remove status by sid" do call "#twitter","destory",%w(mzp:a) end end atig-0.6.1/spec/command/option_spec.rb0000644000175000017500000000617712661500130017522 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/option' describe Atig::Command::Option do include CommandHelper before do @command = init Atig::Command::Option end it "should provide option command" do expect(@command.command_name).to eq(%w(opt opts option options)) end end describe Atig::Command::Option, 'when have many property' do include CommandHelper before do @command = init Atig::Command::Option @opts.foo1 = true @opts.foo2 = 42 @opts.foo3 = 42.1 allow(@opts).to receive(:foo1=) allow(@opts).to receive(:foo2=) allow(@opts).to receive(:foo3=) end it "should list up values" do xs = [] allow(@channel).to receive(:notify){|x| xs << x} call '#twitter', 'opt', %w() expect(xs).to eq([ "foo1 => true", "foo2 => 42", "foo3 => 42.1", ]) end end describe Atig::Command::Option, 'when have bool property' do include CommandHelper before do @command = init Atig::Command::Option allow(@opts).to receive(:foo).and_return true allow(@opts).to receive(:foo=){|v| @value = v } allow(@channel).to receive(:notify) end it "should show the value" do expect(@channel).to receive(:notify).with("foo => true") call '#twitter', 'opt', %w(foo) end it "should update the value" do call '#twitter', 'opt', %w(foo false) expect(@value).to be_falsey end end describe Atig::Command::Option, 'when have int property' do include CommandHelper before do @command = init Atig::Command::Option allow(@opts).to receive(:foo).and_return 42 allow(@opts).to receive(:foo=){|v| @value = v } allow(@channel).to receive(:notify) end it "should show the value" do expect(@channel).to receive(:notify).with("foo => 42") call '#twitter', 'opt', %w(foo) end it "should update the value" do call '#twitter', 'opt', %w(foo 42) expect(@value).to eq(42) end end describe Atig::Command::Option, 'when have float property' do include CommandHelper before do @command = init Atig::Command::Option allow(@opts).to receive(:foo).and_return 1.23 allow(@opts).to receive(:foo=){|v| @value = v } allow(@channel).to receive(:notify) end it "should show the value" do expect(@channel).to receive(:notify).with("foo => 1.23") call '#twitter', 'opt', %w(foo) end it "should update the value" do call '#twitter', 'opt', %w(foo 1.24) expect(@value).to eq(1.24) end end describe Atig::Command::Option, 'when have string property' do include CommandHelper before do @command = init Atig::Command::Option allow(@opts).to receive(:foo).and_return "bar" allow(@opts).to receive(:foo=){|v| @value = v } allow(@channel).to receive(:notify) end it "should show the value" do expect(@channel).to receive(:notify).with("foo => bar") call '#twitter', 'opt', %w(foo) end it "should update the value" do call '#twitter', 'opt', %w(foo baz) expect(@value).to eq('baz') end it "should update the value" do call '#twitter', 'opt', %w(foo blah Blah) expect(@value).to eq('blah Blah') end end atig-0.6.1/spec/command/whois_spec.rb0000644000175000017500000000520212661500130017327 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/whois' require 'atig/command/info' include Net::IRC::Constants describe Atig::Command::Whois do include CommandHelper def time(t) t.utc.strftime("%a %b %d %H:%M:%S +0000 %Y") end before do @command = init Atig::Command::Whois @status = double 'status' allow(@status).to receive(:created_at).and_return(time(::Time.at(42))) @user = double "user" allow(@user).to receive(:name) .and_return('name') allow(@user).to receive(:id) .and_return('10') allow(@user).to receive(:screen_name).and_return('screen_name') allow(@user).to receive(:description).and_return('blah blah') allow(@user).to receive(:protected) .and_return(false) allow(@user).to receive(:location) .and_return('Tokyo, Japan') allow(@user).to receive(:created_at) .and_return(time(::Time.at(0))) allow(@user).to receive(:status) .and_return(@status) allow(::Time).to receive(:now).and_return(::Time.at(50)) allow(@followings).to receive(:find_by_screen_name).with('mzp').and_return(@user) end it "should proide whois command" do expect(@command.command_name).to eq(%w(whois)) end it "should show profile" do commands = [] expect(@gateway).to receive(:post){|_,command,_,_,*params| commands << command case command when RPL_WHOISUSER expect(params).to eq(['id=10', 'twitter.com', "*", 'name / blah blah']) when RPL_WHOISSERVER expect(params).to eq(['twitter.com', 'Tokyo, Japan']) when RPL_WHOISIDLE expect(params).to eq(["8", "0", "seconds idle, signon time"]) when RPL_ENDOFWHOIS expect(params).to eq(["End of WHOIS list"]) end }.at_least(4) call '#twitter','whois',%w(mzp) expect(commands).to eq([ RPL_WHOISUSER, RPL_WHOISSERVER, RPL_WHOISIDLE, RPL_ENDOFWHOIS]) end it "should append /protect if the user is protected" do allow(@user).to receive(:protected).and_return(true) commands = [] expect(@gateway).to receive(:post){|_,command,_,_,*params| commands << command case command when RPL_WHOISUSER expect(params).to eq(['id=10', 'twitter.com/protected', "*", 'name / blah blah']) when RPL_WHOISSERVER expect(params).to eq(['twitter.com/protected', 'Tokyo, Japan']) when RPL_WHOISIDLE expect(params).to eq(["8", "0", "seconds idle, signon time"]) when RPL_ENDOFWHOIS expect(params).to eq(["End of WHOIS list"]) end }.at_least(4) call '#twitter','whois',%w(mzp) expect(commands).to eq([ RPL_WHOISUSER, RPL_WHOISSERVER, RPL_WHOISIDLE, RPL_ENDOFWHOIS]) end end atig-0.6.1/spec/command/thread_spec.rb0000644000175000017500000000520012661500130017443 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/thread' describe Atig::Command::Thread do include CommandHelper before do u = double 'user' @entries = [ entry(u, status(''),'entry-0'), entry(u, status('','in_reply_to_status_id'=>2),'entry-1'), entry(u, status('','in_reply_to_status_id'=>3),'entry-2'), entry(u, status(''),'entry-3'), entry(u, status('','in_reply_to_status_id'=>5),'entry-4') ] @command = init Atig::Command::Thread @messages = [] allow(@channel).to receive(:message){|entry,_| @messages.unshift entry } allow(@statuses).to receive(:find_by_status_id).with(anything){|id| @entries[id.to_i] } end it "should provide thread command" do expect(@gateway.names).to eq(%w( thread )) end it "should show the tweet" do expect(@statuses).to receive(:find_by_tid).with('a').and_return(@entries[0]) call "#twitter","thread",%w(a) expect(@messages).to eq([ @entries[0] ]) end it "should chain the tweets" do expect(@statuses).to receive(:find_by_tid).with('a').and_return(@entries[1]) call "#twitter","thread",%w(a) expect(@messages).to eq(@entries[1..3]) end it "should chain the tweets by screen name" do expect(@statuses).to receive(:find_by_tid).with('mzp').and_return(nil) expect(@statuses).to receive(:find_by_sid).with('mzp').and_return(nil) expect(@statuses).to receive(:find_by_screen_name).with('mzp',:limit=>1).and_return([ @entries[1] ]) call "#twitter","thread",%w(mzp) expect(@messages).to eq(@entries[1..3]) end it "should chain the tweets by sid" do expect(@statuses).to receive(:find_by_tid).with('mzp:a').and_return(nil) expect(@statuses).to receive(:find_by_sid).with('mzp:a').and_return(@entries[1]) call "#twitter","thread",%w(mzp:a) expect(@messages).to eq(@entries[1..3]) end it "should chain the tweets with limit" do expect(@statuses).to receive(:find_by_tid).with('a').and_return(@entries[1]) call "#twitter","thread",%w(a 2) expect(@messages).to eq(@entries[1..2]) end it "should get new tweets" do expect(@statuses).to receive(:find_by_tid).with('a').and_return(@entries[4]) user = user 1, 'mzp' status = status '','user'=>user entry = entry user,status,'new-entry' expect(@statuses).to receive(:add).with(status: status, user: user, source: :thread){ @entries << entry } expect(@api).to receive(:get).with('statuses/show/5').and_return(status) call "#twitter","thread",%w(a) expect(@messages).to eq([@entries[4], entry]) end end atig-0.6.1/spec/command/limit_spec.rb0000644000175000017500000000127112661500130017316 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/limit' describe Atig::Command::Limit do include CommandHelper before do @reset = ::Time.utc(2010,9,25,8,24,12) @command = init Atig::Command::Limit allow(@api).to receive(:limit).and_return(150) allow(@api).to receive(:remain).and_return(148) allow(@api).to receive(:reset).and_return(@reset) end it "should provide limit command" do expect(@gateway.names).to eq(['rls','limit','limits']) end it "should show limit" do expect(@channel).to receive(:notify).with("148 / 150 (reset at 2010-09-25 08:24:12)") call '#twitter', 'limit', [] expect(@gateway.notified).to eq('#twitter') end end atig-0.6.1/spec/command/dm_spec.rb0000644000175000017500000000134612661500130016603 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/dm' describe Atig::Command::Dm do include CommandHelper before do @command = init Atig::Command::Dm end it "should have '/me dm' name" do expect(@gateway.names).to eq(['d', 'dm','dms']) end it "should post the status by API" do expect(@api).to receive(:post).with('direct_messages/new', {screen_name: 'mzp', text: 'blah blah'}) expect(@channel).to receive(:notify).with("Sent message to mzp: blah blah") call '#twitter', "dm", %w(mzp blah blah) end it "should post the status by API" do expect(@channel).to receive(:notify).with("/me dm blah blah") call '#twitter', "dm", %w() end end atig-0.6.1/spec/command/time_spec.rb0000644000175000017500000000277312661500130017146 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/time' describe Atig::Command::Time do include CommandHelper def user(offset, tz) u = double "user-#{offset}" allow(u).to receive(:utc_offset).and_return(offset) allow(u).to receive(:time_zone).and_return(tz) u end before do @command = init Atig::Command::Time @user = user(61*60+1,'Tokyo') end it "should provide time command" do expect(@gateway.names).to eq(['time']) end it "should show offset time on DB" do expect(::Time).to receive(:now).and_return(Time.at(0)) expect(@followings).to receive(:find_by_screen_name).with('mzp').and_return(@user) expect(@channel). to receive(:message). with(anything, Net::IRC::Constants::NOTICE){|s,_| expect(s.status.text).to eq("\x01TIME :1970-01-01T01:01:01+01:01 (Tokyo)\x01") } call '#twitter', 'time', ['mzp'] expect(@gateway.notified).to eq('#twitter') end it "should show offset time via API" do expect(::Time).to receive(:now).and_return(Time.at(0)) expect(@followings).to receive(:find_by_screen_name).with('mzp').and_return(nil) expect(@api).to receive(:get).with('users/show', screen_name:'mzp').and_return(@user) expect(@channel). to receive(:message). with(anything, Net::IRC::Constants::NOTICE){|s,_| expect(s.status.text).to eq("\x01TIME :1970-01-01T01:01:01+01:01 (Tokyo)\x01") } call '#twitter', 'time', ['mzp'] expect(@gateway.notified).to eq('#twitter') end end atig-0.6.1/spec/command/name_spec.rb0000644000175000017500000000062312661500130017120 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/name' describe Atig::Command::Name do include CommandHelper before do @command = init Atig::Command::Name end it "should update name" do expect(@api).to receive(:post).with('account/update_profile',:name=>'mzp') expect(@channel).to receive(:notify).with("You are named mzp.") call '#twitter', 'name', %w(mzp) end end atig-0.6.1/spec/command/location_spec.rb0000644000175000017500000000124012661500130020004 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/location' describe Atig::Command::Location do include CommandHelper before do @command = init Atig::Command::Location end it "should update location" do expect(@api).to receive(:post).with('account/update_profile',:location=>'some place') expect(@channel).to receive(:notify).with("You are in some place now.") call '#twitter','location',%w(some place) end it "should reset location" do expect(@api).to receive(:post).with('account/update_profile',:location=>'') expect(@channel).to receive(:notify).with("You are nowhere now.") call '#twitter','location',%w() end end atig-0.6.1/spec/command/refresh_spec.rb0000644000175000017500000000072612661500130017642 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/refresh' require 'atig/command/info' describe Atig::Command::Refresh do include CommandHelper before do @command = init Atig::Command::Refresh end it "should refresh all" do expect(@followings).to receive(:invalidate) expect(@lists).to receive(:invalidate).with(:all) expect(@channel).to receive(:notify).with("refresh followings/lists...") call '#twitter','refresh', [] end end atig-0.6.1/spec/command/reply_spec.rb0000644000175000017500000000504712661500130017340 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/reply' describe Atig::Command::Reply do include CommandHelper before do @command = init Atig::Command::Reply target = status 'blah blah', 'id'=>'1' entry = entry user(1,'mzp'), target @res = double 'res' stub_status(:find_by_tid,'a' => entry) stub_status(:find_by_sid,'mzp:a' => entry) stub_status(:find_by_screen_name,'mzp' => [ entry ], default: []) end it "should have '/me status' name" do expect(@gateway.names).to eq(%w(mention re reply rp)) end it "should post the status" do expect(@api).to receive(:post). with('statuses/update', {status:'abc @mzp', in_reply_to_status_id:'1'}). and_return(@res) call '#twitter', "reply", %w(a abc @mzp) expect(@gateway.updated).to eq([ @res, '#twitter', 'In reply to mzp: blah blah' ]) expect(@gateway.filtered).to eq({ status: 'abc @mzp', in_reply_to_status_id:'1'}) end it "should post the status by sid" do expect(@api).to receive(:post). with('statuses/update', {status:'abc @mzp', in_reply_to_status_id:'1'}). and_return(@res) call '#twitter', "reply", %w(mzp:a abc @mzp) expect(@gateway.updated).to eq([ @res, '#twitter', 'In reply to mzp: blah blah' ]) expect(@gateway.filtered).to eq({ status: 'abc @mzp', in_reply_to_status_id:'1'}) end it "should post the status by API" do expect(@api).to receive(:post). with('statuses/update', {status:'abc @mzp', in_reply_to_status_id:'1'}). and_return(@res) call '#twitter', "reply", %w(a abc @mzp) expect(@gateway.updated).to eq([ @res, '#twitter', 'In reply to mzp: blah blah' ]) expect(@gateway.filtered).to eq({ status: 'abc @mzp', in_reply_to_status_id:'1'}) end it "should post the status with screen_name" do expect(@api).to receive(:post). with('statuses/update', {status:'abc @mzp', in_reply_to_status_id:'1'}). and_return(@res) call '#twitter', "reply", %w(mzp abc @mzp) expect(@gateway.updated).to eq([ @res, '#twitter', 'In reply to mzp: blah blah' ]) expect(@gateway.filtered).to eq({ status: 'abc @mzp', in_reply_to_status_id:'1'}) end it "should add screen name as prefix" do expect(@api).to receive(:post). with('statuses/update', {status:'@mzp mzp', in_reply_to_status_id:'1'}). and_return(@res) call '#twitter', "reply", %w(a mzp) expect(@gateway.updated).to eq([ @res, '#twitter', 'In reply to mzp: blah blah' ]) expect(@gateway.filtered).to eq({ status: '@mzp mzp', in_reply_to_status_id:'1'}) end end atig-0.6.1/spec/command/retweet_spec.rb0000644000175000017500000000524212661500130017661 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/command/retweet' describe Atig::Command::Retweet do include CommandHelper before do bitly = double("Bitly") allow(bitly).to receive(:shorten){|s| "[#{s}]" } allow(Atig::Bitly).to receive(:no_login).and_return(bitly) @command = init Atig::Command::Retweet target = status 'blah blah blah blah blah blah blah blah', 'id'=>'1' entry = entry user(1,'mzp'), target @res = double 'res' stub_status(:find_by_tid,'a' => entry) stub_status(:find_by_sid,'mzp:a' => entry) stub_status(:find_by_screen_name,'mzp' => [ entry ], default:[]) end it "should have command name" do expect(@gateway.names).to eq(%w(ort rt retweet qt)) end it "should post official retweet without comment" do expect(@api).to receive(:post).with('statuses/retweet/1').and_return(@res) call "#twitter", 'rt', %w(a) expect(@gateway.updated).to eq([ @res, '#twitter', 'RT to mzp: blah blah blah blah blah blah blah blah' ]) end it "should post official retweet without comment by screen name" do expect(@api).to receive(:post).with('statuses/retweet/1').and_return(@res) call "#twitter", 'rt', %w(mzp) expect(@gateway.updated).to eq([ @res, '#twitter', 'RT to mzp: blah blah blah blah blah blah blah blah' ]) end it "should post official retweet without comment by sid" do expect(@api).to receive(:post).with('statuses/retweet/1').and_return(@res) call "#twitter", 'rt', %w(mzp:a) expect(@gateway.updated).to eq([ @res, '#twitter', 'RT to mzp: blah blah blah blah blah blah blah blah' ]) end it "should post un-official retweet with comment" do expect(@api).to receive(:post).with('statuses/update',:status=> "aaa RT @mzp: blah blah blah blah blah blah blah blah").and_return(@res) call "#twitter", 'rt', %w(a aaa) expect(@gateway.updated).to eq([ @res, '#twitter', 'RT to mzp: blah blah blah blah blah blah blah blah' ]) end it "should post un-official retweet with comment by screen name" do expect(@api).to receive(:post).with('statuses/update',:status=> "aaa RT @mzp: blah blah blah blah blah blah blah blah").and_return(@res) call "#twitter", 'rt', %w(mzp aaa) expect(@gateway.updated).to eq([ @res, '#twitter', 'RT to mzp: blah blah blah blah blah blah blah blah' ]) end it "should post un-official retweet with long comment" do expect(@api).to receive(:post).with('statuses/update',:status=> "#{'a' * 94} RT @mzp: b [https://twitter.com/mzp/status/1]").and_return(@res) call "#twitter", 'rt', ['a', 'a' * 94 ] expect(@gateway.updated).to eq([ @res, '#twitter', 'RT to mzp: blah blah blah blah blah blah blah blah' ]) end end atig-0.6.1/spec/db/0000755000175000017500000000000012661500130013607 5ustar uwabamiuwabamiatig-0.6.1/spec/db/sized_uniq_array_spec.rb0000644000175000017500000000255112661500130020521 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/db/sized_uniq_array' require 'ostruct' describe Atig::Db::SizedUniqArray do def item(id) item = double "Item-#{id}" allow(item).to receive(:id).and_return id item end before do @array = Atig::Db::SizedUniqArray.new 3 @item1 = item 1 @item2 = item 2 @item3 = item 3 @item4 = item 4 @array << @item1 @array << @item2 @array << @item3 end it "should include items" do expect(@array.to_a).to eq([ @item1, @item2, @item3 ]) end it "should rorate array" do @array << @item4 expect(@array.to_a).to eq([ @item2, @item3, @item4 ]) end it "should have reverse_each" do xs = [] @array.reverse_each {|x| xs << x } expect(xs).to eq([ @item3, @item2, @item1 ]) end it "should not have duplicate element" do @array << item(1) expect(@array.to_a).to eq([ @item1, @item2, @item3 ]) end it "should be accesible by index" do expect(@array[0]).to eq(@item1) expect(@array[1]).to eq(@item2) expect(@array[2]).to eq(@item3) end it "should not change index" do @array << @item4 expect(@array[0]).to eq(@item4) expect(@array[1]).to eq(@item2) expect(@array[2]).to eq(@item3) end it "should return index when add element" do expect(@array << @item4).to eq(0) expect(@array << @item3).to eq(nil) end end atig-0.6.1/spec/db/statuses_spec.rb0000644000175000017500000001112212661500130017016 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'fileutils' require 'atig/db/statuses' describe Atig::Db::Statuses do def status(id, text, time) OpenStruct.new(id: id, text: text, created_at: time.strftime("%a %b %d %H:%M:%S +0000 %Y")) end def user(name) OpenStruct.new(screen_name: name, id: name) end before do @listened = [] FileUtils.rm_f 'test.db' @db = Atig::Db::Statuses.new 'test.db' @a = status 10, 'a', Time.utc(2010,1,5) @b = status 20, 'b', Time.utc(2010,1,6) @c = status 30, 'c', Time.utc(2010,1,7) @d = status 40, 'd', Time.utc(2010,1,8) @alice = user 'alice' @bob = user 'bob' @db.add status: @a , user: @alice, source: :srcA @db.add status: @b , user: @bob , source: :srcB @db.add status: @c , user: @alice, source: :srcC end after(:all) do FileUtils.rm_f 'test.db' end it "should be re-openable" do Atig::Db::Statuses.new 'test.db' end it "should call listeners" do entry = nil @db.listen{|x| entry = x } @db.add status: @d, user: @alice, source: :timeline, fuga: :hoge expect(entry.source).to eq(:timeline) expect(entry.status).to eq(@d) expect(entry.tid).to match(/\w+/) expect(entry.sid).to match(/\w+/) expect(entry.user).to eq(@alice) expect(entry.source).to eq(:timeline) expect(entry.fuga).to eq(:hoge) end it "should not contain duplicate" do called = false @db.listen{|*_| called = true } @db.add status: @c, user: @bob, source: :timeline expect(called).to be_falsey end it "should be found by id" do entry = @db.find_by_id 1 expect(entry.id).to eq(1) expect(entry.status).to eq(@a) expect(entry.user) .to eq(@alice) expect(entry.tid) .to match(/\w+/) expect(entry.sid).to match(/\w+/) end it "should have unique tid" do db = Atig::Db::Statuses.new 'test.db' db.add status: @d , user: @alice, source: :srcA a = @db.find_by_id(1) d = @db.find_by_id(4) expect(a.tid).not_to eq(d.tid) expect(a.sid).not_to eq(d.cid) end it "should be found all" do db = @db.find_all expect(db.size).to eq(3) a,b,c = db expect(a.status).to eq(@c) expect(a.user) .to eq(@alice) expect(a.tid) .to match(/\w+/) expect(a.sid) .to match(/\w+/) expect(b.status).to eq(@b) expect(b.user) .to eq(@bob) expect(b.tid) .to match(/\w+/) expect(b.sid) .to match(/\w+/) expect(c.status).to eq(@a) expect(c.user).to eq(@alice) expect(c.tid).to match(/\w+/) expect(c.sid).to match(/\w+/) end it "should be found by tid" do entry = @db.find_by_id(1) expect(@db.find_by_tid(entry.tid)).to eq(entry) end it "should be found by sid" do entry = @db.find_by_id(1) expect(@db.find_by_sid(entry.sid)).to eq(entry) end it "should be found by tid" do expect(@db.find_by_tid('__')).to be_nil end it "should be found by user" do a,b = *@db.find_by_user(@alice) expect(a.status).to eq(@c) expect(a.user) .to eq(@alice) expect(a.tid) .to match(/\w+/) expect(a.sid) .to match(/\w+/) expect(b.status).to eq(@a) expect(b.user).to eq(@alice) expect(b.tid).to match(/\w+/) expect(b.sid).to match(/\w+/) end it "should be found by screen_name" do db = @db.find_by_screen_name('alice') expect(db.size).to eq(2) a,b = db expect(a.status).to eq(@c) expect(a.user) .to eq(@alice) expect(a.tid) .to match(/\w+/) expect(a.sid) .to match(/\w+/) expect(b.status).to eq(@a) expect(b.user).to eq(@alice) expect(b.tid).to match(/\w+/) expect(b.sid).to match(/\w+/) end it "should be found by screen_name with limit" do xs = @db.find_by_screen_name('alice', limit: 1) expect(xs.size).to eq(1) a,_ = xs expect(a.status).to eq(@c) expect(a.user) .to eq(@alice) expect(a.tid) .to match(/\w+/) expect(a.sid) .to match(/\w+/) end it "should remove by id" do @db.remove_by_id 1 expect(@db.find_by_id(1)).to be_nil end it "should have uniq tid/sid when removed" do old = @db.find_by_id 3 @db.remove_by_id 3 @db.add status: @c , user: @alice, source: :src new = @db.find_by_id 3 expect(old.tid).not_to eq(new.tid) expect(old.sid).not_to eq(new.sid) end it "should cleanup" do Atig::Db::Statuses::Size = 10 unless defined? Atig::Db::Statuses::Size # hack Atig::Db::Statuses::Size.times do|i| s = status i+100, 'a', Time.utc(2010,1,5)+i+1 @db.add status: s , user: @alice , source: :srcB end @db.cleanup expect(@db.find_by_status_id(@a.id)).to eq(nil) end end atig-0.6.1/spec/db/followings_spec.rb0000644000175000017500000000503412661500130017333 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/db/followings' describe Atig::Db::Followings,"when it is empty" do before do FileUtils.rm_f 'following.test.db' @db = Atig::Db::Followings.new('following.test.db') end after(:all) do FileUtils.rm_f 'following.test.db' end it "should be emtpy" do expect(@db.empty?).to be_truthy end end describe Atig::Db::Followings,"when updated users" do def user(id, name, protect, only) OpenStruct.new(id: id, screen_name:name, protected:protect, only:only) end before do @alice = user 1,'alice' , false, false @bob = user 2,'bob' , true , false @charriey = user 3,'charriey', false, true FileUtils.rm_f 'following.test.db' @db = Atig::Db::Followings.new('following.test.db') @db.update [ @alice, @bob ] @listen = {} @db.listen do|kind, users| @listen[kind] = users end end after(:all) do FileUtils.rm_f 'following.test.db' end it "should return size" do expect(@db.size).to eq(2) end it "should be invalidated" do called = false @db.on_invalidated do called = true end @db.invalidate expect(called).to be_truthy end it "should not empty" do expect(@db.empty?).to be_falsey end it "should call listener with :join" do @db.update [ @alice, @bob, @charriey ] expect(@listen[:join]).to eq([ @charriey ]) expect(@listen[:part]).to eq(nil) expect(@listen[:mode]).to eq(nil) end it "should call listener with :part" do @db.update [ @alice ] expect(@listen[:join]).to eq(nil) expect(@listen[:part]).to eq([ @bob ]) expect(@listen[:mode]).to eq(nil) end it "should not found removed user[BUG]" do expect(@db.include?(@bob)).to eq(true) @db.update [ @alice ] # now, @bob is not member expect(@db.include?(@bob)).to eq(false) end it "should call listener with :mode" do bob = user 5,'bob', false, false @db.update [ @alice, bob ] expect(@listen[:join]).to eq(nil) expect(@listen[:part]).to eq(nil) expect(@listen[:mode]).to eq([ bob ]) end it "should have users" do expect(@db.users).to eq([ @alice, @bob ]) end it "should be found by screen_name" do expect(@db.find_by_screen_name('alice')).to eq(@alice) expect(@db.find_by_screen_name('???')).to eq(nil) end it "should check include" do alice = user @alice.id,'alice', true, true expect(@db.include?(@charriey)).to be_falsey expect(@db.include?(@alice)).to be_truthy expect(@db.include?(alice)).to be_truthy end end atig-0.6.1/spec/db/lists_spec.rb0000644000175000017500000000545612661500130016316 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/db/lists' describe Atig::Db::Lists do def user(id, name, protect, only) OpenStruct.new(id: id, screen_name:name, protected:protect, only:only) end before do FileUtils.rm_rf "test-a.db" @lists = Atig::Db::Lists.new "test-%s.db" @alice = user 1,'alice' , false, false @bob = user 2,'bob' , true , false @charriey = user 3,'charriey', false, true @args = {} @lists.listen{|kind,*args| @args[kind] = args } end after(:all) do %w(test-a.db test-b.db).each do |file| FileUtils.rm_f file end end it "should have list" do @lists.update("a" => [ @alice, @bob ], "b" => [ @alice, @bob , @charriey ]) expect(@lists.find_by_screen_name('alice').sort).to eq(["a", "b"]) expect(@lists.find_by_screen_name('charriey')).to eq(["b"]) end it "should have lists" do @lists.update("a" => [ @alice, @bob ], "b" => [ @alice, @bob , @charriey ]) expect(@lists.find_by_list_name('a')).to eq([ @alice, @bob ]) end it "should have each" do data = { "a" => [ @alice, @bob ], "b" => [ @alice, @bob , @charriey ] } @lists.update(data) hash = {} @lists.each do|name,users| hash[name] = users end expect(hash).to eq(data) end it "should call listener when new list" do @lists.update("a" => [ @alice, @bob ]) expect(@args.keys).to include(:new, :join) expect(@args[:new]).to eq([ "a" ]) expect(@args[:join]).to eq([ "a", [ @alice, @bob ] ]) end it "should call listener when partcial update" do @lists.update("a" => [ @alice ]) @lists["a"].update([ @alice, @bob, @charriey ]) expect(@args[:join]).to eq(["a", [ @bob, @charriey ]]) end it "should call on_invalidated" do called = false @lists.on_invalidated do|name| expect(name).to eq("a") called = true end @lists.invalidate("a") expect(called).to be_truthy end it "should call listener when delete list" do @lists.update("a" => [ @alice, @bob ]) @lists.update({}) expect(@args.keys).to include(:new, :join, :del) expect(@args[:del]).to eq(["a"]) end it "should call listener when join user" do @lists.update("a" => [ @alice ]) @lists.update("a" => [ @alice, @bob, @charriey ]) expect(@args[:join]).to eq(["a", [ @bob, @charriey ]]) end it "should call listener when exit user" do @lists.update("a" => [ @alice, @bob, @charriey ]) @lists.update("a" => [ @alice ]) expect(@args[:part]).to eq(["a", [ @bob, @charriey ]]) end it "should call listener when change user mode" do @lists.update("a" => [ @alice, @bob ]) bob = user @bob.id, 'bob', false, false @lists.update("a" => [ @alice, bob ]) expect(@args[:mode]).to eq([ "a", [ bob ]]) end end atig-0.6.1/spec/db/roman_spec.rb0000644000175000017500000000045212661500130016263 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/db/roman' describe Atig::Db::Roman do before do @roman = Atig::Db::Roman.new end it "should make readble tid" do expect(@roman.make(0)).to eq('a') expect(@roman.make(1)).to eq('i') expect(@roman.make(2)).to eq('u') end end atig-0.6.1/spec/db/listenable_spec.rb0000644000175000017500000000112712661500130017271 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/db/listenable' class SampleListener include Atig::Db::Listenable def hi(*args) notify(*args) end end describe Atig::Db::Listenable, "when it is called" do before do @listeners = SampleListener.new @args = [] @listeners.listen {|*args| @args << args } @listeners.listen {|*args| @args << args } @listeners.listen {|*args| @args << args } end it "should call all listener" do @listeners.hi 1,2,3 expect(@args.length).to eq(3) 1.upto(2) {|i| expect(@args[i]).to eq([1,2,3]) } end end atig-0.6.1/spec/ofilter/0000755000175000017500000000000012661500130014666 5ustar uwabamiuwabamiatig-0.6.1/spec/ofilter/geo_spec.rb0000644000175000017500000000126312661500130017001 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/ofilter/geo' require 'ostruct' describe Atig::OFilter::Geo,"when disabled" do def filtered(text,opt={}) geo = Atig::OFilter::Geo.new(OpenStruct.new(opts:OpenStruct.new(opt))) geo.call status: text end it "should through" do expect(filtered("hi")).to eq({ status: "hi" }) end end describe Atig::OFilter::Geo,"when enabled" do def filtered(text,opt={}) geo = Atig::OFilter::Geo.new(OpenStruct.new(opts:OpenStruct.new(opt))) geo.call status: text end it "add lat & long" do expect(filtered("hi",:ll=>"42.1,43.1")).to eq({ status: "hi", lat: 42.1, long: 43.1 }) end end atig-0.6.1/spec/ofilter/footer_spec.rb0000644000175000017500000000116512661500130017526 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/ofilter/footer' require 'ostruct' describe Atig::OFilter::Footer do before do @opts = OpenStruct.new @filter = Atig::OFilter::Footer.new(OpenStruct.new(opts:@opts)) end it "should pass through" do expect(@filter.call(status: 'hi')).to eq({ status: "hi" }) end it "should append footer" do @opts.footer = '*tw*' expect(@filter.call(status: 'hi')).to eq({ status: "hi *tw*" }) end it "should not append footer" do @opts.footer = false expect(@filter.call(status: 'hi')).to eq({ status: "hi" }) end end atig-0.6.1/spec/ofilter/short_url_spec.rb0000644000175000017500000000562112661500130020252 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/ofilter/short_url' require 'ostruct' describe Atig::OFilter::ShortUrl,"when no-login bitly" do before do logger = double('Logger') bitly = double("Bitly") allow(bitly).to receive(:shorten){|s| "[#{s}]" } expect(Atig::Bitly).to receive(:no_login).with(logger).and_return(bitly) @ofilter = Atig::OFilter::ShortUrl.new(OpenStruct.new(log:logger, opts:OpenStruct.new('bitlify'=>true))) end it "should shorten url by bitly" do expect(@ofilter.call({status: "this is http://example.com/a http://example.com/b"})).to eq({ status: "this is [http://example.com/a] [http://example.com/b]" }) end end describe Atig::OFilter::ShortUrl,"when no-login bitly with size" do before do logger = double('Logger') bitly = double("Bitly") allow(bitly).to receive(:shorten){|s| "[#{s}]" } expect(Atig::Bitly).to receive(:no_login).with(logger).and_return(bitly) @ofilter = Atig::OFilter::ShortUrl.new(OpenStruct.new(log:logger, opts:OpenStruct.new('bitlify'=>13))) end it "should only shorten large url" do expect(@ofilter.call({status: "this is http://example.com/a http://a.com"})).to eq({ status: "this is [http://example.com/a] http://a.com" }) end end describe Atig::OFilter::ShortUrl,"when login bitly" do before do logger = double('Logger') bitly = double("Bitly") allow(bitly).to receive(:shorten){|s| "[#{s}]" } expect(Atig::Bitly).to receive(:login).with(logger,"username","api_key").and_return(bitly) @ofilter = Atig::OFilter::ShortUrl.new(OpenStruct.new(log:logger, opts:OpenStruct.new('bitlify'=>'username:api_key'))) end it "should only shorten large url" do expect(@ofilter.call({status: "this is http://example.com/a http://example.com/b"})).to eq({ status: "this is [http://example.com/a] [http://example.com/b]" }) end end describe Atig::OFilter::ShortUrl,"when login bitly with size" do before do logger = double('Logger') bitly = double("Bitly") allow(bitly).to receive(:shorten){|s| "[#{s}]" } expect(Atig::Bitly).to receive(:login).with(logger,"username","api_key").and_return(bitly) @ofilter = Atig::OFilter::ShortUrl.new(OpenStruct.new(log:logger, opts:OpenStruct.new('bitlify'=>'username:api_key:13'))) end it "should only shorten large url" do expect(@ofilter.call({status: "this is http://example.com/a http://a.com"})).to eq({ status: "this is [http://example.com/a] http://a.com" }) end end describe Atig::OFilter::ShortUrl,"when nop" do before do logger = double('Logger') @ofilter = Atig::OFilter::ShortUrl.new(OpenStruct.new(log:logger, opts:OpenStruct.new())) end it "should only not do anything" do expect(@ofilter.call({status: "this is http://example.com/a http://a.com"})).to eq({ status: "this is http://example.com/a http://a.com" }) end end atig-0.6.1/spec/ofilter/escape_url_spec.rb0000644000175000017500000000262412661500130020353 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/ofilter/escape_url' class Atig::OFilter::EscapeUrl def exist_uri?(_); true end end describe Atig::OFilter::EscapeUrl do before do @logger = double('Logger') expect(@logger).to receive(:info).at_most(:once) expect(@logger).to receive(:error).at_most(:once) expect(@logger).to receive(:debug).at_most(:once) end def filtered(text,opt={}) esc = Atig::OFilter::EscapeUrl.new(OpenStruct.new(log:@logger,:opts=>nil)) esc.call status: text end it "through normal url" do expect(filtered("http://example.com")).to eq({ status: "http://example.com"}) end it "escape only url" do expect(filtered("あああ http://example.com/あああ")).to eq({ status: "あああ http://example.com/%E3%81%82%E3%81%82%E3%81%82" }) end end if defined? ::Punycode then describe Atig::OFilter::EscapeUrl,"when punycode is enabled" do before do @logger = double('Logger') expect(@logger).to receive(:info).at_most(:once) expect(@logger).to receive(:error).at_most(:once) expect(@logger).to receive(:debug).at_most(:once) end def filtered(text,opt={}) esc = Atig::OFilter::EscapeUrl.new(OpenStruct.new(log:@logger,:opts=>nil)) esc.call status: text end it "escape international URL" do expect(filtered("http://あああ.com")).to eq({status: "http://xn--l8jaa.com" }) end end end atig-0.6.1/spec/option_spec.rb0000644000175000017500000000366312661500130016101 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/option' describe Atig::Option do before do @opt = Atig::Option.parse 'a b=1 c=1.2 d=foo' end it "should have bool property" do expect(@opt.a).to be_truthy end it "should have int property" do expect(@opt.b).to eq(1) end it "should have float property" do expect(@opt.c).to eq(1.2) end it "should have string property" do expect(@opt.d).to eq('foo') end it "should not have other property" do expect(@opt.e).to eq(nil) end it "should update the value" do @opt.a = false expect(@opt.a).to be_falsey end it "should be accessed by [name]" do expect(@opt[:a]).to be_truthy expect(@opt['a']).to be_truthy end it "should be updated by [name]=" do @opt[:a] = false expect(@opt.a).to be_falsey expect(@opt[:a]).to be_falsey expect(@opt['a']).to be_falsey end it "should be updated by [name]=" do @opt['a'] = false expect(@opt.a).to be_falsey expect(@opt[:a]).to be_falsey expect(@opt['a']).to be_falsey end it "should be created by [name]=" do @opt['e'] = false expect(@opt.e).to be_falsey expect(@opt[:e]).to be_falsey expect(@opt['e']).to be_falsey end it "should be access to id" do expect(@opt.id).to be_nil @opt.id = 1 expect(@opt.id).to eq(1) end it "should have default value" do expect(@opt.api_base).to eq('https://api.twitter.com/1.1/') end it "should list up all fields" do expect(@opt.fields.map{|x| x.to_s }.sort).to eq(%w(api_base stream_api_base search_api_base a b c d).sort) @opt.e = 1 expect(@opt.fields.map{|x| x.to_s }.sort).to eq(%w(api_base search_api_base stream_api_base a b c d e).sort) end end describe Atig::Option,'with not default value' do before do @opt = Atig::Option.parse 'hoge api_base=twitter.com' end it "should be specified value" do expect(@opt.api_base).to eq('twitter.com') end end atig-0.6.1/spec/ifilter/0000755000175000017500000000000012661500130014660 5ustar uwabamiuwabamiatig-0.6.1/spec/ifilter/expand_url_spec.rb0000644000175000017500000000607012661500130020363 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/ifilter/expand_url' require 'atig/twitter_struct' class Atig::IFilter::ExpandUrl def resolve_http_redirect(uri); "[#{uri}]" end end describe Atig::IFilter::ExpandUrl, "when disable whole url" do def filtered(text) ifilter = Atig::IFilter::ExpandUrl.new OpenStruct.new(log:double('log'),:opts=>OpenStruct.new) ifilter.call status(text) end it "should expand bit.ly" do expect(filtered("This is http://bit.ly/hoge")).to be_text("This is [http://bit.ly/hoge]") expect(filtered("This is http://bitly.com/hoge")).to be_text("This is [http://bitly.com/hoge]") end it "should expand htn.to" do expect(filtered("This is http://htn.to/TZdkXg")).to be_text("This is [http://htn.to/TZdkXg]") expect(filtered("This is http://htnnto/TZdkXg")).to be_text("This is http://htnnto/TZdkXg") end it "should expand tmblr.co" do expect(filtered("This is http://tmblr.co/Z0rNbyxhxUK5")).to be_text("This is [http://tmblr.co/Z0rNbyxhxUK5]") end it "should expand nico.ms" do expect(filtered("This is http://nico.ms/sm11870888")).to be_text("This is [http://nico.ms/sm11870888]") end it "should through other url" do expect(filtered("http://example.com")).to be_text("http://example.com") end end describe Atig::IFilter::ExpandUrl, "when enable whole url" do def filtered(text) context = OpenStruct.new( log: double('log'), opts: OpenStruct.new(untiny_whole_urls:true)) ifilter = Atig::IFilter::ExpandUrl.new(context) ifilter.call status(text) end it "should expand bit.ly" do expect(filtered("This is http://bit.ly/hoge")).to be_text("This is [http://bit.ly/hoge]") end it "should expand other url" do expect(filtered("http://example.com")).to be_text("[http://example.com]") expect(filtered("https://example.com")).to be_text("[https://example.com]") end end describe Atig::IFilter::ExpandUrl, "when has urls entities" do def filtered(text, opts) context = OpenStruct.new( log: double('log'), opts: OpenStruct.new) ifilter = Atig::IFilter::ExpandUrl.new(context) ifilter.call status(text, opts) end it "should expand t.co" do opts = { "entities" => { "urls" => [{ "url" => "http://t.co/1Vyoux4kB8", "expanded_url" => "http://example.com/" }, { "url" => "http://t.co/V1441ye6g2", "expanded_url" => "http://example.org/" }] } } expect(filtered("http://t.co/1Vyoux4kB8", opts)).to be_text("http://example.com/") expect(filtered("http://t.co/1Vyoux4kB8 http://t.co/V1441ye6g2", opts)).to be_text("http://example.com/ http://example.org/") end it "should expand recursive shorten URL" do opts = { "entities" => { "urls" => [{ "url" => "http://t.co/h8sqL5ZMuz", "expanded_url" => "http://bit.ly/1LM4fW" }] } } expect(filtered("http://t.co/h8sqL5ZMuz", opts)).to be_text("[http://bit.ly/1LM4fW]") end end atig-0.6.1/spec/ifilter/sanitize_spec.rb0000644000175000017500000000111612661500130020044 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/ifilter/sanitize' require 'atig/twitter_struct' describe Atig::IFilter::Sanitize do def filtered(text) Atig::IFilter::Sanitize.call status(text) end it "should convert escape html" do expect(filtered("< > "")).to be_text("< > \"") end it "should convert whitespace" do expect(filtered("\r\n")).to be_text(" ") expect(filtered("\r")).to be_text(" ") expect(filtered("\n")).to be_text(" ") end it "should delete \\000\\001 sequence" do expect(filtered("\000\001")).to be_text("") end end atig-0.6.1/spec/ifilter/sid_spec.rb0000644000175000017500000000146512661500130017004 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/ifilter/xid' require 'atig/twitter_struct' describe Atig::IFilter::Sid, "when disable tid" do def filtered(text) ifilter = Atig::IFilter::Sid.new(OpenStruct.new(log:double('log'), opts:OpenStruct.new)) ifilter.call status(text,'sid'=>1) end it "should through text" do expect(filtered("hello")).to be_text("hello") end end describe Atig::IFilter::Sid, "when enable tid" do def filtered(text) ifilter = Atig::IFilter::Sid.new(OpenStruct.new(log:double('log'), opts:OpenStruct.new(sid:true))) ifilter.call status(text,'sid'=>1) end it "should append sid" do expect(filtered("hello")).to be_text("hello \x0310[1]\x0F") end end atig-0.6.1/spec/ifilter/tid_spec.rb0000644000175000017500000000146512661500130017005 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/ifilter/xid' require 'atig/twitter_struct' describe Atig::IFilter::Tid, "when disable tid" do def filtered(text) ifilter = Atig::IFilter::Tid.new(OpenStruct.new(log:double('log'), opts:OpenStruct.new)) ifilter.call status(text,'tid'=>1) end it "should through text" do expect(filtered("hello")).to be_text("hello") end end describe Atig::IFilter::Tid, "when enable tid" do def filtered(text) ifilter = Atig::IFilter::Tid.new(OpenStruct.new(log:double('log'), opts:OpenStruct.new(tid:true))) ifilter.call status(text,'tid'=>1) end it "should append tid" do expect(filtered("hello")).to be_text("hello \x0310[1]\x0F") end end atig-0.6.1/spec/ifilter/strip_spec.rb0000644000175000017500000000077012661500130017364 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/ifilter/strip' require 'atig/twitter_struct' describe Atig::IFilter::Strip do before do @ifilter = Atig::IFilter::Strip.new %w(*tw* _) end it "should strip *tw*" do expect(@ifilter.call(status("hoge *tw*"))).to be_text("hoge") end it "should strip _" do expect(@ifilter.call(status("hoge _"))).to be_text("hoge") end it "should strip white-space" do expect(@ifilter.call(status(" hoge "))).to be_text("hoge") end end atig-0.6.1/spec/ifilter/retweet_time_spec.rb0000644000175000017500000000126212661500130020715 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/ifilter/retweet_time' require 'atig/twitter_struct' describe Atig::IFilter::RetweetTime do def filtered(text, opt={}) Atig::IFilter::RetweetTime.call status(text, opt) end it "should throw normal status" do expect(filtered("hello")).to be_text("hello") end it "should prefix RT for Retweet" do expect(filtered("RT @mzp: hello", 'retweeted_status'=>{ 'text' => 'hello', 'created_at' => 'Sat Sep 25 14:33:19 +0000 2010', 'user' => { 'screen_name' => 'mzp' } })). to be_text("#{@rt}RT @mzp: hello \x0310[2010-09-25 14:33]\x0F") end end atig-0.6.1/spec/ifilter/retweet_spec.rb0000644000175000017500000000120112661500130017670 0ustar uwabamiuwabami# -*- mode:ruby; coding:utf-8 -*- require 'atig/ifilter/retweet' require 'atig/twitter_struct' describe Atig::IFilter::Retweet do def filtered(text, opt={}) Atig::IFilter::Retweet.call status(text, opt) end before do @rt = Atig::IFilter::Retweet::Prefix end it "should throw normal status" do expect(filtered("hello")).to be_text("hello") end it "should prefix RT for Retweet" do expect(filtered("RT: hello...", 'retweeted_status'=>{ 'text' => 'hello', 'user' => { 'screen_name' => 'mzp' } })). to be_text("#{@rt}RT @mzp: hello") end end atig-0.6.1/Gemfile0000644000175000017500000000004712661500130013564 0ustar uwabamiuwabamisource 'https://rubygems.org' gemspec atig-0.6.1/README.md0000644000175000017500000000261412661500130013552 0ustar uwabamiuwabamiAtig.rb - Another Twitter Irc Gateway =========================================== [![Gem Version](https://badge.fury.io/rb/atig.svg)](https://rubygems.org/gems/atig) [![Build Status](https://travis-ci.org/atig/atig.svg)](https://travis-ci.org/atig/atig) [![Coverage Status](https://coveralls.io/repos/github/atig/atig/badge.svg?branch=master)](https://coveralls.io/github/atig/atig?branch=master) [![Code Climate](https://codeclimate.com/github/atig/atig.svg)](https://codeclimate.com/github/atig/atig) OVERVIEW -------- Atig.rb is Twitter Irc Gateway. Atig.rb is forked from cho45's tig.rb. We improve some features of tig.rb. PREREQUISITES ------------- * Ruby 1.9.3 or later * sqlite3-ruby * rspec(for unit test) * rake(for unit test) HOW TO USE ---------- You type: $ cd atig $ bin/atig -d I, [2010-04-05T07:22:07.861527 #62002] INFO -- : Host: localhost Port:16668 and access localhost:16668 by Irc client. DOCUMENTS --------- See `docs/`, if you could read Japanese. BRANCH POLICY ------------- * master: a branch for current release. * testing: a branch for next release. * other branches: feature branch LICENCE ------- This program is free software; you can redistribute it and/or modify it under Ruby Lincence. AUTHOR ------ MIZUNO "mzp" Hiroki (mzp@happyabc.org) AVAILABILITY ------------ The complete atig.rb distribution can be accessed at this[http://mzp.github.com/atig/].. atig-0.6.1/exe/0000755000175000017500000000000012661500130013051 5ustar uwabamiuwabamiatig-0.6.1/exe/atig0000755000175000017500000000037312661500130013726 0ustar uwabamiuwabami#!/usr/bin/env ruby # stolen from termtter self_file = if File.symlink?(__FILE__) require 'pathname' Pathname.new(__FILE__).realpath else __FILE__ end $:.unshift(File.dirname(self_file) + "/../lib") require 'atig' Atig::Client.run