ya-api-direct 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ce703501aca78c8a889a4882c95500b2bb419c30
4
+ data.tar.gz: 2b2be4b799ae9a28908fd44a362635f76e7cd99d
5
+ SHA512:
6
+ metadata.gz: 70218a0c73fc096cbee98e6ad145739338cf52e1f6e94143bb8c263f950a8ac781dcef3fdfff1350c57d2e1bb5642cd0c51c8f1df54331efb6875bb1f728fa02
7
+ data.tar.gz: 7431e2c5e3428697130ad840001c05157813035e4b09465e27ae015f1023ddf5ca069dcbed26e46fec02ccb631ec91757c0f3fd69b6a9412937dc06de6733a63
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016 Alexander Teut
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # ya-api-direct
2
+ A Ruby interface to Yandex Direct API
3
+
4
+ ## Installation
5
+
6
+ Add this line to your application's Gemfile:
7
+
8
+ gem 'ya-api-direct'
9
+
10
+ And then execute:
11
+
12
+ $ bundle
13
+
14
+ Or install it yourself as:
15
+
16
+ $ gem install ya_direct_api
17
+
18
+ Test with rake:
19
+
20
+ $ rake
21
+
22
+ ## Usage
23
+
24
+ OAuth2 Yandex token is needed. Use [omniauth-yandex](https://github.com/evrone/omniauth-yandex/) to receive it. Install it according by following manual: [Rails Authentication with OAuth 2.0 and OmniAuth](https://www.sitepoint.com/rails-authentication-oauth-2-0-omniauth/).
25
+
26
+ Call methods of Yandex Direct API 5:
27
+
28
+ ```ruby
29
+ request = {
30
+ "SelectionCriteria" => {
31
+ "Types" => ["TEXT_CAMPAIGN"]
32
+ },
33
+ "FieldNames" => ["Id", "Name"],
34
+ "TextCampaignFieldNames" => ["BiddingStrategy"]
35
+ }
36
+
37
+ @direct = Ya::API::Direct::Client.new({ token: Token })
38
+ json = direct.campaigns.get request
39
+ ```
40
+
41
+ Call methods from versions 4 and 4 Live:
42
+
43
+ ```ruby
44
+ @direct = Ya::API::Direct::Client.new({ token: Token })
45
+ json = direct.v4.GetCampaignsList []
46
+ ```
47
+
48
+ All names of controllers and methods are equal to ones from Direct API help. 4 vs 4 Live is autodetected.
49
+
50
+ ## Caching
51
+
52
+ By default the client class sends request to clients. You can avoid it by turning this option off, like this:
53
+
54
+
55
+ ```ruby
56
+ @direct = Ya::API::Direct::Client.new({ token: Token, cache: false })
57
+ ```
58
+
59
+ Check [Yandex manuals](https://yandex.ru/adv/edu/direct-api/kak-ispolzovat-api-effektivno-ogranicheniya-i-rekomendatsii) for more info on caching.
60
+
61
+ Date returned with first call of caching method is stored in ``cache_timestamp`` property.
62
+
63
+ ## Units data
64
+
65
+ Units data of last request is stored in ``units_data`` property. There're 3 keys in this hash:
66
+ * ``just_used``
67
+ * ``units_left``
68
+ * ``units_limit``
69
+
70
+ ## Useful links
71
+
72
+ * [Intruduction to Yandex Direct API](https://yandex.ru/adv/edu/direct-api)
73
+ * [Yandex Direct API 5 manual](https://tech.yandex.ru/direct/doc/dg/concepts/about-docpage/)
74
+ * [Yandex Direct API 4 and 4 Live manual](https://tech.yandex.ru/direct/doc/dg-v4/concepts/About-docpage/)
75
+
76
+ ## Contributing
77
+
78
+ 1. Fork it ( http://github.com/rikkimongoose/ya-api-direct/fork )
79
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
80
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
81
+ 4. Push to the branch (`git push origin my-new-feature`)
82
+ 5. Create new Pull Request
@@ -0,0 +1,91 @@
1
+ require "ya/api/direct/constants"
2
+ require "ya/api/direct/gateway"
3
+ require "ya/api/direct/direct_service_v4"
4
+ require "ya/api/direct/direct_service_v5"
5
+ require "ya/api/direct/exception"
6
+
7
+ module Ya::API::Direct
8
+ AllowedAPIVersions = [:v5, :v4]
9
+
10
+ # Yandex Direct API client class
11
+ # @author RikkiMongoose
12
+ class Client
13
+ attr_reader :cache_timestamp, :units_data, :gateway,
14
+ :v4, :v5
15
+
16
+ # Initializes a new Client object
17
+ #
18
+ # @param config [Hash]
19
+ # @return [Ya::API::Direct::Client]
20
+ def initialize(config = {})
21
+ @config = {
22
+ token: nil,
23
+ app_id: nil,
24
+ login: '',
25
+ locale: 'en',
26
+ mode: :sandbox,
27
+ format: :json,
28
+ cache: true,
29
+ api: :v5,
30
+ ssl: true
31
+ }.merge(config)
32
+
33
+ @units_data = {
34
+ just_used: nil,
35
+ units_left: nil,
36
+ units_limit: nil
37
+ }
38
+
39
+ raise "Token can't be empty" if @config[:token].nil?
40
+ raise "Allowed Yandex Direct API versions are #{AllowedVersions}" unless AllowedAPIVersions.include? @config[:api]
41
+
42
+ @gateway = Ya::API::Direct::Gateway.new @config
43
+
44
+ init_v4
45
+ init_v5
46
+ start_cache! if @config[:cache]
47
+ yield self if block_given?
48
+ end
49
+
50
+ # Update units data.
51
+ #
52
+ # @param format [Hash] New units data values
53
+ # @return [Hash] Updated user data values
54
+ def update_units_data(units_data = {})
55
+ @units_data.merge! units_data
56
+ end
57
+
58
+ # Start caching. Executed automatically, if @congif[:cache] is true.
59
+ #
60
+ # @return [String] New timestamp value.
61
+ def start_cache!
62
+ case @config[:api]
63
+ when :v4
64
+ result = @gateway.request("GetChanges", {}, nil, :v4live)
65
+ timestamp = result[:data]['data']['Timestamp']
66
+ when :v5
67
+ result = @gateway.request("checkDictionaries", {}, "changes", :v5)
68
+ timestamp = result[:data]['result']['Timestamp']
69
+ end
70
+ @cache_timestamp = Time.parse(timestamp)
71
+ update_units_data result[:units_data]
72
+ @cache_timestamp
73
+ end
74
+
75
+ private
76
+
77
+ def init_v4
78
+ @v4 = DirectServiceV4.new self, (API_V4 + API_V4_LIVE)
79
+ end
80
+
81
+ def init_v5
82
+ @v5 = {}
83
+ API_V5.each do |service, methods|
84
+ service_item = DirectServiceV5.new(self, service, methods)
85
+ service_key = service_item.service_url
86
+ @v5[service_key] = service_item
87
+ self.class.send :define_method, service_key do @v5[service_key] end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,136 @@
1
+ module Ya
2
+ module API
3
+ module Direct
4
+ API_V5 = {
5
+ "Campaigns" => [
6
+ "add", "update", "delete", "suspend", "resume", "archive", "unarchive", "get"
7
+ ],
8
+ "AdGroups" => [
9
+ "add", "update", "delete", "get"
10
+ ],
11
+ "Ads" => [
12
+ "add", "update", "delete", "suspend", "resume", "archive", "unarchive", "moderate", "get"
13
+ ],
14
+ "Bids" => [
15
+ "set", "setAuto", "get"
16
+ ],
17
+ "BidModifiers" => [
18
+ "add", "delete", "get", "set", "toggle"
19
+ ],
20
+ "Campaigns" => [
21
+ "add", "update", "delete", "suspend", "resume", "archive", "unarchive", "get"
22
+ ],
23
+ "Changes" => [
24
+ "checkDictionaries", "checkCampaigns", "check"
25
+ ],
26
+ "Dictionaries" => [
27
+ "get"
28
+ ],
29
+ "DynamicTextAdTargets" => [
30
+ "add", "delete", "suspend", "resume", "get", "setBids"
31
+ ],
32
+ "Keywords" => [
33
+ "add", "update", "delete", "suspend", "resume", "get"
34
+ ],
35
+ "Sitelinks" => [
36
+ "add", "get", "delete"
37
+ ],
38
+ "VCards" => [
39
+ "add", "get", "delete"
40
+ ]
41
+ }
42
+
43
+ API_V4 = [
44
+ "GetBalance",
45
+ "GetSummaryStat",
46
+ "CreateNewReport",
47
+ "DeleteReport",
48
+ "GetReportList",
49
+
50
+ "CreateNewWordstatReport",
51
+ "DeleteWordstatReport",
52
+ "GetWordstatReport",
53
+ "GetWordstatReportList",
54
+
55
+ "GetKeywordsSuggestion",
56
+
57
+ "CreateNewForecast",
58
+ "DeleteForecastReport",
59
+ "GetForecast",
60
+ "GetForecastList",
61
+
62
+ "CreateInvoice",
63
+ "TransferMoney",
64
+ "GetCreditLimits",
65
+ "PayCampaigns",
66
+
67
+ "GetClientsUnits",
68
+ "GetSubClients",
69
+ "GetClientInfo",
70
+ "UpdateClientInfo",
71
+
72
+ "CreateNewSubclient",
73
+ "GetClientsList",
74
+
75
+ "GetRegions",
76
+ "GetRubrics",
77
+ "GetTimeZones",
78
+ "GetStatGoals",
79
+
80
+ "GetAvailableVersions",
81
+ "GetVersion",
82
+ "PingAPI",
83
+ ]
84
+
85
+
86
+ API_V4_LIVE = [
87
+ "CreateOrUpdateCampaign",
88
+ "GetCampaignsList",
89
+ "GetCampaignsListFilter",
90
+ "GetCampaignsParams",
91
+ "DeleteCampaign",
92
+
93
+ "CreateOrUpdateBanners",
94
+ "GetBanners",
95
+ "GetBannerPhrases",
96
+ "GetBannerPhrasesFilter",
97
+ "DeleteBanners",
98
+ "Keyword",
99
+
100
+ "ModerateBanners",
101
+ "ResumeBanners",
102
+ "StopBanners",
103
+ "ArchiveBanners",
104
+ "UnArchiveBanners",
105
+
106
+ "SetAutoPrice",
107
+ "UpdatePrices",
108
+
109
+ "GetBannersStat",
110
+
111
+ "ResumeCampaign",
112
+ "StopCampaign",
113
+ "ArchiveCampaign",
114
+ "UnArchiveCampaign",
115
+
116
+ "GetBannersTags",
117
+ "UpdateBannersTags",
118
+ "GetCampaignsTags",
119
+ "UpdateCampaignsTags",
120
+
121
+ "AdImage",
122
+ "AdImageAssociation",
123
+
124
+ "GetRetargetingGoals",
125
+ "RetargetingCondition",
126
+ "Retargeting",
127
+
128
+ "EnableSharedAccount",
129
+ "AccountManagement",
130
+
131
+ "GetChanges",
132
+ "GetEventsLog",
133
+ ]
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,28 @@
1
+ require "ya/api/direct/constants"
2
+
3
+ module Ya::API::Direct
4
+ class DirectServiceBase
5
+ attr_reader :method_items, :version
6
+ def initialize(client, methods)
7
+ @client = client
8
+ @method_items = methods
9
+ init_methods
10
+ end
11
+
12
+ protected
13
+
14
+ def exec_request(method, request_body)
15
+ client.gateway.request method, request_body, @version
16
+ end
17
+
18
+ def init_methods
19
+ @method_items.each do |method|
20
+ self.class.send :define_method, method do |params = {}|
21
+ result = exec_request(method, params || {})
22
+ @client.update_units_data result[:units_data]
23
+ result[:data]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,16 @@
1
+ require "ya/api/direct/constants"
2
+ require "ya/api/direct/direct_service_base"
3
+
4
+ module Ya::API::Direct
5
+ class DirectServiceV4 < DirectServiceBase
6
+
7
+ def initialize(client, methods, version = :v4)
8
+ super(client, methods)
9
+ @version = version
10
+ end
11
+
12
+ def exec_request(method, request_body = {})
13
+ @client.gateway.request method, request_body, nil, (API_V4_LIVE.include?(method) ? :v4live : @version)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ require "ya/api/direct/constants"
2
+ require "ya/api/direct/direct_service_base"
3
+
4
+ module Ya::API::Direct
5
+ class DirectServiceV5 < DirectServiceBase
6
+ attr_reader :service, :service_url
7
+
8
+ def initialize(client, service, methods)
9
+ super(client, methods)
10
+ @service = service
11
+ @service_url = service.downcase
12
+ @version = :v5
13
+ end
14
+
15
+ def exec_request(method, request_body={})
16
+ @client.gateway.request method, request_body, @service_url, @version
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ require "ya/api/direct/constants"
2
+
3
+ module Ya::API::Direct
4
+ class Exception < StandardError
5
+ attr_reader :error_detail, :error_code, :error_str
6
+
7
+ def initialize(error_detail, error_str, error_code)
8
+ @error_detail = error_detail
9
+ @error_str = error_str
10
+ @error_code = error_code
11
+ end
12
+
13
+ def to_s
14
+ "#{@error_str} : #{@error_detail} : #{@error_code}"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,59 @@
1
+ require "net/http"
2
+ require "openssl"
3
+ require "json"
4
+
5
+ require "ya/api/direct/constants"
6
+ require "ya/api/direct/url_helper"
7
+
8
+ module Ya::API::Direct
9
+ class Gateway
10
+ attr_reader :config
11
+ def initialize(config)
12
+ @config = config
13
+ end
14
+
15
+ def request(method, params, service = "", version = nil)
16
+ ver = version || (service.nil? ? :v4 : :v5)
17
+ url = UrlHelper.direct_api_url @config[:mode], ver, service
18
+ header = generate_header ver
19
+ body = generate_body method, params, ver
20
+ uri = URI.parse url
21
+ request = Net::HTTP::Post.new(uri.path, initheader = header)
22
+ request.body = body.to_json
23
+ http = Net::HTTP.new(uri.host, uri.port)
24
+ http.use_ssl = true
25
+ http.verify_mode = @config[:ssl] ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
26
+ response = http.request(request)
27
+ if response.kind_of? Net::HTTPSuccess
28
+ UrlHelper.parse_data response
29
+ else
30
+ raise response.inspect
31
+ end
32
+ end
33
+
34
+ def generate_header(ver)
35
+ header = {}
36
+ if ver == :v5
37
+ header.merge!({
38
+ 'Client-Login' => @config[:login],
39
+ 'Accept-Language' => @config[:locale],
40
+ 'Authorization' => "Bearer %{token}" % @config
41
+ })
42
+ end
43
+ end
44
+
45
+ def generate_body(method, params, ver)
46
+ body = {
47
+ "method" => method,
48
+ "params" => params
49
+ }
50
+ if [:v4, :v4live].include? ver
51
+ body.merge!({
52
+ "locale" => @config[:locale],
53
+ "token" => @config[:token]
54
+ })
55
+ end
56
+ body
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,82 @@
1
+ require "json"
2
+
3
+ require "ya/api/direct/constants"
4
+ require "ya/api/direct/exception"
5
+
6
+ module Ya::API::Direct
7
+ RegExUnits = Regexp.new /(\d+)\/(\d+)\/(\d+)/
8
+ # Static class with helping functions for url formatting.
9
+ #
10
+ # @author Rikki Mongoose
11
+ class UrlHelper
12
+ # Generate Yandex Direct API url for a call
13
+ #
14
+ # @param mode [Symbol] Direct API mode, `:sandbox` or `:production`
15
+ # @param mode [Symbol] Direct API version, `:v5` or `:v4` or `:v4live`
16
+ # @param mode [Symbol] Direct API service to use
17
+ # @return [String] Generated Yandex Direct API URL
18
+ def self.direct_api_url(mode = :sandbox, version = :v5, service = "")
19
+ format = :json
20
+ protocol = "https"
21
+ api_prefixes = {
22
+ sandbox: "api-sandbox",
23
+ production: "api"
24
+ }
25
+ api_prefix = api_prefixes[mode || :sandbox]
26
+ site = "%{api}.direct.yandex.ru" % {api: api_prefix}
27
+ api_urls = {
28
+ v4: {
29
+ json: '%{protocol}://%{site}/v4/json',
30
+ soap: '%{protocol}://%{site}/v4/soap',
31
+ wsdl: '%{protocol}://%{site}/v4/wsdl',
32
+ },
33
+ v4live: {
34
+ json: '%{protocol}://%{site}/live/v4/json',
35
+ soap: '%{protocol}://%{site}/live/v4/soap',
36
+ wsdl: '%{protocol}://%{site}/live/v4/wsdl',
37
+ },
38
+ v5: {
39
+ json: '%{protocol}://%{site}/json/v5/%{service}',
40
+ soap: '%{protocol}://%{site}/v5/%{service}',
41
+ wsdl: '%{protocol}://%{site}/v5/%{service}?wsdl',
42
+ }
43
+ }
44
+ api_urls[version][format] % {
45
+ protocol: protocol,
46
+ site: site,
47
+ service: service
48
+ }
49
+ end
50
+
51
+ # Extract Yandex Direct API units values from responce HTTP header
52
+ #
53
+ # @param responce_header [Hash] Yandex Direct API response header
54
+ # @return [Hash] Units data, extracted from header
55
+ def self.extract_response_units(response_header)
56
+ matched = RegExUnits.match response_header["Units"]
57
+ {
58
+ just_used: matched[1].to_i,
59
+ units_left: matched[2].to_i,
60
+ units_limit: matched[3].to_i
61
+ }
62
+ end
63
+
64
+ private
65
+
66
+ def self.parse_data(response)
67
+ response_body = JSON.parse(response.body)
68
+ validate_response! response_body
69
+ {
70
+ data: response_body,
71
+ units_data: self.extract_response_units(response)
72
+ }
73
+ end
74
+
75
+ def self.validate_response!(response_body)
76
+ if response_body.has_key? 'error'
77
+ response_error = response_body['error']
78
+ raise Exception.new(response_error['error_detail'], response_error['error_string'], response_error['error_code'])
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,49 @@
1
+ module Ya
2
+ module API
3
+ module Direct
4
+ module Version
5
+ module_function
6
+
7
+ # @return [Integer]
8
+ def major
9
+ 0
10
+ end
11
+
12
+ # @return [Integer]
13
+ def minor
14
+ 2
15
+ end
16
+
17
+ # @return [Integer]
18
+ def patch
19
+ 0
20
+ end
21
+
22
+ # @return [Integer, NilClass]
23
+ def pre
24
+ nil
25
+ end
26
+
27
+ # @return [Hash]
28
+ def to_h
29
+ {
30
+ major: major,
31
+ minor: minor,
32
+ patch: patch,
33
+ pre: pre,
34
+ }
35
+ end
36
+
37
+ # @return [Array]
38
+ def to_a
39
+ [major, minor, patch, pre].compact
40
+ end
41
+
42
+ # @return [String]
43
+ def to_s
44
+ to_a.join('.')
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,18 @@
1
+ # coding: utf-8
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ require 'ya/api/direct/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'ya-api-direct'
8
+ s.version = '0.0.1'
9
+ s.date = '2016-09-05'
10
+ s.summary = "Yandex Direct API v5"
11
+ s.description = "Ruby implementation for Yandex Direct API of versions 5, 4 and 4 Live"
12
+ s.authors = %w("Alexander Teut")
13
+ s.email = 'alexander.a.teut@gmail.com'
14
+ s.files = %w(LICENSE README.md ya-api-direct.gemspec) + Dir['lib/ya/api/direct/*.rb']
15
+ s.homepage = 'http://rubygems.org/gems/ya-api-direct'
16
+ s.license = 'MIT'
17
+ s.version = Ya::API::Direct::Version
18
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ya-api-direct
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - '"Alexander'
8
+ - Teut"
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-09-05 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Ruby implementation for Yandex Direct API of versions 5, 4 and 4 Live
15
+ email: alexander.a.teut@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - LICENSE
21
+ - README.md
22
+ - ya-api-direct.gemspec
23
+ - lib/ya/api/direct/client.rb
24
+ - lib/ya/api/direct/constants.rb
25
+ - lib/ya/api/direct/direct_service_base.rb
26
+ - lib/ya/api/direct/direct_service_v4.rb
27
+ - lib/ya/api/direct/direct_service_v5.rb
28
+ - lib/ya/api/direct/exception.rb
29
+ - lib/ya/api/direct/gateway.rb
30
+ - lib/ya/api/direct/url_helper.rb
31
+ - lib/ya/api/direct/version.rb
32
+ homepage: http://rubygems.org/gems/ya-api-direct
33
+ licenses:
34
+ - MIT
35
+ metadata: {}
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubyforge_project:
52
+ rubygems_version: 2.0.14.1
53
+ signing_key:
54
+ specification_version: 4
55
+ summary: Yandex Direct API v5
56
+ test_files: []