code_review_notifier 0.2.1 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c3615d3a36962ada56ab1f0e0cc212e73915ec1a656d563a7754490c8697c0f7
4
- data.tar.gz: 5e9b86d5c6ebaa58b3b315b43b212eae412d5ac71d8cad9c27d212b66347af31
3
+ metadata.gz: 6f9deff9b36c8c1ae3ca1b907ec987858753105c7d6d64613b278ec6e3ce6468
4
+ data.tar.gz: d10dfa231848639530eb6422919d051e146b1b89cde7415039a0e895ad280b3b
5
5
  SHA512:
6
- metadata.gz: b9430f29ab8cda0f0096d6b8d67b5c8b7620cec2cae1574b7eccbdf9094d2bee134712f0b718b7b53cbb5167aa2d4adfee57fae06ae1da94c230963a6440f063
7
- data.tar.gz: ceeb73098c2c8a322f30949f7f5af234dc2b153ef1dc48126693d5c1614420f54ec900aa7804e1b3c25ea13cc4d3fb9970be2e38d6c57e5fd095aaa12fcb5625
6
+ metadata.gz: 1790217bfbd2b2c6838b1e4bd30893bb49dd5992f88232bc7366bde1690df241eadc6ee4d033c68174125ff3f39caa512a13e9ea5a68fbdbea2626c8d1917472
7
+ data.tar.gz: 5dab8c6c81b941c87267fc376c5603b63db0218d866d9090d229fda33d0d5d8c83ee405316094e78504da26026588e0201b5d30c9df90fd6301256004b974f62
data/Gemfile CHANGED
@@ -2,5 +2,4 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
- gem "sqlite3"
6
- gem "httparty"
5
+ gem "rubiclifier", "2.0.0"
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "code_review_notifier"
3
+ require_relative "../lib/code_review_notifier.rb"
4
4
 
5
- CodeReviewNotifier.call(ARGV)
5
+ CodeReviewNotifier.new(ARGV).call
@@ -0,0 +1,28 @@
1
+ require "rubiclifier"
2
+ require_relative "./api.rb"
3
+
4
+ class CodeChangeNotification
5
+ attr_reader :code_change_activity
6
+ private :code_change_activity
7
+
8
+ def initialize(code_change_activity)
9
+ @code_change_activity = code_change_activity
10
+ end
11
+
12
+ def send
13
+ code_change = code_change_activity.code_change
14
+ id = code_change.id
15
+ owner = code_change.owner
16
+ subject = code_change.subject
17
+
18
+ message = code_change_activity.message
19
+ author = code_change_activity.author
20
+ Rubiclifier::Notification.new(
21
+ author,
22
+ message,
23
+ "#{owner}: #{subject}",
24
+ Api.current_api.favicon,
25
+ Api.current_api.code_change_url(code_change)
26
+ ).send
27
+ end
28
+ end
@@ -1,74 +1,83 @@
1
- require "io/console"
1
+ require "rubiclifier"
2
2
  require_relative "./api.rb"
3
- require_relative "./notifier.rb"
3
+ require_relative "./code_change_notification.rb"
4
4
 
5
5
  SECONDS_BETWEEN_RUNS = 90
6
6
  SECONDS_BETWEEN_NOTIFICATIONS = 5
7
7
 
8
- class CodeReviewNotifier
9
- def self.setup(args)
10
- system("mkdir -p ~/.code_review_notifier")
11
- system("brew bundle --file #{File.expand_path(File.dirname(__FILE__) + "/..")}/Brewfile")
12
-
13
- if args[0] == "--setup"
14
- print("What's the base URL? (i.e. https://gerrit.google.com) ")
15
- DB.save_setting("base_api_url", STDIN.gets.chomp, is_secret: false)
16
-
17
- print("What's the account username? ")
18
- DB.save_setting("username", STDIN.gets.chomp, is_secret: false)
19
-
20
- print("What's the account password? (hiding input) ")
21
- DB.save_setting("password", STDIN.noecho(&:gets).chomp, is_secret: true)
22
- puts
23
-
24
- print("What's the account id? (check #{Api.current_api.base_api_url}/settings/) ")
25
- DB.save_setting("account_id", STDIN.gets.chomp, is_secret: false)
26
-
27
- puts("All setup!")
28
- puts
29
- puts("It's recommended that you set this up as a system service with serviceman. Check it out here: https://git.rootprojects.org/root/serviceman")
30
- puts("Set code_review_notifier to run on startup with `serviceman add --name code_review_notifier code_review_notifier`")
31
- exit
32
- end
33
-
34
- if !Api.current_api.is_setup?
35
- Notifier.notify("Missing Setup Info", "Run `code_review_notifier --setup` to setup.")
36
- puts
37
- puts("You must finish setup first by running with the `--setup` option.")
38
- puts("`code_review_notifier --setup`")
39
- exit
40
- end
8
+ class CodeReviewNotifier < Rubiclifier::BaseApplication
9
+ def show_help
10
+ puts
11
+ puts("This polls for updates to patch sets/pull requests and notifies you about any relevant changes.")
12
+ puts
13
+ puts("Usage:")
14
+ puts(" code_review_notifier --help | Shows this help menu")
15
+ puts(" code_review_notifier --setup | Runs setup")
16
+ puts(" code_review_notifier | Start listening for changes (should be run in background)")
17
+ puts
18
+ exit
41
19
  end
