├── go.mod ├── go.sum ├── README.md └── main.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mux0x/mulef 2 | 3 | go 1.19 4 | 5 | require github.com/fatih/color v1.15.0 6 | 7 | require ( 8 | github.com/mattn/go-colorable v0.1.13 // indirect 9 | github.com/mattn/go-isatty v0.0.17 // indirect 10 | golang.org/x/sys v0.6.0 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= 2 | github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= 3 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 4 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 5 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 6 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 7 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 8 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 9 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= 10 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mulef 2 | 3 | This tool performs OSINT by finding LinkedIn employees on GitHub. It has two modes: one for searching keywords on the GitHub profiles of the users you're searching for, and the other for scraping the location of the employee from LinkedIn, searching for the name of the employee, and then checking if their location on GitHub matches the one on LinkedIn. 4 | 5 | ## Installation 6 | 7 | To use this tool, you need to have Go installed on your system. You can download and install Go from the official website: [https://golang.org/dl/](https://golang.org/dl/). 8 | 9 | Once you have installed Go, you can download and install this tool by running the following command: 10 | 11 | ``` 12 | go install -v github.com/mux0x/mulef@latest 13 | ``` 14 | 15 | ### Usage 16 | 17 | To use this tool, you need to provide the following command-line arguments: 18 | 19 | ``` 20 | -keywords: comma-separated list of keywords 21 | -mode: mode of finding employees (location, keywords) 22 | -LinkedInRequest: path of the LinkedIn request file 23 | -token: GitHub token 24 | -output: path of the output file 25 | ``` 26 | 27 | ### To get that LinkedIn request 28 | 29 | 1. you open the dev tools in the browser you’re using 30 | 2. open corp page on LinkedIn 31 | 3. click See all x,xxx employees on LinkedIn 32 | 4. Search for `/voyager/api/graphql?includeWebMetadata=true&variables=(start:0,origin:COMPANY_PAGE_CANNED_SEARCH,query` in the requests in the network tab 33 | 5. Right-Click on the request and Copy > Copy Request headers 34 | 6. save it in a txt file 35 | 36 | ### Example Usage 37 | 38 | ``` 39 | mulef -keywords indrive -mode keywords -LinkedInRequest /path/to/linkedin_request.html -token="ghp_xxxxxxxxxxxxxxxx" -output /path/to/output.txt 40 | ``` 41 | 42 | This will search for LinkedIn employees on GitHub who have the keywords "indrive” in their profiles,code, and repos details and output the results to the file `/path/to/output.txt`. 43 | 44 | ### Modes 45 | 46 | ### Keyword Mode 47 | 48 | In this mode, the tool will search for the provided keywords on the GitHub profiles of the users you're searching for. To use this mode, set the `-mode` flag to "keywords" and provide the keywords as a comma-separated list using the `-keywords` flag. 49 | 50 | ### Location Mode 51 | 52 | In this mode, the tool will scrape the location of the employee from LinkedIn, search for the name of the employee, and then check if their location on GitHub matches the one on LinkedIn. To use this mode, set the `-mode` flag to "location" and provide the path of the LinkedIn request file using the `-LinkedInRequest` flag. 53 | 54 | Made by love from a Muslim <3 55 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "io/ioutil" 9 | "math" 10 | "net/http" 11 | "net/url" 12 | "os" 13 | "regexp" 14 | "strconv" 15 | "strings" 16 | "time" 17 | 18 | "github.com/fatih/color" 19 | ) 20 | 21 | type response struct { 22 | Data struct { 23 | Data struct { 24 | SearchDashClustersByAll struct { 25 | Metadata struct { 26 | EntityResultAttributes any `json:"entityResultAttributes"` 27 | TotalResultCount int `json:"totalResultCount"` 28 | SecondaryFilterCluster any `json:"secondaryFilterCluster"` 29 | RecipeTypes []string `json:"$recipeTypes"` 30 | LazyRightRail any `json:"lazyRightRail"` 31 | QueryType any `json:"queryType"` 32 | Type string `json:"$type"` 33 | PrimaryResultType string `json:"primaryResultType"` 34 | PaginationToken any `json:"paginationToken"` 35 | PrimaryFilterCluster any `json:"primaryFilterCluster"` 36 | BlockedQuery bool `json:"blockedQuery"` 37 | EntityActionButtonStyle any `json:"entityActionButtonStyle"` 38 | SearchID string `json:"searchId"` 39 | FilterAppliedCount int `json:"filterAppliedCount"` 40 | ClusterTitleFontSize string `json:"clusterTitleFontSize"` 41 | SimpleInsightAttributes any `json:"simpleInsightAttributes"` 42 | KnowledgeCardRightRail any `json:"knowledgeCardRightRail"` 43 | } `json:"metadata"` 44 | Paging struct { 45 | Count int `json:"count"` 46 | Start int `json:"start"` 47 | Total int `json:"total"` 48 | RecipeTypes []string `json:"$recipeTypes"` 49 | Type string `json:"$type"` 50 | } `json:"paging"` 51 | RecipeTypes []string `json:"$recipeTypes"` 52 | Elements []struct { 53 | Image any `json:"image"` 54 | QuickFilterActions []any `json:"quickFilterActions"` 55 | ClusterRenderType string `json:"clusterRenderType"` 56 | Dismissable bool `json:"dismissable"` 57 | TotalResultCount any `json:"totalResultCount"` 58 | ControlName any `json:"controlName"` 59 | Description any `json:"description"` 60 | Title any `json:"title"` 61 | RecipeTypes []string `json:"$recipeTypes"` 62 | Type string `json:"$type"` 63 | ActionTypeName any `json:"actionTypeName"` 64 | NavigationText any `json:"navigationText"` 65 | Feature any `json:"feature"` 66 | NavigationCardAction any `json:"navigationCardAction"` 67 | Position int `json:"position"` 68 | Items []struct { 69 | Item struct { 70 | EntityResult any `json:"entityResult"` 71 | KeywordsSuggestionCard any `json:"keywordsSuggestionCard"` 72 | Cluster any `json:"cluster"` 73 | SimpleText struct { 74 | TextDirection string `json:"textDirection"` 75 | Text string `json:"text"` 76 | AttributesV2 []any `json:"attributesV2"` 77 | AccessibilityTextAttributesV2 []any `json:"accessibilityTextAttributesV2"` 78 | AccessibilityText any `json:"accessibilityText"` 79 | RecipeTypes []string `json:"$recipeTypes"` 80 | Type string `json:"$type"` 81 | } `json:"simpleText"` 82 | QueryClarificationCard any `json:"queryClarificationCard"` 83 | BannerCard any `json:"bannerCard"` 84 | PromoCard any `json:"promoCard"` 85 | CenteredText any `json:"centeredText"` 86 | SearchSuggestionCard any `json:"searchSuggestionCard"` 87 | SimpleImage any `json:"simpleImage"` 88 | FeedbackCard any `json:"feedbackCard"` 89 | KnowledgeCardV2 any `json:"knowledgeCardV2"` 90 | } `json:"item"` 91 | Position int `json:"position"` 92 | RecipeTypes []string `json:"$recipeTypes"` 93 | Type string `json:"$type"` 94 | } `json:"items"` 95 | Results []any `json:"results"` 96 | TrackingID string `json:"trackingId"` 97 | } `json:"elements"` 98 | Type string `json:"$type"` 99 | } `json:"searchDashClustersByAll"` 100 | RecipeTypes []string `json:"$recipeTypes"` 101 | Type string `json:"$type"` 102 | } `json:"data"` 103 | } `json:"data"` 104 | Included []struct { 105 | EntityUrn string `json:"entityUrn"` 106 | RecipeTypes []string `json:"$recipeTypes"` 107 | Type string `json:"$type"` 108 | Template string `json:"template,omitempty"` 109 | ActorNavigationContext any `json:"actorNavigationContext,omitempty"` 110 | TrackingUrn string `json:"trackingUrn,omitempty"` 111 | ControlName any `json:"controlName,omitempty"` 112 | InterstitialComponent any `json:"interstitialComponent,omitempty"` 113 | PrimaryActions []any `json:"primaryActions,omitempty"` 114 | EntityCustomTrackingInfo struct { 115 | MemberDistance string `json:"memberDistance"` 116 | PrivacySettingsInjectionHolder any `json:"privacySettingsInjectionHolder"` 117 | RecipeTypes []string `json:"$recipeTypes"` 118 | NameMatch bool `json:"nameMatch"` 119 | Type string `json:"$type"` 120 | } `json:"entityCustomTrackingInfo,omitempty"` 121 | Title struct { 122 | TextDirection string `json:"textDirection"` 123 | Text string `json:"text"` 124 | AttributesV2 []any `json:"attributesV2"` 125 | AccessibilityTextAttributesV2 []any `json:"accessibilityTextAttributesV2"` 126 | AccessibilityText any `json:"accessibilityText"` 127 | RecipeTypes []string `json:"$recipeTypes"` 128 | Type string `json:"$type"` 129 | } `json:"title,omitempty"` 130 | OverflowActions []any `json:"overflowActions,omitempty"` 131 | SearchActionType any `json:"searchActionType,omitempty"` 132 | ActorInsights []any `json:"actorInsights,omitempty"` 133 | InsightsResolutionResults []any `json:"insightsResolutionResults,omitempty"` 134 | BadgeIcon any `json:"badgeIcon,omitempty"` 135 | ShowAdditionalCluster bool `json:"showAdditionalCluster,omitempty"` 136 | RingStatus any `json:"ringStatus,omitempty"` 137 | PrimarySubtitle struct { 138 | TextDirection string `json:"textDirection"` 139 | Text string `json:"text"` 140 | AttributesV2 []any `json:"attributesV2"` 141 | AccessibilityTextAttributesV2 []any `json:"accessibilityTextAttributesV2"` 142 | AccessibilityText any `json:"accessibilityText"` 143 | RecipeTypes []string `json:"$recipeTypes"` 144 | Type string `json:"$type"` 145 | } `json:"primarySubtitle,omitempty"` 146 | BadgeText any `json:"badgeText,omitempty"` 147 | TrackingID string `json:"trackingId,omitempty"` 148 | ActorNavigationURL any `json:"actorNavigationUrl,omitempty"` 149 | AddEntityToSearchHistory bool `json:"addEntityToSearchHistory,omitempty"` 150 | Summary any `json:"summary,omitempty"` 151 | Image struct { 152 | Attributes []struct { 153 | ScalingType any `json:"scalingType"` 154 | DetailData struct { 155 | ProfilePictureWithoutFrame any `json:"profilePictureWithoutFrame"` 156 | ProfilePictureWithRingStatus any `json:"profilePictureWithRingStatus"` 157 | CompanyLogo any `json:"companyLogo"` 158 | Icon any `json:"icon"` 159 | SystemImage any `json:"systemImage"` 160 | NonEntityGroupLogo any `json:"nonEntityGroupLogo"` 161 | VectorImage any `json:"vectorImage"` 162 | NonEntityProfessionalEventLogo any `json:"nonEntityProfessionalEventLogo"` 163 | ProfilePicture any `json:"profilePicture"` 164 | ImageURL any `json:"imageUrl"` 165 | ProfessionalEventLogo any `json:"professionalEventLogo"` 166 | NonEntityCompanyLogo any `json:"nonEntityCompanyLogo"` 167 | NonEntitySchoolLogo any `json:"nonEntitySchoolLogo"` 168 | GroupLogo any `json:"groupLogo"` 169 | SchoolLogo any `json:"schoolLogo"` 170 | GhostImage any `json:"ghostImage"` 171 | NonEntityProfilePicture struct { 172 | Profile string `json:"*profile"` 173 | RingStatus any `json:"ringStatus"` 174 | RecipeTypes []string `json:"$recipeTypes"` 175 | VectorImage any `json:"vectorImage"` 176 | Type string `json:"$type"` 177 | } `json:"nonEntityProfilePicture"` 178 | } `json:"detailData"` 179 | TintColor any `json:"tintColor"` 180 | RecipeTypes []string `json:"$recipeTypes"` 181 | TapTargets []any `json:"tapTargets"` 182 | DisplayAspectRatio any `json:"displayAspectRatio"` 183 | Type string `json:"$type"` 184 | } `json:"attributes"` 185 | ActionTarget any `json:"actionTarget"` 186 | AccessibilityTextAttributes []any `json:"accessibilityTextAttributes"` 187 | TotalCount any `json:"totalCount"` 188 | AccessibilityText any `json:"accessibilityText"` 189 | RecipeTypes []string `json:"$recipeTypes"` 190 | Type string `json:"$type"` 191 | } `json:"image,omitempty"` 192 | LazyLoadedActions any `json:"lazyLoadedActions,omitempty"` 193 | SecondarySubtitle struct { 194 | TextDirection string `json:"textDirection"` 195 | Text string `json:"text"` 196 | AttributesV2 []any `json:"attributesV2"` 197 | AccessibilityTextAttributesV2 []any `json:"accessibilityTextAttributesV2"` 198 | AccessibilityText any `json:"accessibilityText"` 199 | RecipeTypes []string `json:"$recipeTypes"` 200 | Type string `json:"$type"` 201 | } `json:"secondarySubtitle,omitempty"` 202 | NavigationURL string `json:"navigationUrl,omitempty"` 203 | EntityEmbeddedObject any `json:"entityEmbeddedObject,omitempty"` 204 | UnreadIndicatorDetails any `json:"unreadIndicatorDetails,omitempty"` 205 | Target any `json:"target,omitempty"` 206 | ActorTrackingUrn any `json:"actorTrackingUrn,omitempty"` 207 | NavigationContext struct { 208 | OpenExternally bool `json:"openExternally"` 209 | RecipeTypes []string `json:"$recipeTypes"` 210 | URL string `json:"url"` 211 | Type string `json:"$type"` 212 | } `json:"navigationContext,omitempty"` 213 | LazyLoadedActions0 string `json:"*lazyLoadedActions,omitempty"` 214 | } `json:"included"` 215 | } 216 | 217 | type githubResponseOfSearchingForUsers struct { 218 | TotalCount int `json:"total_count"` 219 | IncompleteResults bool `json:"incomplete_results"` 220 | Items []struct { 221 | Login string `json:"login"` 222 | ID int `json:"id"` 223 | NodeID string `json:"node_id"` 224 | AvatarURL string `json:"avatar_url"` 225 | GravatarID string `json:"gravatar_id"` 226 | URL string `json:"url"` 227 | HTMLURL string `json:"html_url"` 228 | FollowersURL string `json:"followers_url"` 229 | FollowingURL string `json:"following_url"` 230 | GistsURL string `json:"gists_url"` 231 | StarredURL string `json:"starred_url"` 232 | SubscriptionsURL string `json:"subscriptions_url"` 233 | OrganizationsURL string `json:"organizations_url"` 234 | ReposURL string `json:"repos_url"` 235 | EventsURL string `json:"events_url"` 236 | ReceivedEventsURL string `json:"received_events_url"` 237 | Type string `json:"type"` 238 | SiteAdmin bool `json:"site_admin"` 239 | Score float64 `json:"score"` 240 | } `json:"items"` 241 | } 242 | type Employee struct { 243 | Name string `json:"name"` 244 | Location string `json:"location"` 245 | } 246 | 247 | func getResponseFromFile(filename string, optionalArg ...string) []byte { 248 | rawReq, err := ioutil.ReadFile(filename) 249 | if err != nil { 250 | panic(err) 251 | } 252 | 253 | proxyURL, err := url.Parse("http://127.0.0.1:8080") 254 | if err != nil { 255 | fmt.Println("Error parsing proxy URL:", err) 256 | // return 257 | } 258 | 259 | transport := &http.Transport{ 260 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 261 | Proxy: http.ProxyURL(proxyURL), 262 | } 263 | 264 | // Create a new client with the transport 265 | client := &http.Client{ 266 | Transport: transport, 267 | } 268 | regex := regexp.MustCompile("start:[^,]+") 269 | // Parse the raw HTTP request 270 | reqParts := strings.SplitN(string(rawReq), "\r\n\r\n", 2) 271 | reqLine := strings.Split(strings.TrimSpace(reqParts[0]), " ") 272 | method := reqLine[0] 273 | url := reqLine[1] 274 | url = "https://www.linkedin.com" + url 275 | if len(optionalArg) > 0 { 276 | // url = strings.Replace(url, "username", optionalArg[0], 1) 277 | url = regex.ReplaceAllString(url, "start:"+optionalArg[0]) 278 | } else { 279 | url = regex.ReplaceAllString(url, "start:0") 280 | } 281 | body := "" 282 | if len(reqParts) > 1 { 283 | body = reqParts[1] 284 | } 285 | 286 | // Create a new HTTP request 287 | req, err := http.NewRequest(method, url, strings.NewReader(body)) 288 | if err != nil { 289 | panic(err) 290 | } 291 | 292 | // Parse the headers from the raw HTTP request and add them to the new request 293 | headers := strings.Split(reqParts[0], "\n")[1:] 294 | for _, header := range headers { 295 | headerParts := strings.SplitN(header, ":", 2) 296 | req.Header.Add(strings.TrimSpace(headerParts[0]), strings.TrimSpace(headerParts[1])) 297 | } 298 | 299 | // Send the HTTP request and print the response 300 | resp, err := client.Do(req) 301 | if err != nil { 302 | fmt.Println("Error sending request:", err) 303 | // return 304 | } 305 | defer resp.Body.Close() 306 | 307 | respBody, err := ioutil.ReadAll(resp.Body) 308 | if err != nil { 309 | panic(err) 310 | } 311 | 312 | return respBody 313 | } 314 | func sendHTTPRequest(start string) []Employee { 315 | var employees []Employee 316 | 317 | var reqBody2 response 318 | if err := json.Unmarshal(getResponseFromFile("testdir/request.txt", start), &reqBody2); err != nil { // Parse []byte to the go struct pointer 319 | fmt.Println("Can not unmarshal JSON") 320 | } 321 | 322 | for _, includedRec := range reqBody2.Included { 323 | if includedRec.Title.Text != "null" && includedRec.Title.Text != "" && !strings.Contains(includedRec.Title.Text, "Member") && !strings.Contains(includedRec.Title.Text, "Founder") && !strings.Contains(includedRec.Title.Text, "CEO") && !strings.Contains(includedRec.Title.Text, "CTO") && !strings.Contains(includedRec.Title.Text, "COO") && !strings.Contains(includedRec.Title.Text, "CFO") && !strings.Contains(includedRec.Title.Text, "CIO") && !strings.Contains(includedRec.Title.Text, "CPO") && !strings.Contains(includedRec.Title.Text, "CMO") && !strings.Contains(includedRec.Title.Text, "CDO") && !strings.Contains(includedRec.Title.Text, "CRO") && !strings.Contains(includedRec.Title.Text, "CSO") && !strings.Contains(includedRec.Title.Text, "CLO") && !strings.Contains(includedRec.Title.Text, "might benefit") { 324 | 325 | employeeName := includedRec.Title.Text 326 | employeeLocation := includedRec.SecondarySubtitle.Text 327 | 328 | employees = append(employees, Employee{Name: employeeName, Location: employeeLocation}) 329 | } 330 | 331 | } 332 | // return employeeJSON 333 | return employees 334 | } 335 | 336 | type Employees struct { 337 | Employees []Employee `json:"employees"` 338 | } 339 | type githubUserInfo struct { 340 | Login string `json:"login"` 341 | ID int `json:"id"` 342 | NodeID string `json:"node_id"` 343 | AvatarURL string `json:"avatar_url"` 344 | GravatarID string `json:"gravatar_id"` 345 | URL string `json:"url"` 346 | HTMLURL string `json:"html_url"` 347 | FollowersURL string `json:"followers_url"` 348 | FollowingURL string `json:"following_url"` 349 | GistsURL string `json:"gists_url"` 350 | StarredURL string `json:"starred_url"` 351 | SubscriptionsURL string `json:"subscriptions_url"` 352 | OrganizationsURL string `json:"organizations_url"` 353 | ReposURL string `json:"repos_url"` 354 | EventsURL string `json:"events_url"` 355 | ReceivedEventsURL string `json:"received_events_url"` 356 | Type string `json:"type"` 357 | SiteAdmin bool `json:"site_admin"` 358 | Name string `json:"name"` 359 | Company any `json:"company"` 360 | Blog string `json:"blog"` 361 | Location string `json:"location"` 362 | Email any `json:"email"` 363 | Hireable any `json:"hireable"` 364 | Bio string `json:"bio"` 365 | TwitterUsername string `json:"twitter_username"` 366 | PublicRepos int `json:"public_repos"` 367 | PublicGists int `json:"public_gists"` 368 | Followers int `json:"followers"` 369 | Following int `json:"following"` 370 | CreatedAt time.Time `json:"created_at"` 371 | UpdatedAt time.Time `json:"updated_at"` 372 | } 373 | 374 | func getGithubUser(token string, username string) (githubUserInfo, error) { 375 | url := "https://api.github.com/users/" + username 376 | 377 | req, err := http.NewRequest("GET", url, nil) 378 | if err != nil { 379 | return githubUserInfo{}, err 380 | } 381 | 382 | req.Header.Set("Authorization", "Bearer "+token) 383 | req.Header.Set("Accept", "application/json") 384 | 385 | client := &http.Client{} 386 | resp, err := client.Do(req) 387 | if err != nil { 388 | return githubUserInfo{}, err 389 | } 390 | defer resp.Body.Close() 391 | 392 | var responseBody githubUserInfo 393 | err = json.NewDecoder(resp.Body).Decode(&responseBody) 394 | if err != nil { 395 | return githubUserInfo{}, err 396 | } 397 | time.Sleep(500 * time.Millisecond) 398 | return responseBody, nil 399 | } 400 | 401 | func searchUsers(token string, username string) (githubResponseOfSearchingForUsers, error) { 402 | url := "https://api.github.com/search/users?q=" + username 403 | 404 | req, err := http.NewRequest("GET", url, nil) 405 | if err != nil { 406 | return githubResponseOfSearchingForUsers{}, err 407 | } 408 | 409 | req.Header.Set("Authorization", "Bearer "+token) 410 | req.Header.Set("Accept", "application/json") 411 | 412 | client := &http.Client{} 413 | resp, err := client.Do(req) 414 | if err != nil { 415 | return githubResponseOfSearchingForUsers{}, err 416 | } 417 | defer resp.Body.Close() 418 | 419 | var responseBody githubResponseOfSearchingForUsers 420 | err = json.NewDecoder(resp.Body).Decode(&responseBody) 421 | if err != nil { 422 | return githubResponseOfSearchingForUsers{}, err 423 | } 424 | time.Sleep(500 * time.Millisecond) 425 | return responseBody, nil 426 | 427 | } 428 | func generateLocationVariations(location string) []string { 429 | var rev_slc []string 430 | // split the location string into separate words 431 | words := strings.Split(location, " ") 432 | 433 | // create a slice to store the variations 434 | var variations []string 435 | 436 | // loop through each level of the pyramid 437 | for i := 1; i <= len(words); i++ { 438 | // create a slice to hold the words for this level of the pyramid 439 | levelWords := make([]string, i) 440 | 441 | // copy the first i words into the slice 442 | copy(levelWords, words[:i]) 443 | 444 | // trim any commas from the end of the words 445 | // for j := range levelWords { 446 | 447 | // levelWords[j] = strings.TrimSuffix(levelWords[j], ",") 448 | 449 | // } 450 | 451 | // add the level to the variations slice 452 | wholeOne := strings.Join(levelWords, " ") 453 | wholeOne = strings.TrimSuffix(wholeOne, ",") 454 | variations = append(variations, wholeOne) 455 | // variations = reverseStringSlice(variations) 456 | // rev_slc := []int{} 457 | for i := range variations { 458 | // reverse the order 459 | rev_slc = append(rev_slc, variations[len(variations)-1-i]) 460 | } 461 | } 462 | 463 | return rev_slc 464 | } 465 | func isInSlice(s string, slice []string) bool { 466 | for _, item := range slice { 467 | if strings.Contains(s, item) { 468 | return true 469 | } 470 | } 471 | return false 472 | } 473 | 474 | func getUserReposDetails(username string, token string) string { 475 | 476 | url := "https://api.github.com/users/" + username + "/repos" 477 | 478 | req, err := http.NewRequest("GET", url, nil) 479 | if err != nil { 480 | fmt.Println("error") 481 | } 482 | 483 | req.Header.Set("Authorization", "Bearer "+token) 484 | req.Header.Set("Accept", "application/json") 485 | 486 | client := &http.Client{} 487 | resp, err := client.Do(req) 488 | if err != nil { 489 | fmt.Println("error") 490 | } 491 | defer resp.Body.Close() 492 | 493 | responseBody, err := ioutil.ReadAll(resp.Body) 494 | if err != nil { 495 | fmt.Println("error") 496 | } 497 | 498 | time.Sleep(500 * time.Millisecond) 499 | return string(responseBody) 500 | 501 | } 502 | 503 | func getURLResponse(url string, authToken string) (string, error) { 504 | // Create new HTTP GET request 505 | req, err := http.NewRequest("GET", url, nil) 506 | if err != nil { 507 | return "", err 508 | } 509 | 510 | // Add authorization header to request 511 | req.Header.Add("Authorization", "Bearer "+authToken) 512 | 513 | // Send HTTP request with client 514 | client := &http.Client{} 515 | resp, err := client.Do(req) 516 | if err != nil { 517 | return "", err 518 | } 519 | defer resp.Body.Close() 520 | 521 | // Read response body 522 | body, err := ioutil.ReadAll(resp.Body) 523 | if err != nil { 524 | return "", err 525 | } 526 | 527 | // Convert response body to string 528 | response := string(body) 529 | time.Sleep(500 * time.Millisecond) 530 | return response, nil 531 | } 532 | 533 | type githubsearchresults struct { 534 | TotalCount int `json:"total_count"` 535 | IncompleteResults bool `json:"incomplete_results"` 536 | Items []struct { 537 | Name string `json:"name"` 538 | Path string `json:"path"` 539 | Sha string `json:"sha"` 540 | URL string `json:"url"` 541 | GitURL string `json:"git_url"` 542 | HTMLURL string `json:"html_url"` 543 | Repository struct { 544 | ID int `json:"id"` 545 | NodeID string `json:"node_id"` 546 | Name string `json:"name"` 547 | FullName string `json:"full_name"` 548 | Private bool `json:"private"` 549 | Owner struct { 550 | Login string `json:"login"` 551 | ID int `json:"id"` 552 | NodeID string `json:"node_id"` 553 | AvatarURL string `json:"avatar_url"` 554 | GravatarID string `json:"gravatar_id"` 555 | URL string `json:"url"` 556 | HTMLURL string `json:"html_url"` 557 | FollowersURL string `json:"followers_url"` 558 | FollowingURL string `json:"following_url"` 559 | GistsURL string `json:"gists_url"` 560 | StarredURL string `json:"starred_url"` 561 | SubscriptionsURL string `json:"subscriptions_url"` 562 | OrganizationsURL string `json:"organizations_url"` 563 | ReposURL string `json:"repos_url"` 564 | EventsURL string `json:"events_url"` 565 | ReceivedEventsURL string `json:"received_events_url"` 566 | Type string `json:"type"` 567 | SiteAdmin bool `json:"site_admin"` 568 | } `json:"owner"` 569 | HTMLURL string `json:"html_url"` 570 | Description any `json:"description"` 571 | Fork bool `json:"fork"` 572 | URL string `json:"url"` 573 | ForksURL string `json:"forks_url"` 574 | KeysURL string `json:"keys_url"` 575 | CollaboratorsURL string `json:"collaborators_url"` 576 | TeamsURL string `json:"teams_url"` 577 | HooksURL string `json:"hooks_url"` 578 | IssueEventsURL string `json:"issue_events_url"` 579 | EventsURL string `json:"events_url"` 580 | AssigneesURL string `json:"assignees_url"` 581 | BranchesURL string `json:"branches_url"` 582 | TagsURL string `json:"tags_url"` 583 | BlobsURL string `json:"blobs_url"` 584 | GitTagsURL string `json:"git_tags_url"` 585 | GitRefsURL string `json:"git_refs_url"` 586 | TreesURL string `json:"trees_url"` 587 | StatusesURL string `json:"statuses_url"` 588 | LanguagesURL string `json:"languages_url"` 589 | StargazersURL string `json:"stargazers_url"` 590 | ContributorsURL string `json:"contributors_url"` 591 | SubscribersURL string `json:"subscribers_url"` 592 | SubscriptionURL string `json:"subscription_url"` 593 | CommitsURL string `json:"commits_url"` 594 | GitCommitsURL string `json:"git_commits_url"` 595 | CommentsURL string `json:"comments_url"` 596 | IssueCommentURL string `json:"issue_comment_url"` 597 | ContentsURL string `json:"contents_url"` 598 | CompareURL string `json:"compare_url"` 599 | MergesURL string `json:"merges_url"` 600 | ArchiveURL string `json:"archive_url"` 601 | DownloadsURL string `json:"downloads_url"` 602 | IssuesURL string `json:"issues_url"` 603 | PullsURL string `json:"pulls_url"` 604 | MilestonesURL string `json:"milestones_url"` 605 | NotificationsURL string `json:"notifications_url"` 606 | LabelsURL string `json:"labels_url"` 607 | ReleasesURL string `json:"releases_url"` 608 | DeploymentsURL string `json:"deployments_url"` 609 | } `json:"repository"` 610 | Score float64 `json:"score"` 611 | } `json:"items"` 612 | } 613 | 614 | func appendToFile(filename string, text string) error { 615 | f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 616 | if err != nil { 617 | return err 618 | } 619 | defer f.Close() 620 | _, err = f.WriteString(text) 621 | return err 622 | } 623 | 624 | func threadingInLinkedInEmployees(employee Employee, eligbleUsers []string, locationMethod bool, userKeywords []string, keywordsMethod bool, outputToFile bool, githubToken string, optionalArgs ...string) { 625 | var outputLocation string 626 | 627 | if len(optionalArgs) > 0 && outputToFile { 628 | outputLocation = optionalArgs[0] 629 | } 630 | foundKeyword := false 631 | encodedUserName := url.QueryEscape(employee.Name) 632 | if employee.Name != "" || len(employee.Name) > 0 { 633 | color.Cyan("[+] Searching For: " + employee.Name) 634 | } 635 | foundUsers, err := searchUsers(githubToken, encodedUserName) 636 | locationsGeneratedFromLinkedIn := generateLocationVariations(employee.Location) 637 | if err != nil { 638 | color.Red("[-] Can not search users") 639 | } 640 | for i := 0; i < len(foundUsers.Items); i++ { 641 | // fmt.Println(" [+] Testing user: ", foundUsers.Items[i].Login) 642 | user := foundUsers.Items[i] 643 | userInformaiton, err := getGithubUser(githubToken, user.Login) 644 | 645 | if err != nil { 646 | color.Red("[-] Can not get user information") 647 | } 648 | if locationMethod { 649 | userLocation := userInformaiton.Location 650 | 651 | if isInSlice(userLocation, locationsGeneratedFromLinkedIn) { 652 | color.Green("[*] Found: " + user.Login) 653 | if outputToFile { 654 | appendToFile(outputLocation, user.Login+"\n") 655 | } 656 | } 657 | } else if keywordsMethod { 658 | userReposInfo := getUserReposDetails(user.Login, githubToken) 659 | 660 | for _, keyword := range userKeywords { 661 | if foundKeyword { 662 | break 663 | } else if strings.Contains(keyword, userReposInfo) { 664 | color.Green("[*] Found: " + user.Login + ", keyword: " + keyword) 665 | // eligbleUsers = append(eligbleUsers, user.Login) 666 | if outputToFile { 667 | appendToFile(outputLocation, user.Login+"\n") 668 | } 669 | foundKeyword = true 670 | break 671 | } else { 672 | stringOfUserInfo, err := json.Marshal(userInformaiton) 673 | if err != nil { 674 | fmt.Println(stringOfUserInfo) 675 | color.Red("[-] Can not get user repos information #0") 676 | } 677 | 678 | if strings.Contains(string(stringOfUserInfo), keyword) { 679 | color.Green("[*] Found: " + user.Login + ", keyword: " + keyword) 680 | if outputToFile { 681 | appendToFile(outputLocation, user.Login+"\n") 682 | } 683 | foundKeyword = true 684 | break 685 | } 686 | searchResults, err := getURLResponse("https://api.github.com/search/code?q=user:"+user.Login+"+"+keyword, githubToken) 687 | if err != nil { 688 | color.Red("[-] https://api.github.com/search/code?q=user:" + user.Login + "+" + keyword) 689 | color.Red("[-] Can not get user repos information #1") 690 | } 691 | 692 | var githubsearchresultsobject githubsearchresults 693 | err = json.Unmarshal([]byte(searchResults), &githubsearchresultsobject) 694 | if err != nil { 695 | { 696 | } 697 | } 698 | if githubsearchresultsobject.TotalCount > 0 { 699 | color.Green("[*] Found: " + user.Login + ", keyword: " + keyword) 700 | foundKeyword = true 701 | if outputToFile { 702 | appendToFile(outputLocation, user.Login+"\n") 703 | } 704 | break 705 | } 706 | 707 | } 708 | } 709 | } 710 | 711 | } 712 | } 713 | func main() { 714 | 715 | color.Green("\n\t\t /$$$$$$ ") 716 | color.Green("\t\t /$$$_ $$ ") 717 | color.Green("\t\t /$$$$$$/$$$$ /$$ /$$ /$$ /$$| $$$$\\ $$ /$$ /$$") 718 | color.Green("\t\t| $$_ $$_ $$| $$ | $$| $$ /$$/| $$ $$ $$| $$ /$$/") 719 | color.Green("\t\t| $$ \\ $$ \\ $$| $$ | $$ \\ $$$$/ | $$\\ $$$$ \\ $$$$/ ") 720 | color.Green("\t\t| $$ | $$ | $$| $$ | $$ >$$ $$ | $$ \\ $$$ >$$ $$ ") 721 | color.Green("\t\t| $$ | $$ | $$| $$$$$$/ /$$/\\ $$| $$$$$$/ /$$/\\ $$") 722 | color.Green("\t\t|__/ |__/ |__/ \\______/ |__/ \\__/ \\______/ |__/ \\__/") 723 | fmt.Println() 724 | color.Green("\t\t[+] mulef - LinkedIn Employee Finder - v1.0") 725 | color.Green("\t\t[+] github@mux0x") 726 | fmt.Println() 727 | keywords := flag.String("keywords", "", "comma-separated list of keywords") 728 | mode := flag.String("mode", "", "mode of finding employees (location, keywords)") 729 | requestFile := flag.String("LinkedInRequest", "", "path of the linkedin request file") 730 | githubToken := flag.String("token", "", "github token") 731 | outputLocation := flag.String("output", "", "path of the output file") 732 | var outputToFile bool 733 | flag.Parse() 734 | 735 | if flag.Lookup("keywords") == nil { 736 | color.Red("[-] Keywords flag not specified") 737 | flag.Usage() 738 | os.Exit(1) 739 | } 740 | if flag.Lookup("mode") == nil { 741 | color.Red("[-] Mode flag not specified") 742 | flag.Usage() 743 | os.Exit(1) 744 | } 745 | if flag.Lookup("LinkedInRequest") == nil { 746 | color.Red("[-] LinkedInRequest flag not specified") 747 | flag.Usage() 748 | os.Exit(1) 749 | } 750 | if flag.Lookup("token") == nil { 751 | color.Red("[-] Token flag not specified") 752 | flag.Usage() 753 | os.Exit(1) 754 | } 755 | 756 | if flag.Lookup("output") != nil { 757 | outputToFile = true 758 | } else { 759 | outputToFile = false 760 | } 761 | 762 | allEmployees := Employees{} 763 | var locationMethod bool 764 | var keywordsMethod bool 765 | 766 | if *mode == "location" { 767 | locationMethod = true 768 | } else if *mode == "keywords" { 769 | keywordsMethod = true 770 | } else { 771 | color.Red("[-] Invalid mode") 772 | os.Exit(1) 773 | } 774 | 775 | var reqBody2 response 776 | if err := json.Unmarshal(getResponseFromFile(*requestFile), &reqBody2); err != nil { // Parse []byte to the go struct pointer 777 | color.Red("[-] Can not unmarshal JSON") 778 | } 779 | 780 | employeesCount := reqBody2.Data.Data.SearchDashClustersByAll.Metadata.TotalResultCount 781 | 782 | numPages := int(math.Ceil(float64(employeesCount) / 10)) 783 | // fmt.Println("rounds: ", numPages) 784 | color.Cyan("[+] Processing LinkedIn Request") 785 | for i := 0; i <= numPages; i++ { 786 | startIndex := i * 10 787 | // fmt.Println("start: ", startIndex) 788 | newEmployee := sendHTTPRequest(strconv.Itoa(startIndex)) 789 | allEmployees.Employees = append(allEmployees.Employees, newEmployee...) 790 | 791 | } 792 | userKeywords := strings.Split(*keywords, ",") 793 | // fmt.Println("all employees: ", allEmployees.Employees) 794 | 795 | // maxThreads := 1 796 | threadCount := 1 797 | 798 | done := make(chan bool) 799 | employeeChan := make(chan Employee) 800 | 801 | for i := 0; i < threadCount; i++ { 802 | go func() { 803 | for { 804 | select { 805 | case e := <-employeeChan: 806 | threadingInLinkedInEmployees(e, userKeywords, locationMethod, userKeywords, keywordsMethod, outputToFile, *githubToken, *outputLocation) 807 | case <-done: 808 | return 809 | } 810 | } 811 | }() 812 | } 813 | 814 | for _, e := range allEmployees.Employees { 815 | employeeChan <- e 816 | } 817 | 818 | close(employeeChan) 819 | 820 | for i := 0; i < threadCount; i++ { 821 | done <- true 822 | } 823 | 824 | } 825 | --------------------------------------------------------------------------------