code_review_notifier 0.1.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -3
- data/bin/code_review_notifier +2 -2
- data/lib/code_change_notification.rb +28 -0
- data/lib/code_review_notifier.rb +56 -32
- data/lib/gerrit_api.rb +41 -6
- data/lib/models/code_change.rb +1 -2
- data/lib/models/code_change_activity.rb +4 -2
- data/migrations.rb +8 -0
- metadata +6 -8
- data/Brewfile +0 -2
- data/lib/base_api.rb +0 -62
- data/lib/cipher.rb +0 -31
- data/lib/database.rb +0 -48
- data/lib/notifier.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 790df27d9acb825ba25b7d709407a665325c197e9ee8033569d5f86e72e0f6a0
|
4
|
+
data.tar.gz: 3dd80bdd477b6f251d40d45bbaf56d574584d194d7b858ddd255fe7327443f9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 557585a22db594cb9f12c20e3ef98898a0e81bb7ba606648f0ea526f3140d8a920652b30816a7614d13dbd6d640aefb837c57ece0df6096b87b35ce5a21f0a74
|
7
|
+
data.tar.gz: 89c9687da7c0b4947b6589b65e5766f0c3bb4cf0ff3767bd3f23ae468dd96656219b515f9705ed09e97fa2e6a8316af8b7418a254867b5e385df28e887465d60
|
data/Gemfile
CHANGED
data/bin/code_review_notifier
CHANGED
@@ -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
|
data/lib/code_review_notifier.rb
CHANGED
@@ -1,36 +1,24 @@
|
|
1
|
-
require "
|
1
|
+
require "rubiclifier"
|
2
2
|
require_relative "./api.rb"
|
3
|
-
require_relative "./
|
3
|
+
require_relative "./code_change_notification.rb"
|
4
4
|
|
5
|
-
SECONDS_BETWEEN_RUNS =
|
5
|
+
SECONDS_BETWEEN_RUNS = 90
|
6
6
|
SECONDS_BETWEEN_NOTIFICATIONS = 5
|
7
7
|
|
8
|
-
class CodeReviewNotifier
|
9
|
-
def
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
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
|
29
19
|
end
|
30
20
|
|
31
|
-
def
|
32
|
-
setup(args)
|
33
|
-
|
21
|
+
def run_application
|
34
22
|
while true
|
35
23
|
is_first_run = is_first_run?
|
36
24
|
puts
|
@@ -48,15 +36,51 @@ class CodeReviewNotifier
|
|
48
36
|
code_change_activity.notified
|
49
37
|
unless is_first_run
|
50
38
|
puts("Notifying of change!")
|
51
|
-
|
52
|
-
sleep
|
39
|
+
CodeChangeNotification.new(code_change_activity).send
|
40
|
+
sleep(SECONDS_BETWEEN_NOTIFICATIONS)
|
53
41
|
end
|
54
42
|
end
|
55
|
-
sleep
|
43
|
+
sleep(SECONDS_BETWEEN_RUNS)
|
56
44
|
end
|
57
45
|
end
|
58
46
|
|
59
|
-
def
|
60
|
-
|
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?
|
61
85
|
end
|
62
86
|
end
|
data/lib/gerrit_api.rb
CHANGED
@@ -1,9 +1,15 @@
|
|
1
|
-
|
1
|
+
require "rubiclifier"
|
2
2
|
require_relative "./models/code_change.rb"
|
3
3
|
require_relative "./models/code_change_activity.rb"
|
4
4
|
|
5
|
-
class
|
6
|
-
def
|
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
|
7
13
|
res = post("/login/", {
|
8
14
|
body: "username=#{username}&password=#{URI.escape(password)}&rememberme=1",
|
9
15
|
headers: {
|
@@ -11,15 +17,24 @@ class GerritApi < BaseApi
|
|
11
17
|
},
|
12
18
|
follow_redirects: false
|
13
19
|
})
|
14
|
-
|
15
|
-
|
20
|
+
set_cookie_header = res.headers["set-cookie"] || ""
|
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
|
16
31
|
end
|
17
32
|
|
18
33
|
def self.all_code_changes
|
19
34
|
wrap_with_authentication do
|
20
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", {
|
21
36
|
headers: {
|
22
|
-
"Cookie" => "GerritAccount=#{
|
37
|
+
"Cookie" => "GerritAccount=#{api_token};"
|
23
38
|
}
|
24
39
|
})
|
25
40
|
end.parsed_response.flat_map { |js| js.map { |j| code_change_from_json(j) } }
|
@@ -42,4 +57,24 @@ class GerritApi < BaseApi
|
|
42
57
|
def self.code_change_url(code_change)
|
43
58
|
"#{base_api_url}/c/#{code_change.id}"
|
44
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
|
45
80
|
end
|
data/lib/models/code_change.rb
CHANGED
@@ -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
|
@@ -10,7 +9,7 @@ class CodeChange
|
|
10
9
|
@id = id
|
11
10
|
@owner = owner
|
12
11
|
@project = project
|
13
|
-
@subject = subject.gsub("'", "")
|
12
|
+
@subject = subject.gsub("'", "’")
|
14
13
|
@updated_at = updated_at
|
15
14
|
end
|
16
15
|
|
@@ -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)
|
data/migrations.rb
ADDED
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.
|
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-
|
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/
|
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
|
-
-
|
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
data/lib/base_api.rb
DELETED
@@ -1,62 +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.authenticate
|
14
|
-
raise NotImplementedError
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.all_code_changes
|
18
|
-
raise NotImplementedError
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.favicon
|
22
|
-
raise NotImplementedError
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.code_change_url(code_change)
|
26
|
-
raise NotImplementedError
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.wrap_with_authentication(&block)
|
30
|
-
res = block.call
|
31
|
-
if res.code == 401 || res.code == 403
|
32
|
-
self.authenticate
|
33
|
-
block.call
|
34
|
-
else
|
35
|
-
res
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def self.is_setup?
|
40
|
-
base_api_url && username && password && account_id
|
41
|
-
end
|
42
|
-
|
43
|
-
def self.base_api_url
|
44
|
-
@base_api_url ||= begin
|
45
|
-
url = DB.get_setting("base_api_url")
|
46
|
-
base_uri(url)
|
47
|
-
url.chomp("/")
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.username
|
52
|
-
@username ||= DB.get_setting("username")
|
53
|
-
end
|
54
|
-
|
55
|
-
def self.password
|
56
|
-
@password ||= DB.get_setting("password")
|
57
|
-
end
|
58
|
-
|
59
|
-
def self.account_id
|
60
|
-
@account_id ||= DB.get_setting("account_id")
|
61
|
-
end
|
62
|
-
end
|
data/lib/cipher.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
require "base64"
|
2
|
-
require "openssl"
|
3
|
-
require "digest/sha1"
|
4
|
-
|
5
|
-
KEY = ""
|
6
|
-
|
7
|
-
class Cipher
|
8
|
-
def self.encrypt(text)
|
9
|
-
cipher = OpenSSL::Cipher.new("aes-256-cbc")
|
10
|
-
cipher.encrypt
|
11
|
-
key = Digest::SHA1.hexdigest("yourpass")[0..31]
|
12
|
-
iv = cipher.random_iv
|
13
|
-
cipher.key = key
|
14
|
-
cipher.iv = iv
|
15
|
-
encrypted = cipher.update(text)
|
16
|
-
encrypted << cipher.final
|
17
|
-
["#{key}:#{Base64.encode64(iv).encode('utf-8')}", Base64.encode64(encrypted).encode('utf-8')]
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.decrypt(encrypted, salt)
|
21
|
-
key, iv = salt.split(":")
|
22
|
-
iv = Base64.decode64(iv.encode('ascii-8bit'))
|
23
|
-
cipher = OpenSSL::Cipher.new("aes-256-cbc")
|
24
|
-
cipher.decrypt
|
25
|
-
cipher.key = key
|
26
|
-
cipher.iv = iv
|
27
|
-
decrypted = cipher.update(Base64.decode64(encrypted.encode('ascii-8bit')))
|
28
|
-
decrypted << cipher.final
|
29
|
-
decrypted
|
30
|
-
end
|
31
|
-
end
|
data/lib/database.rb
DELETED
@@ -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
|
data/lib/notifier.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
require_relative './api.rb'
|
2
|
-
|
3
|
-
class Notifier
|
4
|
-
def self.notify(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
|
-
system("terminal-notifier -title '#{author}' -subtitle '#{owner}: #{subject}' -message '#{message}' -appIcon #{Api.current_api.favicon} -open '#{Api.current_api.code_change_url(code_change)}'")
|
13
|
-
end
|
14
|
-
end
|