em-msn 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
File without changes
data/lib/em-msn.rb ADDED
@@ -0,0 +1,25 @@
1
+ module Msn
2
+ ApplicationId = "CFE80F9D-180F-4399-82AB-413F33A1FA11"
3
+ end
4
+
5
+ require 'eventmachine'
6
+ require 'rest-client'
7
+ require 'fiber'
8
+ require 'cgi'
9
+ require 'guid'
10
+ require 'base64'
11
+ require 'digest/md5'
12
+ require 'digest/hmac'
13
+ require 'erb'
14
+ require "nokogiri"
15
+ require 'rexml/text'
16
+
17
+ require_relative 'msn/authentication_error'
18
+ require_relative 'msn/protocol'
19
+ require_relative 'msn/message'
20
+ require_relative 'msn/contact'
21
+ require_relative 'msn/nexus'
22
+ require_relative 'msn/challenge'
23
+ require_relative 'msn/notification_server'
24
+ require_relative 'msn/switchboard'
25
+ require_relative 'msn/messenger'
@@ -0,0 +1,2 @@
1
+ class Msn::AuthenticationError < StandardError
2
+ end
@@ -0,0 +1,47 @@
1
+ class Msn::Challenge
2
+ ProductKey = "RG@XY*28Q5QHS%Q5"
3
+ ProductId = "PROD0113H11T8$X_"
4
+ F = "0x7FFFFFFF".to_i(16)
5
+ E = "0x0E79A9C1".to_i(16)
6
+
7
+ class << self
8
+ def challenge(challenge, product_key = ProductKey, product_id = ProductId)
9
+ md5hash = Digest::MD5.hexdigest "#{challenge}#{product_key}"
10
+ md5array = md5hash.scan(/.{8}/)
11
+ new_hash_parts = md5array.map! { |s| s.scan(/.{2}/).reverse.join.to_i(16) }
12
+ md5array = new_hash_parts.map { |n| n & F }
13
+
14
+ chlstring = "#{challenge}#{product_id}"
15
+ chlstring = "#{chlstring}#{'0' * (8 - chlstring.length % 8)}"
16
+
17
+ chlstring_array = chlstring.scan(/.{4}/)
18
+ chlstring_array.map! { |str| str.bytes.map { |b| b.to_s(16) }.reverse.join.to_i(16) }
19
+
20
+ low = high = 0
21
+
22
+ i = 0
23
+ while i < chlstring_array.length
24
+ temp = (md5array[0] * (((E * chlstring_array[i]) % F) + high) + md5array[1]) % F
25
+ high = (md5array[2] * ((chlstring_array[i + 1] + temp) % F) + md5array[3]) % F
26
+ low = low + high + temp;
27
+
28
+ i += 2
29
+ end
30
+
31
+ high = (high + md5array[1]) % F
32
+ low = (low + md5array[3]) % F
33
+ key = (high << 32) + low
34
+
35
+ new_hash_parts[0] ^= high;
36
+ new_hash_parts[1] ^= low;
37
+ new_hash_parts[2] ^= high;
38
+ new_hash_parts[3] ^= low;
39
+
40
+ new_hash_parts.map do |num|
41
+ str = num.to_s(16)
42
+ str = "#{'0' * (8 - str.length)}#{str}" if str.length != 8
43
+ str.scan(/.{2}/).reverse.join
44
+ end.join
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,32 @@
1
+ class Msn::Contact
2
+ attr_accessor :email
3
+ attr_accessor :display_name
4
+ attr_accessor :allow
5
+ attr_accessor :block
6
+ attr_accessor :reverse
7
+ attr_accessor :pending
8
+
9
+ def initialize(email, display_name = nil)
10
+ @email = email
11
+ @display_name = display_name
12
+ end
13
+
14
+ def to_s
15
+ if display_name && display_name.length > 0
16
+ str = "#{display_name} <#{email}>"
17
+ else
18
+ str = "#{email}"
19
+ end
20
+ if allow || reverse || pending
21
+ str << ' ('
22
+ pieces = []
23
+ pieces << 'allow' if allow
24
+ pieces << 'block' if block
25
+ pieces << 'reverse' if reverse
26
+ pieces << 'pending' if pending
27
+ str << pieces.join(', ')
28
+ str << ')'
29
+ end
30
+ str
31
+ end
32
+ end
@@ -0,0 +1,11 @@
1
+ class Msn::Message
2
+ attr_accessor :email
3
+ attr_accessor :display_name
4
+ attr_accessor :text
5
+
6
+ def initialize(email, display_name, text)
7
+ @email = email
8
+ @display_name = display_name
9
+ @text = text
10
+ end
11
+ end
@@ -0,0 +1,118 @@
1
+ class Msn::Messenger
2
+ attr_reader :username
3
+ attr_reader :password
4
+
5
+ def initialize(username, password)
6
+ @username = username
7
+ @password = password
8
+ end
9
+
10
+ def connect
11
+ @notification_server = EM.connect 'messenger.hotmail.com', 1863, Msn::NotificationServer, self
12
+ end
13
+
14
+ def set_online_status(status)
15
+ case status
16
+ when :available, :online
17
+ @notification_server.chg "NLN", 0
18
+ when :busy
19
+ @notification_server.chg "BSY", 0
20
+ when :idle
21
+ @notification_server.chg "IDL", 0
22
+ when :brb, :be_right_back
23
+ @notification_server.chg "BRB", 0
24
+ when :away
25
+ @notification_server.chg "AWY", 0
26
+ when :phone, :on_the_phone
27
+ @notification_server.chg "PHN", 0
28
+ when :lunch, :out_to_lunch
29
+ @notification_server.chg "LUN", 0
30
+ else
31
+ raise "Wrong online status: #{status}"
32
+ end
33
+ end
34
+
35
+ def add_contact(email)
36
+ send_contact_command email, 'ADL', '1'
37
+ end
38
+
39
+ def remove_contact(email)
40
+ send_contact_command email, 'RML', '1'
41
+ end
42
+
43
+ def block_contact(email)
44
+ send_contact_command email, 'RML', '2'
45
+ end
46
+
47
+ def unblock_contact(email)
48
+ send_contact_command email, 'ADL', '2'
49
+ end
50
+
51
+ def get_contacts
52
+ @notification_server.get_contacts
53
+ end
54
+
55
+ def send_contact_command(email, command, list)
56
+ username, domain = email.split '@', 2
57
+ @notification_server.send_payload_command_and_wait command, %Q(<ml><d n="#{domain}"><c n="#{username}" t="1" l="#{list}" /></d></ml>)
58
+ end
59
+
60
+ def on_ready(&handler)
61
+ @on_ready_handler = handler
62
+ end
63
+
64
+ def on_login_failed(&handler)
65
+ @on_login_failed = handler
66
+ end
67
+
68
+ def on_disconnect(&handler)
69
+ @on_disconnect = handler
70
+ end
71
+
72
+ def on_message(&handler)
73
+ @on_message_handler = handler
74
+ end
75
+
76
+ def on_contact_request(&handler)
77
+ @on_contact_request = handler
78
+ end
79
+
80
+ def send_message(email, text)
81
+ @notification_server.send_message email, text
82
+ end
83
+
84
+ def accept_message(message)
85
+ call_handler @on_message_handler, message
86
+ end
87
+
88
+ def contact_request(email, display_name)
89
+ call_handler @on_contact_request, email, display_name
90
+ end
91
+
92
+ def ready
93
+ call_handler @on_ready_handler
94
+ end
95
+
96
+ def login_failed(message)
97
+ call_handler @on_login_failed, message
98
+ end
99
+
100
+ def disconnected
101
+ call_handler @on_disconnect
102
+ end
103
+
104
+ def call_handler(handler, *args)
105
+ if handler
106
+ Fiber.new { handler.call(*args) }.resume
107
+ end
108
+ end
109
+
110
+ def self.debug
111
+ @debug
112
+ end
113
+
114
+ def self.debug=(debug)
115
+ @debug = debug
116
+ end
117
+ end
118
+
data/lib/msn/nexus.rb ADDED
@@ -0,0 +1,101 @@
1
+ class Msn::Nexus
2
+ attr_reader :messenger
3
+ attr_reader :policy
4
+ attr_reader :nonce
5
+
6
+ Namespaces = {
7
+ "wsse" => "http://schemas.xmlsoap.org/ws/2003/06/secext",
8
+ "wst" => "http://schemas.xmlsoap.org/ws/2004/04/trust",
9
+ "wsp" => "http://schemas.xmlsoap.org/ws/2002/12/policy",
10
+ "wsa" => "http://schemas.xmlsoap.org/ws/2004/03/addressing",
11
+ "S" => "http://schemas.xmlsoap.org/soap/envelope/",
12
+ }
13
+
14
+ def initialize(messenger, policy, nonce)
15
+ @messenger = messenger
16
+ @policy = policy
17
+ @nonce = nonce
18
+ end
19
+
20
+ def sso_token
21
+ fetch_data
22
+ @sso_token
23
+ end
24
+
25
+ def ticket_token
26
+ fetch_data
27
+ @ticket_token
28
+ end
29
+
30
+ def secret
31
+ fetch_data
32
+ @secret
33
+ end
34
+
35
+ def fetch_data
36
+ return if @fetched_data
37
+ @fetched_data = true
38
+
39
+ @sso_token, secret, @ticket_token = get_binary_secret messenger.username, messenger.password
40
+
41
+ @secret = compute_return_value secret
42
+ end
43
+
44
+ def get_binary_secret(username, password)
45
+ msn_sso_template_file = File.expand_path('../soap/msn_sso_template.xml', __FILE__)
46
+ msn_sso_template = ERB.new File.read(msn_sso_template_file)
47
+ soap = msn_sso_template.result(binding)
48
+
49
+ response = RestClient.post "https://login.live.com/RST.srf", soap
50
+
51
+ xml = Nokogiri::XML(response)
52
+
53
+ # Check invalid login
54
+ fault = xml.xpath("//S:Fault/faultstring")
55
+ if fault
56
+ raise Msn::AuthenticationError.new(fault.text)
57
+ end
58
+
59
+ rstr = xml.xpath "//wst:RequestSecurityTokenResponse[wsp:AppliesTo/wsa:EndpointReference/wsa:Address='messengerclear.live.com']", Namespaces
60
+ token = rstr.xpath("wst:RequestedSecurityToken/wsse:BinarySecurityToken[@Id='Compact1']", Namespaces).first.text
61
+ secret = rstr.xpath("wst:RequestedProofToken/wst:BinarySecret", Namespaces).first.text
62
+
63
+ rstr2 = xml.xpath "//wst:RequestSecurityTokenResponse[wsp:AppliesTo/wsa:EndpointReference/wsa:Address='contacts.msn.com']", Namespaces
64
+ ticket_token = rstr2.xpath("wst:RequestedSecurityToken/wsse:BinarySecurityToken[@Id='Compact2']", Namespaces).first.text
65
+
66
+ [token, secret, ticket_token]
67
+ end
68
+
69
+ def compute_return_value(binary_secret, iv = Random.new.bytes(8))
70
+ key1 = Base64.decode64 binary_secret
71
+
72
+ key2 = compute_key key1, "WS-SecureConversationSESSION KEY HASH"
73
+ key3 = compute_key key1, "WS-SecureConversationSESSION KEY ENCRYPTION"
74
+
75
+ hash = sha1_hmac key2, @nonce
76
+
77
+ nonce = "#{@nonce}#{8.chr * 8}"
78
+
79
+ des = OpenSSL::Cipher::Cipher.new("des-ede3-cbc")
80
+ des.encrypt
81
+ des.iv = iv
82
+ des.key = key3
83
+ encrypted_data = des.update(nonce) + des.final
84
+
85
+ final = [28, 1, 0x6603, 0x8004, 8, 20, 72, iv, hash, encrypted_data].pack "L<L<L<L<L<L<L<A8A20A72"
86
+ Base64.strict_encode64 final
87
+ end
88
+
89
+ def compute_key(key, hash)
90
+ hash1 = sha1_hmac(key, hash)
91
+ hash2 = sha1_hmac(key, "#{hash1}#{hash}")
92
+ hash3 = sha1_hmac(key, hash1)
93
+ hash4 = sha1_hmac(key, "#{hash3}#{hash}")
94
+
95
+ "#{hash2}#{hash4[0 ... 4]}"
96
+ end
97
+
98
+ def sha1_hmac(data, key)
99
+ Digest::HMAC.digest(key, data, Digest::SHA1)
100
+ end
101
+ end
@@ -0,0 +1,160 @@
1
+ class Msn::NotificationServer < EventMachine::Connection
2
+ include Msn::Protocol
3
+
4
+ ContactsNamespace = {'ns' => 'http://www.msn.com/webservices/AddressBook'}
5
+
6
+ attr_reader :messenger
7
+ attr_reader :guid
8
+
9
+ def initialize(messenger)
10
+ @messenger = messenger
11
+ @guid = Guid.new.to_s
12
+ @switchboards = {}
13
+
14
+ on_event 'ADL' do |header, data|
15
+ data = Nokogiri::XML(data)
16
+ domain = data.xpath('//ml/d').first['n']
17
+ c = data.xpath('//ml/d/c').first
18
+ username = c['n']
19
+ display_name = c['f']
20
+ messenger.contact_request "#{username}@#{domain}", display_name
21
+ end
22
+ end
23
+
24
+ def username_guid
25
+ @username_guid ||= "#{messenger.username};{#{guid}}"
26
+ end
27
+
28
+ def send_message(email, text)
29
+ switchboard = @switchboards[email]
30
+ if switchboard
31
+ switchboard.send_message text
32
+ else
33
+ Fiber.new do
34
+ response = xfr "SB"
35
+ switchboard = create_switchboard email, response[3]
36
+ switchboard.on_event 'JOI' do
37
+ switchboard.clear_event 'JOI'
38
+ switchboard.send_message text
39
+ end
40
+ switchboard.usr username_guid, response[5]
41
+ switchboard.cal email
42
+ end.resume
43
+ end
44
+ end
45
+
46
+ def username
47
+ messenger.username
48
+ end
49
+
50
+ def password
51
+ messenger.password
52
+ end
53
+
54
+ def get_contacts
55
+ contacts = {}
56
+
57
+ xml = Nokogiri::XML(get_soap_contacts.body)
58
+ xml.xpath('//ns:Membership', ContactsNamespace).each do |membership|
59
+ role = membership.xpath('ns:MemberRole', ContactsNamespace).text
60
+ membership.xpath('ns:Members/ns:Member', ContactsNamespace).each do |member|
61
+ cid = member.xpath('ns:CID', ContactsNamespace).text
62
+ email = member.xpath('ns:PassportName', ContactsNamespace).text
63
+ display_name = member.xpath('ns:DisplayName', ContactsNamespace)
64
+
65
+ contact = contacts[cid] ||= Msn::Contact.new(email, display_name ? display_name.text : nil)
66
+ case role
67
+ when 'Allow' then contact.allow = true
68
+ when 'Block' then contact.block = true
69
+ when 'Reverse' then contact.reverse = true
70
+ when 'Pending' then contact.pending = true
71
+ end
72
+ end
73
+ end
74
+
75
+ contacts.values
76
+ end
77
+
78
+ def get_soap_contacts
79
+ msn_get_contacts_template_file = File.expand_path('../soap/msn_get_contacts_template.xml', __FILE__)
80
+ msn_get_contacts_template = ERB.new File.read(msn_get_contacts_template_file)
81
+ soap = msn_get_contacts_template.result(binding)
82
+
83
+ RestClient.post "https://local-bay.contacts.msn.com/abservice/SharingService.asmx", soap, {
84
+ 'SOAPAction' => 'http://www.msn.com/webservices/AddressBook/FindMembership',
85
+ 'Content-Type' => 'text/xml',
86
+ }
87
+ end
88
+
89
+ def post_init
90
+ super
91
+
92
+ login
93
+ end
94
+
95
+ def login
96
+ Fiber.new do
97
+ begin
98
+ ver "MSNP18", "CVR0"
99
+ cvr "0x0409", "winnt", "5.1", "i386", "MSNMSGR", "8.5.1302", "BC01", username
100
+ response = usr "SSO", "I", username
101
+ if response[0] == "XFR" && response[2] == "NS"
102
+ host, port = response[3].split ':'
103
+ @reconnect_host, @reconnect_port = response[3].split ':'
104
+ close_connection
105
+ else
106
+ login_to_nexus(response[4], response[5])
107
+ end
108
+ rescue Msn::AuthenticationError => ex
109
+ messenger.login_failed(ex.message)
110
+ close_connection
111
+ end
112
+ end.resume
113
+ end
114
+
115
+ def login_to_nexus(policy, nonce)
116
+ @nexus = Msn::Nexus.new self, policy, nonce
117
+
118
+ first_msg = true
119
+ on_event('MSG') do
120
+ if first_msg
121
+ first_msg = false
122
+ messenger.ready
123
+ end
124
+ end
125
+
126
+ on_event('RNG') do |header|
127
+ switchboard = create_switchboard header[5], header[2]
128
+ switchboard.ans username_guid, header[4], header[1]
129
+ end
130
+
131
+ response = usr "SSO", "S", @nexus.sso_token, @nexus.secret, guid
132
+ if response[2] != "OK"
133
+ raise Msn::AuthenticationError.new("Didn't receive OK from SSO")
134
+ end
135
+ end
136
+
137
+ def create_switchboard(email, host_and_port)
138
+ host, port = host_and_port.split(':')
139
+ switchboard = EM.connect host, port, Msn::Switchboard, messenger
140
+ switchboard.on_event 'BYE' do |header|
141
+ destroy_switchboard email if header[1] =~ /#{email}/
142
+ end
143
+ @switchboards[email] = switchboard
144
+ end
145
+
146
+ def destroy_switchboard(email)
147
+ switchboard = @switchboards.delete email
148
+ switchboard.close_connection if switchboard
149
+ end
150
+
151
+ def unbind
152
+ if @reconnect_host
153
+ reconnect @reconnect_host, @reconnect_port.to_i
154
+ @reconnect_host = @reconnect_port = nil
155
+ login
156
+ else
157
+ messenger.disconnected
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,112 @@
1
+ module Msn::Protocol
2
+ include EventMachine::Protocols::LineText2
3
+
4
+ def post_init
5
+ @trid = 1
6
+ @command_fibers = {}
7
+ end
8
+
9
+ def receive_line(line)
10
+ puts "<< #{line}" if Msn::Messenger.debug
11
+
12
+ pieces = line.split(' ')
13
+
14
+ case pieces[0]
15
+ when 'CHL'
16
+ answer_challenge pieces[2]
17
+ when 'RNG'
18
+ handle_event pieces
19
+ when 'MSG', 'NOT', 'GCF', 'UBX'
20
+ handle_payload_command pieces
21
+ when 'QRY'
22
+ # ignore
23
+ when 'ADL', 'RML'
24
+ if pieces[2] == 'OK'
25
+ handle_normal_command pieces
26
+ else
27
+ handle_payload_command pieces
28
+ end
29
+ else
30
+ handle_normal_command pieces
31
+ end
32
+ end
33
+
34
+ def handle_normal_command(pieces)
35
+ if fiber = @command_fibers.delete(pieces[1].to_i)
36
+ fiber.resume pieces
37
+ else
38
+ handle_event pieces
39
+ end
40
+ end
41
+
42
+ def handle_payload_command(pieces)
43
+ @header = pieces
44
+
45
+ size = pieces.last.to_i
46
+ set_binary_mode size
47
+ end
48
+
49
+ def receive_binary_data(data)
50
+ puts "<<* #{data}" if Msn::Messenger.debug
51
+
52
+ handle_event @header, data
53
+ end
54
+
55
+ def handle_event(header, data = nil)
56
+ return unless @event_handlers
57
+
58
+ handler = @event_handlers[header[0]]
59
+ if handler
60
+ Fiber.new do
61
+ handler.call header, data
62
+ end.resume
63
+ end
64
+ end
65
+
66
+ def answer_challenge(challenge_string)
67
+ send_payload_command "QRY", Msn::Challenge::ProductId, Msn::Challenge.challenge(challenge_string)
68
+ end
69
+
70
+ def send_command(command, *args)
71
+ @command_fibers[@trid] = Fiber.current
72
+
73
+ text = "#{command} #{@trid} #{args.join ' '}\r\n"
74
+ send_command_internal text
75
+
76
+ Fiber.yield
77
+ end
78
+
79
+ def send_payload_command(command, *args)
80
+ payload = args.pop
81
+ args.push payload.length
82
+ send_command_internal "#{command} #{@trid} #{args.join ' '}\r\n#{payload}"
83
+ end
84
+
85
+ def send_payload_command_and_wait(command, *args)
86
+ @command_fibers[@trid] = Fiber.current
87
+
88
+ send_payload_command command, *args
89
+
90
+ Fiber.yield
91
+ end
92
+
93
+ def send_command_internal(text)
94
+ puts ">> #{text}" if Msn::Messenger.debug
95
+ send_data text
96
+ @trid += 1
97
+ end
98
+
99
+ def on_event(kind, &block)
100
+ @event_handlers ||= {}
101
+ @event_handlers[kind] = block
102
+ end
103
+
104
+ def clear_event(kind)
105
+ @event_handlers.delete kind
106
+ end
107
+
108
+ def method_missing(name, *args)
109
+ send_command name.upcase, *args
110
+ end
111
+ end
112
+
@@ -0,0 +1,23 @@
1
+ class Msn::Switchboard < EventMachine::Connection
2
+ include Msn::Protocol
3
+
4
+ def initialize(messenger)
5
+ @messenger = messenger
6
+
7
+ on_event 'MSG' do |header, data|
8
+ email = header[1]
9
+ display_name = header[2]
10
+ head, body = data.split "\r\n\r\n", 2
11
+ headers = Hash[head.split("\r\n").map { |line| line.split ':', 2 }]
12
+
13
+ if headers['Content-Type'] =~ %r(text/plain)
14
+ @messenger.accept_message Msn::Message.new(email, display_name, body)
15
+ end
16
+ end
17
+ end
18
+
19
+ def send_message(text)
20
+ send_payload_command "MSG", "N", "MIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\nUser-Agent: pidgin/2.10.5devel\r\nX-MMS-IM-Format: FN=Segoe%20UI; EF=; CO=0; PF=0; RL=0\r\n\r\n#{text}"
21
+ end
22
+ end
23
+
@@ -0,0 +1,3 @@
1
+ module Msn
2
+ VERSION = "0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-msn
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ary Borenszweig
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rest-client
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: nokogiri
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: guid
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rspec
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: '2.7'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: '2.7'
94
+ description: An MSN client for Ruby written on top of EventMachine
95
+ email: aborenszweig@manas.com.ar
96
+ executables: []
97
+ extensions: []
98
+ extra_rdoc_files:
99
+ - README.md
100
+ files:
101
+ - lib/em-msn.rb
102
+ - lib/msn/authentication_error.rb
103
+ - lib/msn/message.rb
104
+ - lib/msn/contact.rb
105
+ - lib/msn/nexus.rb
106
+ - lib/msn/messenger.rb
107
+ - lib/msn/challenge.rb
108
+ - lib/msn/notification_server.rb
109
+ - lib/msn/protocol.rb
110
+ - lib/msn/switchboard.rb
111
+ - lib/msn/version.rb
112
+ - README.md
113
+ homepage: http://github.com/manastech/em-msn
114
+ licenses: []
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - ! '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubyforge_project:
133
+ rubygems_version: 1.8.23
134
+ signing_key:
135
+ specification_version: 3
136
+ summary: MSN client (EventMachine + Ruby)
137
+ test_files: []