ruby-skype 0.1.0.pre.1-mswin32

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 Matthew Scharley
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a
4
+ copy of this software and associated documentation files (the "Software"),
5
+ to deal in the Software without restriction, including without limitation
6
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
7
+ and/or sell copies of the Software, and to permit persons to whom the
8
+ Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included
11
+ in all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19
+ DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ Skype Public API for Ruby
2
+ =========================
3
+
4
+ **GitHub:** https://github.com/mscharley/ruby-skype
5
+ **Author:** Matthew Scharley
6
+ **Contributors:** [See contributors on GitHub][gh-contrib]
7
+ **Bugs/Support:** [Github Issues][gh-issues]
8
+ **Copyright:** 2012
9
+ **License:** [MIT license][license]
10
+
11
+ Synopsis
12
+ --------
13
+
14
+ This library is a binding for the [Skype Public API][skype-api]. Currently,
15
+ due to the Skype API being accessed in different ways on different platforms
16
+ this library will initially only be supported on Linux/DBus and Windows. The
17
+ Skype Public API is a way to hook into the Skype client released by Microsoft
18
+ and automate it. To use this library *you must have a copy of the Skype
19
+ client installed locally*. This is not a stand-alone library to access the
20
+ Skype network.
21
+
22
+ **This is very much still under development**
23
+
24
+ If you want to use this or help out, please feel free to clone/fork and play
25
+ with this, but it is prone to break or change at any time.
26
+
27
+ Installation
28
+ ------------
29
+
30
+ For now, you will need to install from the git repository. Simply clone it
31
+ to wherever you like and then add it to your include path if needed.
32
+
33
+ Mac OS X Support
34
+ ----------------
35
+
36
+ OS X integration should be possible, however I don't have a Mac to
37
+ test/develop with. If you want to help out, then look at the
38
+ `Skype::Communication::*` classes. `Skype::Communication::Protocol` is the
39
+ base interface you need to implement. Any help would be greatly appreciated!
40
+
41
+ Documentation
42
+ -------------
43
+
44
+ There is some documentation about the Skype API itself in the doc folder. The
45
+ [ruby-skype API documentation is available online][ruby-skype-rubydoc]. To
46
+ generate them locally, simply clone the library, then run `yard` in the repo
47
+ root. The API documentation will be available in `/doc/api` when complete.
48
+
49
+ Goals
50
+ -----
51
+
52
+ Generate a Ruby friendly front-end to access the Skype public API.
53
+
54
+
55
+ [skype-api]: http://developer.skype.com/public-api-reference
56
+ [license]: https://raw.github.com/mscharley/ruby-skype/master/LICENSE
57
+ [ruby-skype-rubydoc]: http://rubydoc.info/github/mscharley/ruby-skype/master/frames
58
+ [gh-contrib]: https://github.com/mscharley/ruby-skype/graphs/contributors
59
+ [gh-issues]: https://github.com/mscharley/ruby-skype/issues
60
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0.pre.1
data/examples/call ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.push('lib', '../lib')
4
+
5
+ require 'skype'
6
+ Skype.DEBUG = true
7
+ skype = Skype.new('ruby-skype-test')
8
+ puts "Using ruby-skype-#{Skype.VERSION}"
9
+ skype.connect
10
+ puts "Connected using protocol version #{skype.protocol_version}"
11
+
12
+ puts "Attempting to call the echo service..."
13
+ skype.send_raw_command('CALL echo123')
14
+ skype.run
data/examples/listen ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This program simply attaches to Skype and runs an event loop. Can be used as a starting point, or a way to
4
+ # listen for new update commands that we may not know about yet.
5
+
6
+ $:.push('lib', '../lib')
7
+
8
+ require 'skype'
9
+ Skype.DEBUG = true
10
+ puts "Using ruby-skype-#{Skype.VERSION}"
11
+ skype = Skype.new('ruby-skype-listen-in')
12
+ skype.connect
13
+ puts "Connected using protocol version #{skype.protocol_version}"
14
+ skype.run
@@ -0,0 +1,26 @@
1
+
2
+ class Skype
3
+ # This class is used to manage updates coming back from Skype.
4
+ #
5
+ # @see #process_message
6
+ class CommandManager
7
+ def initialize(skype)
8
+ @skype = skype
9
+ end
10
+
11
+ def process_command(command)
12
+ (command, args) = command.split(/\s+/, 2)
13
+ command = command.downcase.to_sym
14
+
15
+ self.send(command, args) if self.public_methods.include? command
16
+ end
17
+
18
+ def connstatus(args)
19
+ @skype.send :update_connection_status, args.downcase.to_sym
20
+ end
21
+
22
+ def userstatus(args)
23
+ @skype.send :update_user_status, DataMaps::USER_VISIBILITY.invert[args]
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,104 @@
1
+
2
+ require 'dbus'
3
+ require 'skype/communication/protocol'
4
+
5
+ # Monkey-patch dbus to fix an error till it makes it into a release
6
+ class DBus::Connection
7
+ def update_buffer
8
+ @buffer += @socket.read_nonblock(MSG_BUF_SIZE)
9
+ rescue EOFError
10
+ raise # the caller expects it
11
+ rescue Errno::EWOULDBLOCK
12
+ # simply fail the read if it would block
13
+ return
14
+ rescue Exception => e
15
+ puts "Oops:", e
16
+ raise if @is_tcp # why?
17
+ puts "WARNING: read_nonblock failed, falling back to .recv"
18
+ @buffer += @socket.recv(MSG_BUF_SIZE)
19
+ end
20
+ end
21
+
22
+ class Skype
23
+ module Communication
24
+ # This class handles communication with Skype via DBus.
25
+ #
26
+ # This communication method is available under Linux.
27
+ class DBus
28
+ include Skype::Communication::Protocol
29
+
30
+ # DBus service name to connect to.
31
+ SKYPE_DBUS_SERVICE = 'com.Skype.API'
32
+ # DBus communication path for client -> Skype communication.
33
+ SKYPE_SERVER_PATH = '/com/Skype'
34
+ # Interface for the client -> Skype communication function.
35
+ SKYPE_SERVER_INTERFACE = 'com.Skype.API'
36
+ # DBus communication path for Skype -> client communication.
37
+ SKYPE_CLIENT_PATH = '/com/Skype/Client'
38
+ # Interface for the Skype -> client communication function.
39
+ SKYPE_CLIENT_INTERFACE = 'com.Skype.API.Client'
40
+
41
+ # Create a communication link to Skype via DBus. This initialises DBus,
42
+ # but doesn't attempt to connect to Skype yet.
43
+ #
44
+ # @see #connect.
45
+ def initialize(application_name)
46
+ @application_name = application_name
47
+ @dbus = ::DBus::SessionBus.instance
48
+ @dbus_service = @dbus.service(SKYPE_DBUS_SERVICE)
49
+ @skype = @dbus_service.object(SKYPE_SERVER_PATH)
50
+ @skype.introspect
51
+ @skype.default_iface = SKYPE_SERVER_INTERFACE
52
+ end
53
+
54
+ # Attempt to connect to Skype.
55
+ #
56
+ # For DBus, this includes exporting the client interface and then
57
+ # identifying ourselves and negotiating protocol version.
58
+ #
59
+ # @return [void]
60
+ def connect
61
+ value = @skype.Invoke("NAME " + @application_name)
62
+ unless value == %w{OK}
63
+ Skype::Errors::ExceptionFactory.generate_exception *value
64
+ end
65
+ @protocol_version = @skype.Invoke("PROTOCOL 8")[0].
66
+ sub(/^PROTOCOL\s+/, '').to_i
67
+ @connected = true
68
+ end
69
+
70
+ # Send a command to Skype.
71
+ #
72
+ # @param [string] message The message to send to Skype
73
+ # @return [string] The direct response from Skype
74
+ def send(message)
75
+ unless @connected
76
+ raise "You must be connected before sending data."
77
+ end
78
+ puts "-> #{message}" if ::Skype.DEBUG
79
+ ret = @skype.Invoke(message)[0]
80
+ puts "<- #{ret}" if ::Skype.DEBUG
81
+ ret
82
+ end
83
+
84
+ # Poll DBus for incoming messages. We use this method for watching for
85
+ # our messages as it is simpler, and an event loop is required no matter
86
+ # what.
87
+ #
88
+ # @return [void]
89
+ def tick
90
+ @dbus.update_buffer
91
+ @dbus.messages.each do |msg|
92
+ # Pass messages through DBus
93
+ @dbus.process(msg)
94
+
95
+ # Process messages to us.
96
+ if msg.interface == SKYPE_CLIENT_INTERFACE &&
97
+ msg.path == SKYPE_CLIENT_PATH
98
+ receive(msg.params[0])
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,70 @@
1
+
2
+ require 'observer'
3
+
4
+ class Skype
5
+ # You shouldn't try including this module, it is used purely for organisation.
6
+ # @private
7
+ module Communication
8
+ # Interface for the Skype::Communication::* classes. This provides basic
9
+ # input and output functionality with Skype.
10
+ #
11
+ # Includes Observable. Notifications sent will have a single string
12
+ # parameter which is the incoming command from Skype.
13
+ module Protocol
14
+ include Observable
15
+
16
+ # The protocol version supported by this communication protocol
17
+ #
18
+ # @return [Boolean]
19
+ attr_reader :protocol_version
20
+
21
+ # @!attribute [r] connected?
22
+ # Have we connected to Skype yet?
23
+ #
24
+ # @return [Boolean]
25
+ def connected?
26
+ @connected
27
+ end
28
+
29
+ # Sends a message to Skype.
30
+ #
31
+ # Must be implemented by Protocol implementers.
32
+ #
33
+ # @param [string] message The message to send to Skype
34
+ # @return [string] The direct response from Skype
35
+ # @return [string] The direct response from Skype
36
+ def send(message)
37
+ raise "#send(message) must be implemented."
38
+ end
39
+
40
+ # Connects to Skype.
41
+ #
42
+ # Must be implemented by Protocol implementers.
43
+ #
44
+ # @return [void]
45
+ def connect
46
+ raise "#connect must be implemented"
47
+ end
48
+
49
+ # Update processing. This is where you get a chance to check for input.
50
+ #
51
+ # Should be implemented by Protocol implementers, but no error is thrown
52
+ # if not.
53
+ #
54
+ # @return [void]
55
+ def tick
56
+ end
57
+
58
+ private
59
+
60
+ # Internal method to implement Observable.
61
+ #
62
+ # Protocol implementers may use this to send notifications of incoming
63
+ # commands.
64
+ def receive(message)
65
+ changed
66
+ notify_observers(message)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,365 @@
1
+
2
+ require 'ffi'
3
+
4
+ class Skype
5
+ module Communication
6
+ class Windows
7
+ # This module is used to provide access to the Win32 API to Ruby. There
8
+ # is lots of stuff here that is poorly named but tries stick closely to
9
+ # the original Win32 API for ease of reference.
10
+ #
11
+ # BEWARE: Here there be dragons aplenty.
12
+ module Win32
13
+ extend FFI::Library
14
+ ffi_lib('user32', 'kernel32')
15
+ ffi_convention(:stdcall)
16
+
17
+ private
18
+
19
+ def self._func(*args)
20
+ attach_function *args
21
+ case args.size
22
+ when 3
23
+ module_function args[0]
24
+ when 4
25
+ module_function args[0]
26
+ alias_method(args[1], args[0])
27
+ module_function args[1]
28
+ end
29
+ end
30
+
31
+ # @!group Windows Data Types
32
+ # @see http://msdn.microsoft.com/en-us/library/aa383751%28VS.85%29.aspx
33
+
34
+ # Check for 64b operating systems.
35
+ if [nil].pack('p').bytesize == 8
36
+ ULONG_PTR = FFI::TypeDefs[:uint64]
37
+ LONG_PTR = FFI::TypeDefs[:int64]
38
+ else
39
+ ULONG_PTR = FFI::TypeDefs[:ulong]
40
+ LONG_PTR = FFI::TypeDefs[:long]
41
+ end
42
+
43
+ ULONG = FFI::TypeDefs[:ulong]
44
+ LONG = FFI::TypeDefs[:long]
45
+ PVOID = FFI::TypeDefs[:pointer]
46
+ LPVOID = FFI::TypeDefs[:pointer]
47
+ INT = FFI::TypeDefs[:int]
48
+ BYTE = FFI::TypeDefs[:uint16]
49
+ DWORD = FFI::TypeDefs[:ulong]
50
+ BOOL = FFI::TypeDefs[:int]
51
+ UINT = FFI::TypeDefs[:uint]
52
+ POINTER = FFI::TypeDefs[:pointer]
53
+ VOID = FFI::TypeDefs[:void]
54
+
55
+ HANDLE = ULONG_PTR
56
+ HWND = HANDLE
57
+ HICON = HANDLE
58
+ HCURSOR = HANDLE
59
+ HBRUSH = HANDLE
60
+ HINSTANCE = HANDLE
61
+ HGDIOBJ = HANDLE
62
+ HMENU = HANDLE
63
+ HMODULE = HANDLE
64
+
65
+ LPARAM = LONG_PTR
66
+ WPARAM = ULONG_PTR
67
+ LPMSG = LPVOID
68
+ LPCTSTR = LPVOID
69
+ LRESULT = LONG_PTR
70
+ ATOM = BYTE
71
+
72
+ # @!endgroup
73
+
74
+ public
75
+
76
+ # Provide a NULL constant so we can be a little more explicit.
77
+ NULL = 0
78
+
79
+ # This is the callback function used to process window messages.
80
+ WNDPROC = callback(:WindowProc, [HWND, UINT, WPARAM, LPARAM], LRESULT)
81
+
82
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms633577.aspx MSDN
83
+ class WNDCLASSEX < FFI::Struct
84
+ layout :cbSize, UINT,
85
+ :style, UINT,
86
+ :lpfnWndProc, WNDPROC,
87
+ :cbClsExtra, INT,
88
+ :cbWndExtra, INT,
89
+ :hInstance, HANDLE,
90
+ :hIcon, HICON,
91
+ :hCursor, HCURSOR,
92
+ :hbrBackground, HBRUSH,
93
+ :lpszMenuName, LPCTSTR,
94
+ :lpszClassName, LPCTSTR,
95
+ :hIconSm, HICON
96
+
97
+ def initialize(*args)
98
+ super
99
+ self[:cbSize] = self.size
100
+ @atom = 0
101
+ end
102
+
103
+ # Register class with Windows.
104
+ def register_class_ex
105
+ # According to MSDN, you must add 1 to this value before
106
+ # registering. We shouldn't expect client code to remember to
107
+ # always do this.
108
+ if self[:hbrBackground] > 0
109
+ self[:hbrBackground] = self[:hbrBackground] + 1
110
+ end
111
+
112
+ (@atom = Win32::RegisterClassEx(self)) != 0 ?
113
+ @atom : raise("RegisterClassEx Error")
114
+ end
115
+
116
+ # @!attribute [r] handle
117
+ #
118
+ # Returns a handle to use the windo class with CreateWindowEx()
119
+ def handle
120
+ @atom != 0 ? @atom : register_class_ex
121
+ end
122
+ end # WNDCLASSEX
123
+
124
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/dd162805.aspx
125
+ class POINT < FFI::Struct
126
+ layout :x, LONG,
127
+ :y, LONG
128
+ end
129
+
130
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms644958.aspx
131
+ class MSG < FFI::Struct
132
+ layout :hwnd, HWND,
133
+ :message, UINT,
134
+ :wParam, WPARAM,
135
+ :lParam, LPARAM,
136
+ :time, DWORD,
137
+ :pt, POINT
138
+ end
139
+
140
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms649010.aspx
141
+ class COPYDATASTRUCT < FFI::Struct
142
+ layout :dwData, ULONG_PTR,
143
+ :cbData, DWORD,
144
+ :lpData, PVOID
145
+ end
146
+
147
+ # @!method RegisterWindowMessage(message_name)
148
+ #
149
+ # Registers a Window Message with Windows or returns a handle to an
150
+ # existing message.
151
+ #
152
+ # @param [String] message_name The name of the message to register
153
+ # @return [Handle]
154
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms644947.aspx MSDN
155
+ _func(:RegisterWindowMessage, :RegisterWindowMessageA, [LPCTSTR], UINT)
156
+
157
+ # @!method GetModuleHandle(module_name)
158
+ #
159
+ # Used to obtain a handle to a module loaded by the application. If
160
+ # passed DL::NULL then returns a handle to the current module.
161
+ #
162
+ # @param [String|DL::NULL] module_name The name of the module to return
163
+ # a handle to.
164
+ # @return [ModuleHandle]
165
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms683199.aspx MSDN
166
+ _func(:GetModuleHandle, :GetModuleHandleA, [LPCTSTR], HMODULE)
167
+
168
+ # @!method RegisterClassEx(class_definition)
169
+ #
170
+ # Registers a Window Class for use with CreateWindowEx.
171
+ #
172
+ # @param [WindowClass] class_definition
173
+ # @return [Handle]
174
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms633587.aspx MSDN
175
+ _func(:RegisterClassEx, :RegisterClassExA, [LPVOID], ATOM)
176
+
177
+ # @!method CreateWindowEx(extended_style, window_class, window_name, style, x, y, width, height, parent, menu, instance)
178
+ #
179
+ # Creates a new window.
180
+ #
181
+ # @param [Integer] extended_style A combination of WS_EX_* constant
182
+ # values defining the extended style for this window.
183
+ # @param [String] window_class This matches up with a registered
184
+ # WindowClass's lpszClassName parameter.
185
+ # @param [String] window_name This is the title of the newly created
186
+ # window.
187
+ # @param [Integer] style A combination of the WS_* constant values
188
+ # defining the style for this window.
189
+ # @param [Integer] x The horizontal position of the window on the
190
+ # screen.
191
+ # @param [Integer] y The vertical position of the window on the screen.
192
+ # @param [Integer] width The width of the window to create.
193
+ # @param [Integer] height The height of the window to create.
194
+ # @param [WindowHandle] parent A parent window for this one.
195
+ # @param [MenuHandle] menu The menu for this window.
196
+ # @param [InstanceHandle] instance
197
+ # @return [WindowHandle]
198
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms632680.aspx MSDN
199
+ _func(:CreateWindowEx, :CreateWindowExA, [DWORD, LPCTSTR, LPCTSTR,
200
+ DWORD, INT, INT, INT, INT,
201
+ HWND, HMENU, HINSTANCE,
202
+ LPVOID],
203
+ HWND)
204
+
205
+ # @!method GetMessage(message, window, filter_min, filter_max)
206
+ #
207
+ # Get a message from the message queue. Blocks until there is one to
208
+ # return. Compare with PeekMessage().
209
+ #
210
+ # @param [MSG] message **[out]** A message structure to output the
211
+ # incoming message to.
212
+ # @param [WindowHandle] window Which window to get messages for.
213
+ # @param [Integer] filter_min The first message to return, numerically.
214
+ # For suggestions, see MSDN.
215
+ # @param [Integer] filter_max The last message to return, numerically.
216
+ # If min and max are both 0 then all messages are returned. For
217
+ # suggestions, see MSDN.
218
+ # @return [Integer] -1 on error, otherwise 0 or 1 indicating whether to
219
+ # keep processing.
220
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms644936.aspx MSDN
221
+ _func(:GetMessage, :GetMessageA, [LPMSG, HWND, UINT, UINT], BOOL)
222
+
223
+ # @!method PeekMessage(message, window, filter_min, filter_max, remove_message)
224
+ #
225
+ # Peek at a message from the message queue and optionally remove it.
226
+ # Never blocks. Compare with GetMessage().
227
+ #
228
+ # @param [MSG] message **[out]** A message structure to output the
229
+ # incoming message to.
230
+ # @param [WindowHandle] window Which window to get messages for.
231
+ # @param [Integer] filter_min The first message to return, numerically.
232
+ # For suggestions, see MSDN.
233
+ # @param [Integer] filter_max The last message to return, numerically.
234
+ # If min and max are both 0 then all messages are returned. For
235
+ # suggestions, see MSDN.
236
+ # @param [Integer] remove_message One of the PM_* values.
237
+ # @return [Integer] 0 or 1 indicating whether to keep processing.
238
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms644943.aspx MSDN
239
+ _func(:PeekMessage, :PeekMessageA, [LPMSG, HWND, UINT, UINT, UINT], BOOL)
240
+
241
+ # @!method SendMessage(window, message, wParam, lParam)
242
+ #
243
+ # Send a message to another window.
244
+ #
245
+ # @param [WindowHandle] window Which window to send the message to.
246
+ # @param [Integer] message WM_* message to send.
247
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms644950.aspx MSDN
248
+ _func(:SendMessage, :SendMessageA, [HWND, UINT, WPARAM, LPARAM], LRESULT)
249
+
250
+ # @!method PostMessage(window, message, wParam, lParam)
251
+ #
252
+ # Send a message to another window and return without waiting for a reply.
253
+ #
254
+ # @param [WindowHandle] window Which window to send the message to.
255
+ # @param [Integer] message WM_* message to send.
256
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms644950.aspx MSDN
257
+ _func(:PostMessage, :PostMessageA, [HWND, UINT, WPARAM, LPARAM], LRESULT)
258
+
259
+ # @!method TranslateMessage(message)
260
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms644955.aspx MSDN
261
+ _func(:TranslateMessage, [LPVOID], BOOL)
262
+
263
+ # @!method DispatchMessage(message)
264
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms644934.aspx MSDN
265
+ _func(:DispatchMessage, :DispatchMessageA, [LPVOID], BOOL)
266
+
267
+ # @!method DefWindowProc(message, window, filter_min, filter_max)
268
+ # @return [Object] Return value is dependant on message type
269
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms633572.aspx MSDN
270
+ _func(:DefWindowProc, :DefWindowProcA, [HWND, UINT, WPARAM, LPARAM], LRESULT)
271
+
272
+ # @!group Predefined WindowHandle's
273
+ #
274
+ # These are WindowHandle's provided by the Win32 API for special
275
+ # purposes.
276
+
277
+ # Target for SendMessage(). Broadcast to all windows.
278
+ HWND_BROADCAST = 0xffff
279
+ # Used as a parent in CreateWindow(). Signifies that this should be a
280
+ # message-only window.
281
+ HWND_MESSAGE = -3
282
+
283
+ # @!endgroup
284
+
285
+ # CreateWindow Use Default Value
286
+ CW_USEDEFAULT = 0x80000000
287
+
288
+ #@!group HBRUSH colours
289
+
290
+ # System window colour
291
+ COLOR_WINDOW = 5
292
+
293
+ # @!group Class Style contants.
294
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ff729176.aspx MSDN
295
+
296
+ # Redraws the entire window if a movement or size adjustment changes
297
+ # the height of the client area.
298
+ CS_VREDRAW = 0x0001
299
+ # Redraws the entire window if a movement or size adjustment changes
300
+ # the width of the client area.
301
+ CS_HREDRAW = 0x0002
302
+
303
+ # @!group Window Message constants
304
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms644927.aspx#system_defined MSDN
305
+
306
+ # An application sends the WM_COPYDATA message to pass data to another
307
+ # application.
308
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms649011.aspx MSDN
309
+ WM_COPYDATA = 0x004A
310
+
311
+ # @!group PeekMessage constants
312
+
313
+ # Messages are not removed from the queue after processing by
314
+ # #PeekMessage().
315
+ PM_NOREMOVE = 0
316
+ # Messages are removed from the queue after processing by
317
+ # #PeekMessage().
318
+ PM_REMOVE = 1
319
+
320
+ # @!group Window Style constants
321
+ #
322
+ # This is only a subset.
323
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms632600.aspx
324
+
325
+ # The window has a thin-line border.
326
+ WS_BORDER = 0x00800000
327
+ # The window has a title bar
328
+ WS_CAPTION = 0x00C00000
329
+ # The window is initially disabled. A disabled window cannot receive
330
+ # input from the user.
331
+ WS_DISABLED = 0x08000000
332
+ # The window is an overlapped window. An overlapped window has a title
333
+ # bar and a border.
334
+ WS_OVERLAPPED = 0x00000000
335
+ # The windows is a pop-up window.
336
+ WS_POPUP = 0x80000000
337
+ # The window has a sizing border.
338
+ WS_SIZEBOX = 0x00040000
339
+ # The window has a window menu on its title bar.
340
+ WS_SYSMENU = 0x00080000
341
+ # The window has a sizing border.
342
+ WS_THICKFRAME = 0x00040000
343
+ # The window has a maximize button.
344
+ WS_MAXIMIZEBOX = 0x00010000
345
+ # The window has a minimize button.
346
+ WS_MINIMIZEBOX = 0x00020000
347
+ # The window is an overlapped window.
348
+ WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
349
+ WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
350
+ # The window is a pop-up window.
351
+ WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU
352
+
353
+ # @!group Window Extended Style constants
354
+ #
355
+ # This is only a subset.
356
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ff700543.aspx
357
+
358
+ # The window has generic left-aligned properties. This is the default.
359
+ WS_EX_LEFT = 0
360
+
361
+ # @!endgroup
362
+ end
363
+ end
364
+ end
365
+ end
@@ -0,0 +1,200 @@
1
+
2
+ require 'skype/communication/protocol'
3
+ require 'skype/communication/windows/win32'
4
+
5
+ class Skype
6
+ module Communication
7
+ # Utilises the Windows API to send and receive Window Messages to/from
8
+ # Skype.
9
+ #
10
+ # This protocol is only available on Windows and Cygwin.
11
+ class Windows
12
+ include Skype::Communication::Protocol
13
+
14
+ # Sets up access to Skype
15
+ #
16
+ # @see http://msdn.microsoft.com/en-us/library/bb384843.aspx Creating
17
+ # Win32-Based Applications
18
+ def initialize(application_name)
19
+ @application_name = application_name
20
+
21
+ # Get the message id's for the Skype Control messages
22
+ @api_discover_message_id =
23
+ Win32::RegisterWindowMessage('SkypeControlAPIDiscover')
24
+ @api_attach_message_id =
25
+ Win32::RegisterWindowMessage('SkypeControlAPIAttach')
26
+
27
+ instance = Win32::GetModuleHandle(nil)
28
+
29
+ @window_class = Win32::WNDCLASSEX.new
30
+ @window_class[:style] = Win32::CS_HREDRAW | Win32::CS_VREDRAW
31
+ @window_class[:lpfnWndProc] = method(:message_pump)
32
+ @window_class[:hInstance] = instance
33
+ @window_class[:hbrBackground] = Win32::COLOR_WINDOW
34
+ @window_class[:lpszClassName] =
35
+ FFI::MemoryPointer.from_string 'ruby-skype'
36
+
37
+ @window = Win32::CreateWindowEx(Win32::WS_EX_LEFT,
38
+ FFI::Pointer.new(@window_class.handle),
39
+ 'ruby-skype',
40
+ Win32::WS_OVERLAPPEDWINDOW,
41
+ 0, 0, 0, 0, Win32::NULL, Win32::NULL,
42
+ instance, nil)
43
+ end
44
+
45
+ # Connects to Skype.
46
+ #
47
+ # @return [void]
48
+ def connect
49
+ # Do setup before sending message as Windows will process messages as
50
+ # well while in SendMessage()
51
+ @msg = Win32::MSG.new
52
+ @authorized = nil
53
+ @message_counter = 0
54
+ @replies = {}
55
+
56
+ Win32::PostMessage(Win32::HWND_BROADCAST, @api_discover_message_id,
57
+ @window, 0)
58
+ end
59
+
60
+ # Update processing.
61
+ #
62
+ # This executes a Windows event loop while there are messages still
63
+ # pending, then dumps back out to let other things take over and do their
64
+ # thing.
65
+ #
66
+ # @return [void]
67
+ def tick
68
+ while Win32::PeekMessage(@msg, Win32::NULL, 0, 0, Win32::PM_REMOVE) > 0
69
+ Win32::TranslateMessage(@msg)
70
+ Win32::DispatchMessage(@msg)
71
+ end
72
+
73
+ # Don't simplify this as we rely on false != nil for tribool values
74
+ #noinspection RubySimplifyBooleanInspection
75
+ if @authorized == false
76
+ Skype::Errors::ExceptionFactory.generate_exception("ERROR 68")
77
+ end
78
+ end
79
+
80
+ # Sends a message to Skype.
81
+ #
82
+ # @param [string] message The message to send to Skype
83
+ # @return [string] The direct response from Skype
84
+ def send(message)
85
+ puts "-> #{message}" if Skype.DEBUG
86
+
87
+ counter = next_message_counter
88
+ message = "##{counter} #{message}"
89
+
90
+ data = Win32::COPYDATASTRUCT.new
91
+ data[:dwData] = 0
92
+ data[:cbData] = message.length + 1
93
+ data[:lpData] = FFI::MemoryPointer.from_string(message + "\0")
94
+
95
+ Win32::SendMessage(@skype_window, Win32::WM_COPYDATA, @window,
96
+ pointer_to_long(data.to_ptr))
97
+
98
+ while @replies[counter].nil?
99
+ tick
100
+ sleep(0.1)
101
+ end
102
+
103
+ ret = @replies[counter]
104
+ @replies.delete(counter)
105
+ ret
106
+ end
107
+
108
+ # Attached to Skype successfully.
109
+ API_ATTACH_SUCCESS = 0
110
+ # Skype indicated that we should hold on.
111
+ API_ATTACH_PENDING = 1
112
+ # Attachment to Skype was refused.
113
+ API_ATTACH_REFUSED = 2
114
+ # Attachment to Skype isn't available currently. Typically there is no
115
+ # user logged in.
116
+ API_ATTACH_NOT_AVAILABLE = 3
117
+
118
+ private
119
+
120
+ def next_message_counter
121
+ @message_counter += 1
122
+ end
123
+
124
+ LPARAM_BITS = Win32::LPARAM.size * 8
125
+
126
+ # Convert a ulong pointer value to a long int for use as a LPARAM because
127
+ # someone at Microsoft thought it'd be a good idea to pass around
128
+ # pointers as signed values.
129
+ def pointer_to_long(pointer)
130
+ pointer = pointer.to_i
131
+ pointer > (2 ** (LPARAM_BITS - 1)) ?
132
+ pointer - (2 ** LPARAM_BITS) : pointer
133
+ end
134
+
135
+ # Allows us to unwrap a pointer from a long. See #pointer_to_long
136
+ def long_to_pointer(long)
137
+ long < 0 ? long + (2 ** LPARAM_BITS) : long
138
+ end
139
+
140
+ # This is our message pump that receives messages from Windows.
141
+ #
142
+ # The return value from DefWindowProc is important and must be returned
143
+ # somehow.
144
+ #
145
+ # @see
146
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms633573.aspx
147
+ # MSDN
148
+ def message_pump(window_handle, message_id, wParam, lParam)
149
+ case message_id
150
+ when @api_discover_message_id
151
+ # Drop WM_API_DISCOVER messages on the floor
152
+ when @api_attach_message_id
153
+ case lParam
154
+ when API_ATTACH_SUCCESS
155
+ @skype_window = wParam
156
+ send("NAME " + @application_name)
157
+ @protocol_version = send("PROTOCOL 8").sub(/^PROTOCOL\s+/, '').
158
+ to_i
159
+ @authorized = true
160
+
161
+ when API_ATTACH_REFUSED
162
+ # Signal to the message pump that we were deauthorised
163
+ @authorized = false
164
+
165
+ else
166
+ # Ignore pending signal
167
+ puts "WM: Ignoring WM_API_ATTACH response: #{lParam}" if
168
+ Skype.DEBUG
169
+
170
+ end
171
+ when Win32::WM_COPYDATA
172
+ pointer = FFI::Pointer.new(long_to_pointer(lParam))
173
+ data = Win32::COPYDATASTRUCT.new pointer
174
+
175
+ input = data[:lpData].read_string(data[:cbData] - 1)
176
+ if input[0] == '#'
177
+ (counter, input) = input.split(/\s+/, 2)
178
+ counter = counter.gsub(/^#/, '').to_i
179
+ else
180
+ counter = nil
181
+ end
182
+
183
+ if counter.nil?
184
+ receive(input)
185
+ else
186
+ @replies[counter] = input
187
+ puts "<- #{input}" if Skype.DEBUG
188
+ end
189
+
190
+ # Let Skype know we got it successfully
191
+ return 1
192
+ else
193
+ puts "Unhandled WM: #{sprintf("0x%04x", message_id)}" if Skype.DEBUG
194
+ return Win32::DefWindowProc(
195
+ window_handle, message_id, wParam, lParam)
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,18 @@
1
+
2
+ class Skype
3
+ module DataMaps
4
+ # Data mapping between user visibilities as returned by Skype and the
5
+ # symbols that this API cares about.
6
+ USER_VISIBILITY = {
7
+ :unknown => 'UNKNOWN',
8
+ :online => 'ONLINE',
9
+ :offline => 'OFFLINE',
10
+ :skype_me => 'SKYPEME',
11
+ :away => 'AWAY',
12
+ :not_available => 'NA',
13
+ :do_not_disturb => 'DND',
14
+ :invisible => 'INVISIBLE',
15
+ :logged_out => 'LOGGEDOUT',
16
+ }
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+
2
+ class Skype
3
+ # You shouldn't try including this module, it is used purely for organisation.
4
+ # @private
5
+ module DataMaps
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+
2
+ class Skype
3
+ module Errors
4
+ # This is a map of error codes to error messages used by ExceptionFactory
5
+ # to generate it's messages.
6
+ #
7
+ # @see http://developer.skype.com/public-api-reference#ERRORS Skype Public
8
+ # API error codes
9
+ ERROR_MESSAGES = {
10
+ 68 => 'Access denied',
11
+ }
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+
2
+ require 'skype/errors/error_messages'
3
+ require 'skype/errors/general_error'
4
+
5
+ class Skype
6
+ # You shouldn't try including this module, it is used purely for organisation.
7
+ # @private
8
+ module Errors
9
+ # This class allows the library to generate exceptions based on error codes
10
+ # from Skype easily.
11
+ class ExceptionFactory
12
+ # This function will raise an exception. It never returns.
13
+ #
14
+ # @param [String] error_message The ERROR #error_id response from Skype.
15
+ def self.generate_exception(error_message)
16
+ data = error_message.match(/^ERROR\s+(\d+)(?:\s+(.+))?$/)
17
+ error_code = data[1].to_i
18
+ exception_message = ERROR_MESSAGES[error_code] || data[2] ||
19
+ "Unknown error: #{error_code}"
20
+
21
+ raise ::Skype::Errors::GeneralError.new(error_code), exception_message,
22
+ caller
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+
2
+ class Skype
3
+ module Errors
4
+ # A generalised Exception. This adds an error code field which holds the
5
+ # numeric error code as returned by Skype. The Exception's message is a
6
+ # plain text version of the error code, usually suitable for display to
7
+ # users.
8
+ class GeneralError < Exception
9
+ # This is the error code reported by Skype.
10
+ attr_reader :code
11
+
12
+ # Create a new GeneralError with the specified code. The error message
13
+ # should be provided to raise from ERROR_MESSAGES
14
+ def initialize(error_code)
15
+ @code = error_code
16
+ end
17
+ end
18
+ end
19
+ end
data/lib/skype.rb ADDED
@@ -0,0 +1,213 @@
1
+
2
+ require 'skype/command_manager'
3
+ require 'skype/errors/exception_factory'
4
+ require 'skype/data_maps/user_visibility'
5
+
6
+ # This class is the main interface between Ruby and Skype.
7
+ class Skype
8
+ private
9
+
10
+ def platform
11
+ @platform ||=
12
+ case RbConfig::CONFIG['host_os']
13
+ when /mingw|cygwin|mswin/
14
+ :windows
15
+ when /linux/
16
+ :linux
17
+ else
18
+ :unknown
19
+ end
20
+ end
21
+
22
+ public
23
+
24
+ # Initialises the Skype library and sets up a communication protocol, but
25
+ # doesn't connect yet.
26
+ #
27
+ # @param [String] application_name Name to use when identifying to Skype. Not
28
+ # all platforms use this value as Skype will automatically assign a name.
29
+ def initialize(application_name, communication_protocol = nil)
30
+ if communication_protocol.nil?
31
+ case platform
32
+ when :windows
33
+ require 'skype/communication/windows'
34
+ @skype = Skype::Communication::Windows.new(application_name)
35
+ when :linux
36
+ require 'skype/communication/dbus'
37
+ @skype = Skype::Communication::DBus.new(application_name)
38
+ else
39
+ puts "Unfortunately, we don't support your platform currently."
40
+ puts "Please file an issue if you think this is incorrect."
41
+ exit 1
42
+ end
43
+ else
44
+ @skype = communication_protocol
45
+ end
46
+
47
+ @command_manager = CommandManager.new(self)
48
+ @skype.add_observer(self, :received_command)
49
+ end
50
+
51
+ # Controls whether the library should output extra debugging information or
52
+ # not. Currently controls whether we should output all network throughput.
53
+ #
54
+ # @return [Boolean]
55
+ def self.DEBUG
56
+ @debug_mode
57
+ end
58
+
59
+ # (see DEBUG)
60
+ def self.DEBUG=(value)
61
+ @debug_mode = value
62
+ end
63
+
64
+ # Returns the currently in use version of the ruby-skype library.
65
+ #
66
+ # @return [String]
67
+ def self.VERSION
68
+ @version ||=
69
+ IO.read(File.join(File.dirname(__FILE__), '..', 'VERSION')).chomp
70
+ end
71
+
72
+ # Connect to Skype and negotiate a communication channel. Blocks till the
73
+ # connection is fully established.
74
+ #
75
+ # @return [Symbol] Initial value for user_status.
76
+ def connect
77
+ @skype.connect
78
+
79
+ until @user_status
80
+ tick
81
+ sleep(0.1)
82
+ end
83
+ @user_status
84
+ end
85
+
86
+ # Are we connected to Skype?
87
+ #
88
+ # @return [Boolean]
89
+ def connected?
90
+ @skype.connected?
91
+ end
92
+
93
+ # Execute a single run of the Skype event loop
94
+ #
95
+ # @return [void]
96
+ def tick
97
+ @skype.tick
98
+ nil
99
+ end
100
+
101
+ # Executes the Skype event loops. Doesn't return unless #quit is called.
102
+ #
103
+ # @return [void]
104
+ def run
105
+ @finished = false
106
+ until @finished
107
+ tick
108
+ sleep(0.1)
109
+ end
110
+ end
111
+
112
+ # Stops the Skype event loop from running.
113
+ #
114
+ # @return [void]
115
+ def quit
116
+ @finished = true
117
+ end
118
+
119
+ # Sends a raw command to Skype
120
+ #
121
+ # @param [String] command The command to send
122
+ # @return [String] The value returned by Skype
123
+ # @api private
124
+ def send_raw_command(command)
125
+ send_message(command)
126
+ end
127
+
128
+ #######################
129
+ ### ###
130
+ ### BEGIN SKYPE API ###
131
+ ### ###
132
+ #######################
133
+
134
+ # Network connection status.
135
+ #
136
+ # Valid values:
137
+ #
138
+ # * `:offline`
139
+ # * `:connecting`
140
+ # * `:pausing`
141
+ # * `:online`
142
+ #
143
+ # @return [Symbol]
144
+ # @api skype
145
+ attr_reader :connection_status
146
+
147
+ # @!attribute [rw] user_status
148
+ # User visibility for the current user.
149
+ #
150
+ # Valid values:
151
+ #
152
+ # * `:unknown`
153
+ # * `:online`
154
+ # * `:offline`
155
+ # * `:skype_me`
156
+ # * `:away`
157
+ # * `:not_available`
158
+ # * `:do_not_disturb`
159
+ # * `:invisible`
160
+ #
161
+ # @return [Symbol]
162
+ # @api skype
163
+ def user_status
164
+ @user_status
165
+ end
166
+
167
+ # (see #user_status)
168
+ def user_status=(value)
169
+ send_message("SET USERSTATUS " + DataMaps::USER_VISIBILITY[value])
170
+ nil
171
+ end
172
+
173
+ # The protocol version in use for the connection with Skype. This value is
174
+ # only reliable once connected.
175
+ #
176
+ # @return [Integer] The version number of the protocol in use.
177
+ def protocol_version
178
+ @skype.protocol_version
179
+ end
180
+
181
+ protected
182
+
183
+ # Callback for receiving updates from Skype.
184
+ #
185
+ # @param [String] command The command string to process.
186
+ # @return [void]
187
+ def received_command(command)
188
+ @command_manager.process_command(command)
189
+ puts "<= #{command}" if ::Skype.DEBUG
190
+ end
191
+
192
+ def update_user_status(status)
193
+ @user_status = status
194
+ end
195
+
196
+ def update_connection_status(status)
197
+ @connection_status = status
198
+ end
199
+
200
+ private
201
+
202
+ # Handles sending messages and handling possible errors returned by Skype
203
+ #
204
+ # @param [String] message The message to send to Skype
205
+ # @return [String] The reply from Skype or throws an exception on an error
206
+ def send_message(message)
207
+ ret = @skype.send(message)
208
+ if ret[0,6] == "ERROR "
209
+ Errors::ExceptionFactory.generate_exception(ret)
210
+ end
211
+ ret
212
+ end
213
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-skype
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.pre.1
5
+ prerelease: 6
6
+ platform: mswin32
7
+ authors:
8
+ - Matthew Scharley
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: yard
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
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: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
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: cane
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
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: ffi
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
+ description: ! " ruby-skype is a binding to the Skype Public API. The Public API
79
+ is a method\n to talk to the official Skype client running on the local computer
80
+ and\n allows for automation of the client.\n"
81
+ email: matt.scharley@gmail.com
82
+ executables: []
83
+ extensions: []
84
+ extra_rdoc_files: []
85
+ files:
86
+ - lib/skype/errors/exception_factory.rb
87
+ - lib/skype/errors/error_messages.rb
88
+ - lib/skype/errors/general_error.rb
89
+ - lib/skype/data_maps.rb
90
+ - lib/skype/data_maps/user_visibility.rb
91
+ - lib/skype/command_manager.rb
92
+ - lib/skype/communication/protocol.rb
93
+ - lib/skype/communication/windows/win32.rb
94
+ - lib/skype/communication/dbus.rb
95
+ - lib/skype/communication/windows.rb
96
+ - lib/skype.rb
97
+ - VERSION
98
+ - README.md
99
+ - LICENSE
100
+ - examples/call
101
+ - examples/listen
102
+ homepage: https://github.com/mscharley/ruby-skype
103
+ licenses:
104
+ - MIT
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ~>
113
+ - !ruby/object:Gem::Version
114
+ version: 1.9.0
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ! '>'
119
+ - !ruby/object:Gem::Version
120
+ version: 1.3.1
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 1.8.24
124
+ signing_key:
125
+ specification_version: 3
126
+ summary: Ruby binding to the Skype Public API.
127
+ test_files: []
128
+ has_rdoc: