markify 0.1.0
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.
- data/.gitignore +19 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +674 -0
- data/README.md +41 -0
- data/Rakefile +6 -0
- data/bin/markify +32 -0
- data/examples/config.yml.example +12 -0
- data/examples/hashes.txt +3 -0
- data/lib/markify.rb +67 -0
- data/lib/markify/bot.rb +36 -0
- data/lib/markify/database.rb +83 -0
- data/lib/markify/mark.rb +51 -0
- data/lib/markify/optparser.rb +96 -0
- data/lib/markify/scraper.rb +107 -0
- data/lib/markify/settings.rb +101 -0
- data/lib/markify/version.rb +42 -0
- data/markify.gemspec +31 -0
- data/spec/markify/database_spec.rb +87 -0
- data/spec/markify/mark_spec.rb +68 -0
- data/spec/spec_helper.rb +39 -0
- metadata +195 -0
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Markify
|
2
|
+
|
3
|
+
Markify is a ruby script to detect new marks in the
|
4
|
+
Studierendeninformationssystem (SIS) of the University of Applied Sciences
|
5
|
+
Bonn-Rhein-Sieg. If markify detects new marks, they will send you a xmpp
|
6
|
+
message for every change.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Install Markify via rubygems:
|
11
|
+
|
12
|
+
$ gem install markify
|
13
|
+
|
14
|
+
## Usage
|
15
|
+
|
16
|
+
To create the initial markify configuration run:
|
17
|
+
|
18
|
+
$ markify --init
|
19
|
+
|
20
|
+
The default configuration path is ``/home/$user/markify/config.yml``. You have to add your SIS and xmpp login data.
|
21
|
+
|
22
|
+
Possible options to run markify:
|
23
|
+
|
24
|
+
$ markify [options]
|
25
|
+
|
26
|
+
Optional options:
|
27
|
+
-s, --send Send xmpp messages
|
28
|
+
-n, --noop No operation, only stout output
|
29
|
+
-v, --[no-]verbose Run verbosely
|
30
|
+
|
31
|
+
-f, --config-file FILE Config file (default: ~/markify/config.yml)
|
32
|
+
|
33
|
+
Common options:
|
34
|
+
--init Create example configuration
|
35
|
+
--test Test configuration and accounts
|
36
|
+
-h, --help Show this message
|
37
|
+
--version Show version inforamtion
|
38
|
+
|
39
|
+
## License
|
40
|
+
|
41
|
+
Released under the GNU GENERAL PUBLIC LICENSE Version 3. © Daniel Meißner, 2013
|
data/Rakefile
ADDED
data/bin/markify
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
=begin
|
4
|
+
Copyright Daniel Meißner <meise+markify@3st.be>, 2013
|
5
|
+
|
6
|
+
This file is part of Markify.
|
7
|
+
|
8
|
+
Markify is free software: you can redistribute it and/or modify
|
9
|
+
it under the terms of the GNU General Public License as published by
|
10
|
+
the Free Software Foundation, either version 3 of the License, or
|
11
|
+
(at your option) any later version.
|
12
|
+
|
13
|
+
Markify is distributed in the hope that it will be useful,
|
14
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
+
GNU General Public License for more details.
|
17
|
+
|
18
|
+
You should have received a copy of the GNU General Public License
|
19
|
+
along with Markify. If not, see <http://www.gnu.org/licenses/>.
|
20
|
+
=end
|
21
|
+
|
22
|
+
begin
|
23
|
+
require 'markify'
|
24
|
+
rescue LoadError
|
25
|
+
if File.symlink? __FILE__
|
26
|
+
require File.dirname(File.readlink(__FILE__)) + '/../lib/markify'
|
27
|
+
else
|
28
|
+
require File.dirname(__FILE__) + '/../lib/markify'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
Markify.run!
|
data/examples/hashes.txt
ADDED
data/lib/markify.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
=begin
|
3
|
+
Copyright Daniel Meißner <meise+markify@3st.be>, 2013
|
4
|
+
|
5
|
+
This file is part of Markify.
|
6
|
+
|
7
|
+
Markify is free software: you can redistribute it and/or modify
|
8
|
+
it under the terms of the GNU General Public License as published by
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
10
|
+
(at your option) any later version.
|
11
|
+
|
12
|
+
Markify is distributed in the hope that it will be useful,
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
GNU General Public License for more details.
|
16
|
+
|
17
|
+
You should have received a copy of the GNU General Public License
|
18
|
+
along with Markify. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
=end
|
20
|
+
|
21
|
+
require 'date'
|
22
|
+
require 'digest'
|
23
|
+
require 'yaml'
|
24
|
+
require 'pathname'
|
25
|
+
|
26
|
+
require "markify/version"
|
27
|
+
require "markify/scraper"
|
28
|
+
require "markify/database"
|
29
|
+
require "markify/bot"
|
30
|
+
require "markify/settings"
|
31
|
+
require "markify/optparser"
|
32
|
+
require 'markify/mark'
|
33
|
+
|
34
|
+
module Markify
|
35
|
+
|
36
|
+
def self.run!
|
37
|
+
@options = Markify::OptParser.parse!
|
38
|
+
@config = Markify::Settings.load!(@options[:config_file])
|
39
|
+
|
40
|
+
if @options[:test]
|
41
|
+
Markify::Settings.test_settings(@config)
|
42
|
+
exit
|
43
|
+
end
|
44
|
+
|
45
|
+
mark_database = Markify::Database.new(@config['general']['database_file'])
|
46
|
+
|
47
|
+
all_marks = Markify::Scraper.new(@config['sis']['login_name'], @config['sis']['login_password']).scrape!
|
48
|
+
new_marks = mark_database.check_for_new_marks(all_marks)
|
49
|
+
|
50
|
+
if new_marks.count == 0 && (@config['general']['verbose'] || @options[:noop])
|
51
|
+
puts "No new marks."
|
52
|
+
exit
|
53
|
+
end
|
54
|
+
|
55
|
+
bot = Markify::Bot.new(@config['xmpp']['bot_id'], @config['xmpp']['bot_password']) if @options[:send]
|
56
|
+
|
57
|
+
new_marks.sort_by{|mark| mark.date}.each do |mark|
|
58
|
+
unless @options[:noop]
|
59
|
+
mark_database.write_checksum(mark.hash)
|
60
|
+
end
|
61
|
+
|
62
|
+
bot.send_message(@config['xmpp']['recipients'], mark.to_s) if @options[:send]
|
63
|
+
|
64
|
+
puts mark.to_s if @config['general']['verbose'] || @options[:verbose]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/markify/bot.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
=begin
|
3
|
+
Copyright Daniel Meißner <meise+markify@3st.be>, 2013
|
4
|
+
|
5
|
+
This file is part of Markify.
|
6
|
+
|
7
|
+
Markify is free software: you can redistribute it and/or modify
|
8
|
+
it under the terms of the GNU General Public License as published by
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
10
|
+
(at your option) any later version.
|
11
|
+
|
12
|
+
Markify is distributed in the hope that it will be useful,
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
GNU General Public License for more details.
|
16
|
+
|
17
|
+
You should have received a copy of the GNU General Public License
|
18
|
+
along with Markify. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
=end
|
20
|
+
|
21
|
+
require 'xmpp4r/client'
|
22
|
+
|
23
|
+
class Markify::Bot
|
24
|
+
def initialize(id, password)
|
25
|
+
@client = Jabber::Client.new(id)
|
26
|
+
|
27
|
+
@client.connect
|
28
|
+
@client.auth(password)
|
29
|
+
end
|
30
|
+
|
31
|
+
def send_message(receiver, message)
|
32
|
+
receiver.each do |r|
|
33
|
+
@client.send(Jabber::Message.new(r, message))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
=begin
|
3
|
+
Copyright Daniel Meißner <meise+markify@3st.be>, 2013
|
4
|
+
|
5
|
+
This file is part of Markify.
|
6
|
+
|
7
|
+
Markify is free software: you can redistribute it and/or modify
|
8
|
+
it under the terms of the GNU General Public License as published by
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
10
|
+
(at your option) any later version.
|
11
|
+
|
12
|
+
Markify is distributed in the hope that it will be useful,
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
GNU General Public License for more details.
|
16
|
+
|
17
|
+
You should have received a copy of the GNU General Public License
|
18
|
+
along with Markify. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
=end
|
20
|
+
|
21
|
+
class Markify::Database
|
22
|
+
|
23
|
+
attr_reader :file_path
|
24
|
+
|
25
|
+
def initialize(path = nil)
|
26
|
+
@file_path = path || Pathname(ENV['HOME'] + '/markify/hashes.txt')
|
27
|
+
@checksums = read_checksum_file
|
28
|
+
end
|
29
|
+
|
30
|
+
def check_for_new_marks(marks)
|
31
|
+
new_marks = marks.clone
|
32
|
+
|
33
|
+
read_checksum_file.each do |line|
|
34
|
+
marks.each do |mark|
|
35
|
+
if mark.hash.match(line)
|
36
|
+
new_marks.delete(mark)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
new_marks
|
42
|
+
end
|
43
|
+
|
44
|
+
def write_checksum(checksum)
|
45
|
+
File.open(@file_path, 'a+') do |file|
|
46
|
+
file.puts checksum
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def checksums
|
51
|
+
checksums = read_checksum_file
|
52
|
+
|
53
|
+
checksums.each do |checksum|
|
54
|
+
if checksum.match(/#/)
|
55
|
+
checksums.delete(checksum)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
def create_checksum_file
|
63
|
+
unless @file_path.dirname.exist?
|
64
|
+
@file_path.dirname.mkdir
|
65
|
+
end
|
66
|
+
|
67
|
+
File.open(@file_path, 'a+') do |file|
|
68
|
+
file.puts "# markify hash database"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def read_checksum_file
|
73
|
+
hashes = []
|
74
|
+
|
75
|
+
if @file_path.exist?
|
76
|
+
hashes = File.open(@file_path, 'r').read.split(/\n/)
|
77
|
+
else
|
78
|
+
create_checksum_file
|
79
|
+
end
|
80
|
+
|
81
|
+
hashes
|
82
|
+
end
|
83
|
+
end
|
data/lib/markify/mark.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
=begin
|
3
|
+
Copyright Daniel Meißner <meise+markify@3st.be>, 2013
|
4
|
+
|
5
|
+
This file is part of Markify.
|
6
|
+
|
7
|
+
Markify is free software: you can redistribute it and/or modify
|
8
|
+
it under the terms of the GNU General Public License as published by
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
10
|
+
(at your option) any later version.
|
11
|
+
|
12
|
+
Markify is distributed in the hope that it will be useful,
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
GNU General Public License for more details.
|
16
|
+
|
17
|
+
You should have received a copy of the GNU General Public License
|
18
|
+
along with Markify. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
=end
|
20
|
+
|
21
|
+
class Markify::Mark
|
22
|
+
|
23
|
+
attr_reader :name, :id, :date, :mark, :passed, :try, :hash
|
24
|
+
|
25
|
+
def initialize(name, id, mark, passed, try, date)
|
26
|
+
@name = name
|
27
|
+
@id = id.to_i
|
28
|
+
@mark = mark.to_f
|
29
|
+
@passed = passed
|
30
|
+
@try = try.to_i
|
31
|
+
@date = Date.strptime(date, '%d.%m.%Y')
|
32
|
+
@hash = self.to_sha256.to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
message =<<MESSAGE
|
37
|
+
exam: #{@name}
|
38
|
+
mark: #{@mark}
|
39
|
+
passed: #{@passed}
|
40
|
+
try: #{@try}
|
41
|
+
date: #{@date}
|
42
|
+
|
43
|
+
hash: #{@hash}
|
44
|
+
|
45
|
+
MESSAGE
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_sha256
|
49
|
+
Digest::SHA256.new << "#{@name}#{@id}#{@date}#{@mark}#{@try}#{@passed}"
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
=begin
|
3
|
+
Copyright Daniel Meißner <meise+markify@3st.be>, 2013
|
4
|
+
|
5
|
+
This file is part of Markify.
|
6
|
+
|
7
|
+
Markify is free software: you can redistribute it and/or modify
|
8
|
+
it under the terms of the GNU General Public License as published by
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
10
|
+
(at your option) any later version.
|
11
|
+
|
12
|
+
Markify is distributed in the hope that it will be useful,
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
GNU General Public License for more details.
|
16
|
+
|
17
|
+
You should have received a copy of the GNU General Public License
|
18
|
+
along with Markify. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
=end
|
20
|
+
|
21
|
+
require 'optparse'
|
22
|
+
|
23
|
+
module Markify::OptParser
|
24
|
+
|
25
|
+
def self.parse!
|
26
|
+
parse
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def self.parse
|
32
|
+
options = {}
|
33
|
+
|
34
|
+
oparser = OptionParser.new do |opts|
|
35
|
+
opts.banner = "Usage: #{$0} [options]"
|
36
|
+
|
37
|
+
opts.separator ""
|
38
|
+
opts.separator "Optional options:"
|
39
|
+
|
40
|
+
opts.on('-s', '--send', 'Send xmpp messages') do |s|
|
41
|
+
options[:send] = s
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on('-n', '--noop', 'No operation, only stout output') do |n|
|
45
|
+
options[:noop] = n
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
|
49
|
+
options[:verbose] = v
|
50
|
+
end
|
51
|
+
|
52
|
+
opts.separator ""
|
53
|
+
|
54
|
+
opts.on("-f", "--config-file FILE", "Config file (default: ~/markify/config.yml)") do |file|
|
55
|
+
options[:config_file] = Pathname(file)
|
56
|
+
end
|
57
|
+
|
58
|
+
opts.separator ''
|
59
|
+
opts.separator 'Common options:'
|
60
|
+
|
61
|
+
opts.on_tail('--init', 'Create example configuration') do
|
62
|
+
if options[:config_file]
|
63
|
+
Markify::Settings.create_exmple_config(options[:config_file])
|
64
|
+
else
|
65
|
+
Markify::Settings.create_example_config
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
opts.on_tail('--test', 'Test configuration and accounts') do
|
70
|
+
options[:test] = true
|
71
|
+
end
|
72
|
+
|
73
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
74
|
+
puts opts
|
75
|
+
exit
|
76
|
+
end
|
77
|
+
|
78
|
+
opts.on_tail('--version', 'Show version inforamtion') do
|
79
|
+
puts Markify::LICENCE
|
80
|
+
exit
|
81
|
+
end
|
82
|
+
|
83
|
+
if ARGV.size == 0
|
84
|
+
puts Markify::DESCRIPTION
|
85
|
+
puts
|
86
|
+
puts opts
|
87
|
+
puts
|
88
|
+
puts Markify::LICENCE
|
89
|
+
|
90
|
+
exit
|
91
|
+
end
|
92
|
+
end.parse!
|
93
|
+
|
94
|
+
options
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
=begin
|
3
|
+
Copyright Daniel Meißner <meise+markify@3st.be>, 2013
|
4
|
+
|
5
|
+
This file is part of Markify.
|
6
|
+
|
7
|
+
Markify is free software: you can redistribute it and/or modify
|
8
|
+
it under the terms of the GNU General Public License as published by
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
10
|
+
(at your option) any later version.
|
11
|
+
|
12
|
+
Markify is distributed in the hope that it will be useful,
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
GNU General Public License for more details.
|
16
|
+
|
17
|
+
You should have received a copy of the GNU General Public License
|
18
|
+
along with Markify. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
=end
|
20
|
+
|
21
|
+
require 'mechanize'
|
22
|
+
|
23
|
+
class Markify::Scraper
|
24
|
+
|
25
|
+
attr_reader :marks
|
26
|
+
|
27
|
+
def initialize(login_name, login_password, sis_login_page = nil)
|
28
|
+
@agent = Mechanize.new
|
29
|
+
|
30
|
+
@sis = {}
|
31
|
+
@sis[:login_page] = sis_login_page || 'https://dias.fh-bonn-rhein-sieg.de/d3/SISEgo.asp?UserAcc=Gast&DokID=DiasSWeb&ADias2Dction=Login'
|
32
|
+
@sis[:login_name] = login_name
|
33
|
+
@sis[:login_password] = login_password
|
34
|
+
|
35
|
+
@marks = []
|
36
|
+
end
|
37
|
+
|
38
|
+
def scrape!
|
39
|
+
scrape
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_login
|
43
|
+
login_page = @agent.get(@sis[:login_page])
|
44
|
+
error_page = sis_login(@sis[:login_name], @sis[:login_password], login_page)
|
45
|
+
|
46
|
+
if error_page.search(".//*[@id='inhalt']/table/tbody/tr/td/form/center/table/tr[3]/td").text =~
|
47
|
+
/Benutzername unbekannt oder falsches Kennwort eingegeben!/
|
48
|
+
puts "SIS: Username and password wrong."
|
49
|
+
else
|
50
|
+
puts "SIS: Login works."
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
def scrape
|
57
|
+
login_page = @agent.get(@sis[:login_page])
|
58
|
+
|
59
|
+
first_sis_page = sis_login(@sis[:login_name], @sis[:login_password], login_page)
|
60
|
+
marks_table = get_marks_table(first_sis_page)
|
61
|
+
|
62
|
+
get_marks(marks_table)
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_marks(marks_table)
|
66
|
+
marks_table.each do |m|
|
67
|
+
mark = m.search('./td')
|
68
|
+
|
69
|
+
# use only lines with a id
|
70
|
+
unless mark[0].text =~ /\d{4,5}/
|
71
|
+
next
|
72
|
+
end
|
73
|
+
|
74
|
+
@marks << Markify::Mark.new(
|
75
|
+
mark[1].text, # curse name
|
76
|
+
mark[0].text, # id
|
77
|
+
mark[4].text, # mark
|
78
|
+
mark[7].text, # passed
|
79
|
+
mark[8].text, # try
|
80
|
+
mark[12].text # date
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
@marks
|
85
|
+
end
|
86
|
+
|
87
|
+
def get_marks_table(page_after_login)
|
88
|
+
marks_table = nil
|
89
|
+
|
90
|
+
if link = page_after_login.link_with(text: "Notenspiegel (vollständig)")
|
91
|
+
mark_page = link.click
|
92
|
+
marks_table = mark_page.search(".//*[@id='inhalt']/table/tbody/tr/td/center/table/tr")
|
93
|
+
else
|
94
|
+
puts "Page after login not available."
|
95
|
+
exit
|
96
|
+
end
|
97
|
+
|
98
|
+
marks_table
|
99
|
+
end
|
100
|
+
|
101
|
+
def sis_login(name, password, login_page)
|
102
|
+
page_after_login = login_page.form_with(action: '/d3/SISEgo.asp?formact=Login') do |form|
|
103
|
+
form.txtBName = name
|
104
|
+
form.txtKennwort = password
|
105
|
+
end.click_button
|
106
|
+
end
|
107
|
+
end
|