PageRenderTime 55ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/plugins/defaults/standard_commands.rb

https://github.com/Epictetus/termtter
Ruby | 591 lines | 537 code | 45 blank | 9 comment | 31 complexity | 4f081bddf3af8f03e55b892aa5995eaf MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. require 'erb'
  3. require 'set'
  4. config.plugins.standard.set_default(
  5. :limit_format,
  6. '<<%=remaining_color%>><%=limit.remaining_hits%></<%=remaining_color%>>/<%=limit.hourly_limit%> until <%=Time.parse(limit.reset_time).getlocal%> (<%=remaining_time%> remaining)')
  7. config.set_default(:easy_reply, false)
  8. config.plugins.standard.set_default(
  9. :one_line_profile_format,
  10. '<90>[<%=user_id%>]</90> <%= mark %> <<%=color%>><%= user.screen_name %>: <%= padding %><%= (user.description || "").gsub(/\r?\n/, "") %></<%=color%>>')
  11. module Termtter::Client
  12. register_command(
  13. :name => :reload,
  14. :exec => lambda {|arg|
  15. # NOTE: If edit this command, please check and edit lib/plugins/channel.rb too, please.
  16. args = @since_id ? [{:since_id => @since_id}] : []
  17. statuses = Termtter::API.twitter.home_timeline(*args)
  18. unless statuses.empty?
  19. Termtter::Client.clear_line
  20. @since_id = statuses[0].id
  21. output(statuses, Termtter::Event.new(:update_friends_timeline, :type => :main))
  22. Readline.refresh_line if arg =~ /\-r/
  23. end
  24. },
  25. :help => ['reload', 'Reload time line']
  26. )
  27. register_command(
  28. :name => :update, :alias => :u,
  29. :exec => lambda {|arg|
  30. return if arg.empty?
  31. params =
  32. if config.easy_reply && /^\s*(@\w+)/ =~ arg
  33. user_name = normalize_as_user_name($1)
  34. in_reply_to_status_id =
  35. Termtter::API.twitter.user(user_name).status.id rescue nil
  36. in_reply_to_status_id ?
  37. {:in_reply_to_status_id => in_reply_to_status_id} : {}
  38. else
  39. {}
  40. end
  41. # "u $aa msg" is likely to be a mistake of
  42. # "re $aa msg".
  43. if /^\s*\d+\s/ =~ arg
  44. case HighLine.new.ask("Does it mean `re[ply] #{arg}` [N/y]? ")
  45. when /^[yY]$/
  46. Termtter::Client.execute("re #{arg}")
  47. break
  48. when /^[nN]?$/
  49. else
  50. puts "Invalid answer. Please input [yYnN] or nothing."
  51. break
  52. end
  53. end
  54. result = Termtter::API.twitter.update(arg, params)
  55. if result.text == arg
  56. puts "updated => #{result.text}"
  57. else
  58. puts TermColor.parse("<red>Failed to update :(</red>")
  59. end
  60. },
  61. :help => ["update,u TEXT", "Post a new message"]
  62. )
  63. register_command(
  64. :name => :delete, :aliases =>[:del],
  65. :exec_proc => lambda {|arg|
  66. id =
  67. case arg
  68. when ''
  69. Termtter::API.twitter.user_timeline(config.user_name)[0].id
  70. when /^\d+$/
  71. arg.to_i
  72. end
  73. if id
  74. result = Termtter::API.twitter.remove_status(id)
  75. puts "deleted => #{result.text}"
  76. end
  77. },
  78. :help => ['delete,del [STATUS ID]', 'Delete a status']
  79. )
  80. unless defined? DirectMessage
  81. class DirectMessage < Struct.new(:id, :text, :user, :created_at)
  82. def method_missing(*args, &block)
  83. nil
  84. end
  85. end
  86. end
  87. register_command(
  88. :direct, :alias => :d,
  89. :help => ["direct,d USERNAME TEXT", "Send direct message"]) do |arg|
  90. if /^([^\s]+)\s+?(.*)\s*$/ =~ arg
  91. user, text = normalize_as_user_name($1), $2
  92. Termtter::API.twitter.direct_message(user, text)
  93. puts "=> to:#{user} message:#{text}"
  94. end
  95. end
  96. register_command(
  97. 'direct list', :help => ["direct list", 'List direct messages']) do |arg|
  98. output(
  99. Termtter::API.twitter.direct_messages.map { |d|
  100. DirectMessage.new(d.id, "#{d.text} => #{d.recipient_screen_name}", d.sender, d.created_at)
  101. },
  102. Termtter::Event.new(
  103. :direct_messages,
  104. :type => :direct_message)
  105. )
  106. end
  107. register_command(
  108. 'direct sent_list',
  109. :help => ["direct sent_list", 'List sent direct messages']) do |arg|
  110. output(
  111. Termtter::API.twitter.sent_direct_messages.map { |d|
  112. DirectMessage.new(
  113. d.id, "#{d.text} => #{d.recipient_screen_name}", d.sender, d.created_at)
  114. },
  115. Termtter::Event.new(
  116. :direct_messages,
  117. :type => :direct_message)
  118. )
  119. end
  120. def self.get_friends(user_name, max)
  121. self.get_friends_or_followers(:friends, user_name, max)
  122. end
  123. def self.get_followers(user_name, max)
  124. self.get_friends_or_followers(:followers, user_name, max)
  125. end
  126. def self.get_friends_or_followers(type, user_name, max)
  127. raise "type should be :friends or :followers" unless [:friends, :followers].include? type
  128. users = []
  129. cursor = -1
  130. begin
  131. tmp = Termtter::API::twitter.__send__(type, user_name, :cursor => cursor)
  132. cursor = tmp[:next_cursor]
  133. users += tmp[:users]
  134. puts "#{users.length}/#{max}" if max > 100
  135. rescue
  136. break
  137. end until (cursor.zero? or users.length > max)
  138. users.take(max)
  139. end
  140. register_command(
  141. :name => :friends, :aliases => [:following],
  142. :exec_proc => lambda {|arg|
  143. friends_or_followers_command(:friends, arg)
  144. },
  145. :help => ["friends [USERNAME] [-COUNT]", "Show user's friends."]
  146. )
  147. register_command(
  148. :name => :followers,
  149. :exec_proc => lambda {|arg|
  150. friends_or_followers_command(:followers, arg)
  151. },
  152. :help => ["followers [USERNAME]", "Show user's followers."]
  153. )
  154. def self.friends_or_followers_command(type, arg)
  155. raise "type should be :friends or :followers" unless [:friends, :followers].include? type
  156. limit = 20
  157. if /\-([\d]+)/ =~ arg
  158. limit = $1.to_i
  159. arg = arg.gsub(/\-([\d]+)/, '')
  160. end
  161. arg.strip!
  162. user_name = arg.empty? ? config.user_name : arg
  163. users = get_friends_or_followers(type, user_name, limit)
  164. longest = users.map{ |u| u.screen_name.length}.max
  165. users.reverse.each{|user|
  166. padding = ' ' * (longest - user.screen_name.length)
  167. user_id = Termtter::Client.data_to_typable_id(user.id) rescue ''
  168. color = user.following ? config.plugins.stdout.colors.first : config.plugins.stdout.colors.last
  169. mark = user.following ? '♥' : '✂'
  170. erbed_text = ERB.new(config.plugins.standard.one_line_profile_format).result(binding)
  171. puts TermColor.unescape(TermColor.parse(erbed_text))
  172. }
  173. end
  174. class SearchEvent < Termtter::Event
  175. def initialize(query)
  176. super :search, {:query => query, :type => :search}
  177. end
  178. end
  179. public_storage[:search_keywords] = Set.new
  180. register_command(
  181. :name => :search, :aliases => [:s],
  182. :exec_proc => lambda {|arg|
  183. search_option = config.search.option.empty? ? {} : config.search.option
  184. statuses = Termtter::API.twitter.search(arg, search_option)
  185. public_storage[:search_keywords] << arg
  186. output(statuses, SearchEvent.new(arg))
  187. },
  188. :completion_proc => lambda {|cmd, arg|
  189. public_storage[:search_keywords].grep(/^#{Regexp.quote(arg)}/).map {|i| "#{cmd} #{i}" }
  190. },
  191. :help => ["search,s TEXT", "Search for Twitter"]
  192. )
  193. register_hook(:highlight_for_search_query, :point => :pre_coloring) do |text, event|
  194. case event
  195. when SearchEvent
  196. query = event.query.split(/\s/).map {|q|Regexp.quote(q)}.join("|")
  197. text.gsub(/#{query}/i, '<on_magenta><white>\0</white></on_magenta>')
  198. else
  199. text
  200. end
  201. end
  202. register_command(
  203. :name => :replies, :aliases => [:r],
  204. :exec_proc => lambda {|arg|
  205. if /\-([\d]+)/ =~ arg
  206. options = {:count => $1}
  207. arg = arg.gsub(/\-([\d]+)/, '')
  208. else
  209. options = {}
  210. end
  211. res = Termtter::API.twitter.replies(options)
  212. unless arg.empty?
  213. res = res.select {|e| e.user.screen_name == arg }
  214. end
  215. output(res, Termtter::Event.new(:replies, :type => :reply))
  216. },
  217. :help => ["replies,r [username]", "List the replies (from the user)"]
  218. )
  219. register_command(
  220. :name => :show,
  221. :exec_proc => lambda {|arg|
  222. id = arg.gsub(/.*:\s*/, '')
  223. output([Termtter::API.twitter.show(id)], Termtter::Event.new(:show, :type => :show))
  224. },
  225. :completion_proc => lambda {|cmd, arg|
  226. case arg
  227. when /(\w+):\s*(\d+)\s*$/
  228. find_status_ids($2).map {|id| "#{cmd} #{$1}: #{id}"}
  229. else
  230. users = find_users(arg)
  231. unless users.empty?
  232. users.map {|user| "#{cmd} #{user}:"}
  233. else
  234. find_status_ids(arg).map {|id| "#{cmd} #{id}"}
  235. end
  236. end
  237. },
  238. :help => ["show ID", "Show a single status"]
  239. )
  240. register_command(
  241. :name => :shows,
  242. :exec_proc => lambda {|arg|
  243. id = arg.gsub(/.*:\s*/, '')
  244. # TODO: Implement
  245. #output([Termtter::API.twitter.show(id)], :show)
  246. puts "Not implemented yet."
  247. },
  248. :completion_proc => get_command(:show).completion_proc
  249. )
  250. register_command(
  251. :name => :follow, :aliases => [],
  252. :exec_proc => lambda {|args|
  253. args.split(' ').each do |arg|
  254. user_name = normalize_as_user_name(arg)
  255. user = Termtter::API::twitter.follow(user_name)
  256. puts "followed #{user.screen_name}"
  257. end
  258. },
  259. :help => ['follow USER', 'Follow user']
  260. )
  261. register_command(
  262. :name => :leave, :aliases => [:remove],
  263. :exec_proc => lambda {|args|
  264. args.split(' ').each do |arg|
  265. user_name = normalize_as_user_name(arg)
  266. user = Termtter::API::twitter.leave(user_name)
  267. puts "left #{user.screen_name}"
  268. end
  269. },
  270. :help => ['leave USER', 'Leave user']
  271. )
  272. register_command(
  273. :name => :block, :aliases => [],
  274. :exec_proc => lambda {|args|
  275. args.split(' ').each do |arg|
  276. user_name = normalize_as_user_name(arg)
  277. user = Termtter::API::twitter.block(user_name)
  278. puts "blocked #{user.screen_name}"
  279. end
  280. },
  281. :help => ['block USER', 'Block user']
  282. )
  283. register_command(
  284. :name => :unblock, :aliases => [],
  285. :exec_proc => lambda {|args|
  286. args.split(' ').each do |arg|
  287. user_name = normalize_as_user_name(arg)
  288. user = Termtter::API::twitter.unblock(user_name)
  289. puts "unblocked #{user.screen_name}"
  290. end
  291. },
  292. :help => ['unblock USER', 'Unblock user']
  293. )
  294. help = ['favorites,favlist USERNAME', 'show user favorites']
  295. register_command(:favorites, :alias => :favlist, :help => help) do |arg|
  296. output(Termtter::API.twitter.favorites(arg), Termtter::Event.new(:user_timeline, :type => :favorite))
  297. end
  298. register_command(
  299. :name => :favorite, :aliases => [:fav],
  300. :exec_proc => lambda {|args|
  301. args.split(' ').each do |arg|
  302. id =
  303. case arg
  304. when /^\d+/
  305. arg.to_i
  306. when /^@([A-Za-z0-9_]+)/
  307. user_name = normalize_as_user_name($1)
  308. statuses = Termtter::API.twitter.user_timeline(user_name)
  309. return if statuses.empty?
  310. statuses[0].id
  311. when %r{twitter.com/(?:\#!/)[A-Za-z0-9_]+/status(?:es)?/\d+}
  312. status_id = URI.parse(arg).path.split(%{/}).last
  313. when %r{twitter.com/[A-Za-z0-9_]+}
  314. user_name = normalize_as_user_name(URI.parse(arg).path.split(%{/}).last)
  315. statuses = Termtter::API.twitter.user_timeline(user_name)
  316. return if statuses.empty?
  317. statuses[0].id
  318. when /^\/(.*)$/
  319. word = $1
  320. raise "Not implemented yet."
  321. else
  322. if public_storage[:typable_id] && typable_id?(arg)
  323. typable_id_convert(arg)
  324. else
  325. return
  326. end
  327. end
  328. begin
  329. r = Termtter::API.twitter.favorite id
  330. puts "Favorited status ##{r.id} on user @#{r.user.screen_name} #{r.text}"
  331. rescue => e
  332. handle_error e
  333. end
  334. end
  335. },
  336. :completion_proc => lambda {|cmd, arg|
  337. case arg
  338. when /(\d+)/
  339. find_status_ids(arg).map {|id| "#{cmd} #{id}"}
  340. else
  341. if data = Termtter::Client.typable_id_to_data(arg)
  342. "#{cmd} #{data}"
  343. else
  344. %w(favorite).grep(/^#{Regexp.quote arg}/)
  345. end
  346. end
  347. },
  348. :help => ['favorite,fav (ID|@USER|TYPABLE|/WORD)', 'Mark a status as a favorite']
  349. )
  350. def self.show_settings(conf, level = 0)
  351. conf.__values__.each do |k, v|
  352. if v.instance_of? Termtter::Config
  353. puts "#{k}:"
  354. show_settings v, level + 1
  355. else
  356. print ' ' * level
  357. puts "#{k} = #{v.nil? ? 'nil' : v.inspect}"
  358. end
  359. end
  360. end
  361. register_command(
  362. :name => :settings, :aliases => [:set],
  363. :exec_proc => lambda {|_|
  364. show_settings config
  365. },
  366. :help => ['settings,set', 'Show your settings']
  367. )
  368. # TODO: Change colors when remaining_hits is low.
  369. # TODO: Simmulate remaining_hits.
  370. register_command(
  371. :name => :limit, :aliases => [:lm],
  372. :exec_proc => lambda {|arg|
  373. limit = Termtter::API.twitter.limit_status
  374. remaining_time = "%dmin %dsec" % (Time.parse(limit.reset_time) - Time.now).divmod(60)
  375. remaining_color =
  376. case limit.remaining_hits / limit.hourly_limit.to_f
  377. when 0.2..0.4 then :yellow
  378. when 0..0.2 then :red
  379. else :green
  380. end
  381. erbed_text = ERB.new(config.plugins.standard.limit_format).result(binding)
  382. puts TermColor.parse(erbed_text)
  383. },
  384. :help => ["limit,lm", "Show the API limit status"]
  385. )
  386. register_command(
  387. :name => :pause,
  388. :exec_proc => lambda {|arg| pause},
  389. :help => ["pause", "Pause updating"]
  390. )
  391. register_command(
  392. :name => :resume,
  393. :exec_proc => lambda {|arg| resume},
  394. :help => ["resume", "Resume updating"]
  395. )
  396. register_command(
  397. :name => :exit, :aliases => [:quit],
  398. :exec_proc => lambda {|arg| exit},
  399. :help => ['exit,quit', 'Exit']
  400. )
  401. register_command(
  402. :name => :help, :aliases => [:h],
  403. :exec_proc => lambda {|arg|
  404. helps = []
  405. @commands.map do |name, command|
  406. next unless command.help
  407. case command.help[0]
  408. when String
  409. helps << command.help
  410. when Array
  411. helps += command.help
  412. end
  413. end
  414. helps.compact!
  415. unless arg.empty?
  416. helps = helps.select {|n, _| /#{arg}/ =~ n }
  417. end
  418. puts formatted_help(helps)
  419. },
  420. :help => ["help,h", "Print this help message"]
  421. )
  422. def self.formatted_help(helps)
  423. helps = helps.sort_by {|help| help[0] }
  424. width = helps.map {|n, _| n.size }.max
  425. space = 3
  426. helps.map {|name, desc|
  427. name.to_s.ljust(width + space) + desc.to_s
  428. }.join("\n")
  429. end
  430. register_command(
  431. :name => :plug,
  432. :alias => :plugin,
  433. :exec_proc => lambda {|arg|
  434. if arg.empty?
  435. puts plugin_list.join(', ')
  436. return
  437. end
  438. begin
  439. result = plug arg
  440. rescue LoadError
  441. ensure
  442. puts "=> #{result.inspect}"
  443. end
  444. },
  445. :completion_proc => lambda {|cmd, args|
  446. plugin_list.grep(/#{Regexp.quote(args)}/).map {|i| "#{cmd} #{i}"}
  447. },
  448. :help => ['plug FILE', 'Load a plugin']
  449. )
  450. ## plugin_list :: IO ()
  451. def self.plugin_list
  452. (Dir["#{File.dirname(__FILE__)}/../**/*.rb"] + Dir["#{Termtter::CONF_DIR}/plugins/**/*.rb"]).
  453. map {|f| File.expand_path(f).scan(/.*plugins\/(.*)\.rb/).flatten[0] }.
  454. sort
  455. end
  456. register_command(
  457. :name => :reply,
  458. :aliases => [:re],
  459. :exec_proc => lambda {|arg|
  460. case arg
  461. when /^\s*(?:list|ls)\s*(?:\s+(\w+))?\s*$/
  462. public_storage[:log4re] =
  463. if $1
  464. public_storage[:log].
  465. select {|l| l.user.screen_name == $1}.
  466. sort {|a,b| a.id <=> b.id}
  467. else
  468. public_storage[:log].sort {|a,b| a.id <=> b.id}
  469. end
  470. public_storage[:log4re].each_with_index do |s, i|
  471. puts "#{i}: #{s.user.screen_name}: #{s.text}"
  472. end
  473. when /^\s*(?:up(?:date)?)\s+(\d+)\s+(.+)$/
  474. id = public_storage[:log4re][$1.to_i].id
  475. text = $2
  476. user = public_storage[:log4re][$1.to_i].user
  477. update_with_user_and_id(text, user.screen_name, id) if user
  478. public_storage.delete :log4re
  479. when /^\s*(\d+)\s+(.+)$/
  480. s = Termtter::API.twitter.show($1) rescue nil
  481. if s
  482. update_with_user_and_id($2, s.user.screen_name, s.id)
  483. end
  484. when /^\s*(@\w+)/
  485. user_name = normalize_as_user_name($1)
  486. s = Termtter::API.twitter.user(user_name).status
  487. if s
  488. params = s ? {:in_reply_to_status_id => s.id} : {}
  489. Termtter::API.twitter.update(arg, params)
  490. puts "=> #{arg}"
  491. end
  492. else
  493. text = arg
  494. last_reply = Termtter::API.twitter.replies({:count => 1}).first
  495. update_with_user_and_id(text, last_reply.user.screen_name, last_reply.id)
  496. end
  497. },
  498. :completion_proc => lambda {|cmd, arg|
  499. case arg
  500. when /(\d+)/
  501. find_status_ids(arg).map {|id| "#{cmd} #{id}"}
  502. end
  503. },
  504. :help => ["reply,re @USERNAME or STATUS_ID", "Send a reply"]
  505. )
  506. register_command(
  507. :name => :redo,
  508. :aliases => [:"."],
  509. :exec_proc => lambda {|arg|
  510. break if Readline::HISTORY.length < 2
  511. i = Readline::HISTORY.length - 2
  512. input = ""
  513. begin
  514. input = Readline::HISTORY[i]
  515. i -= 1
  516. return if i <= 0
  517. end while input == "redo" or input == "."
  518. begin
  519. Termtter::Client.execute(input)
  520. rescue CommandNotFound => e
  521. warn "Unknown command \"#{e}\""
  522. warn 'Enter "help" for instructions'
  523. rescue => e
  524. handle_error e
  525. end
  526. },
  527. :help => ["redo,.", "Execute previous command"]
  528. )
  529. class << self
  530. def update_with_user_and_id(text, username, id)
  531. text = "@#{username} #{text}"
  532. result = Termtter::API.twitter.update(text, {'in_reply_to_status_id' => id })
  533. puts "replied => #{result.text}"
  534. end
  535. def normalize_as_user_name(text)
  536. text.strip.sub(/^@/, '')
  537. end
  538. def find_status_ids(text)
  539. public_storage[:status_ids].select {|id| /#{Regexp.quote(text)}/ =~ id.to_s }
  540. end
  541. def find_users(text)
  542. public_storage[:users].select {|user| /^#{Regexp.quote(text)}/ =~ user }
  543. end
  544. end
  545. end