em-msn 0.1

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/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: []