42
20
 
43
- def self.call(args)
44
- setup(args)
45
-
21
+ def run_application
46
22
  while true
47
- is_first_run = is_first_run?
48
- puts
49
- puts("Querying API...")
50
- all_code_changes = Api.current_api.all_code_changes
51
- puts("Checking for notifications to display...")
52
- all_activity = []
53
- all_code_changes.each do |cc|
54
- activity_for_code_change = cc.code_change_activity
55
- activity_for_code_change.sort! { |a, b| a.created_at <=> b.created_at }
56
- cc.activity_from_self_at = activity_for_code_change.find { |a| a.is_self }&.created_at
57
- all_activity.concat(activity_for_code_change)
58
- end
59
- all_activity.select(&:should_notify?).each do |code_change_activity|
60
- code_change_activity.notified
61
- unless is_first_run
62
- puts("Notifying of change!")
63
- Notifier.notify_about_code_change(code_change_activity)
64
- sleep(SECONDS_BETWEEN_NOTIFICATIONS)
23
+ unless Rubiclifier::IdleDetector.is_idle?
24
+ is_first_run = is_first_run?
25
+ puts
26
+ puts("Querying API...")
27
+ all_code_changes = Api.current_api.all_code_changes
28
+ puts("Checking for notifications to display...")
29
+ all_activity = []
30
+ all_code_changes.each do |cc|
31
+ cc.code_change_activity.sort! { |a, b| a.created_at <=> b.created_at }
32
+ all_activity.concat(cc.code_change_activity)
33
+ end
34
+ all_activity.select(&:should_notify?).each do |code_change_activity|
35
+ code_change_activity.notified
36
+ unless is_first_run
37
+ puts("Notifying of change!")
38
+ CodeChangeNotification.new(code_change_activity).send
39
+ sleep(SECONDS_BETWEEN_NOTIFICATIONS)
40
+ end
65
41
  end
66
42
  end
67
43
  sleep(SECONDS_BETWEEN_RUNS)
68
44
  end
69
45
  end
70
46
 
71
- def self.is_first_run?
72
- DB.query_single_row("SELECT id FROM code_change_activity_notified;").nil?
47
+ def not_setup
48
+ Rubiclifier::Notification.new(
49
+ "Missing Setup Info",
50
+ "Run `code_review_notifier --setup` to setup."
51
+ ).send
52
+ end
53
+
54
+ def features
55
+ [
56
+ Rubiclifier::Feature::BACKGROUND,
57
+ Rubiclifier::Feature::DATABASE,
58
+ Rubiclifier::Feature::IDLE_DETECTION,
59
+ Rubiclifier::Feature::NOTIFICATIONS
60
+ ]
61
+ end
62
+
63
+ def settings
64
+ @settings ||= [
65
+ Rubiclifier::Setting.new("base_api_url", "base URL", explanation: "e.g. https://gerrit.google.com"),
66
+ Rubiclifier::Setting.new("username", "account username"),
67
+ Rubiclifier::Setting.new("password", "account password", explanation: "input hidden", is_secret: true),
68
+ Rubiclifier::Setting.new("account_id", "account ID", explanation: -> {"check #{Api.current_api.base_api_url}/settings/"})
69
+ ]
70
+ end
71
+
72
+ def data_directory
73
+ "~/.code_review_notifier"
74
+ end
75
+
76
+ def migrations_location
77
+ "#{File.expand_path(File.dirname(__FILE__) + "/..")}/migrations.rb"
78
+ end
79
+
80
+ def is_first_run?
81
+ Rubiclifier::DB.query_single_row("SELECT id FROM code_change_activity_notified;").nil?
73
82
  end
