-
Notifications
You must be signed in to change notification settings - Fork 0
/
pipl-client.go
235 lines (210 loc) · 8.39 KB
/
pipl-client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
package pipl
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
// SourceLevel is used internally to represent the possible values
// for show_sources in queries to be submitted: {"all", "matching"/"true", "false"}
type SourceLevel string
// MatchRequirements specifies the conditions for a successful person match in our search.
// This is useful for saving money with the Pipl API, as you only need to pay for the
// data you wanted back. If your search results didn't satisfy the match requirements, then
// no data is returned and you don't pay.
type MatchRequirements string
// SourceCategoryRequirements specifies the data categories that must be included in
// results for a successful match. If there is no data from the requested categories,
// then the results returned are empty and you're not charged.
type SourceCategoryRequirements string
const (
// PiplAPIEndpoint is where we POST queries to
PiplAPIEndpoint string = "https://api.pipl.com/search/"
// ShowSourcesNone specifies that we don't need source info back with search results
ShowSourcesNone SourceLevel = "false"
// ShowSourcesAll specifies that we want all source info back with our search results
ShowSourcesAll SourceLevel = "all"
// ShowSourcesMatching specifies that we want source info that corresponds to data that satisfies our match requirements
ShowSourcesMatching SourceLevel = "true"
// MatchRequirementsNone specifies that we don't have any match requirements for this search
MatchRequirementsNone MatchRequirements = ""
// SourceCategoryRequirementsNone specifies that we don't require any specific sources in our results.
SourceCategoryRequirementsNone SourceCategoryRequirements = ""
)
// Client holds client configuration settings
type Client struct {
// HTTPClient carries out the POST operations
HTTPClient *http.Client
// Parameters contains the search parameters that are submitted with your query,
// which may affect the data returned
SearchParameters *SearchParameters
}
// SearchParameters holds options that can affect data returned by a search.
type SearchParameters struct {
// APIKey is required
APIKey string
// MinimumProbability is the mininum acceptable probability for inferred data
MinimumProbability float32
// InferPersons specifies whether or not the Pipl should return results inferred by statistical analysis
InferPersons bool
// MinimumMatch specifies the minimum match confidence for a possible person to be returned in search results
MinimumMatch float32
// ShowSources specifies the level of sources info to return with search results, one of {ShowSourcesMatching, ShowSourcesAll, ShowSourcesNone}
ShowSources SourceLevel
// HideSponsored specifies whether to omit sponsored data from search results
HideSponsored bool
// LiveFeeds specifies whether to use live data sources
LiveFeeds bool
// MatchRequirements specifies the criteria for a successful Person match.
// Results that don't fit your match requirements are discarded. If the remaining
// search results would be empty, you are not charged for the query.
MatchRequirements MatchRequirements
// SourceCategoryRequirements specifies the data categories that must be included in
// results for a successful match. If there is no data from the requested categories,
// then the results returned are empty and you're not charged.
SourceCategoryRequirements SourceCategoryRequirements
}
// NewClient creates a new search client to submit queries with.
// Parameters values are set to the defaults defined by Pipl.
// For more information:
// https://docs.pipl.com/reference#configuration-parameters
func NewClient(APIKey string) (client *Client) {
piplClient := new(Client)
piplClient.HTTPClient = new(http.Client)
piplClient.SearchParameters = new(SearchParameters)
piplClient.SearchParameters.APIKey = APIKey
piplClient.SearchParameters.MinimumProbability = 0.9
piplClient.SearchParameters.InferPersons = false
piplClient.SearchParameters.MinimumMatch = 0.0
piplClient.SearchParameters.ShowSources = ShowSourcesNone
piplClient.SearchParameters.HideSponsored = false
piplClient.SearchParameters.LiveFeeds = true
piplClient.SearchParameters.MatchRequirements = MatchRequirementsNone
piplClient.SearchParameters.SourceCategoryRequirements = SourceCategoryRequirementsNone
return piplClient
}
// meetsMinimumCriteria is used internally by SearchByPerson to do some very
// basic verification that the verify that search object has enough terms to
// meet the requirements for a search.
// From Pipl documentation:
// "The minimal requirement to run a search is to have at least one full
// name, email, phone, username, user_id, URL or a single valid US address
// (down to a house number). We can’t search for a job title or location
// alone. We’re not a directory and can't provide bulk lists of people,
// rather we specialize in identity resolution of single individuals."
func meetsMinimumCriteria(searchObject *Person) bool {
if len(searchObject.Names) > 0 {
for _, name := range searchObject.Names {
if ((name.First != "") && (name.Last != "")) || (name.Raw != "") {
return true
}
}
}
if len(searchObject.Emails) > 0 {
for _, email := range searchObject.Emails {
if email.Address != "" {
return true
}
}
}
if len(searchObject.Phones) > 0 {
for _, phone := range searchObject.Phones {
if ((phone.CountryCode != 0) && (phone.Number != 0)) || (phone.Raw != "") {
return true
}
}
}
if len(searchObject.Usernames) > 0 {
for _, username := range searchObject.Usernames {
if username.Content != "" {
return true
}
}
}
if len(searchObject.UserIDs) > 0 {
for _, userID := range searchObject.UserIDs {
if userID.Content != "" {
return true
}
}
}
if len(searchObject.URLs) > 0 {
for _, url := range searchObject.URLs {
if url.URL != "" {
return true
}
}
}
return false
}
// SearchByPerson takes a person object (filled with search terms) and returns the
// results in the form of a Response struct. If successful, the response struct
// will contains the results, and err will be nil. If an error occurs, the struct pointer
// will be nil and you should check err for additional information.
func (searchClient *Client) SearchByPerson(searchObject *Person) (*Response, error) {
if !meetsMinimumCriteria(searchObject) {
return nil, &ErrInsufficientSearch{}
}
postData := url.Values{}
postData.Add("key", searchClient.SearchParameters.APIKey)
if searchClient.SearchParameters.ShowSources != ShowSourcesNone {
postData.Add("show_sources", string(searchClient.SearchParameters.ShowSources))
}
if searchClient.SearchParameters.MatchRequirements != MatchRequirementsNone {
postData.Add("match_requirements", string(searchClient.SearchParameters.MatchRequirements))
}
if searchClient.SearchParameters.SourceCategoryRequirements != SourceCategoryRequirementsNone {
postData.Add("source_category_requirements", string(searchClient.SearchParameters.SourceCategoryRequirements))
}
personJSON, err := json.Marshal(searchObject)
if err != nil {
return nil, err
}
postData.Add("person", string(personJSON))
request, err := http.NewRequest("POST", PiplAPIEndpoint, strings.NewReader(postData.Encode()))
if err != nil {
return nil, err
}
request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
response, err := searchClient.HTTPClient.Do(request)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
piplResponse := new(Response)
err = json.Unmarshal(body, piplResponse)
if err != nil {
return nil, err
}
return piplResponse, nil
}
// SearchByPointer takes a search pointer string and returns the full
// information for the person associated with that pointer
func (searchClient *Client) SearchByPointer(searchPointer string) (*Person, error) {
postData := url.Values{}
postData.Add("key", searchClient.SearchParameters.APIKey)
postData.Add("search_pointer", searchPointer)
request, err := http.NewRequest("POST", PiplAPIEndpoint, strings.NewReader(postData.Encode()))
if err != nil {
return nil, err
}
request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
response, err := searchClient.HTTPClient.Do(request)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
piplResponse := new(Response)
err = json.Unmarshal(body, piplResponse)
if err != nil {
return nil, err
}
return &piplResponse.Person, nil
}