code_review_notifier 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c3615d3a36962ada56ab1f0e0cc212e73915ec1a656d563a7754490c8697c0f7
4
- data.tar.gz: 5e9b86d5c6ebaa58b3b315b43b212eae412d5ac71d8cad9c27d212b66347af31
3
+ metadata.gz: 790df27d9acb825ba25b7d709407a665325c197e9ee8033569d5f86e72e0f6a0
4
+ data.tar.gz: 3dd80bdd477b6f251d40d45bbaf56d574584d194d7b858ddd255fe7327443f9c
5
5
  SHA512:
6
- metadata.gz: b9430f29ab8cda0f0096d6b8d67b5c8b7620cec2cae1574b7eccbdf9094d2bee134712f0b718b7b53cbb5167aa2d4adfee57fae06ae1da94c230963a6440f063
7
- data.tar.gz: ceeb73098c2c8a322f30949f7f5af234dc2b153ef1dc48126693d5c1614420f54ec900aa7804e1b3c25ea13cc4d3fb9970be2e38d6c57e5fd095aaa12fcb5625
6
+ metadata.gz: 557585a22db594cb9f12c20e3ef98898a0e81bb7ba606648f0ea526f3140d8a920652b30816a7614d13dbd6d640aefb837c57ece0df6096b87b35ce5a21f0a74
7
+ data.tar.gz: 89c9687da7c0b4947b6589b65e5766f0c3bb4cf0ff3767bd3f23ae468dd96656219b515f9705ed09e97fa2e6a8316af8b7418a254867b5e385df28e887465d60
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,4 +2,4 @@
2
2
 
3
3
  require "code_review_notifier"
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,48 +1,24 @@
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
23
  is_first_run = is_first_run?
48
24
  puts
@@ -60,7 +36,7 @@ class CodeReviewNotifier
60
36
  code_change_activity.notified
61
37
  unless is_first_run
62
38
  puts("Notifying of change!")
63
- Notifier.notify_about_code_change(code_change_activity)
39
+ CodeChangeNotification.new(code_change_activity).send
64
40
  sleep(SECONDS_BETWEEN_NOTIFICATIONS)
65
41
  end
66
42
  end
@@ -68,7 +44,43 @@ class CodeReviewNotifier
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 is_background_service?
55
+ true
56
+ end
57
+
58
+ def sends_notifications?
59
+ true
60
+ end
61
+
62
+ def settings
63
+ @settings ||= [
64
+ Rubiclifier::Setting.new("base_api_url", "base URL", explanation: "e.g. https://gerrit.google.com"),
65
+ Rubiclifier::Setting.new("username", "account username"),
66
+ Rubiclifier::Setting.new("password", "account password", explanation: "input hidden", is_secret: true),
67
+ Rubiclifier::Setting.new("account_id", "account ID", explanation: -> {"check #{Api.current_api.base_api_url}/settings/"})
68
+ ]
69
+ end
70
+
71
+ def executable_name
72
+ "code_review_notifier"
73
+ end
74
+
75
+ def data_directory
76
+ "~/.code_review_notifier"
77
+ end
78
+
79
+ def migrations_location
80
+ "#{File.expand_path(File.dirname(__FILE__) + "/..")}/migrations.rb"
81
+ end
82
+
83
+ def is_first_run?
84
+ Rubiclifier::DB.query_single_row("SELECT id FROM code_change_activity_notified;").nil?
73
85
  end
74
86
  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,5 +1,4 @@
1
1
  require "date"
2
- require_relative "../database.rb"
3
2
 
4
3
  class CodeChange
5
4
  attr_accessor :code_change_activity
@@ -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.2.2
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-09 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 `code_review_notifier --setup`\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