diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..fa75df1
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,3 @@
+source 'https://rubygems.org'
+
+gemspec
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000..a3463c4
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,16 @@
+PATH
+ remote: .
+ specs:
+ skyper (0.1.0)
+ rb-appscript (>= 0.3.0)
+
+GEM
+ remote: https://rubygems.org/
+ specs:
+ rb-appscript (0.6.1)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ skyper!
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..45e4b29
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2012 Andriy Dmytrenko
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
index f508b4e..be8ccc9 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,34 @@
-skyper
-======
+# Skyper
-Ruby Skype API
\ No newline at end of file
+A ruby gem, implementing Skype public API.
+https://developer.skype.com/public-api-reference
+
+Currently works only on MacOS X, using AppleScript to connect to Skype.
+Inspired by rb-skypemac.
+
+
+## Installation
+
+Add this line to your application's Gemfile:
+
+ gem 'skyper'
+
+And then execute:
+
+ $ bundle
+
+Or install it yourself as:
+
+ $ gem install skyper
+
+## Usage
+
+TODO: Write usage instructions here
+
+## Contributing
+
+1. Fork it
+2. Create your feature branch (`git checkout -b my-new-feature`)
+3. Commit your changes (`git commit -am 'Added some feature'`)
+4. Push to the branch (`git push origin my-new-feature`)
+5. Create new Pull Request
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..f57ae68
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,2 @@
+#!/usr/bin/env rake
+require "bundler/gem_tasks"
diff --git a/lib/skyper.rb b/lib/skyper.rb
new file mode 100644
index 0000000..0b31c9b
--- /dev/null
+++ b/lib/skyper.rb
@@ -0,0 +1,8 @@
+$:.unshift File.expand_path('../',__FILE__)
+module Skyper
+ autoload :SkypeObject, 'skyper/skype_object'
+ autoload :User, 'skyper/user'
+ autoload :Chat, 'skyper/chat'
+ autoload :Group, 'skyper/group'
+end
+Dir[File.join(File.dirname(__FILE__), 'skyper/**/*.rb')].sort.each { |lib| require lib }
diff --git a/lib/skyper/call.rb b/lib/skyper/call.rb
new file mode 100644
index 0000000..99e916a
--- /dev/null
+++ b/lib/skyper/call.rb
@@ -0,0 +1,50 @@
+module Skyper
+ # Represents a Skype call. Developer is responsible for calling Call#hangup at the end of each call, whether it was placed
+ # by the caller or is an answered call.
+ class Call < SkypeObject
+ property_reader :TIMESTAMP => Time, :PARTNER_HANDLE => Skyper::User, :RATE => Integer
+ property_reader *%w[PARTNER_DISPNAME TARGET_IDENTITY CONF_ID TYPE STATUS VIDEO_STATUS VIDEO_SEND_STATUS FAILUREREASON SUBJECT
+ PSTN_NUMBER DURATION PSTN_STATUS CONF_PARTICIPANTS_COUNT VM_DURATION VM_ALLOWED_DURATION RATE_CURRENCY RATE_PRECISION INPUT OUTPUT CAPTURE_MIC VAA_INPUT_STATUS
+ FORWARDED_BY TRANSFER_ACTIVE TRANSFER_STATUS TRANSFERRED_BY TRANSFERRED_TO]
+ self.object_name = "CALL"
+ class << self
+ def active_call_ids
+ r = Skype.send_command "SEARCH ACTIVECALLS"
+ r.gsub(/CALLS /, "").split(", ")
+ end
+
+ def active_calls
+ Call.active_call_ids.map { |id| Call.new id unless id == "COMMAND_PENDING"}
+ end
+
+ def create(*users)
+ user_handles = users.map { |u| (u.is_a? User) ? u.handle : u }
+ status = Skype.send_command "CALL #{user_handles.join(', ')}"
+ if status =~ /CALL (\d+) STATUS/
+ call = Call.new($1)
+ else
+ raise RuntimeError.new("Call failed. Skype returned '#{status}'")
+ end
+ end
+ end
+
+ # Attempts to hang up a call. Note: If Skype hangs while placing the call, this method could hang indefinitely.
+ # Use Skype#Call instead of this method unless you like memory leaks.
+ # Raises SkypeError if an error is reported from the Skype API
+ def hangup
+ self.status = "FINISHED"
+ end
+
+ def send_video(toggle_flag)
+ alter("#{bool_to_flag(toggle_flag)}_VIDEO_SEND")
+ end
+
+ def rcv_video(toggle_flag)
+ alter("#{bool_to_flag(toggle_flag)}_VIDEO_RECEIVE")
+ end
+ protected
+ def bool_to_flag(bool)
+ bool ? "START" : "STOP"
+ end
+ end
+end
diff --git a/lib/skyper/chat.rb b/lib/skyper/chat.rb
new file mode 100644
index 0000000..0f09844
--- /dev/null
+++ b/lib/skyper/chat.rb
@@ -0,0 +1,42 @@
+module Skyper
+ class Chat < SkypeObject
+ property_accessor *%w[NAME TIMESTAMP ADDER POSTERS TOPIC TOPICXML ACTIVEMEMBERS FRIENDLYNAME BOOKMARKED MEMBEROBJECTS PASSWORDHINT GUIDELINES
+ OPTIONS DESCRIPTION DIALOG_PARTNER ACTIVITY_TIMESTAMP TYPE MYSTATUS MYROLE BLOB APPLICANTS]
+ property_reader :RECENTCHATMESSAGES => Array
+ property_reader :STATUS, :MEMBERS, :CHATMESSAGES
+ self.object_name = "CHAT"
+ attr_reader :id
+
+ class << self
+ # create by user_handle
+ def create(user_handle)
+ r = Skype.send_command "CHAT CREATE #{user_handle}"
+ Chat.parse_from r
+ end
+
+ # Returns an array of your Skype recent chats
+ def recent_chats
+ r = Skype.send_command "SEARCH RECENTCHATS"
+ chat_ids = parse_type(r.sub(/^CHATS\s+/, ""), Array)
+ chat_ids.map do |id|
+ Chat.new(id)
+ end
+ end
+ end
+
+ # chat message to chat
+ def chat_message(message)
+ r = Skype.send_command "CHATMESSAGE #{id} #{message}"
+ ChatMessage.parse_from(r)
+ end
+
+ def recent_chat_messages
+ self.recentchatmessages.map {|id| ChatMessage.new(id)}
+ end
+
+ def set_topic(topic)
+ alter('SETTOPIC', topic)
+ end
+
+ end
+end
diff --git a/lib/skyper/chat_message.rb b/lib/skyper/chat_message.rb
new file mode 100644
index 0000000..604e8b7
--- /dev/null
+++ b/lib/skyper/chat_message.rb
@@ -0,0 +1,7 @@
+module Skyper
+ class ChatMessage < Skyper::SkypeObject
+ property_accessor :BODY, :FROM_DISPNAME, :TYPE, :STATUS, :LEAVEREASON, :CHATNAME, :IS_EDITABLE, :EDITED_BY, :EDITED_TIMESTAMP, :OPTIONS, :ROLE
+ property_reader :TIMESTAMP => Time, :FROM_HANDLE => Skyper::User, :USERS => Array
+ self.object_name = "CHATMESSAGE"
+ end
+end
diff --git a/lib/skyper/group.rb b/lib/skyper/group.rb
new file mode 100644
index 0000000..88a4ffa
--- /dev/null
+++ b/lib/skyper/group.rb
@@ -0,0 +1,31 @@
+module Skyper
+ class Group < SkypeObject
+ #TYPE: {ALL | CUSTOM | HARDWIRED | SHARED_GROUP | PROPOSED_SHARED_GROUP}
+ property_reader :TYPE, :CUSTOM_GROUP_ID, :NROFUSERS, :NROFUSERS_ONLINE, :USERS
+ property_accessor :DISPLAYNAME
+ self.object_name = "GROUP"
+
+ class << self
+ # Returns hash of symols (group types) => Group objects
+ def groups
+ r = Skype.send_command "SEARCH GROUPS ALL"
+ r.gsub!(/^\D+/, "")
+ group_ids = r.split /,\s*/
+ group_ids.map do |id|
+ Group.new(id)
+ end
+ end
+ end
+
+ # Returns array of skype names of users in this group
+ def member_user_names
+ r = users
+ r && r.sub(/^.*USERS /, "").split(", ") or []
+ end
+
+ # Returns array of Users in this Group
+ def user_objects
+ member_user_names.map { |h| User.new h }
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/skyper/iam.rb b/lib/skyper/iam.rb
new file mode 100644
index 0000000..4819f2a
--- /dev/null
+++ b/lib/skyper/iam.rb
@@ -0,0 +1,18 @@
+module Skyper
+
+ # Singleton for managing Skype user status
+ class Iam
+ @@STATUSES = [:ONLINE, :OFFLINE, :SKYPEME, :AWAY, :NA, :DND, :INVISIBLE]
+
+ def Iam.set_user_status(status)
+ raise NoMethodError.new("#{status} in #{Iam.to_s}") if not @@STATUSES.index status.upcase.to_sym
+ Skype.send_command "SET USERSTATUS #{status}"
+ end
+
+ # Handles all of the user status permutations accepted by Skype otherwise Errors.
+ # For example, Iam.away is legal.
+ def Iam.method_missing(id)
+ Iam.set_user_status(id.id2name)
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/skyper/platform/mac.rb b/lib/skyper/platform/mac.rb
new file mode 100644
index 0000000..578efed
--- /dev/null
+++ b/lib/skyper/platform/mac.rb
@@ -0,0 +1,26 @@
+module Skyper
+ module Mac
+ class Connection
+ attr_accessor :logger
+ attr_reader :options
+
+ def initialize(options={})
+ @options = options
+ @logger ||= Logger.new(STDOUT).tap do |logger|
+ logger.level = Logger::ERROR
+ end
+ end
+
+ # @param[String] command Skype command
+ def send_command(command)
+ params = {:script_name => options[:script_name].to_s, :command => command}
+ logger.debug "#{self.class}##{__method__} params: #{params.inspect}"
+ response = Appscript::app('Skype').send_ params
+ response.tap do
+ logger.debug "#{self.class}##{__method__} response: #{response}"
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/skyper/profile.rb b/lib/skyper/profile.rb
new file mode 100644
index 0000000..06a572f
--- /dev/null
+++ b/lib/skyper/profile.rb
@@ -0,0 +1,9 @@
+module Skyper
+ class Profile < SkypeObject
+ property_reader *%w[PSTN_BALANCE PSTN_BALANCE_CURRENCY SMS_VALIDATED_NUMBERS]
+ property_accessor *%w[FULLNAME SEX LANGUAGES COUNTRY IPCOUNTRY PROVINCE CITY PHONE_HOME PHONE_OFFICE PHONE_MOBILE HOMEPAGE ABOUT MOOD_TEXT RICH_MOOD_TEXT TIMEZONE
+ CALL_APPLY_CF CALL_NOANSWER_TIMEOUT CALL_FORWARD_RULES CALL_SEND_TO_VM]
+ property_accessor :BIRTHDAY => Time
+ self.object_name = "PROFILE"
+ end
+end
diff --git a/lib/skyper/skype.rb b/lib/skyper/skype.rb
new file mode 100644
index 0000000..0992f19
--- /dev/null
+++ b/lib/skyper/skype.rb
@@ -0,0 +1,95 @@
+require 'appscript'
+require 'logger'
+require 'singleton'
+require 'forwardable'
+
+module Skyper
+
+ # Singleton for interfacing with Skype
+ class Skype
+ include Singleton
+ attr_accessor :script_name
+ attr_reader :connection
+
+ def initialize
+ @connection = Skyper::Mac::Connection.new
+ send_command("PROTOCOL 8")
+ end
+
+ # @param[String] command Skype command
+ def send_command(command)
+ @connection.send_command(command)
+ end
+
+ def profile
+ @profile ||= Profile.new(nil)
+ end
+
+ # Initiates a Skype call
+ def call(*users)
+ Call.create(*users)
+ end
+
+ # Returns an Array of Call IDs if there is an incoming Skype call otherwise nil
+ def incoming_calls
+ Call.active_calls
+ end
+
+ # Answers a call given a skype Call ID. Returns an Array of Call objects.
+ def answer(call)
+ cmd = "ALTER CALL #{call.call_id} ANSWER"
+ r = Skype.send_command cmd
+ raise RuntimeError("Failed to answer call. Skype returned '#{r}'") unless r == cmd
+ end
+
+ # Returns an Array of Group
+ def groups
+ @groups ||= Group.groups
+ end
+
+ # Returns Array of all User in a particular Group type. Accepts types as defined by Group.types
+ def find_users_of_type(group_type)
+ groups.find { |g| g.type == group_type}.users
+ end
+
+ # Returns an array of users online friends as User objects
+ def online_friends
+ find_users_of_type "ONLINE_FRIENDS"
+ end
+
+ # Array of all User that are friends of the current user
+ def all_friends
+ find_users_of_type "ALL_FRIENDS"
+ end
+
+ # Array of all User defined as Skype Out users
+ def skypeout_friends
+ find_users_of_type "SKYPEOUT_FRIENDS"
+ end
+
+ # Array of all User that the user knows
+ def all_users
+ find_users_of_type "ALL_USERS"
+ end
+
+ # Array of User recently contacted by the user, friends or not
+ def recently_contacted_users
+ find_users_of_type "RECENTLY_CONTACTED_USERS"
+ end
+
+ # Array of User waiting for authorization
+ def users_waiting_for_authorization
+ find_users_of_type "USERS_WAITING_MY_AUTHORIZATION"
+ end
+
+ # Array of User blocked
+ def blocked_users
+ find_users_of_type "USERS_BLOCKED_BY_ME"
+ end
+ class << self
+ extend Forwardable
+ def_delegators :instance, *Skyper::Skype.instance_methods(false)
+ end
+ end
+
+end
diff --git a/lib/skyper/skype_error.rb b/lib/skyper/skype_error.rb
new file mode 100644
index 0000000..ea8b9de
--- /dev/null
+++ b/lib/skyper/skype_error.rb
@@ -0,0 +1,4 @@
+module Skyper
+ class SkypeError #
+ def parse_from(response)
+ match_data = response.match %r/^#{@object_name} ([^\ ]+)/
+ raise SkypeError, "Can not parse response: '#{response}'" unless match_data
+ self.new(match_data[1])
+ end
+
+ def parse_type(value, type)
+ return value unless value && type
+ return type.call(value) if type.is_a? Proc
+ case type.to_s
+ when "Integer" || "Fixnum"
+ value.to_i
+ when "Array"
+ value.split(/,\s*/)
+ when "Time"
+ Time.at(value.to_i)
+ when "bool"
+ value == "TRUE"
+ else
+ if type.superclass == Skyper::SkypeObject
+ type.new(value)
+ else
+ value
+ end
+ end
+ end
+
+ protected
+ def extract_options!(array)
+ opts = if array.last.is_a? Hash
+ array.pop
+ else
+ {}
+ end
+ opts.merge! Hash[array.zip(Array.new(array.size))]
+ end
+
+ end # class << self
+
+ end
+end
\ No newline at end of file
diff --git a/lib/skyper/sms.rb b/lib/skyper/sms.rb
new file mode 100644
index 0000000..3d5a818
--- /dev/null
+++ b/lib/skyper/sms.rb
@@ -0,0 +1,21 @@
+module Skyper
+ class SMS < SkypeObject
+ property_accessor *%w[BODY TYPE REPLY_TO_NUMBER TARGET_NUMBERS]
+ property_reader *%w[STATUS FAILUREREASON IS_FAILED_UNSEEN PRICE_CURRENCY TARGET_STATUSES]
+ property_reader :TIMESTAMP => Time, :PRICE => Integer, :PRICE_PERICISION => Integer
+ self.object_name = "SMS"
+
+ class << self
+ # @params [Symbol] type one of following: OUTGOING CONFIRMATION_CODE_REQUEST CONFIRMATION_CODE_SUBMIT
+ def create(target, type=:OUTGOING)
+ r = Skyper::Skype.send_command("CREATE SMS #{type.to_s.upcase} #{target}") #"SMS 354924 STATUS COMPOSING"
+ self.parse_from(r)
+ end
+ end
+
+ def send_sms
+ alter("SEND")
+ end
+
+ end
+end
\ No newline at end of file
diff --git a/lib/skyper/user.rb b/lib/skyper/user.rb
new file mode 100644
index 0000000..a2e1d54
--- /dev/null
+++ b/lib/skyper/user.rb
@@ -0,0 +1,15 @@
+module Skyper
+ class User < SkypeObject
+ property_reader :IS_VIDEO_CAPABLE => :bool, :IS_VOICEMAIL_CAPABLE => :bool, :IS_CF_ACTIVE => :bool, :HASCALLEQUIPMENT => :bool, :LASTONLINETIMESTAMP => Time
+ property_reader *%w[HANDLE FULLNAME BIRTHDAY SEX LANGUAGE COUNTRY PROVINCE CITY PHONE_HOME PHONE_OFFICE PHONE_MOBILE HOMEPAGE ABOUT
+ ONLINESTATUS SkypeOut SKYPEME CAN_LEAVE_VM RECEIVEDAUTHREQUEST MOOD_TEXT
+ RICH_MOOD_TEXT ALIASES TIMEZONE NROF_AUTHED_BUDDIES]
+ property_accessor *%w[BUDDYSTATUS ISBLOCKED ISAUTHORIZED SPEEDDIAL DISPLAYNAME]
+ self.object_name = "USER"
+
+ def chat
+ Chat.create id
+ end
+
+ end
+end
diff --git a/lib/skyper/version.rb b/lib/skyper/version.rb
new file mode 100644
index 0000000..d38dc64
--- /dev/null
+++ b/lib/skyper/version.rb
@@ -0,0 +1,3 @@
+module Skyper
+ VERSION = "0.1.0"
+end
diff --git a/skyper.gemspec b/skyper.gemspec
new file mode 100644
index 0000000..c203fb8
--- /dev/null
+++ b/skyper.gemspec
@@ -0,0 +1,18 @@
+# -*- encoding: utf-8 -*-
+require File.expand_path('../lib/skyper/version', __FILE__)
+
+Gem::Specification.new do |gem|
+ gem.authors = ["Andriy Dmytrenko"]
+ gem.email = ["refresh.xss@gmail.com"]
+ gem.description = %q{Ruby interface to Skype on Mac OS X}
+ gem.summary = %q{Ruby interface to Skype on Mac OS X}
+ gem.homepage = ""
+
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ gem.files = `git ls-files`.split("\n")
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ gem.name = "skyper"
+ gem.require_paths = ["lib"]
+ gem.version = Skyper::VERSION
+ gem.add_runtime_dependency 'rb-appscript', '>=0.3.0'
+end