74
83
  end
@@ -1,10 +1,15 @@
1
- require_relative "./base_api.rb"
1
+ require "rubiclifier"
2
2
  require_relative "./models/code_change.rb"
3
3
  require_relative "./models/code_change_activity.rb"
4
4
 
5
- class GerritApi < BaseApi
6
- def self.authenticate
7
- @token = nil
5
+ class HTTParty::Parser
6
+ def json
7
+ JSON.parse(body.gsub(")]}'", ""))
8
+ end
9
+ end
10
+
11
+ class GerritApi < Rubiclifier::BaseApi
12
+ def self.login_and_get_api_token
8
13
  res = post("/login/", {
9
14
  body: "username=#{username}&password=#{URI.escape(password)}&rememberme=1",
10
15
  headers: {
@@ -13,22 +18,23 @@ class GerritApi < BaseApi
13
18
  follow_redirects: false
14
19
  })
15
20
  set_cookie_header = res.headers["set-cookie"] || ""
16
- token = set_cookie_header.match("GerritAccount=(.*?);")&.to_a&.fetch(1)
17
- if token.nil?
18
- Notifier.notify("Incorrect Credentials", "Trying running `code_review_notifier --setup` again.")
19
- sleep(120)
20
- exit
21
- else
22
- DB.save_setting("api_token", token, is_secret: true)
23
- token
24
- end
21
+ set_cookie_header.match("GerritAccount=(.*?);")&.to_a&.fetch(1)
22
+ end
23
+
24
+ def self.invalid_credentials_error
25
+ Rubiclifier::Notification.new(
26
+ "Incorrect Credentials",
27
+ "Trying running `code_review_notifier --setup` again."
28
+ ).send
29
+ sleep(120)
30
+ exit
25
31
  end
26
32
 
27
33
  def self.all_code_changes
28
34
  wrap_with_authentication do
29
35
  get("/changes/?S=0&q=is%3Aopen%20owner%3Aself%20-is%3Awip%20-is%3Aignored%20limit%3A25&q=is%3Aopen%20owner%3Aself%20is%3Awip%20limit%3A25&q=is%3Aopen%20-owner%3Aself%20-is%3Awip%20-is%3Aignored%20reviewer%3Aself%20limit%3A25&q=is%3Aclosed%20-is%3Aignored%20%28-is%3Awip%20OR%20owner%3Aself%29%20%28owner%3Aself%20OR%20reviewer%3Aself%29%20-age%3A4w%20limit%3A10&o=DETAILED_ACCOUNTS&o=MESSAGES", {
30
36
  headers: {
31
- "Cookie" => "GerritAccount=#{token};"
37
+ "Cookie" => "GerritAccount=#{api_token};"
32
38
  }
33
39
  })
34
40
  end.parsed_response.flat_map { |js| js.map { |j| code_change_from_json(j) } }
@@ -51,4 +57,24 @@ class GerritApi < BaseApi
51
57
  def self.code_change_url(code_change)
52
58
  "#{base_api_url}/c/#{code_change.id}"
53
59
  end
60
+
61
+ def self.api_token_db_key
62
+ "api_token"
63
+ end
64
+
65
+ def self.base_api_url_db_key
66
+ "base_api_url"
67
+ end
68
+
69
+ def self.username
70
+ @username ||= Rubiclifier::DB.get_setting("username")
71
+ end
72
+
73
+ def self.password
74
+ @password ||= Rubiclifier::DB.get_setting("password")
75
+ end
76
+
77
+ def self.account_id
78
+ @account_id ||= Rubiclifier::DB.get_setting("account_id")
79
+ end
54
80
  end
@@ -1,10 +1,8 @@
1
1
  require "date"
2
- require_relative "../database.rb"
3
2
 
4
3
  class CodeChange
5
4
  attr_accessor :code_change_activity
6
5
  attr_reader :id, :owner, :project, :subject, :updated_at
7
- attr_writer :activity_from_self_at
8
6
 
9
7
  def initialize(id, owner, project, subject, updated_at)
10
8
  @id = id
@@ -15,7 +13,6 @@ class CodeChange
15
13
  end
16
14
 
17
15
  def activity_from_self_at
18
- raise "@activity_from_self_at is not set" unless instance_variable_defined?("@activity_from_self_at")
19
- @activity_from_self_at
16
+ @activity_from_self_at ||= code_change_activity.find { |a| a.is_self }&.created_at
20
17
  end
21
18
  end
@@ -1,3 +1,5 @@
1
+ require "rubiclifier"
2
+
1
3
  MESSAGES_TO_IGNORE = [/Uploaded patch set 1/, /Build Started/, /owns \d+% of/]
2
4
  AUTHOR_TRANSLATIONS = {
3
5
  "Service Cloud Jenkins" => "Jenkins",
@@ -17,14 +19,14 @@ class CodeChangeActivity
17
19
  end
18
20
 
19
21
  def notified
20
- DB.execute("INSERT INTO code_change_activity_notified (id, notified_at) VALUES('#{id}', CURRENT_TIMESTAMP);")
22
+ Rubiclifier::DB.execute("INSERT INTO code_change_activity_notified (id, notified_at) VALUES('#{id}', CURRENT_TIMESTAMP);")
21
23
  end
22
24
 
23
25
  def should_notify?
24
26
  !is_self &&
25
27
  code_change.activity_from_self_at && created_at > code_change.activity_from_self_at &&
26
28
  MESSAGES_TO_IGNORE.none? { |m| message.match(m) } &&
27
- !DB.query_single_row("SELECT id FROM code_change_activity_notified WHERE id = '#{id}'")
29
+ !Rubiclifier::DB.query_single_row("SELECT id FROM code_change_activity_notified WHERE id = '#{id}'")
28
30
  end
29
31
 
30
32
  def self.translate_author(author)
@@ -0,0 +1,8 @@
1
+ migrations = []
2
+ migrations << <<SQL
3
+ CREATE TABLE IF NOT EXISTS code_change_activity_notified (
4
+ id VARCHAR(50) PRIMARY KEY,
5
+ notified_at TIMESTAMP
6
+ );
7
+ SQL
8
+ migrations
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: code_review_notifier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kyle Grinstead
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-08 00:00:00.000000000 Z
11
+ date: 2020-07-10 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: kyleag@hey.com
@@ -17,24 +17,22 @@ executables:
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
- - Brewfile
21
20
  - Gemfile
22
21
  - bin/code_review_notifier
23
22
  - lib/api.rb
24
- - lib/base_api.rb
25
- - lib/cipher.rb
23
+ - lib/code_change_notification.rb
26
24
  - lib/code_review_notifier.rb
27
- - lib/database.rb
28
25
  - lib/gerrit_api.rb
29
26
  - lib/models/code_change.rb
30
27
  - lib/models/code_change_activity.rb
31
- - lib/notifier.rb
28
+ - migrations.rb
32
29
  homepage: https://rubygems.org/gems/code_review_notifier
33
30
  licenses:
34
31
  - MIT
35
32
  metadata:
36
33
  source_code_uri: https://github.com/MrGrinst/code_review_notifier
37
- post_install_message:
34
+ post_install_message: "\n\e[32mThanks for installing code_review_notifier!\e[0m\n\e[32mSet
35
+ it up by running `\e[0mcode_review_notifier --setup\e[32m`\e[0m\n\n"
38
36
  rdoc_options: []
39
37
  require_paths:
40
38
  - lib
data/Brewfile DELETED
@@ -1,2 +0,0 @@
1
- brew "sqlite"
2
- brew "terminal-notifier"
@@ -1,66 +0,0 @@
1
- require "httparty"
2
- require_relative "./gerrit_api.rb"
3
-
4
- class HTTParty::Parser
5
- def json
6
- JSON.parse(body.gsub(")]}'", ""))
7
- end
8
- end
9
-
10
- class BaseApi
11
- include HTTParty
12
-
13
- def self.token
14
- @token ||= DB.get_setting("api_token") || self.authenticate
15
- end
16
-
17
- def self.authenticate
18
- raise NotImplementedError
19
- end
20
-
21
- def self.all_code_changes
22
- raise NotImplementedError
23
- end
24
-
25
- def self.favicon
26
- raise NotImplementedError
27
- end
28
-
29
- def self.code_change_url(code_change)
30
- raise NotImplementedError
31
- end
32
-
33
- def self.wrap_with_authentication(&block)
34
- res = block.call
35
- if res.code == 401 || res.code == 403
36
- self.authenticate
37
- block.call
38
- else
39
- res
40
- end
41
- end
42
-
43
- def self.is_setup?
44
- base_api_url && username && password && account_id
45
- end
46
-
47
- def self.base_api_url
48
- @base_api_url ||= begin
49
- url = DB.get_setting("base_api_url")
50
- base_uri(url)
51
- url&.chomp("/")
52
- end
53
- end
54
-
55
- def self.username
56
- @username ||= DB.get_setting("username")
57
- end
58
-
59
- def self.password
60
- @password ||= DB.get_setting("password")
61
- end
62
-
63
- def self.account_id
64
- @account_id ||= DB.get_setting("account_id")
65
- end
66
- end
@@ -1,29 +0,0 @@
1
- require "base64"
2
- require "openssl"
3
- require "digest/sha1"
4
-
5
- class Cipher
6
- def self.encrypt(text)
7
- cipher = OpenSSL::Cipher.new("aes-256-cbc")
8
- cipher.encrypt
9
- key = Digest::SHA1.hexdigest("yourpass")[0..31]
10
- iv = cipher.random_iv
11
- cipher.key = key
12
- cipher.iv = iv
13
- encrypted = cipher.update(text)
14
- encrypted << cipher.final
15
- ["#{key}:#{Base64.encode64(iv).encode("utf-8")}", Base64.encode64(encrypted).encode("utf-8")]
16
- end
17
-
18
- def self.decrypt(encrypted, salt)
19
- key, iv = salt.split(":")
20
- iv = Base64.decode64(iv.encode("ascii-8bit"))
21
- cipher = OpenSSL::Cipher.new("aes-256-cbc")
22
- cipher.decrypt
23
- cipher.key = key
24
- cipher.iv = iv
25
- decrypted = cipher.update(Base64.decode64(encrypted.encode("ascii-8bit")))
26
- decrypted << cipher.final
27
- decrypted
28
- end
29
- end
@@ -1,48 +0,0 @@
1
- require "sqlite3"
2
- require_relative "./cipher.rb"
3
-
4
- class DB
5
- def self.db
6
- @db ||= begin
7
- db = SQLite3::Database.new(File.expand_path("~/.code_review_notifier/data.db"))
8
- migrate_if_needed(db)
9
- db
10
- end
11
- end
12
-
13
- def self.migrate_if_needed(db)
14
- db.execute("CREATE TABLE IF NOT EXISTS code_change_activity_notified (id VARCHAR(50) PRIMARY KEY, notified_at TIMESTAMP);")
15
- db.execute("CREATE TABLE IF NOT EXISTS settings (key VARCHAR(50) PRIMARY KEY, value TEXT, salt TEXT);")
16
- end
17
-
18
- def self.execute(sql)
19
- db.execute(sql)
20
- end
21
-
22
- def self.query_single_row(sql)
23
- db.execute(sql) do |row|
24
- return row
25
- end
26
- return nil
27
- end
28
-
29
- def self.save_setting(key, value, is_secret:)
30
- salt = "NULL"
31
- if is_secret
32
- salt, encrypted = Cipher.encrypt(value)
33
- salt = "'#{salt}'"
34
- value = encrypted
35
- end
36
- db.execute("DELETE FROM settings WHERE key = '#{key}';")
37
- db.execute("INSERT INTO settings (key, value, salt) VALUES('#{key}', '#{value}', #{salt});")
38
- end
39
-
40
- def self.get_setting(key)
41
- row = query_single_row("SELECT value, salt FROM settings WHERE key = '#{key}'")
42
- if row && row[1]
43
- Cipher.decrypt(row[0], row[1])
44
- elsif row
45
- row[0]
46
- end
47
- end
48
- end
@@ -1,31 +0,0 @@
1
- require_relative "./api.rb"
2
-
3
- class Notifier
4
- def self.notify_about_code_change(code_change_activity)
5
- code_change = code_change_activity.code_change
6
- id = code_change.id
7
- owner = code_change.owner
8
- subject = code_change.subject
9
-
10
- message = code_change_activity.message
11
- author = code_change_activity.author
12
- notify(author, message, "#{owner}: #{subject}", Api.current_api.favicon, Api.current_api.code_change_url(code_change))
13
- end
14
-
15
- def self.notify(title, message, subtitle = nil, icon = nil, url = nil)
16
- args = {
17
- "title" => title,
18
- "message" => message,
19
- "subtitle" => subtitle,
20
- "appIcon" => icon,
21
- "open" => url
22
- }
23
- all_args = args.keys.reduce("") do |arg_string, key|
24
- if args[key]
25
- arg_string += " -#{key} '#{args[key]}'"
26
- end
27
- arg_string
28
- end
29
- system("/usr/local/bin/terminal-notifier #{all_args}")
30
- end
31
- end