Skip to content

Instantly share code, notes, and snippets.

@syusui-s
Last active September 7, 2021 19:09
Show Gist options
  • Select an option

  • Save syusui-s/083efde9e638073149b9 to your computer and use it in GitHub Desktop.

Select an option

Save syusui-s/083efde9e638073149b9 to your computer and use it in GitHub Desktop.
時間割 to iCalendar
# 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

時間割 to ICalendar

某大学の時間割をGoogleカレンダーなどでインポートできる形式に変換する地味に便利ツール。 ちょっといじれば、ライブラリとしても使えます。

使い方

  1. RAINBOWのIDとパスワードを入力(取得以外には利用しません)
  2. 学期の開始日を入力
  3. 出力先ファイルを指定
  4. 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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment