voipfone_client 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 37f340ce592c3b388bdfb7c98340793eeb79deb7
4
- data.tar.gz: 7ccd3c5a76eb5901acb6e3c4b9266a4acce49b1a
3
+ metadata.gz: 674d936e84e8ce57b7812a00109d9a0b3c9215e1
4
+ data.tar.gz: 2b20d0fbbf04be97aadd6e6ed045104e1381b071
5
5
  SHA512:
6
- metadata.gz: 94d775d0d8379aaf9deac0e4b714970bf926e4c10ce9d83cceda82bcb3cd53a95d3406210dca66cc7ab5318f0f767b6dd5426ff80b332746c946893f838c2710
7
- data.tar.gz: e95d03921ab1de9ec1558358dce8bee2d87ac53022326e3993ecfd3215d18f36ff7771aef9db52e66ac55f55b9614dc9f29c23fa6dba5b4fa27a096aa1f64e43
6
+ metadata.gz: 81af18eb50031ea7abd7a30cfe8d4143ff1545af826bafba7512931c7cac09e2f6dac6bec03717967fc88aa0482c77095d4211b79ba0a7db914bf67cb7e52ceb
7
+ data.tar.gz: 3e779ee451c959c3136598f32d1c643dfc5b45f5df8c1fe2a3dd5dba78f1dc8535932f7ab686fee2f49aa9c1b9d48084d4e13afae48a3229971979cf8cb79688
data/README.md CHANGED
@@ -4,6 +4,11 @@ A gem to programatically manipulate your [Voipfone](http://www.voipfone.co.uk) a
4
4
 
5
5
  Voipfone is a brilliant SIP VOIP provider with a very neat set of features, but unfortunately no API. So this gem hopefully fills that gap by using the same JSON API their web interface uses.
6
6
 
7
+ ## A quick note about using your account balance
8
+ This is rather obvious, but let's say it anyway! This client uses your Voipfone account. Sending SMS messages, setting diverts, setting up calls and other functions will use up your account balance! We're not magically doing stuff for free here.
9
+
10
+ [Here's a page about Voipfone's charges](https://www.voipfone.co.uk/prices_new.php). We think they're very good value.
11
+
7
12
  ## Installation
8
13
 
9
14
  This gem is in Rubygems, so you can add this line to your application's Gemfile:
@@ -20,29 +25,98 @@ Or you can manually install the gem using:
20
25
 
21
26
  ##Configuration and Use
22
27
 
23
- Before you can instantiate a `VoipfoneClient::Client` object, you need to need to configure it:
28
+ Before you can use the gem you need to configure it:
24
29
 
25
30
  ```ruby
26
31
  VoipfoneClient.configure do |config|
27
32
  config.username = "your@email.address"
28
33
  config.password = "yourpass"
34
+ config.user_agent_string = "something" #default is set to "VoipfoneClient/[version] http://github.com/errorstudio/voipfone_client" in voipfone_client.rb
29
35
  end
30
36
  ```
31
37
 
32
38
  This approach gives us lots of options for adding more config options in the future.
33
39
 
34
- After that you can create a new object to call Voipfone:
40
+ After you've done that, there are various classes which you can use to access different parts of your Voipfone Account.
41
+
42
+ ### VoipfoneClient::Account
43
+ The `Voipfone::Account` class has methods relating to your account:
44
+
45
+ * `balance()` returns your balance as a float
46
+ * `details` returns a hash of your account details (email, name etc)
47
+ * `phone numbers()` returns an array of phone numbers associated with your account
48
+ * `voicemail()` gives you a list of your voicemails, but not (yet) the audio. Pull requests welcome!
49
+
50
+ ### Voipfone::RegisteredMobile
51
+ The `Voipfone::RegisteredMobile` class only has one class method at the moment, `Voipfone::RegisteredMobile.all()` which returns an array of `Voipfone::RegisteredMobile` objects. These respond to `number()` and `name()`.
52
+
53
+ These are particularly useful to know for sending SMS messages, because the 'from' number has to be in this list. You can't send an SMS from an arbitrary number.
54
+
55
+ ```ruby
56
+
57
+ include VoipfoneClient
58
+ mobiles = RegisteredMobile.all #this will be an array
59
+ m = mobiles.first
60
+ m.name #this will be the name of the mobile
61
+ m.number #this will be the number of the mobile
62
+
63
+ ```
64
+
65
+ ### Voipfone::SMS
66
+ The `Voipfone::SMS` class allows you to send SMS messages from your account.
67
+
68
+ You need to make sure that you're sending _from_ a number which is in the list of registered mobiles.
69
+
70
+ ``` ruby
71
+ include VoipfoneClient
72
+ s = SMS.new
73
+ s.from = "[sender number]" # a number which is in the list of registered mobiles at voipfone
74
+ s.to = "[recipient number]" #your recipient number
75
+ s.message = "your message" #message is truncated at 160 chars; UTF-8 not supported.
76
+
77
+ Spaces are stripped from phone numbers (which need to be supplied as a string); international format with a + symbol is OK.
78
+
79
+ ### Voipfone::GlobalDivert
80
+ The `Voipfone::GlobalDivert` class handles diverts for your whole account (all extensions).
81
+
82
+ Diverts are one of four __types__ in Voipfone, which we describe thus:
83
+
84
+ * `:all` - divert all the time
85
+ * `:fail` - divert when something's gone wrong with your VOIP equipment
86
+ * `:busy` - divert when all lines are busy
87
+ * `:noanswer` - divert when there's no answer on any line (we haven't experimented about the relationship between this and voicemail)
88
+
89
+ You can get a list of all the diverts on your account with the `Voipfone::GlobalDivert.all()` class method, which returns an array of diverts, each with their type and number.
90
+
91
+ To create a divert, you need to set a __type__ and a __number__:
35
92
 
36
93
  ```ruby
37
- c = VoipfoneClient::Client.new
38
- c.account_balance #will return your balance as a float
94
+
95
+ include VoipfoneClient
96
+ d = GlobalDivert.new
97
+ d.type = :all # divert for all calls - e.g. your whole team is out of the office; our most common use-case
98
+ d.number = "your number as a strong" # this can be any phone number
99
+ d.save # will return true on success
100
+
39
101
  ```
102
+ In the Voipfone web interface you have to set up a list of divert recipients (and you can do that in this client too), but you don't _need_ to - you can set any number for a divert. As with all other numbers you set, it should be passed in as a string, spaces will be stripped, the + symbol is allowed.
103
+
104
+ ##To Do
105
+
106
+ There's quite a list of stuff which would be nice to do. If you feel inclined, we'd love pull requests.
107
+
108
+ * Find a way to get the audio for voicemails
109
+ * per-extension diverts
110
+ * Setting up calls
111
+ * Downloading call records
112
+ * Downloading invoices
113
+
40
114
 
41
115
  ## Contributing
42
116
 
43
117
  We'd love your input!
44
118
 
45
- 1. Fork it ( http://github.com/errorstudio/voipfone_client/fork )
119
+ 1. Fork the repo ( http://github.com/errorstudio/voipfone_client/fork )
46
120
  2. Create your feature branch (`git checkout -b my-new-feature`)
47
121
  3. Commit your changes (`git commit -am 'Add some feature'`)
48
122
  4. Push to the branch (`git push origin my-new-feature`)
@@ -0,0 +1,29 @@
1
+ module VoipfoneClient
2
+ class Account < Session
3
+ # Return the balance of the account as a float.
4
+ # @return [Float] Account balance as a float. Should be rounded to 2dp before presentation.
5
+ def balance
6
+ request = @browser.get("#{VoipfoneClient::API_GET_URL}?balance&builder")
7
+ parse_response(request)["balance"]
8
+ end
9
+
10
+ def details
11
+ request = @browser.get("#{VoipfoneClient::API_GET_URL}?account")
12
+ parse_response(request)
13
+ end
14
+
15
+ # Return the phone numbers for this account, as strings
16
+ # @return [Array] Phone numbers as strings.
17
+ def phone_numbers
18
+ request = @browser.get("#{VoipfoneClient::API_GET_URL}?nums")
19
+ parse_response(request)["nums"].collect {|n| n.first}
20
+ end
21
+
22
+ #Return a list of voicemail entries, with details for each
23
+ #@return [Hash] of voicemail information. This includes a reference to a WAV file which isn't accessible (yet?)
24
+ def voicemail
25
+ request = @browser.get("#{VoipfoneClient::API_GET_URL}?vm_view")
26
+ parse_response(request)["vm_view"].first["voicemail"]
27
+ end
28
+ end
29
+ end
@@ -1,8 +1,8 @@
1
1
  module VoipfoneClient
2
- class DivertListItem < Client
2
+ class DivertListItem < Session
3
3
  attr_accessor :name, :number
4
4
 
5
- # Constructor for `DivertListItem` which accepts the name and number of the phone number to add to the diverts list.
5
+ # Constructor for {DivertListItem} which accepts the name and number of the phone number to add to the diverts list.
6
6
  # @param name [String] the name of the phone to be diverted to
7
7
  # @param number [String] the number of the phone to be diverted to.
8
8
  def initialize(name: nil, number: nil)
@@ -14,7 +14,7 @@ module VoipfoneClient
14
14
  # Add a new number to the list of numbers which can be diverted to. Requires a name
15
15
  # and a phone number, which will have spaces stripped from it. May be in international
16
16
  # format.
17
- # @return [Boolean] true on success or a failure message (in which case a `VoipfoneAPIError` will be raised)
17
+ # @return [Boolean] true on success or a failure message (in which case a {VoipfoneAPIError} will be raised)
18
18
  def save
19
19
  if @name.nil? || @number.nil?
20
20
  raise ArgumentError, "You need to include a name and number to add to the diverts list"
@@ -7,11 +7,11 @@ module VoipfoneClient
7
7
  # - when there is a failure in the phone system
8
8
  # - when the phone(s) are busy
9
9
  # - when there's no answer
10
- class GlobalDivert < Client
10
+ class GlobalDivert < Session
11
11
  attr_accessor :type, :number
12
12
 
13
- # Constructor for `GlobalDivert` which allows you to pass in a `DivertListItem` to be used as the receiving number.
14
- # @param divert_list_item [DivertListItem] a `DivertListItem` object which will be used to fill the `GlobalDivert` number.
13
+ # Constructor for {GlobalDivert} which allows you to pass in a {DivertListItem} to be used as the receiving number.
14
+ # @param divert_list_item [DivertListItem] a {DivertListItem} object which will be used to fill the {GlobalDivert} number.
15
15
  # You still need to specify the divert type.
16
16
  def initialize(divert_list_item = nil)
17
17
  if divert_list_item.is_a?(DivertListItem)
@@ -30,7 +30,7 @@ module VoipfoneClient
30
30
  }
31
31
 
32
32
  #Save the divert on your account
33
- # @return [Boolean] true on success, or a failure message (in which case a `VoipfoneAPIError` will be raised)
33
+ # @return [Boolean] true on success, or a failure message (in which case a {VoipfoneAPIError} will be raised)
34
34
  def save
35
35
  if @type.nil? || @number.nil?
36
36
  raise ArgumentError, "You need to set a divert type and divert number before you can save the divert."
@@ -39,13 +39,13 @@ module VoipfoneClient
39
39
  end
40
40
 
41
41
  #Clear all diverts
42
- # @return [Boolean] true on success, or a failure message (in which case a `VoipfoneAPIError` will be raised)
42
+ # @return [Boolean] true on success, or a failure message (in which case a {VoipfoneAPIError} will be raised)
43
43
  def clear!
44
44
  set_diverts()
45
45
  end
46
46
  class << self
47
47
  # Get current diverts
48
- # @return [Array] A nested set of arrays with divert information for each type of divert currently set
48
+ # @return [Array] An array of {GlobalDivert} objects with divert information for each type of divert currently set
49
49
  def all
50
50
  g = self.new
51
51
  request = g.browser.get("#{VoipfoneClient::API_GET_URL}?divertsMain")
@@ -56,6 +56,13 @@ module VoipfoneClient
56
56
  divert
57
57
  end
58
58
  end
59
+
60
+ # Clear all diverts - this is a class method
61
+ # @return [Boolean] true on success, or a failure message (in which case a {VoipfoneAPIError} will be raised)
62
+ def clear!
63
+ g = self.new
64
+ g.clear!
65
+ end
59
66
  end
60
67
 
61
68
  private
@@ -70,7 +77,7 @@ module VoipfoneClient
70
77
  # @param fail [String] The number to which calls will be diverted in the event of a failure
71
78
  # @param busy [String] The number to which calls will be diverted if the phones are busy
72
79
  # @param no_answer [String] The number to which calls will be diverted if there's no answer
73
- # @return [Boolean] true on success, or a failure message (in which case a `VoipfoneAPIError` will be raised)
80
+ # @return [Boolean] true on success, or a failure message (in which case a {VoipfoneAPIError} will be raised)
74
81
  def set_diverts(all: nil, fail: nil, busy: nil, no_answer: nil)
75
82
  all ||= ""
76
83
  fail ||= ""
@@ -1,5 +1,5 @@
1
1
  module VoipfoneClient
2
- class RegisteredMobile < Client
2
+ class RegisteredMobile < Session
3
3
  attr_accessor :number, :name
4
4
 
5
5
  class << self
@@ -0,0 +1,74 @@
1
+ module VoipfoneClient
2
+ class Session
3
+ attr_reader :browser
4
+ # Intantiates a new Voipfone client, with username and password from the [VoipfoneClient::Configuration] class
5
+ # @return [VoipfoneClient::Client] the object created
6
+ def initialize
7
+ if VoipfoneClient.configuration.username.nil? || VoipfoneClient.configuration.password.nil?
8
+ raise LoginCredentialsMissing, "You need to include a username and password to log in."
9
+ end
10
+ @browser = Mechanize.new
11
+ @browser.user_agent = VoipfoneClient.configuration.user_agent_string
12
+ login()
13
+ end
14
+
15
+ # check if the user is authenticated. This is a little convoluted because the voipfone private API
16
+ # always returns '200 OK', but returns 'authfirst' for every response if there is no authentication.
17
+ # So we make a request to a simple endpoint and expect an empty hash; if we get 'authfirst' we're not authenticated
18
+ # @return [Boolean] to identify whether we're authenticated or not.
19
+ def authenticated?
20
+ request = @browser.get("#{VoipfoneClient::API_GET_URL}?builder")
21
+ response = parse_response(request)["builder"]
22
+ if response == "authfirst"
23
+ return false
24
+ else
25
+ return true
26
+ end
27
+ end
28
+
29
+ # login to Voipfone, using the configured username and password.
30
+ # Unless explicitly specified, this method caches cookies on disk to
31
+ # allow classes inheriting {Voipfone::Session} to use these instead of
32
+ # logging in each time.
33
+ # @return [Boolean] true on success or {NotAuthenticatedError} on failure
34
+ def login
35
+ username = VoipfoneClient.configuration.username
36
+ password = VoipfoneClient.configuration.password
37
+ cookie_file = File.join(VoipfoneClient::TMP_FOLDER,"cookies")
38
+
39
+ # load existing cookies from the file on disk
40
+ if File.exists?(cookie_file)
41
+ @browser.cookie_jar.load(cookie_file)
42
+ end
43
+
44
+ # if we're authenticated at this point, we're done.
45
+ return true if authenticated?
46
+
47
+ # …otherwise we need to login to the service
48
+ login_url = "#{VoipfoneClient::BASE_URL}/login.php?method=process"
49
+ @browser.post(login_url,{"hash" => "urlHash", "login" => username, "password" => password})
50
+
51
+ # If we're authenticated at this point, save the cookies and return true
52
+ if authenticated?
53
+ @browser.cookie_jar.save(cookie_file, session: true)
54
+ return true
55
+ # otherwise, we've tried to authenticate and failed, which means we have a
56
+ # bad username / password combo and it's time to raise an error.
57
+ else
58
+ raise NotAuthenticatedError, "Username or Password weren't accepted."
59
+ end
60
+ end
61
+
62
+ # Responses from the private Voipfone API are always in the form ["message", {content}]
63
+ # We will strip the message (hopefully "OK"), raise if not OK, and return the content.
64
+ # @param request [JSON] The raw request response from the Voipfone API
65
+ # @return [Hash] the parsed JSON
66
+ def parse_response(request)
67
+ raw = JSON.parse(request.body)
68
+ unless raw.first == "ok" || raw.first == "OK"
69
+ raise VoipfoneAPIError, raw.first
70
+ end
71
+ raw.last
72
+ end
73
+ end
74
+ end
@@ -1,5 +1,5 @@
1
1
  module VoipfoneClient
2
- class SMS < Client
2
+ class SMS < Session
3
3
  attr_accessor :from, :to, :message
4
4
 
5
5
  # Constructor to create an SMS - optionally pass in to, from and message
@@ -1,3 +1,3 @@
1
1
  module VoipfoneClient
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -5,8 +5,9 @@ require_rel 'voipfone_client'
5
5
 
6
6
  module VoipfoneClient
7
7
  BASE_URL = "https://www.voipfone.co.uk"
8
- API_GET_URL = "https://www.voipfone.co.uk/api/srv"
9
- API_POST_URL = "https://www.voipfone.co.uk/api/upd"
8
+ API_GET_URL = "#{BASE_URL}/api/srv"
9
+ API_POST_URL = "#{BASE_URL}/api/upd"
10
+ TMP_FOLDER = File.join(File.dirname(__FILE__),"..","/tmp")
10
11
 
11
12
  class << self
12
13
  attr_accessor :configuration
@@ -23,8 +24,11 @@ module VoipfoneClient
23
24
  class Configuration
24
25
  attr_accessor :username, :password, :user_agent_string
25
26
 
27
+ # Configuration initializer, to set up default configuration options
26
28
  def initialize
27
-
29
+
30
+ # By default we set the user agent string to "VoipfoneClient/[version] http://github.com/errorstudio/voipfone_client"
31
+ @user_agent_string = "VoipfoneClient/#{VoipfoneClient::VERSION} (http://github.com/errorstudio/voipfone_client)"
28
32
  end
29
33
  end
30
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: voipfone_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Error Creative Studio
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-11 00:00:00.000000000 Z
11
+ date: 2014-05-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -96,17 +96,14 @@ files:
96
96
  - README.md
97
97
  - Rakefile
98
98
  - lib/voipfone_client.rb
99
- - lib/voipfone_client/account_balance.rb
100
- - lib/voipfone_client/account_details.rb
101
- - lib/voipfone_client/client.rb
99
+ - lib/voipfone_client/account.rb
102
100
  - lib/voipfone_client/divert_list_item.rb
103
101
  - lib/voipfone_client/errors.rb
104
102
  - lib/voipfone_client/global_divert.rb
105
- - lib/voipfone_client/phone_numbers.rb
106
103
  - lib/voipfone_client/registered_mobile.rb
104
+ - lib/voipfone_client/session.rb
107
105
  - lib/voipfone_client/sms.rb
108
106
  - lib/voipfone_client/version.rb
109
- - lib/voipfone_client/voicemail.rb
110
107
  - voipfone_client.gemspec
111
108
  homepage: https://github.com/errorstudio/voipfone_client
112
109
  licenses:
@@ -1,8 +0,0 @@
1
- class VoipfoneClient::Client
2
- # Return the balance of the account as a float.
3
- # @return [Float] Account balance as a float. Should be rounded to 2dp before presentation.
4
- def account_balance
5
- request = @browser.get("#{VoipfoneClient::API_GET_URL}?balance&builder")
6
- parse_response(request)["balance"]
7
- end
8
- end
@@ -1,8 +0,0 @@
1
- class VoipfoneClient::Client
2
- # Return the basic account details, as stored by Voipfone.
3
- # @return [Hash] of account details
4
- def account_details
5
- request = @browser.get("#{VoipfoneClient::API_GET_URL}?account")
6
- parse_response(request)
7
- end
8
- end
@@ -1,34 +0,0 @@
1
- module VoipfoneClient
2
- class Client
3
- attr_reader :browser
4
- # Intantiates a new Voipfone client, with username and password from the `VoipfoneClient::Configuration` class
5
- # @return [VoipfoneClient::Client] the object created
6
- def initialize()
7
- if VoipfoneClient.configuration.username.nil? || VoipfoneClient.configuration.password.nil?
8
- raise LoginCredentialsMissing, "You need to include a username and password to log in."
9
- end
10
- username = VoipfoneClient.configuration.username
11
- password = VoipfoneClient.configuration.password
12
- @browser = Mechanize.new
13
- login_url = "#{VoipfoneClient::BASE_URL}/login.php?method=process"
14
- @browser.post(login_url,{"hash" => "urlHash", "login" => username, "password" => password})
15
- #We'll do a call to a cheap endpoint (the balance) and if it returns 'authfirst', there's a problem
16
- #with the username / password. Oddly it still returns 200 OK.
17
- if account_balance == "authfirst"
18
- raise NotAuthenticatedError, "Username or Password weren't accepted. You'll need to instantiate a new VoipfoneClient::Client object."
19
- end
20
- end
21
-
22
- # Responses from the private Voipfone API are always in the form ["message", {content}]
23
- # We will strip the message (hopefully "OK"), raise if not OK, and return the content.
24
- # @param request [JSON] The raw request response from the Voipfone API
25
- # @return [Hash] the parsed JSON
26
- def parse_response(request)
27
- raw = JSON.parse(request.body)
28
- unless raw.first == "ok" || raw.first == "OK"
29
- raise VoipfoneAPIError, raw.first
30
- end
31
- raw.last
32
- end
33
- end
34
- end
@@ -1,10 +0,0 @@
1
- module VoipfoneClient
2
- class Client
3
- # Return the phone numbers for this account, as strings
4
- # @return [Array] Phone numbers as strings.
5
- def phone_numbers
6
- request = @browser.get("#{VoipfoneClient::API_GET_URL}?nums")
7
- parse_response(request)["nums"].collect {|n| n.first}
8
- end
9
- end
10
- end
@@ -1,8 +0,0 @@
1
- class VoipfoneClient::Client
2
- #Return a list of voicemail entries, with details for each
3
- #@return [Hash] of voicemail information. This includes a reference to a WAV file which isn't accessible (yet?)
4
- def voicemail
5
- request = @browser.get("#{VoipfoneClient::API_GET_URL}?vm_view")
6
- parse_response(request)["vm_view"].first["voicemail"]
7
- end
8
- end