某大学の時間割をGoogleカレンダーなどでインポートできる形式に変換する地味に便利ツール。 ちょっといじれば、ライブラリとしても使えます。
- RAINBOWのIDとパスワードを入力(取得以外には利用しません)
- 学期の開始日を入力
- 出力先ファイルを指定
- Googleカレンダーなどでインポート。
フォーク・プルリク等お待ちしております。
MIT Licenseです。作品名とクレジットの表記をよろしくお願いします。 詳しくは、LICENSEファイルをご覧ください。
| # Created by https://www.gitignore.io/api/ruby,vim | |
| ### Ruby ### | |
| *.gem | |
| *.rbc | |
| /.config | |
| /coverage/ | |
| /InstalledFiles | |
| /pkg/ | |
| /spec/reports/ | |
| /spec/examples.txt | |
| /test/tmp/ | |
| /test/version_tmp/ | |
| /tmp/ | |
| ## Specific to RubyMotion: | |
| .dat* | |
| .repl_history | |
| build/ | |
| *.bridgesupport | |
| build-iPhoneOS/ | |
| build-iPhoneSimulator/ | |
| ## Specific to RubyMotion (use of CocoaPods): | |
| # | |
| # We recommend against adding the Pods directory to your .gitignore. However | |
| # you should judge for yourself, the pros and cons are mentioned at: | |
| # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control | |
| # | |
| # vendor/Pods/ | |
| ## Documentation cache and generated files: | |
| /.yardoc/ | |
| /_yardoc/ | |
| /doc/ | |
| /rdoc/ | |
| ## Environment normalization: | |
| /.bundle/ | |
| /vendor/bundle | |
| /lib/bundler/man/ | |
| # for a library or gem, you might want to ignore these files since the code is | |
| # intended to run in multiple environments; otherwise, check them in: | |
| # Gemfile.lock | |
| # .ruby-version | |
| # .ruby-gemset | |
| # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: | |
| .rvmrc | |
| ### Vim ### | |
| # swap | |
| [._]*.s[a-w][a-z] | |
| [._]s[a-w][a-z] | |
| # session | |
| Session.vim | |
| # temporary | |
| .netrwhist | |
| *~ | |
| # auto-generated tag files | |
| tags | |
| *.ical |
某大学の時間割をGoogleカレンダーなどでインポートできる形式に変換する地味に便利ツール。 ちょっといじれば、ライブラリとしても使えます。
フォーク・プルリク等お待ちしております。
MIT Licenseです。作品名とクレジットの表記をよろしくお願いします。 詳しくは、LICENSEファイルをご覧ください。
| source 'https://rubygems.org' | |
| gem 'nokogiri' | |
| gem 'icalendar' | |
| gem 'rits_api', git: 'https://syusui_s@bitbucket.org/syusui_s/rits_api.git', branch: 'master' | |
| group :development do | |
| gem 'pry' | |
| end |
| GIT | |
| remote: https://syusui_s@bitbucket.org/syusui_s/rits_api.git | |
| revision: 68192cd210de400b38ce1dc4730bbe7846310061 | |
| branch: master | |
| specs: | |
| rits_api (0.1.0) | |
| icalendar (>= 2.3.0) | |
| nokogiri | |
| GEM | |
| remote: https://rubygems.org/ | |
| specs: | |
| coderay (1.1.1) | |
| icalendar (2.3.0) | |
| method_source (0.8.2) | |
| mini_portile2 (2.0.0) | |
| nokogiri (1.6.7.2) | |
| mini_portile2 (~> 2.0.0.rc2) | |
| pry (0.10.3) | |
| coderay (~> 1.1.0) | |
| method_source (~> 0.8.1) | |
| slop (~> 3.4) | |
| slop (3.6.0) | |
| PLATFORMS | |
| ruby | |
| DEPENDENCIES | |
| icalendar | |
| nokogiri | |
| pry | |
| rits_api! | |
| BUNDLED WITH | |
| 1.11.2 |
| Copyright (c) 2015 Shusui Moyatani | |
| 以下に定める条件に従い、本ソフトウェアおよび関連文書のファイル(以下「ソフトウェア」)の複製を取得するすべての人に対し、 | |
| ソフトウェアを無制限に扱うことを無償で許可します。これには、ソフトウェアの複製を使用、複写、変更、結合、掲載、頒布、 | |
| サブライセンス、および/または販売する権利、およびソフトウェアを提供する相手に同じことを許可する権利も無制限に含まれます。 | |
| 上記の著作権表示および本許諾表示を、ソフトウェアのすべての複製または重要な部分に記載するものとします。 | |
| ソフトウェアは「現状のまま」で、明示であるか暗黙であるかを問わず、何らの保証もなく提供されます。 | |
| ここでいう保証とは、商品性、特定の目的への適合性、および権利非侵害についての保証も含みますが、 | |
| それに限定されるものではありません。 作者または著作権者は、契約行為、不法行為、またはそれ以外であろうと、 | |
| ソフトウェアに起因または関連し、あるいはソフトウェアの使用またはその他の扱いによって生じる | |
| 一切の請求、損害、その他の義務について何らの責任も負わないものとします。 |
| require 'io/console' | |
| require 'nkf' | |
| require 'uri' | |
| require 'json' | |
| require 'date' | |
| require 'net/http' | |
| require 'net/https' | |
| require 'nokogiri' | |
| require 'webrick/cookie' | |
| require 'bundler' | |
| Bundler.require | |
| class RitsClass | |
| EXAM_TIME = { | |
| 1 => {start: [ 9, 00], end: [10, 30]}, | |
| 2 => {start: [10, 40], end: [12, 10]}, | |
| 3 => {start: [13, 00], end: [14, 30]}, | |
| 4 => {start: [14, 40], end: [16, 10]}, | |
| 5 => {start: [16, 20], end: [17, 50]}, | |
| 6 => {start: [18, 00], end: [19, 30]}, | |
| 7 => {start: [19, 40], end: [21, 10]} | |
| } | |
| WEEKDAYS = "日月火水木金土".freeze | |
| def initialize(name:, class_name:, weekday:, start_time:, end_time:, room:, code:, teacher:, url:) | |
| @name = name | |
| @class_name = class_name | |
| @weekday = weekday =~ /^\d+$/ ? weekday : WEEKDAYS.index(weekday) | |
| @start_time = start_time | |
| @end_time = end_time | |
| @room = room | |
| @code = code | |
| @teacher = teacher | |
| @syllabus_url = url | |
| end | |
| def to_ical_event(term_start_date) | |
| event = Icalendar::Event.new | |
| date = Date.civil(term_start_date.year, term_start_date.month, term_start_date.day) | |
| date += 1 until date.wday == @weekday | |
| year, month, day = date.year, date.month, date.day | |
| event.dtstart = DateTime.civil(year, month, day, *EXAM_TIME[@start_time.to_i][:start]) | |
| event.dtend = DateTime.civil(year, month, day, *EXAM_TIME[@end_time.to_i][:end]) | |
| event.summary = "#{@name} (#{@class_name})" | |
| event.location = @room | |
| event.description = "先生: #{@teacher}, コード:#{@code}, シラバスURL: #{@syllabus_url}" | |
| alarm = Icalendar::Alarm.new | |
| alarm.action = "DISPLAY" | |
| alarm.description = "授業開始" | |
| alarm.trigger = "-P0DT0H10M0S" # 10分前 | |
| event.add_alarm(alarm) | |
| recur = Icalendar::Values::Recur.new(nil) | |
| recur.frequency = "WEEKLY" | |
| recur.count = 15 | |
| event.rrule << recur | |
| return event | |
| end | |
| end | |
| class RitsICalParse | |
| def initialize(api) | |
| @api = api | |
| end | |
| def get_class_codes() | |
| res = @api.action('campus/portal/TopMyJikanwari.do', { 'kinouID' => 'PJJ010' }) | |
| noko = Nokogiri.parse(NKF.nkf('-w', res.body)) | |
| if !noko.css('body > table:nth-child(4) > tbody > tr:nth-child(1) > td:nth-child(2) > table > tbody > tr > td > div > h2') | |
| # succeed | |
| selector = "body > table:nth-child(4) > tr:nth-child(1) > td:nth-child(2) > table > tr:nth-of-type(3) > td > table > tr > td > a" | |
| return noko.css(selector).map {|e| e[:href].match(/syousai\('(\d+)'\)/)[1] | |
| }.compact.uniq | |
| else | |
| # failed | |
| res = @api.action('campus/svc/gak/risyu/RisyuKikangaiKak.do', { | |
| 'kinouID' => 'JR0014', | |
| 'dlogout' => '0', | |
| 'kinouNM' => "\x8E\xF3\x8Du\x93o\x98^\x8C\x8B\x89ʁi\x8FƉ\xEF\x81j" | |
| }) | |
| noko = Nokogiri.parse(NKF.nkf('-w', res.body)) | |
| selector = "body table:nth-child(5) table:nth-child(4) table table tr td:nth-child(2) b:nth-child(1)" | |
| return noko.css(selector).map{|e| e.text.match(/^\d+/)[0] }.uniq | |
| end | |
| end | |
| def get_class_data(code, year) | |
| res = @api.action('campus/portal/GjikanSyosai.do', { 'kinouID' => 'PJJ020', 'jyugyocd' => "#{code}", 'kaikonen' => "#{year}" }) | |
| nil unless res.code == "200" | |
| noko = Nokogiri.parse(NKF.nkf('-w', res.body)) | |
| time, teacher, name_and_class, room = noko.css("table:nth-child(3) > tr > td > table > tr > td").map{|e| e.text.strip } | |
| match = name_and_class.match(/^([^(]+)\(([^)]+)\)$/) | |
| name, class_name = match[1], match[2] | |
| match = time.match(/([^\/]+)\/([^\/]+)\/(\d+)\((\d+)-(\d+)\)/) | |
| term, weekday, time, start_time, end_time = match[1], match[2], match[3], match[4], match[5] | |
| teacher.gsub!(/\s+/, ", ") | |
| url = noko.css("table:nth-child(3) > tr:nth-child(4) > td > a").first.attribute('href').value | |
| return RitsClass.new( | |
| name: name, class_name: class_name, weekday: weekday, start_time: time, end_time: time, | |
| room: room, code: code, url: url, teacher: teacher ) | |
| end | |
| def create_ical(term_start_date) | |
| cal = Icalendar::Calendar.new | |
| codes = self.get_class_codes() | |
| codes.each do |code| | |
| data = get_class_data(code, term_start_date.year) | |
| cal.add_event(data.to_ical_event(term_start_date)) | |
| end | |
| cal.publish | |
| return cal | |
| end | |
| end | |
| def main | |
| api = RitsAPI::CampusWeb.new() | |
| puts "Rits SSO認証" | |
| print 'ユーザ名: ' | |
| user = $stdin.gets.chomp | |
| print 'パスワード: ' | |
| $stdin.echo = false | |
| passwd = $stdin.gets.chomp | |
| $stdin.echo = true | |
| puts "\n認証処理中..." | |
| unless api.authorize(user, passwd) | |
| puts "認証に失敗しました!" | |
| exit 1 | |
| else | |
| puts '認証に成功しました' | |
| end | |
| puts "\n学期開始日" | |
| print '年: '; year = $stdin.gets.chomp.to_i | |
| print '月: '; month = $stdin.gets.chomp.to_i | |
| print '日: '; day = $stdin.gets.chomp.to_i | |
| puts "データを取得中... (約10秒程度)" | |
| parser = RitsICalParse.new(api) | |
| cal = parser.create_ical(Date.new(year, month, day)) | |
| puts "取得が完了しました。" | |
| while true | |
| print "出力ファイル名: "; output_file = $stdin.gets.chomp | |
| break if not File::exists?(output_file) | |
| puts "ファイルがすでに存在します!別の名前を指定してください。" | |
| end | |
| f = File::open(output_file, 'w+') | |
| f.puts cal.to_ical | |
| f.close | |
| puts "完了" | |
| end | |
| main() |