├── .codeclimate.yml
├── .dockerignore
├── .github
├── ISSUE_TEMPLATE
│ └── bug_report.md
├── stale.yml
└── workflows
│ ├── build.yml
│ ├── client.yml
│ ├── deploy-docs.yml
│ ├── dockerimage.yml
│ └── goreleaser.yml
├── .gitignore
├── .goreleaser.yml
├── CODEOWNERS
├── Dockerfile
├── LICENSE
├── README.md
├── api
├── controllers.go
├── openapi.yml
├── response.go
├── response_test.go
├── server.go
├── server_test.go
└── validators.go
├── client
├── .gitignore
├── README.md
├── cypress.json
├── jest.config.js
├── package.json
├── public
│ ├── css
│ │ ├── bootstrap-vue.min.css
│ │ └── bootstrap.min.css
│ ├── icon.svg
│ └── index.html
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.svg
│ ├── components
│ │ ├── GoogleSearch.vue
│ │ ├── LocalScan.vue
│ │ ├── NumverifyScan.vue
│ │ └── OVHScan.vue
│ ├── config
│ │ └── index.ts
│ ├── main.ts
│ ├── router
│ │ └── index.ts
│ ├── shims-tsx.d.ts
│ ├── shims-vue.d.ts
│ ├── store
│ │ └── index.ts
│ ├── utils
│ │ └── index.ts
│ └── views
│ │ ├── NotFound.vue
│ │ └── Scan.vue
├── tests
│ ├── e2e
│ │ ├── .eslintrc.js
│ │ ├── plugins
│ │ │ └── index.js
│ │ ├── specs
│ │ │ └── test.js
│ │ └── support
│ │ │ ├── commands.js
│ │ │ └── index.js
│ └── unit
│ │ ├── config.spec.ts
│ │ └── utils.spec.ts
├── tsconfig.json
├── vue.config.js
└── yarn.lock
├── cmd
├── recon.go
├── root.go
├── scan.go
├── serve.go
└── version.go
├── docker-compose.traefik.yml
├── docker-compose.yml
├── docs
├── contribute.md
├── formatting.md
├── go-module-usage.md
├── images
│ ├── banner.png
│ ├── logo.svg
│ ├── logo_white.svg
│ └── screenshot.png
├── index.md
├── install.md
├── jetbrains.svg
├── resources.md
└── usage.md
├── go.mod
├── go.sum
├── main.go
├── mkdocs.yml
├── pkg
├── config
│ ├── config_test.go
│ └── version.go
├── scanners
│ ├── google.go
│ ├── google_test.go
│ ├── local.go
│ ├── local_test.go
│ ├── numverify.go
│ ├── numverify_test.go
│ ├── ovh.go
│ ├── ovh_test.go
│ └── scanners.go
└── utils
│ ├── logger.go
│ ├── mocks
│ └── Color.go
│ ├── utils.go
│ └── utils_test.go
├── renovate.json
└── scripts
├── docker_push.sh
└── format.sh
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | plugins:
2 | golint:
3 | enabled: true
4 | gofmt:
5 | enabled: true
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | .idea
3 | .buildconfig
4 | __pycache__/
5 | .vscode/
6 | geckodriver.log
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | What command did you run ? (hide any personal information such as phone number or ip address)
15 |
16 | **Expected behavior**
17 | A clear and concise description of what you expected to happen.
18 |
19 | **Screenshots (optional)**
20 | If applicable, add screenshots to help explain your problem.
21 |
22 | **Desktop (please complete the following information):**
23 | - OS: [e.g. Windows 10, Ubuntu 18.04 ...]
24 | - PhoneInfoga exact version (run `phoneinfoga version`)
25 | - Go exact version (if running it with Go) (run `go version`)
26 |
27 | **Additional context**
28 | Add any other context about the problem here.
29 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 60
3 |
4 | # Number of days of inactivity before a stale issue is closed
5 | daysUntilClose: 15
6 |
7 | # Issues with these labels will never be considered stale
8 | exemptLabels:
9 | - pinned
10 | - security
11 |
12 | # Label to use when marking an issue as stale
13 | staleLabel: wontfix
14 |
15 | # Set to true to ignore issues in a project (defaults to false)
16 | exemptProjects: false
17 |
18 | # Set to true to ignore issues in a milestone (defaults to false)
19 | exemptMilestones: false
20 |
21 | # Set to true to ignore issues with an assignee (defaults to false)
22 | exemptAssignees: false
23 |
24 | # Limit the number of actions per hour, from 1-30. Default is 30
25 | limitPerRun: 30
26 |
27 | pulls:
28 | markComment: |-
29 | This pull request has been marked 'stale' due to lack of recent activity. If there is no further activity, the PR will be closed in another 15 days. Thank you for your contribution!
30 | unmarkComment: >-
31 | This pull request is no longer marked for closure.
32 | closeComment: >-
33 | This pull request has been closed due to inactivity. If you feel this is in error, please reopen the pull request or file a new PR with the relevant details.
34 | issues:
35 | markComment: |-
36 | This issue has been marked 'stale' due to lack of recent activity. If there is no further activity, the issue will be closed in another 15 days. Thank you for your contribution!
37 | unmarkComment: >-
38 | This issue is no longer marked for closure.
39 | closeComment: >-
40 | This issue has been closed due to inactivity. If you feel this is in error, please reopen the issue or file a new issue with the relevant details.
41 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | name: Build
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Set up Go 1.14
11 | uses: actions/setup-go@v1
12 | with:
13 | go-version: 1.14
14 | id: go
15 | - name: Check out code into the Go module directory
16 | uses: actions/checkout@v1
17 |
18 | - name: Get dependencies
19 | run: |
20 | go get -v -t -d ./...
21 | if [ -f Gopkg.toml ]; then
22 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
23 | dep ensure
24 | fi
25 |
26 | - name: Use Node.js ${{ matrix.node-version }}
27 | uses: actions/setup-node@v1
28 | with:
29 | node-version: 12.16.x
30 |
31 | - name: Building static assets
32 | run: (cd client && yarn && yarn build)
33 |
34 | - name: Build
35 | run: |
36 | go get -v github.com/jessevdk/go-assets-builder
37 | go generate ./...
38 | go build -v .
39 |
40 | - name: Lint
41 | run: |
42 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.24.0
43 | ./bin/golangci-lint run -D errcheck
44 |
45 | - name: Test
46 | env:
47 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
48 | run: |
49 | go test -race -coverprofile=coverage.txt -covermode=atomic -v -bench=. ./...
50 | bash <(curl -s https://codecov.io/bash)
51 |
--------------------------------------------------------------------------------
/.github/workflows/client.yml:
--------------------------------------------------------------------------------
1 | name: Web client CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | node-version: [12.18]
11 | steps:
12 | - uses: actions/checkout@v1
13 | - name: Use Node.js ${{ matrix.node-version }}
14 | uses: actions/setup-node@v1
15 | with:
16 | node-version: ${{ matrix.node-version }}
17 | - name: Install & lint
18 | run: |
19 | cd client
20 | yarn
21 | yarn lint
22 | - name: Build
23 | run: |
24 | cd client
25 | yarn build
26 | - name: Test
27 | run: |
28 | cd client
29 | yarn test:unit
30 | # - name: Upload coverage to Codecov
31 | # uses: codecov/codecov-action@v1.0.2
32 | # with:
33 | # token: ${{secrets.CODECOV_TOKEN}}
34 | # file: ./client/coverage/coverage-final.json
35 | # flags: unittests
36 | # name: codecov-umbrella
37 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-docs.yml:
--------------------------------------------------------------------------------
1 | name: Documentation CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | publish:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v1
13 | - name: Set up Python 3.8
14 | uses: actions/setup-python@v1
15 | with:
16 | python-version: 3.8
17 |
18 | - name: Install dependencies
19 | run: |
20 | python -m pip install --upgrade pip
21 | pip install mkdocs==1.1 mkdocs-material==5.1.0 mkdocs-minify-plugin==0.3.0
22 |
23 | - name: Deploy
24 | run: |
25 | SSH_DIR="${HOME}/.ssh"
26 | mkdir "${SSH_DIR}"
27 | ssh-keyscan -t rsa github.com > "${SSH_DIR}/known_hosts"
28 | echo "${{ secrets.ACTIONS_DEPLOY_KEY }}" > "${SSH_DIR}/id_rsa"
29 | chmod 400 "${SSH_DIR}/id_rsa"
30 | git remote set-url origin git@github.com:sundowndev/PhoneInfoga.git
31 | git config user.name "${{ secrets.GITHUB_USER }}"
32 | git config user.email "${{ secrets.GITHUB_USER }}@users.noreply.github.com"
33 | mkdocs gh-deploy --force
34 |
--------------------------------------------------------------------------------
/.github/workflows/dockerimage.yml:
--------------------------------------------------------------------------------
1 | name: Docker Image CI
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@master
12 | - name: Publish to Registry
13 | uses: elgohr/Publish-Docker-Github-Action@master
14 | with:
15 | name: sundowndev/phoneinfoga
16 | username: ${{ secrets.DOCKER_USERNAME }}
17 | password: ${{ secrets.DOCKER_PASSWORD }}
18 |
19 | #- uses: actions/checkout@master
20 | #- name: Deploy production
21 | # run: |
22 | # SSH_DIR="${HOME}/.ssh"
23 | # mkdir "${SSH_DIR}"
24 | # ssh-keyscan -t rsa xx.xx.xx.xx > "${SSH_DIR}/known_hosts"
25 | # echo "${{ secrets.PRODUCTION_DEPLOY_KEY }}" > "${SSH_DIR}/id_rsa"
26 | # chmod 400 "${SSH_DIR}/id_rsa"
27 | # ssh ${{ secrets.PRODUCTION_SSH_USER }}@xx.xx.xx.xx -p xxxx /bin/bash << EOF
28 | # IMAGE="sundowndev/phoneinfoga:latest"
29 | # docker pull $IMAGE;
30 | # docker restart $(docker ps | grep "$IMAGE" | awk '{ print $1 }')
31 | # EOF
32 |
--------------------------------------------------------------------------------
/.github/workflows/goreleaser.yml:
--------------------------------------------------------------------------------
1 | name: goreleaser
2 |
3 | on: [push]
4 |
5 | jobs:
6 | goreleaser:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - name: Checkout
10 | uses: actions/checkout@v2
11 |
12 | - name: Unshallow
13 | run: git fetch --prune --unshallow
14 |
15 | - name: Use Node.js ${{ matrix.node-version }}
16 | uses: actions/setup-node@v1
17 | with:
18 | node-version: 12.16.x
19 |
20 | - name: Building static assets
21 | run: (cd client && yarn && yarn build)
22 |
23 | - name: Set up Go
24 | uses: actions/setup-go@v1
25 | with:
26 | go-version: 1.14.x
27 |
28 | - name: Install go-assets-builder
29 | run: |
30 | go get -v github.com/jessevdk/go-assets-builder
31 |
32 | - name: Run GoReleaser
33 | uses: goreleaser/goreleaser-action@v1
34 | with:
35 | version: latest
36 | args: release --rm-dist --skip-validate
37 | env:
38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 | # vendor/
16 | .vscode/
17 |
18 | .DS_Store
19 | coverage
20 | coverage.*
21 | .idea
22 |
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | before:
2 | hooks:
3 | - go generate ./...
4 | - go mod download
5 | builds:
6 | - env:
7 | - CGO_ENABLED=0
8 | - GO111MODULE=on
9 | binary: phoneinfoga
10 | goos:
11 | - linux
12 | - darwin
13 | goarch:
14 | - 386
15 | - amd64
16 | - arm
17 | - arm64
18 | - aarch64
19 | archives:
20 | - name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
21 | replacements:
22 | darwin: Darwin
23 | linux: Linux
24 | windows: Windows
25 | 386: i386
26 | amd64: x86_64
27 | files:
28 | - none*
29 | checksum:
30 | name_template: '{{ .ProjectName }}_checksums.txt'
31 | snapshot:
32 | name_template: "{{ .Tag }}-next"
33 | changelog:
34 | sort: asc
35 | filters:
36 | exclude:
37 | - '^docs:'
38 | - '^test:'
39 | - '^chore:'
40 | - '^ci:'
41 | - Merge pull request
42 | - Merge branch
43 |
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @sundowndev
2 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12.18-alpine AS client_builder
2 |
3 | WORKDIR /app
4 |
5 | COPY ./client .
6 |
7 | RUN yarn
8 |
9 | RUN yarn build
10 |
11 | FROM golang:1.14-alpine as go_builder
12 |
13 | LABEL maintainer="Sundowndev" \
14 | org.label-schema.name="phoneinfoga" \
15 | org.label-schema.description="Advanced information gathering & OSINT tool for phone numbers." \
16 | org.label-schema.url="https://github.com/sundowndev/PhoneInfoga" \
17 | org.label-schema.vcs-url="https://github.com/sundowndev/PhoneInfoga" \
18 | org.label-schema.vendor="Sundowndev" \
19 | org.label-schema.schema-version="1.0"
20 |
21 | WORKDIR /app
22 |
23 | COPY . .
24 |
25 | RUN go get -v -t -d ./...
26 |
27 | COPY --from=client_builder /app/dist ./client/dist
28 |
29 | RUN go get -v github.com/jessevdk/go-assets-builder
30 |
31 | RUN go generate ./...
32 |
33 | RUN go build -v -o phoneinfoga .
34 |
35 | FROM golang:1.14-alpine
36 |
37 | WORKDIR /app
38 |
39 | COPY --from=go_builder /app/phoneinfoga .
40 |
41 | EXPOSE 5000
42 |
43 | ENTRYPOINT ["/app/phoneinfoga"]
44 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
22 |
23 | Advanced information gathering & OSINT framework for phone numbers
24 |
25 |
26 | Documentation •
27 | API documentation •
28 | Demo instance •
29 | Related blog post
30 |
31 |
32 | 
33 |
34 | ## About
35 |
36 | PhoneInfoga is one of the most advanced tools to scan international phone numbers using only free resources. The goal is to first gather standard information such as country, area, carrier and line type on any international phone numbers with a very good accuracy. Then search for footprints on search engines to try to find the VoIP provider or identify the owner.
37 |
38 | ## Current status
39 |
40 | **This project is under active development but is stable and production-ready. Numverify scan is pointless on demo instance because my server's IP got blocked due to spam**. [Roadmap is here](https://github.com/sundowndev/PhoneInfoga/projects/1).
41 |
42 | This project has recently been rewritten in Go language (previously Python). Why ? To improve code base, maintainability, have a stronger test suite and be able to compile code base. PhoneInfoga v2 brings new features such as serving a REST API and a web client. Usage of scanners was improved in order to drop usage of Selenium/Geckodriver which has cause many users to have troubleshoots using the tool. You can still use the legacy version in [tag v1.11](https://github.com/sundowndev/PhoneInfoga/tree/v1.11) and the legacy Docker image (`sundowndev/phoneinfoga:legacy`). Some features were not included in the v2 MVP such as input/output CLI options. The roadmap of the project changed so we can focus on the web client features such as downloading scan results as CSV, Instagram/Whatsapp lookup, and more. **Version 2 does not scan Google results anymore**, [read more](https://sundowndev.github.io/PhoneInfoga/usage/#available-scanners).
43 |
44 | ## Features
45 |
46 | - Check if phone number exists and is possible
47 | - Gather standard informations such as country, line type and carrier
48 | - OSINT footprinting using external APIs, Google Hacking, phone books & search engines
49 | - Check for reputation reports, social media, disposable numbers and more
50 | - Scan several numbers at once
51 | - Use custom formatting for more effective OSINT reconnaissance
52 | - **NEW**: Serve a web client along with a REST API to run scans from the browser
53 | - **NEW**: Run your own web instance as a service
54 | - **NEW**: Programmatic usage with Go modules
55 |
56 | ## Anti-features
57 |
58 | - Does not claim to provide relevant or verified data, it's just a tool !
59 | - Does not allow to "track" a phone or its owner in real time
60 | - Does not allow to get the precise phone location
61 | - Does not allow to hack a phone
62 |
63 | 
64 |
65 | ## License
66 |
67 | [](https://app.fossa.com/projects/git%2Bgithub.com%2Fsundowndev%2FPhoneInfoga?ref=badge_shield)
68 |
69 | This tool is licensed under the GNU General Public License v3.0.
70 |
71 | [Icon](https://www.flaticon.com/free-icon/fingerprint-search-symbol-of-secret-service-investigation_48838) made by Freepik from flaticon.com is licensed by CC 3.0 BY.
72 |
73 | ## Support
74 |
75 | [](https://www.jetbrains.com/?from=sundowndev)
76 |
77 | Thanks to [JetBrains](https://www.jetbrains.com/?from=sundowndev) for supporting my open-source projects.
78 |
--------------------------------------------------------------------------------
/api/controllers.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "gopkg.in/sundowndev/phoneinfoga.v2/pkg/config"
6 | "gopkg.in/sundowndev/phoneinfoga.v2/pkg/scanners"
7 | )
8 |
9 | type scanResultResponse struct {
10 | JSONResponse
11 | Result interface{} `json:"result"`
12 | }
13 |
14 | type listNumbersResponse struct {
15 | JSONResponse
16 | Numbers []scanners.Number `json:"numbers"`
17 | }
18 |
19 | func getAllNumbers(c *gin.Context) {
20 | c.JSON(200, listNumbersResponse{
21 | JSONResponse: JSONResponse{Success: true},
22 | Numbers: []scanners.Number{},
23 | })
24 | }
25 |
26 | func validate(c *gin.Context) {
27 | c.JSON(200, successResponse("The number is valid"))
28 | }
29 |
30 | func localScan(c *gin.Context) {
31 | result, _ := c.Get("number")
32 |
33 | c.JSON(200, scanResultResponse{
34 | JSONResponse: JSONResponse{Success: true},
35 | Result: result.(*scanners.Number),
36 | })
37 | }
38 |
39 | func numverifyScan(c *gin.Context) {
40 | number, _ := c.Get("number")
41 |
42 | result, err := scanners.NumverifyScan(number.(*scanners.Number))
43 |
44 | if err != nil {
45 | c.JSON(500, errorResponse())
46 | return
47 | }
48 |
49 | c.JSON(200, scanResultResponse{
50 | JSONResponse: JSONResponse{Success: true},
51 | Result: result,
52 | })
53 | }
54 |
55 | func googleSearchScan(c *gin.Context) {
56 | number, _ := c.Get("number")
57 |
58 | result := scanners.GoogleSearchScan(number.(*scanners.Number))
59 |
60 | c.JSON(200, scanResultResponse{
61 | JSONResponse: JSONResponse{Success: true},
62 | Result: result,
63 | })
64 | }
65 |
66 | func ovhScan(c *gin.Context) {
67 | number, _ := c.Get("number")
68 |
69 | result, err := scanners.OVHScan(number.(*scanners.Number))
70 |
71 | if err != nil {
72 | c.JSON(500, errorResponse())
73 | return
74 | }
75 |
76 | c.JSON(200, scanResultResponse{
77 | JSONResponse: JSONResponse{Success: true},
78 | Result: result,
79 | })
80 | }
81 |
82 | func healthHandler(c *gin.Context) {
83 | c.JSON(200, gin.H{
84 | "success": true,
85 | "version": config.Version,
86 | })
87 | }
88 |
--------------------------------------------------------------------------------
/api/openapi.yml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.0
2 | info:
3 | title: PhoneInfoga
4 | version: '1.0'
5 | description: 'PhoneInfoga is one of the most advanced tools to scan phone numbers using only free resources. The goal is to first gather standard information such as country, area, carrier and line type on any international phone numbers with a very good accuracy. Then search for footprints on search engines to try to find the VoIP provider or identify the owner.'
6 | servers:
7 | - url: 'http://localhost:3000/api'
8 | description: API URL
9 | paths:
10 | /numbers:
11 | get:
12 | summary: Get saved numbers
13 | tags: []
14 | responses:
15 | '200':
16 | description: ''
17 | headers: {}
18 | content:
19 | application/json:
20 | schema:
21 | type: object
22 | properties:
23 | numbers:
24 | type: array
25 | items:
26 | $ref: '#/components/schemas/Number'
27 | examples:
28 | Success:
29 | value:
30 | numbers:
31 | - local: '2623'
32 | E164: '+2622623'
33 | international: '2622623'
34 | countryCode: 262
35 | country: RE
36 | carrier: ''
37 | Error:
38 | value:
39 | success: false
40 | message: An error occurred
41 | application/xml:
42 | schema:
43 | type: object
44 | properties: {}
45 | multipart/form-data:
46 | schema:
47 | type: object
48 | properties: {}
49 | '500':
50 | description: Internal Server Error
51 | content:
52 | application/json:
53 | schema:
54 | $ref: '#/components/schemas/ErrorResponse'
55 | examples: {}
56 | operationId: get-numbers
57 | description: |-
58 | Get all known numbers.
59 |
60 | This route is actually not used yet.
61 | parameters: []
62 | requestBody: {}
63 | '/numbers/{number}/scan/local':
64 | parameters:
65 | - schema:
66 | type: string
67 | name: number
68 | in: path
69 | required: true
70 | get:
71 | summary: Local scan
72 | tags: []
73 | responses:
74 | '200':
75 | description: OK
76 | headers: {}
77 | content:
78 | application/json:
79 | schema:
80 | type: object
81 | properties:
82 | success:
83 | type: boolean
84 | error:
85 | type: string
86 | result:
87 | $ref: '#/components/schemas/LocalScanResult'
88 | examples:
89 | Success:
90 | value:
91 | success: true
92 | error: ''
93 | result:
94 | local: '2623'
95 | E164: '+2622623'
96 | international: '2622623'
97 | countryCode: 262
98 | country: RE
99 | carrier: ''
100 | operationId: get-numbers-scan-number-local
101 | description: ''
102 | '/numbers/{number}/scan/numverify':
103 | parameters:
104 | - schema:
105 | type: string
106 | name: number
107 | in: path
108 | required: true
109 | get:
110 | summary: Numverify scan
111 | tags: []
112 | responses:
113 | '200':
114 | description: OK
115 | content:
116 | application/json:
117 | schema:
118 | type: object
119 | properties:
120 | success:
121 | type: boolean
122 | error:
123 | type: string
124 | result:
125 | $ref: '#/components/schemas/NumverifyScanResult'
126 | operationId: get-numbers-number-scan-numverify
127 | '/numbers/{number}/scan/googlesearch':
128 | parameters:
129 | - schema:
130 | type: string
131 | name: number
132 | in: path
133 | required: true
134 | get:
135 | summary: Google search scan
136 | tags: []
137 | responses:
138 | '200':
139 | description: OK
140 | content:
141 | application/json:
142 | schema:
143 | type: object
144 | properties:
145 | success:
146 | type: boolean
147 | error:
148 | type: string
149 | result:
150 | $ref: '#/components/schemas/NumverifyScanResult'
151 | operationId: get-numbers-number-scan-googlesearch
152 | '/numbers/{number}/scan/ovh':
153 | get:
154 | summary: OVH Telecom scan
155 | tags: []
156 | responses:
157 | '200':
158 | description: OK
159 | content:
160 | application/json:
161 | schema:
162 | type: object
163 | properties:
164 | success:
165 | type: boolean
166 | error:
167 | type: string
168 | result:
169 | $ref: '#/components/schemas/OVHScanResult'
170 | operationId: get-numbers-number-scan-ovh
171 | components:
172 | schemas:
173 | Number:
174 | title: Number
175 | type: object
176 | description: A phone number
177 | properties:
178 | id:
179 | type: integer
180 | number:
181 | type: integer
182 | countryISO:
183 | type: integer
184 | country:
185 | type: string
186 | carrier:
187 | type: string
188 | ErrorResponse:
189 | title: ErrorResponse
190 | type: object
191 | description: Generic error response schema.
192 | x-examples:
193 | Example:
194 | success: false
195 | message: An error occurred
196 | properties:
197 | success:
198 | type: boolean
199 | message:
200 | type:
201 | - string
202 | - 'null'
203 | required:
204 | - success
205 | - message
206 | LocalScanResult:
207 | title: LocalScanResult
208 | type: object
209 | properties:
210 | local:
211 | type: string
212 | E164:
213 | type: string
214 | international:
215 | type: string
216 | countryCode:
217 | type: number
218 | country:
219 | type: string
220 | carrier:
221 | type: string
222 | NumverifyScanResult:
223 | title: NumverifyScanResult
224 | type: object
225 | properties:
226 | valid:
227 | type: boolean
228 | number:
229 | type: string
230 | local_format:
231 | type: string
232 | international_format:
233 | type: string
234 | country_prefix:
235 | type: string
236 | country_code:
237 | type: string
238 | country_name:
239 | type: string
240 | location:
241 | type: string
242 | carrier:
243 | type: string
244 | line_type:
245 | type: string
246 | description: ''
247 | GoogleSearchScanResult:
248 | title: GoogleSearchScanResult
249 | type: object
250 | properties:
251 | socialMedia:
252 | type: array
253 | uniqueItems: false
254 | items:
255 | type: object
256 | properties:
257 | URL:
258 | type: string
259 | dork:
260 | type: string
261 | number:
262 | type: string
263 | disposableProviders:
264 | type: array
265 | items:
266 | type: object
267 | properties:
268 | URL:
269 | type: string
270 | dork:
271 | type: string
272 | number:
273 | type: string
274 | reputation:
275 | type: array
276 | items:
277 | type: object
278 | properties:
279 | URL:
280 | type: string
281 | dork:
282 | type: string
283 | number:
284 | type: string
285 | individuals:
286 | type: array
287 | items:
288 | type: object
289 | properties:
290 | URL:
291 | type: string
292 | dork:
293 | type: string
294 | number:
295 | type: string
296 | general:
297 | type: array
298 | items:
299 | type: object
300 | properties:
301 | URL:
302 | type: string
303 | dork:
304 | type: string
305 | number:
306 | type: string
307 | OVHScanResult:
308 | title: OVHScanResult
309 | type: object
310 | properties:
311 | found:
312 | type: boolean
313 | numberRange:
314 | type: string
315 | city:
316 | type: string
317 | zipCode:
318 | type: string
319 | securitySchemes: {}
320 |
--------------------------------------------------------------------------------
/api/response.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "strings"
5 | )
6 |
7 | func successResponse(msg ...string) JSONResponse {
8 | var message string = ""
9 |
10 | if len(msg) > 0 {
11 | message = strings.Join(msg, " ")
12 | }
13 |
14 | return JSONResponse{
15 | Success: true,
16 | Error: message,
17 | }
18 | }
19 |
20 | func errorResponse(msg ...string) JSONResponse {
21 | var message string = "An error occurred"
22 |
23 | if len(msg) > 0 {
24 | message = strings.Join(msg, " ")
25 | }
26 |
27 | return JSONResponse{
28 | Success: false,
29 | Error: message,
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/api/response_test.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestResponse(t *testing.T) {
10 | assert := assert.New(t)
11 |
12 | t.Run("successResponse", func(t *testing.T) {
13 | t.Run("should return success response", func(t *testing.T) {
14 | result := successResponse()
15 |
16 | assert.IsType(JSONResponse{}, result)
17 | assert.Equal(result.Success, true, "they should be equal")
18 | assert.Equal(result.Error, "", "they should be equal")
19 | })
20 |
21 | t.Run("should return success response with custom message", func(t *testing.T) {
22 | result := successResponse("test")
23 |
24 | assert.IsType(JSONResponse{}, result)
25 | assert.Equal(result.Success, true, "they should be equal")
26 | assert.Equal(result.Error, "test", "they should be equal")
27 | })
28 | })
29 |
30 | t.Run("errorResponse", func(t *testing.T) {
31 | t.Run("should return error response", func(t *testing.T) {
32 | result := errorResponse()
33 |
34 | assert.IsType(JSONResponse{}, result)
35 | assert.Equal(result.Success, false, "they should be equal")
36 | assert.Equal(result.Error, "An error occurred", "they should be equal")
37 | })
38 |
39 | t.Run("should return error response with custom message", func(t *testing.T) {
40 | result := errorResponse("test")
41 |
42 | assert.IsType(JSONResponse{}, result)
43 | assert.Equal(result.Success, false, "they should be equal")
44 | assert.Equal(result.Error, "test", "they should be equal")
45 | })
46 | })
47 | }
48 |
--------------------------------------------------------------------------------
/api/server.go:
--------------------------------------------------------------------------------
1 | // Package api is about the REST API of PhoneInfoga
2 | //go:generate $GOPATH/bin/go-assets-builder ../client/dist -o ./assets.go -p api
3 | package api
4 |
5 | import (
6 | "net/http"
7 | "strings"
8 |
9 | "github.com/gin-gonic/gin"
10 | )
11 |
12 | const (
13 | clientDistPath = "/client/dist/"
14 | staticPath = "/"
15 | )
16 |
17 | func detectContentType(path string, data []byte) string {
18 | arr := strings.Split(path, ".")
19 | ext := arr[len(arr)-1]
20 |
21 | switch ext {
22 | case "js":
23 | return "application/javascript"
24 | case "css":
25 | return "text/css"
26 | case "svg":
27 | return "image/svg+xml"
28 | default:
29 | return http.DetectContentType(data)
30 | }
31 | }
32 |
33 | func registerClientRoute(router *gin.Engine) {
34 | for name, file := range Assets.Files {
35 | if file.IsDir() {
36 | continue
37 | }
38 |
39 | path := strings.ReplaceAll(name, clientDistPath, staticPath)
40 | data := file.Data
41 |
42 | if path == staticPath+"index.html" {
43 | path = staticPath
44 | }
45 |
46 | router.GET(path, func(c *gin.Context) {
47 | c.Header("Content-Type", detectContentType(path, data))
48 | c.Writer.WriteHeader(http.StatusOK)
49 | c.Writer.Write(data)
50 | c.Abort()
51 | })
52 | }
53 | }
54 |
55 | // Serve launches the web client
56 | // Using Gin & Vue.js
57 | func Serve(router *gin.Engine, disableClient bool) *gin.Engine {
58 | router.Group("/api").
59 | GET("/", healthHandler).
60 | GET("/numbers", getAllNumbers).
61 | GET("/numbers/:number/validate", ValidateScanURL, validate).
62 | GET("/numbers/:number/scan/local", ValidateScanURL, localScan).
63 | GET("/numbers/:number/scan/numverify", ValidateScanURL, numverifyScan).
64 | GET("/numbers/:number/scan/googlesearch", ValidateScanURL, googleSearchScan).
65 | GET("/numbers/:number/scan/ovh", ValidateScanURL, ovhScan)
66 |
67 | if !disableClient {
68 | registerClientRoute(router)
69 | }
70 |
71 | router.Use(func(c *gin.Context) {
72 | c.JSON(404, JSONResponse{
73 | Success: false,
74 | Error: "Resource not found",
75 | })
76 | })
77 |
78 | return router
79 | }
80 |
--------------------------------------------------------------------------------
/api/server_test.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "io/ioutil"
5 | "net/http"
6 | "net/http/httptest"
7 | "testing"
8 |
9 | "github.com/gin-gonic/gin"
10 | assertTest "github.com/stretchr/testify/assert"
11 | "gopkg.in/h2non/gock.v1"
12 | "gopkg.in/sundowndev/phoneinfoga.v2/pkg/config"
13 | "gopkg.in/sundowndev/phoneinfoga.v2/pkg/scanners"
14 | )
15 |
16 | var r *gin.Engine
17 |
18 | func performRequest(r http.Handler, method, path string) (*httptest.ResponseRecorder, error) {
19 | req, err := http.NewRequest(method, path, nil)
20 | w := httptest.NewRecorder()
21 | r.ServeHTTP(w, req)
22 | return w, err
23 | }
24 |
25 | func BenchmarkAPI(b *testing.B) {
26 | assert := assertTest.New(b)
27 | r = gin.Default()
28 | r = Serve(r, true)
29 |
30 | b.Run("localScan - /api/numbers/:number/scan/local", func(b *testing.B) {
31 | for i := 0; i < b.N; i++ {
32 | res, err := performRequest(r, "GET", "/api/numbers/3312345253/scan/local")
33 | assert.Equal(nil, err)
34 | assert.Equal(res.Result().StatusCode, 200, "should be equal")
35 | }
36 | })
37 | }
38 |
39 | func TestApi(t *testing.T) {
40 | assert := assertTest.New(t)
41 | r = gin.Default()
42 | r = Serve(r, false)
43 |
44 | t.Run("detectContentType", func(t *testing.T) {
45 | contentType := detectContentType("/file.hash.css", []byte{})
46 | assert.Equal("text/css", contentType, "should be equal")
47 |
48 | contentType = detectContentType("/file.hash.js", []byte{})
49 | assert.Equal("application/javascript", contentType, "should be equal")
50 |
51 | contentType = detectContentType("/file.hash.svg", []byte{})
52 | assert.Equal("image/svg+xml", contentType, "should be equal")
53 |
54 | contentType = detectContentType("/file.html", []byte(""))
55 | assert.Equal("text/html; charset=utf-8", contentType, "should be equal")
56 | })
57 |
58 | t.Run("Serve", func(t *testing.T) {
59 | t.Run("getAllNumbers - /api/numbers", func(t *testing.T) {
60 | res, err := performRequest(r, "GET", "/api/numbers")
61 |
62 | body, _ := ioutil.ReadAll(res.Body)
63 |
64 | assert.Equal(err, nil, "should be equal")
65 | assert.Equal(res.Result().StatusCode, 200, "should be equal")
66 | assert.Equal(string(body), "{\"success\":true,\"error\":\"\",\"numbers\":[]}", "should be equal")
67 | })
68 |
69 | t.Run("validate - /api/numbers/:number/validate", func(t *testing.T) {
70 | t.Run("valid number", func(t *testing.T) {
71 | res, err := performRequest(r, "GET", "/api/numbers/3312345253/validate")
72 |
73 | body, _ := ioutil.ReadAll(res.Body)
74 |
75 | assert.Equal(err, nil, "should be equal")
76 | assert.Equal(res.Result().StatusCode, 200, "should be equal")
77 | assert.Equal(string(body), "{\"success\":true,\"error\":\"The number is valid\"}", "should be equal")
78 | })
79 |
80 | t.Run("invalid number", func(t *testing.T) {
81 | res, err := performRequest(r, "GET", "/api/numbers/azerty/validate")
82 |
83 | body, _ := ioutil.ReadAll(res.Body)
84 |
85 | assert.Equal(err, nil, "should be equal")
86 | assert.Equal(res.Result().StatusCode, 400, "should be equal")
87 | assert.Equal(string(body), "{\"success\":false,\"error\":\"Parameter 'number' must be a valid integer.\"}", "should be equal")
88 | })
89 |
90 | t.Run("invalid country code", func(t *testing.T) {
91 | res, err := performRequest(r, "GET", "/api/numbers/09880/validate")
92 |
93 | body, _ := ioutil.ReadAll(res.Body)
94 |
95 | assert.Equal(err, nil, "should be equal")
96 | assert.Equal(res.Result().StatusCode, 400, "should be equal")
97 | assert.Equal(string(body), "{\"success\":false,\"error\":\"invalid country code\"}", "should be equal")
98 | })
99 | })
100 |
101 | t.Run("localScan - /api/numbers/:number/scan/local", func(t *testing.T) {
102 | t.Run("valid number", func(t *testing.T) {
103 | res, err := performRequest(r, "GET", "/api/numbers/3312345253/scan/local")
104 |
105 | body, _ := ioutil.ReadAll(res.Body)
106 |
107 | assert.Equal(err, nil, "should be equal")
108 | assert.Equal(res.Result().StatusCode, 200, "should be equal")
109 | assert.Equal(string(body), `{"success":true,"error":"","result":{"rawLocal":"12345253","local":"12345253","E164":"+3312345253","international":"3312345253","countryCode":33,"country":"FR","carrier":""}}`, "should be equal")
110 | })
111 |
112 | t.Run("invalid number", func(t *testing.T) {
113 | res, err := performRequest(r, "GET", "/api/numbers/9999999999/scan/local")
114 |
115 | body, _ := ioutil.ReadAll(res.Body)
116 |
117 | assert.Equal(err, nil, "should be equal")
118 | assert.Equal(res.Result().StatusCode, 400, "should be equal")
119 | assert.Equal(string(body), `{"success":false,"error":"invalid country code"}`, "should be equal")
120 | })
121 | })
122 |
123 | t.Run("numverifyScan - /api/numbers/:number/scan/numverify", func(t *testing.T) {
124 | t.Run("should succeed", func(t *testing.T) {
125 | defer gock.Off() // Flush pending mocks after test execution
126 |
127 | expectedResult := scanners.NumverifyScannerResponse{
128 | Valid: true,
129 | Number: "79516566591",
130 | LocalFormat: "9516566591",
131 | InternationalFormat: "+79516566591",
132 | CountryPrefix: "+7",
133 | CountryCode: "RU",
134 | CountryName: "Russian Federation",
135 | Location: "Saint Petersburg and Leningrad Oblast",
136 | Carrier: "OJSC St. Petersburg Telecom (OJSC Tele2-Saint-Petersburg)",
137 | LineType: "mobile",
138 | }
139 |
140 | gock.New("http://numverify.com").
141 | Get("/").
142 | Reply(200).BodyString(``)
143 |
144 | gock.New("https://numverify.com").
145 | Get("/php_helper_scripts/phone_api.php").
146 | MatchParam("secret_key", "5ad5554ac240e4d3d31107941b35a5eb").
147 | MatchParam("number", "79516566591").
148 | Reply(200).
149 | JSON(expectedResult)
150 |
151 | res, err := performRequest(r, "GET", "/api/numbers/79516566591/scan/numverify")
152 |
153 | body, _ := ioutil.ReadAll(res.Body)
154 |
155 | assert.Equal(err, nil, "should be equal")
156 | assert.Equal(res.Result().StatusCode, 200, "should be equal")
157 | assert.Equal(string(body), `{"success":true,"error":"","result":{"valid":true,"number":"79516566591","local_format":"9516566591","international_format":"+79516566591","country_prefix":"+7","country_code":"RU","country_name":"Russian Federation","location":"Saint Petersburg and Leningrad Oblast","carrier":"OJSC St. Petersburg Telecom (OJSC Tele2-Saint-Petersburg)","line_type":"mobile"}}`, "should be equal")
158 |
159 | assert.Equal(gock.IsDone(), true, "there should have no pending mocks")
160 | })
161 | })
162 |
163 | t.Run("googleSearchScan - /api/numbers/:number/scan/googlesearch", func(t *testing.T) {
164 | t.Run("should return google search dorks", func(t *testing.T) {
165 | res, err := performRequest(r, "GET", "/api/numbers/330365179268/scan/googlesearch")
166 |
167 | body, _ := ioutil.ReadAll(res.Body)
168 |
169 | assert.Equal(err, nil, "should be equal")
170 | assert.Equal(res.Result().StatusCode, 200, "should be equal")
171 | assert.Equal(string(body), `{"success":true,"error":"","result":{"socialMedia":[{"number":"+33365179268","dork":"site:facebook.com intext:\"33365179268\" OR intext:\"+33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Afacebook.com+intext%3A%2233365179268%22+OR+intext%3A%22%2B33365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:twitter.com intext:\"33365179268\" OR intext:\"+33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Atwitter.com+intext%3A%2233365179268%22+OR+intext%3A%22%2B33365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:linkedin.com intext:\"33365179268\" OR intext:\"+33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Alinkedin.com+intext%3A%2233365179268%22+OR+intext%3A%22%2B33365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:instagram.com intext:\"33365179268\" OR intext:\"+33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Ainstagram.com+intext%3A%2233365179268%22+OR+intext%3A%22%2B33365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:vk.com intext:\"33365179268\" OR intext:\"+33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Avk.com+intext%3A%2233365179268%22+OR+intext%3A%22%2B33365179268%22+OR+intext%3A%220365179268%22"}],"disposableProviders":[{"number":"+33365179268","dork":"site:hs3x.com intext:\"33365179268\"","URL":"https://www.google.com/search?q=site%3Ahs3x.com+intext%3A%2233365179268%22"},{"number":"+33365179268","dork":"site:receive-sms-now.com intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Areceive-sms-now.com+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:smslisten.com intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Asmslisten.com+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:smsnumbersonline.com intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Asmsnumbersonline.com+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:freesmscode.com intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Afreesmscode.com+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:catchsms.com intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Acatchsms.com+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:smstibo.com intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Asmstibo.com+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:smsreceiving.com intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Asmsreceiving.com+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:getfreesmsnumber.com intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Agetfreesmsnumber.com+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:sellaite.com intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Asellaite.com+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:receive-sms-online.info intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Areceive-sms-online.info+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:receivesmsonline.com intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Areceivesmsonline.com+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:receive-a-sms.com intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Areceive-a-sms.com+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:sms-receive.net intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Asms-receive.net+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:receivefreesms.com intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Areceivefreesms.com+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:receive-sms.com intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Areceive-sms.com+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:receivetxt.com intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Areceivetxt.com+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:freephonenum.com intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Afreephonenum.com+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:freesmsverification.com intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Afreesmsverification.com+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:receive-sms-online.com intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Areceive-sms-online.com+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:smslive.co intext:\"33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Asmslive.co+intext%3A%2233365179268%22+OR+intext%3A%220365179268%22"}],"reputation":[{"number":"+33365179268","dork":"site:whosenumber.info intext:\"+33365179268\" intitle:\"who called\"","URL":"https://www.google.com/search?q=site%3Awhosenumber.info+intext%3A%22%2B33365179268%22+intitle%3A%22who+called%22"},{"number":"+33365179268","dork":"intitle:\"Phone Fraud\" intext:\"33365179268\" OR intext:\"+33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=intitle%3A%22Phone+Fraud%22+intext%3A%2233365179268%22+OR+intext%3A%22%2B33365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:findwhocallsme.com intext:\"+33365179268\" OR intext:\"33365179268\"","URL":"https://www.google.com/search?q=site%3Afindwhocallsme.com+intext%3A%22%2B33365179268%22+OR+intext%3A%2233365179268%22"},{"number":"+33365179268","dork":"site:yellowpages.ca intext:\"+33365179268\"","URL":"https://www.google.com/search?q=site%3Ayellowpages.ca+intext%3A%22%2B33365179268%22"},{"number":"+33365179268","dork":"site:phonenumbers.ie intext:\"+33365179268\"","URL":"https://www.google.com/search?q=site%3Aphonenumbers.ie+intext%3A%22%2B33365179268%22"},{"number":"+33365179268","dork":"site:who-calledme.com intext:\"+33365179268\"","URL":"https://www.google.com/search?q=site%3Awho-calledme.com+intext%3A%22%2B33365179268%22"},{"number":"+33365179268","dork":"site:usphonesearch.net intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Ausphonesearch.net+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:whocalled.us inurl:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Awhocalled.us+inurl%3A%220365179268%22"},{"number":"+33365179268","dork":"site:quinumero.info intext:\"0365179268\" OR intext:\"33365179268\"","URL":"https://www.google.com/search?q=site%3Aquinumero.info+intext%3A%220365179268%22+OR+intext%3A%2233365179268%22"},{"number":"+33365179268","dork":"site:uk.popularphotolook.com inurl:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Auk.popularphotolook.com+inurl%3A%220365179268%22"}],"individuals":[{"number":"+33365179268","dork":"site:numinfo.net intext:\"33365179268\" OR intext:\"+33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Anuminfo.net+intext%3A%2233365179268%22+OR+intext%3A%22%2B33365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:sync.me intext:\"33365179268\" OR intext:\"+33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Async.me+intext%3A%2233365179268%22+OR+intext%3A%22%2B33365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:whocallsyou.de intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Awhocallsyou.de+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:pastebin.com intext:\"33365179268\" OR intext:\"+33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Apastebin.com+intext%3A%2233365179268%22+OR+intext%3A%22%2B33365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:whycall.me intext:\"33365179268\" OR intext:\"+33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Awhycall.me+intext%3A%2233365179268%22+OR+intext%3A%22%2B33365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:locatefamily.com intext:\"33365179268\" OR intext:\"+33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Alocatefamily.com+intext%3A%2233365179268%22+OR+intext%3A%22%2B33365179268%22+OR+intext%3A%220365179268%22"},{"number":"+33365179268","dork":"site:spytox.com intext:\"0365179268\"","URL":"https://www.google.com/search?q=site%3Aspytox.com+intext%3A%220365179268%22"}],"general":[{"number":"+33365179268","dork":"intext:\"33365179268\" OR intext:\"+33365179268\" OR intext:\"0365179268\" OR intext:\"03 65 17 92 68\"","URL":"https://www.google.com/search?q=intext%3A%2233365179268%22+OR+intext%3A%22%2B33365179268%22+OR+intext%3A%220365179268%22+OR+intext%3A%2203+65+17+92+68%22"},{"number":"+33365179268","dork":"(ext:doc OR ext:docx OR ext:odt OR ext:pdf OR ext:rtf OR ext:sxw OR ext:psw OR ext:ppt OR ext:pptx OR ext:pps OR ext:csv OR ext:txt OR ext:xls) intext:\"33365179268\" OR intext:\"+33365179268\" OR intext:\"0365179268\"","URL":"https://www.google.com/search?q=%28ext%3Adoc+OR+ext%3Adocx+OR+ext%3Aodt+OR+ext%3Apdf+OR+ext%3Artf+OR+ext%3Asxw+OR+ext%3Apsw+OR+ext%3Appt+OR+ext%3Apptx+OR+ext%3Apps+OR+ext%3Acsv+OR+ext%3Atxt+OR+ext%3Axls%29+intext%3A%2233365179268%22+OR+intext%3A%22%2B33365179268%22+OR+intext%3A%220365179268%22"}]}}`, "should be equal")
172 | })
173 | })
174 |
175 | t.Run("ovhScan - /api/numbers/:number/scan/ovh", func(t *testing.T) {
176 | t.Run("should find number on OVH", func(t *testing.T) {
177 | defer gock.Off() // Flush pending mocks after test execution
178 |
179 | gock.New("https://api.ovh.com").
180 | Get("/1.0/telephony/number/detailedZones").
181 | MatchParam("country", "fr").
182 | Reply(200).
183 | JSON([]scanners.OVHAPIResponseNumber{
184 | {
185 | ZneList: []string{},
186 | MatchingCriteria: "",
187 | Prefix: 33,
188 | InternationalNumber: "003336517xxxx",
189 | Country: "fr",
190 | ZipCode: "",
191 | Number: "036517xxxx",
192 | City: "Abbeville",
193 | AskedCity: "",
194 | },
195 | })
196 |
197 | res, err := performRequest(r, "GET", "/api/numbers/330365179268/scan/ovh")
198 |
199 | body, _ := ioutil.ReadAll(res.Body)
200 |
201 | assert.Equal(err, nil, "should be equal")
202 | assert.Equal(res.Result().StatusCode, 200, "should be equal")
203 | assert.Equal(string(body), `{"success":true,"error":"","result":{"found":true,"numberRange":"036517xxxx","city":"Abbeville","zipCode":""}}`, "should be equal")
204 |
205 | assert.Equal(gock.IsDone(), true, "there should have no pending mocks")
206 | })
207 | })
208 |
209 | t.Run("healthHandler - /api/", func(t *testing.T) {
210 | res, err := performRequest(r, "GET", "/api/")
211 |
212 | body, _ := ioutil.ReadAll(res.Body)
213 |
214 | assert.Equal(nil, err, "should be equal")
215 | assert.Equal(200, res.Result().StatusCode, "should be equal")
216 | assert.Equal("{\"success\":true,\"version\":\""+config.Version+"\"}", string(body), "should be equal")
217 | })
218 |
219 | t.Run("404 error - /api/notfound", func(t *testing.T) {
220 | res, err := performRequest(r, "GET", "/api/notfound")
221 |
222 | body, _ := ioutil.ReadAll(res.Body)
223 |
224 | assert.Equal(err, nil, "should be equal")
225 | assert.Equal(res.Result().StatusCode, 404, "should be equal")
226 | assert.Equal(string(body), "{\"success\":false,\"error\":\"Resource not found\"}", "should be equal")
227 | })
228 |
229 | t.Run("Client - /", func(t *testing.T) {
230 | res, err := performRequest(r, "GET", "/")
231 |
232 | assert.Equal(nil, err, "should be equal")
233 | assert.Equal(200, res.Result().StatusCode, "should be equal")
234 | assert.Equal(http.Header{"Content-Type": []string{"text/html; charset=utf-8"}}, res.Header(), "should be equal")
235 | })
236 | })
237 | }
238 |
--------------------------------------------------------------------------------
/api/validators.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gin-gonic/gin"
7 | "gopkg.in/sundowndev/phoneinfoga.v2/pkg/scanners"
8 | )
9 |
10 | // JSONResponse is the default API response type
11 | type JSONResponse struct {
12 | Success bool `json:"success"`
13 | Error string `json:"error"`
14 | }
15 |
16 | type scanURL struct {
17 | Number uint `uri:"number" binding:"required,min=2"`
18 | }
19 |
20 | // ValidateScanURL validates scan URLs
21 | func ValidateScanURL(c *gin.Context) {
22 | var v scanURL
23 |
24 | if err := c.ShouldBindUri(&v); err != nil {
25 | errorHandling(c, "Parameter 'number' must be a valid integer.")
26 | return
27 | }
28 |
29 | number, err := scanners.LocalScan(c.Param("number"))
30 |
31 | if err != nil {
32 | errorHandling(c, err.Error())
33 | return
34 | }
35 |
36 | c.Set("number", number)
37 | }
38 |
39 | func errorHandling(c *gin.Context, msg string) {
40 | c.JSON(http.StatusBadRequest, JSONResponse{Success: false, Error: msg})
41 | c.Abort()
42 | }
43 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | /tests/e2e/videos/
6 | /tests/e2e/screenshots/
7 |
8 | # local env files
9 | .env.local
10 | .env.*.local
11 |
12 | # Log files
13 | npm-debug.log*
14 | yarn-debug.log*
15 | yarn-error.log*
16 |
17 | # Editor directories and files
18 | .idea
19 | .vscode
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # client
2 |
3 | ## Project setup
4 | ```
5 | yarn install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | yarn serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | yarn build
16 | ```
17 |
18 | ### Run your unit tests
19 | ```
20 | yarn test:unit
21 | ```
22 |
23 | ### Run your end-to-end tests
24 | ```
25 | yarn test:e2e
26 | ```
27 |
28 | ### Lints and fixes files
29 | ```
30 | yarn lint
31 | ```
32 |
33 | ### Customize configuration
34 | See [Configuration Reference](https://cli.vuejs.org/config/).
35 |
--------------------------------------------------------------------------------
/client/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "pluginsFile": "tests/e2e/plugins/index.js"
3 | }
4 |
--------------------------------------------------------------------------------
/client/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | transform: {
3 | "^.+\\.vue$": "vue-jest",
4 | ".+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$":
5 | "jest-transform-stub",
6 | "^.+\\.tsx?$": "ts-jest",
7 | },
8 | testMatch: [
9 | "**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)",
10 | ],
11 | collectCoverage: true,
12 | collectCoverageFrom: [
13 | "src/(store|config|components|views|utils|models)/**/*.(ts|vue)",
14 | ],
15 | };
16 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "build:watch": "vue-cli-service build --watch",
9 | "test": "yarn test:unit && yarn test:e2e",
10 | "test:unit": "vue-cli-service test:unit",
11 | "test:e2e": "vue-cli-service test:e2e",
12 | "lint": "vue-cli-service lint --no-fix",
13 | "lint:fix": "vue-cli-service lint --fix"
14 | },
15 | "dependencies": {
16 | "axios": "0.19.2",
17 | "bootstrap": "4.5.0",
18 | "bootstrap-vue": "2.15.0",
19 | "jquery": "3.5.1",
20 | "popper.js": "1.16.1",
21 | "vue": "2.6.11",
22 | "vue-class-component": "7.2.3",
23 | "vue-property-decorator": "8.5.0",
24 | "vue-router": "3.3.4",
25 | "vuex": "3.4.0"
26 | },
27 | "devDependencies": {
28 | "@types/jest": "26.0.0",
29 | "@typescript-eslint/eslint-plugin": "2.34.0",
30 | "@typescript-eslint/parser": "2.34.0",
31 | "@vue/cli-plugin-e2e-cypress": "4.4.4",
32 | "@vue/cli-plugin-eslint": "4.4.4",
33 | "@vue/cli-plugin-router": "4.4.4",
34 | "@vue/cli-plugin-typescript": "4.4.4",
35 | "@vue/cli-plugin-unit-jest": "4.4.4",
36 | "@vue/cli-plugin-vuex": "4.4.4",
37 | "@vue/cli-service": "4.4.4",
38 | "@vue/eslint-config-prettier": "6.0.0",
39 | "@vue/eslint-config-typescript": "5.0.2",
40 | "@vue/test-utils": "1.0.3",
41 | "babel-core": "7.0.0-bridge.0",
42 | "eslint": "7.2.0",
43 | "eslint-plugin-prettier": "3.1.4",
44 | "eslint-plugin-vue": "6.2.2",
45 | "jest": "26.0.1",
46 | "prettier": "2.0.5",
47 | "ts-jest": "26.1.0",
48 | "typescript": "3.9.5",
49 | "vue-jest": "3.0.5",
50 | "vue-template-compiler": "2.6.11"
51 | },
52 | "eslintConfig": {
53 | "root": true,
54 | "env": {
55 | "node": true
56 | },
57 | "extends": [
58 | "plugin:vue/essential",
59 | "eslint:recommended",
60 | "@vue/typescript/recommended",
61 | "@vue/prettier",
62 | "@vue/prettier/@typescript-eslint"
63 | ],
64 | "parserOptions": {
65 | "ecmaVersion": 2020
66 | },
67 | "rules": {},
68 | "overrides": [
69 | {
70 | "files": [
71 | "**/__tests__/*.{j,t}s?(x)",
72 | "**/tests/unit/**/*.spec.{j,t}s?(x)"
73 | ],
74 | "env": {
75 | "jest": true
76 | }
77 | }
78 | ]
79 | },
80 | "browserslist": [
81 | "> 1%",
82 | "last 2 versions"
83 | ]
84 | }
85 |
--------------------------------------------------------------------------------
/client/public/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | PhoneInfoga
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/client/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
14 | {{ config.appName }}
15 |
16 |
17 |
18 |
19 | {{ config.appDescription }}
20 |
21 |
22 |
23 |
24 |
25 |
26 | GitHub
31 | Resources
36 | Documentation
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | {{ err.message }}
60 |
61 |
62 |
63 |
64 |
65 |
66 |
73 |
74 |
75 |
76 |
77 | {{ config.appName }} {{ version }}
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
110 |
--------------------------------------------------------------------------------
/client/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/client/src/components/GoogleSearch.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ name }}
6 |
7 |
8 |
9 |
Toggle results
16 |
17 |
18 |
19 |
20 | General footprints
21 |
22 | Open all links
28 |
29 |
30 |
31 |
32 | {{ value.dork }}
39 |
40 |
41 |
42 |
43 |
44 | Social networks footprints
45 |
46 | Open all links
52 |
53 |
54 |
55 |
56 | {{ value.dork }}
63 |
64 |
65 |
66 |
67 |
68 | Individual footprints
69 |
70 | Open all links
76 |
77 |
78 |
79 |
80 | {{ value.dork }}
87 |
88 |
89 |
90 |
91 |
92 | Reputation footprints
93 |
94 | Open all links
100 |
101 |
102 |
103 |
104 | {{ value.dork }}
111 |
112 |
113 |
114 |
115 |
116 | Temporary number providers footprints
117 |
118 | Open all links
124 |
125 |
126 |
127 |
128 | {{ value.dork }}
135 |
136 |
137 |
138 |
139 |
140 |
141 |
228 |
--------------------------------------------------------------------------------
/client/src/components/LocalScan.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ name }}
4 |
5 |
6 |
7 |
8 |
9 |
70 |
--------------------------------------------------------------------------------
/client/src/components/NumverifyScan.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ name }}
5 |
6 | Toggle results
13 |
14 |
20 |
21 |
22 |
23 |
24 |
92 |
--------------------------------------------------------------------------------
/client/src/components/OVHScan.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ name }}
5 |
6 | Toggle results
13 |
14 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
87 |
--------------------------------------------------------------------------------
/client/src/config/index.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | appName: "PhoneInfoga",
3 | appDescription:
4 | "Advanced information gathering & OSINT tool for phone numbers",
5 | apiUrl: "/api",
6 | };
7 |
--------------------------------------------------------------------------------
/client/src/main.ts:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import { BootstrapVue, IconsPlugin } from "bootstrap-vue";
3 |
4 | import App from "./App.vue";
5 | import router from "./router";
6 | import store from "./store";
7 |
8 | Vue.config.productionTip = false;
9 |
10 | // Install BootstrapVue
11 | Vue.use(BootstrapVue);
12 | // Optionally install the BootstrapVue icon components plugin
13 | Vue.use(IconsPlugin);
14 |
15 | new Vue({
16 | router,
17 | store,
18 | render: (h) => h(App),
19 | }).$mount("#app");
20 |
--------------------------------------------------------------------------------
/client/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import VueRouter from "vue-router";
3 | import Scan from "../views/Scan.vue";
4 | import NotFound from "../views/NotFound.vue";
5 |
6 | Vue.use(VueRouter);
7 |
8 | const routes = [
9 | {
10 | path: "/",
11 | name: "Scan",
12 | component: Scan,
13 | },
14 | {
15 | path: "*",
16 | name: "NotFound",
17 | component: NotFound,
18 | },
19 | ];
20 |
21 | const router = new VueRouter({
22 | mode: "hash",
23 | base: process.env.BASE_URL,
24 | routes,
25 | });
26 |
27 | export default router;
28 |
--------------------------------------------------------------------------------
/client/src/shims-tsx.d.ts:
--------------------------------------------------------------------------------
1 | import Vue, { VNode } from "vue";
2 |
3 | declare global {
4 | namespace JSX {
5 | // tslint:disable no-empty-interface
6 | interface Element extends VNode {}
7 | // tslint:disable no-empty-interface
8 | interface ElementClass extends Vue {}
9 | interface IntrinsicElements {
10 | [elem: string]: any;
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/client/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.vue" {
2 | import Vue from "vue";
3 | export default Vue;
4 | }
5 |
--------------------------------------------------------------------------------
/client/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import Vuex, { Store } from "vuex";
3 |
4 | Vue.use(Vuex);
5 |
6 | interface ErrorAlert {
7 | message: string;
8 | }
9 |
10 | interface StoreInterface {
11 | number: string;
12 | errors: ErrorAlert[];
13 | }
14 |
15 | const store: Store = new Vuex.Store({
16 | state: {
17 | number: "",
18 | errors: [] as ErrorAlert[],
19 | },
20 | mutations: {
21 | pushError(state, error: ErrorAlert): void {
22 | state.errors.push(error);
23 | },
24 | setNumber(state, number: string): void {
25 | state.number = number;
26 | },
27 | resetState(state) {
28 | state.number = "";
29 | state.errors = [];
30 |
31 | return state;
32 | },
33 | },
34 | getters: {},
35 | actions: {
36 | resetState(context): void {
37 | context.commit("resetState");
38 | },
39 | },
40 | modules: {},
41 | });
42 |
43 | export default store;
44 |
--------------------------------------------------------------------------------
/client/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | const formatNumber = (number: string): string => {
2 | return number.replace(/[_\W]+/g, "");
3 | };
4 |
5 | const isValid = (number: string): boolean => {
6 | const formatted = formatNumber(number);
7 |
8 | return formatted.match(/^[0-9]+$/) !== null && formatted.length > 2;
9 | };
10 |
11 | export { formatNumber, isValid };
12 |
--------------------------------------------------------------------------------
/client/src/views/NotFound.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Page not found
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/client/src/views/Scan.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
18 |
19 |
20 |
27 | Run scan
28 |
29 |
30 | Reset
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
118 |
--------------------------------------------------------------------------------
/client/tests/e2e/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ["cypress"],
3 | env: {
4 | mocha: true,
5 | "cypress/globals": true,
6 | },
7 | rules: {
8 | strict: "off",
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/client/tests/e2e/plugins/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable arrow-body-style */
2 | // https://docs.cypress.io/guides/guides/plugins-guide.html
3 |
4 | // if you need a custom webpack configuration you can uncomment the following import
5 | // and then use the `file:preprocessor` event
6 | // as explained in the cypress docs
7 | // https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples
8 |
9 | // /* eslint-disable import/no-extraneous-dependencies, global-require */
10 | // const webpack = require('@cypress/webpack-preprocessor')
11 |
12 | module.exports = (on, config) => {
13 | // on('file:preprocessor', webpack({
14 | // webpackOptions: require('@vue/cli-service/webpack.config'),
15 | // watchOptions: {}
16 | // }))
17 |
18 | return Object.assign({}, config, {
19 | fixturesFolder: "tests/e2e/fixtures",
20 | integrationFolder: "tests/e2e/specs",
21 | screenshotsFolder: "tests/e2e/screenshots",
22 | videosFolder: "tests/e2e/videos",
23 | supportFile: "tests/e2e/support/index.js",
24 | });
25 | };
26 |
--------------------------------------------------------------------------------
/client/tests/e2e/specs/test.js:
--------------------------------------------------------------------------------
1 | // https://docs.cypress.io/api/introduction/api.html
2 |
3 | describe("My First Test", () => {
4 | it("Visits the app root url", () => {
5 | cy.visit("/");
6 | cy.contains("h1", "Welcome to Your Vue.js + TypeScript App");
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/client/tests/e2e/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This is will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/client/tests/e2e/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import "./commands";
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/client/tests/unit/config.spec.ts:
--------------------------------------------------------------------------------
1 | // import { shallowMount } from "@vue/test-utils";
2 | // import HelloWorld from "@/components/HelloWorld.vue";
3 | import config from "../../src/config";
4 |
5 | describe("HelloWorld.vue", () => {
6 | it("renders props.msg when passed", () => {
7 | // const msg = "new message";
8 | // const wrapper = shallowMount(HelloWorld, {
9 | // propsData: { msg }
10 | // });
11 | expect(config.appName).toBe("PhoneInfoga");
12 | expect(config.appDescription).toBe(
13 | "Advanced information gathering & OSINT tool for phone numbers"
14 | );
15 | expect(config.apiUrl).toBe("/api");
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/client/tests/unit/utils.spec.ts:
--------------------------------------------------------------------------------
1 | import { formatNumber, isValid } from "../../src/utils";
2 |
3 | describe("src/utils", () => {
4 | describe("#formatNumber", () => {
5 | it("should format phone number", () => {
6 | expect(formatNumber("+1 (555) 444-3333")).toBe("15554443333");
7 | expect(formatNumber("1/(555)_444-3333")).toBe("15554443333");
8 | });
9 | });
10 |
11 | describe("#isValid", () => {
12 | it("should check if number is valid", () => {
13 | expect(isValid("+1/(555)_444-3333")).toBe(true);
14 | expect(isValid("1 555 4443333")).toBe(true);
15 | expect(isValid("this1 555 4443333")).toBe(false);
16 | });
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "importHelpers": true,
8 | "moduleResolution": "node",
9 | "experimentalDecorators": true,
10 | "esModuleInterop": true,
11 | "allowSyntheticDefaultImports": true,
12 | "sourceMap": true,
13 | "baseUrl": ".",
14 | "types": [
15 | "webpack-env",
16 | "jest"
17 | ],
18 | "paths": {
19 | "@/*": [
20 | "src/*"
21 | ]
22 | },
23 | "lib": [
24 | "esnext",
25 | "dom",
26 | "dom.iterable",
27 | "scripthost"
28 | ]
29 | },
30 | "include": [
31 | "src/**/*.ts",
32 | "src/**/*.tsx",
33 | "src/**/*.vue",
34 | "tests/**/*.ts",
35 | "tests/**/*.tsx"
36 | ],
37 | "exclude": [
38 | "node_modules"
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/client/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | publicPath: "/",
3 | };
4 |
--------------------------------------------------------------------------------
/cmd/recon.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | "gopkg.in/sundowndev/phoneinfoga.v2/pkg/utils"
6 | )
7 |
8 | func init() {
9 | // Register command
10 | rootCmd.AddCommand(reconCmd)
11 |
12 | // Register flags
13 | reconCmd.PersistentFlags().StringVarP(&number, "number", "n", "", "The phone number to scan (E164 or international format)")
14 | reconCmd.PersistentFlags().StringVarP(&input, "input", "i", "", "Text file containing a list of phone numbers to scan (one per line)")
15 | reconCmd.PersistentFlags().StringVarP(&output, "output", "o", "", "Output to save scan results")
16 | }
17 |
18 | var reconCmd = &cobra.Command{
19 | Deprecated: "Not implemented yet.",
20 | Use: "recon",
21 | Short: "Launch custom format reconnaissance",
22 | Run: func(cmd *cobra.Command, args []string) {
23 | utils.LoggerService.Infoln("Custom recon for phone number", number)
24 |
25 | utils.LoggerService.Errorln("Not implemented yet.")
26 |
27 | utils.LoggerService.Infoln("Job finished.")
28 | },
29 | }
30 |
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/spf13/cobra"
8 | )
9 |
10 | var number string
11 | var input string // TODO: implement input
12 | var output string // TODO: implement output
13 |
14 | var rootCmd = &cobra.Command{
15 | Use: "phoneinfoga [COMMANDS] [OPTIONS]",
16 | Short: "Advanced information gathering & OSINT tool for phone numbers",
17 | Long: "PhoneInfoga is one of the most advanced tools to scan phone numbers using only free resources.",
18 | Example: "phoneinfoga scan -n ",
19 | }
20 |
21 | // Execute is a function that executes the root command
22 | func Execute() {
23 | if err := rootCmd.Execute(); err != nil {
24 | fmt.Println(err)
25 | os.Exit(1)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/cmd/scan.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/spf13/cobra"
7 | "gopkg.in/sundowndev/phoneinfoga.v2/pkg/scanners"
8 | "gopkg.in/sundowndev/phoneinfoga.v2/pkg/utils"
9 | )
10 |
11 | func init() {
12 | // Register command
13 | rootCmd.AddCommand(scanCmd)
14 |
15 | // Register flags
16 | scanCmd.PersistentFlags().StringVarP(&number, "number", "n", "", "The phone number to scan (E164 or international format)")
17 | // scanCmd.PersistentFlags().StringVarP(&input, "input", "i", "", "Text file containing a list of phone numbers to scan (one per line)")
18 | // scanCmd.PersistentFlags().StringVarP(&output, "output", "o", "", "Output to save scan results")
19 | }
20 |
21 | var scanCmd = &cobra.Command{
22 | Use: "scan",
23 | Short: "Scan a phone number",
24 | Run: func(cmd *cobra.Command, args []string) {
25 | utils.LoggerService.Infoln("Scanning phone number", number)
26 |
27 | if valid := utils.IsValid(number); !valid {
28 | utils.LoggerService.Errorln("Number is not valid.")
29 | os.Exit(1)
30 | }
31 |
32 | scanners.ScanCLI(number)
33 |
34 | utils.LoggerService.Infoln("Job finished.")
35 | },
36 | }
37 |
--------------------------------------------------------------------------------
/cmd/serve.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "log"
5 | "net/http"
6 | "strconv"
7 |
8 | "github.com/gin-gonic/gin"
9 | "github.com/spf13/cobra"
10 | api "gopkg.in/sundowndev/phoneinfoga.v2/api"
11 | )
12 |
13 | var httpPort int
14 | var disableClient bool
15 |
16 | func init() {
17 | // Register command
18 | rootCmd.AddCommand(serveCmd)
19 |
20 | // Register flags
21 | serveCmd.PersistentFlags().IntVarP(&httpPort, "port", "p", 5000, "HTTP port")
22 | serveCmd.PersistentFlags().BoolVar(&disableClient, "no-client", false, "Disable web client (REST API only)")
23 | }
24 |
25 | var serveCmd = &cobra.Command{
26 | Use: "serve",
27 | Short: "Serve web client",
28 | Run: func(cmd *cobra.Command, args []string) {
29 | router := gin.Default()
30 |
31 | api.Serve(router, disableClient)
32 |
33 | httpPort := ":" + strconv.Itoa(httpPort)
34 |
35 | srv := &http.Server{
36 | Addr: httpPort,
37 | Handler: router,
38 | }
39 |
40 | if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
41 | log.Fatalf("listen: %s\n", err)
42 | }
43 | },
44 | }
45 |
--------------------------------------------------------------------------------
/cmd/version.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | "gopkg.in/sundowndev/phoneinfoga.v2/pkg/config"
6 | "gopkg.in/sundowndev/phoneinfoga.v2/pkg/utils"
7 | )
8 |
9 | func init() {
10 | // Register command
11 | rootCmd.AddCommand(versionCmd)
12 | }
13 |
14 | var versionCmd = &cobra.Command{
15 | Use: "version",
16 | Short: "Print current version of the tool",
17 | Run: func(cmd *cobra.Command, args []string) {
18 | utils.LoggerService.Infoln("PhoneInfoga", config.Version)
19 | utils.LoggerService.Infoln("Coded by Sundowndev https://github.com/sundowndev/PhoneInfoga")
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/docker-compose.traefik.yml:
--------------------------------------------------------------------------------
1 | version: '3.7'
2 |
3 | services:
4 | phoneinfoga:
5 | # Disabled by default because it prevent scaling service
6 | # container_name: phoneinfoga
7 | restart: on-failure
8 | image: sundowndev/phoneinfoga:latest
9 | command:
10 | - "serve"
11 | networks:
12 | - default
13 | - web
14 | environment:
15 | - GIN_MODE=release
16 | labels:
17 | - 'traefik.docker.network=web'
18 | - 'traefik.enable=true'
19 | - 'traefik.domain=demo.phoneinfoga.crvx.fr'
20 | - 'traefik.basic.frontend.rule=Host:demo.phoneinfoga.crvx.fr'
21 | - 'traefik.basic.port=5000'
22 | - 'traefik.basic.protocol=http'
23 | - 'traefik.frontend.headers.SSLRedirect=true'
24 | - 'traefik.frontend.headers.STSSeconds=315360000'
25 | - 'traefik.frontend.headers.browserXSSFilter=true'
26 | - 'traefik.frontend.headers.contentTypeNosniff=true'
27 | - 'traefik.frontend.headers.forceSTSHeader=true'
28 | - "traefik.frontend.headers.contentSecurityPolicy=default-src 'self';frame-ancestors 'self';style-src 'self';script-src 'self';img-src 'self';font-src 'self'"
29 | - 'traefik.frontend.headers.referrerPolicy=no-referrer'
30 | - 'traefik.frontend.headers.frameDeny=true'
31 |
32 | networks:
33 | web:
34 | external: true
35 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.7'
2 |
3 | services:
4 | phoneinfoga:
5 | container_name: phoneinfoga
6 | restart: on-failure
7 | build:
8 | context: .
9 | dockerfile: Dockerfile
10 | command:
11 | - "serve"
12 | ports:
13 | - "80:5000"
14 |
--------------------------------------------------------------------------------
/docs/contribute.md:
--------------------------------------------------------------------------------
1 | # Contribute
2 |
3 | This page describe the project structure and gives you a bit of help to start contributing.
4 |
5 | The project is maintained by a single person: [sundowndev](https://github.com/sundowndev). Contributions are welcome !
6 |
7 | !!! tip "Want to contribute ? Clone the project and open some pull requests !"
8 |
9 | ## Project
10 |
11 | ### Building from source
12 |
13 | **Requirements :**
14 |
15 | - Node.js >= v10.x
16 | - npm or yarn
17 | - Go >= 1.13
18 |
19 | **Note:** if you're using npm, just replace `yarn ` by `npm run `.
20 |
21 | ```shell
22 | # Build static assets
23 | # This will create dist directory containing client's static files
24 | $ (cd client && yarn && yarn build)
25 |
26 | # Generate in-memory assets
27 | $ go get -v github.com/jessevdk/go-assets-builder
28 | # This will put content of dist directory in memory. It's usually needed to build but
29 | # the design requires you to do it anyway.
30 | # This step is needed at each change if you're developing on the client.
31 | $ go generate ./...
32 |
33 | # Build the whole project
34 | $ go build -v .
35 | ```
36 |
37 | If you're developing, you don't need to build at each changes, you can compile then run with the `go run` command :
38 |
39 | ```
40 | $ go run main.go
41 | ```
42 |
43 | ### File structure
44 |
45 | ```shell
46 | api # REST API code
47 | client # web client code
48 | cmd # Command-line app code
49 | docs # Documentation
50 | pkg # Code base for scanners, utils ...
51 | scripts # Development & deployment scripts
52 | go.mod # Go modules file
53 | main.go # Application entrypoint
54 | ```
55 |
56 | ## Testing
57 |
58 | ### Go code
59 |
60 | ```shell
61 | # Run test suite
62 | go test -v ./...
63 |
64 | # Collect coverage
65 | go test -coverprofile=coverage.out ./...
66 |
67 | # Open coverage file as HTML
68 | go tool cover -html=coverage.out
69 | ```
70 |
71 | ### Typescript code
72 |
73 | Developping on the web client.
74 |
75 | ```shell
76 | cd client
77 |
78 | yarn test
79 | yarn test:unit
80 | yarn test:e2e
81 | ```
82 |
83 | If you're developing on the client, you can watch changes with `yarn build:watch`.
84 |
85 | ## Formatting
86 |
87 | ### Go code
88 |
89 | We use a shell script to format Go files.
90 |
91 | ```shell
92 | sh ./scripts/format.sh
93 |
94 | # You can also use GolangCI
95 | golangci-lint run -D errcheck
96 | ```
97 |
98 | ### Typescript code
99 |
100 | ```shell
101 | cd client
102 |
103 | yarn lint
104 | yarn lint:fix
105 | ```
106 |
107 | ## Documentation
108 |
109 | We use [mkdocs](https://www.mkdocs.org/) to write our documentation.
110 |
111 | ### Install mkdocs
112 |
113 | ```shell
114 | python3 -m pip install mkdocs
115 | ```
116 |
117 | ### Serve documentation on localhost
118 |
119 | This is the only command you need to start working on docs.
120 |
121 | ```shell
122 | mkdocs serve
123 | # or
124 | python3 -m mkdocs serve
125 | ```
126 |
127 | ### Build website
128 |
129 | ```shell
130 | mkdocs build
131 | ```
132 |
133 | ### Deploy on github pages
134 |
135 | ```shell
136 | mkdocs gh-deploy
137 | ```
138 |
--------------------------------------------------------------------------------
/docs/formatting.md:
--------------------------------------------------------------------------------
1 | # Formatting phone numbers
2 |
3 | ## Basics
4 |
5 | The tool only accepts E164 and International formats as input.
6 |
7 | - E164: +3396360XXXX
8 | - International: +33 9 63 60 XX XX
9 | - National: 09 63 60 XX XX
10 | - RFC3966: tel:+33-9-63-60-XX-XX
11 | - Out-of-country format from US: 011 33 9 63 60 XX XX
12 |
13 | E.164 formatting for phone numbers entails the following:
14 |
15 | - A + (plus) sign
16 | - International Country Calling code
17 | - Local Area code
18 | - Local Phone number
19 |
20 | For example, here’s a US-based number in standard local formatting: (415) 555-2671
21 |
22 | 
23 |
24 | Here’s the same phone number in E.164 formatting: +14155552671
25 |
26 | 
27 |
28 | In the UK, and many other countries internationally, local dialing may require the addition of a '0' in front of the subscriber number. With E.164 formatting, this '0' must usually be removed.
29 |
30 | For example, here’s a UK-based number in standard local formatting: 020 7183 8750
31 |
32 | Here’s the same phone number in E.164 formatting: +442071838750
33 |
34 |
35 | ## Custom formatting
36 |
37 | Sometimes the phone number has footprints but is used with a different formatting. This is a problem because for example if we search for "+15417543010", we'll not find web pages that write it that way : "(541) 754–3010". So the tool use a (optional) custom formatting given by the user to find further and more accurate results. To use this feature properly and make the results more valuable, try to use a format that someone of the number' country would usually use to share the phone number online.
38 |
39 | For example, French people usually write numbers that way online : `06.20.30.40.50` or `06 20 30 40 50`.
40 |
41 | For US-based numbers, the most common format is : `543-456-1234`.
42 |
43 | ### Examples
44 |
45 | Here are some examples of custom formatting for US-based phone numbers :
46 |
47 | - `+1 618-555-xxxx`
48 | - `(+1)618-555-xxxx`
49 | - `+1/618-555-xxxx`
50 | - `(618) 555xxxx`
51 | - `(618) 555-xxxx`
52 | - `(618) 555.xxxx`
53 | - `(618)555xxxx`
54 | - `(618)555-xxxx`
55 | - `(618)555.xxxx`
56 |
57 | For European countries (France as example) :
58 |
59 | - `+3301 86 48 xx xx`
60 | - `+33018648xxxx`
61 | - `+33018 648 xxx x`
62 | - `(0033)018648xxxx`
63 | - `(+33)018 648 xxx x`
64 | - `+33/018648xxxx`
65 | - `(0033)018 648 xxx x`
66 | - `+33018-648-xxx-x`
67 | - `(+33)018648xxxx`
68 | - `(+33)01 86 48 xx xx`
69 | - `+33/018-648-xxx-x`
70 | - `+33/01-86-48-xx-xx`
71 | - `+3301-86-48-xx-xx`
72 | - `(0033)01 86 48 xx xx`
73 | - `+33/01 86 48 xx xx`
74 | - `(+33)018-648-xxx-x`
75 | - `(+33)01-86-48-xx-xx`
76 | - `(0033)01-86-48-xx-xx`
77 | - `(0033)018-648-xxx-x`
78 | - `+33/018 648 xxx x`
79 |
80 | ## Local scan formatting (developers)
81 |
82 | The local scan create several variables to be usable in OSINT footprinting and other scanners.
83 |
84 | Examples :
85 |
86 | ```
87 | {
88 | 'input': '+1 512-618-xxxx',
89 | 'default': '1512618xxxx',
90 | 'local': '512618xxxx',
91 | 'international': '+1 512-618-xxxx',
92 | 'country': 'United States',
93 | 'countryCode': '+1',
94 | 'countryIsoCode': 'US',
95 | 'location': 'Texas',
96 | 'carrier': ''
97 | }
98 | ```
99 |
100 | ```
101 | {
102 | 'input': '+33 066651xxxx',
103 | 'default': '3366651xxxx',
104 | 'local': '66651xxxx',
105 | 'international': '+33 66651xxxx',
106 | 'country': 'France',
107 | 'countryCode': '+33',
108 | 'countryIsoCode': 'FR',
109 | 'location': 'France',
110 | 'carrier': 'Bouygues'
111 | }
112 | ```
--------------------------------------------------------------------------------
/docs/go-module-usage.md:
--------------------------------------------------------------------------------
1 | # Go module usage
2 |
3 | You can easily use scanners in your own Golang script. You can find [Go documentation here](https://godoc.org/github.com/sundowndev/PhoneInfoga).
4 |
5 | ### Install the module
6 |
7 | ```
8 | go get -v gopkg.in/sundowndev/phoneinfoga.v2
9 | ```
10 |
11 | ### Usage example
12 |
13 | ```go
14 | package main
15 |
16 | import (
17 | "fmt"
18 | "log"
19 |
20 | phoneinfoga "gopkg.in/sundowndev/phoneinfoga.v2/pkg/scanners"
21 | )
22 |
23 | func main() {
24 | number, err := phoneinfoga.LocalScan("")
25 |
26 | if err != nil {
27 | log.Fatal(err)
28 | }
29 |
30 | links := phoneinfoga.GoogleSearchScan(number)
31 |
32 | for _, link := range links.Individuals {
33 | fmt.Println(link.URL) // Google search link to scan
34 | }
35 | }
36 |
37 | ```
38 |
--------------------------------------------------------------------------------
/docs/images/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drumplayer123456/git-clone-https-github.com-Wes974-PhoneInfoga.../1e4646bfcc81058b5b4318ebc728097da60ead02/docs/images/banner.png
--------------------------------------------------------------------------------
/docs/images/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/docs/images/logo_white.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/docs/images/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drumplayer123456/git-clone-https-github.com-Wes974-PhoneInfoga.../1e4646bfcc81058b5b4318ebc728097da60ead02/docs/images/screenshot.png
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # Welcome to the PhoneInfoga documentation
2 |
3 | PhoneInfoga is one of the most advanced tools to scan phone numbers using only free resources. The goal is to first gather standard information such as country, area, carrier and line type on any international phone numbers with a very good accuracy. Then search for footprints on search engines to try to find the VoIP provider or identify the owner.
4 |
5 | ### [Read the related blog post](https://medium.com/@SundownDEV/phone-number-scanning-osint-recon-tool-6ad8f0cac27b)
6 |
7 | ## Features
8 |
9 | - Check if phone number exists and is possible
10 | - Gather standard informations such as country, line type and carrier
11 | - OSINT footprinting using external APIs, Google Hacking, phone books & search engines
12 | - Check for reputation reports, social media, disposable numbers and more
13 | - Scan several numbers at once
14 | - Use custom formatting for more effective OSINT reconnaissance
15 | - **NEW**: Serve a web client along with a REST API to run scans from the browser
16 | - **NEW**: Run your own web instance as a service
17 | - **NEW**: Programmatic usage with Go modules
18 |
19 | ## Anti-features
20 |
21 | - Does not claim to provide relevant or verified data, it's just a tool !
22 | - Does not allow to "track" a phone or its owner in real time
23 | - Does not allow to get the precise phone location
24 | - Does not allow to hack a phone
25 | ----
26 |
27 | !!! bug
28 | Found a bug ? Feel free to [open an issue](https://github.com/sundowndev/PhoneInfoga/issues).
29 |
30 | You can also [reach me on Twitter](https://twitter.com/sundowndev) or at raphael(at)crvx.fr.
31 |
32 | PGP: [B64687AB97F268F43E67B97A8916203E540C65A4](https://crvx.fr/publickey.asc)
33 |
--------------------------------------------------------------------------------
/docs/install.md:
--------------------------------------------------------------------------------
1 | To install PhoneInfoga, you'll need to download the binary or build the software from its source code.
2 |
3 | !!! info
4 | For now, only Linux and MacOS are supported. If you don't see your OS/arch on the [release page on GitHub](https://github.com/sundowndev/PhoneInfoga/releases), it means it's not explicitly supported. You can always build from source by yourself. Want your OS to be supported ? Please [open an issue on GitHub](https://github.com/sundowndev/PhoneInfoga/issues).
5 |
6 | ## Binary installation (recommanded)
7 |
8 | Follow the instructions :
9 |
10 | - Go to [release page on GitHub](https://github.com/sundowndev/PhoneInfoga/releases)
11 | - Choose your OS and architecture
12 | - Download the archive, extract the binary then run it in a terminal
13 |
14 | You can also do it from the terminal:
15 |
16 | ```shell
17 | # Download the archive
18 | curl -L "https://github.com/sundowndev/phoneinfoga/releases/download/v2.0.8/phoneinfoga_$(uname -s)_$(uname -m).tar.gz" -o phoneinfoga.tar.gz
19 |
20 | # Extract the binary
21 | tar xfv phoneinfoga.tar.gz
22 |
23 | # Run the software
24 | ./phoneinfoga --help
25 |
26 | # You can install it globally
27 | mv ./phoneinfoga /usr/bin/phoneinfoga
28 | ```
29 |
30 | If the installation fails, it probably means your OS/arch is not suppored.
31 |
32 | Please check the output of `echo "$(uname -s)_$(uname -m)"` in your terminal and see if it's available on the [GitHub release page](https://github.com/sundowndev/PhoneInfoga/releases).
33 |
34 | ## Using Docker
35 |
36 | ### From docker hub
37 |
38 | You can pull the repository directly from Docker hub
39 |
40 | ```shell
41 | docker pull sundowndev/phoneinfoga:latest
42 | ```
43 |
44 | Then run the tool
45 |
46 | ```shell
47 | docker run --rm -it sundowndev/phoneinfoga version
48 | ```
49 |
50 | ### Docker-compose
51 |
52 | You can use a single docker-compose file to run the tool without downloading the source code.
53 |
54 | ```
55 | version: '3.7'
56 |
57 | services:
58 | phoneinfoga:
59 | container_name: phoneinfoga
60 | restart: on-failure
61 | image: phoneinfoga:latest
62 | command: serve
63 | ports:
64 | - "80:5000"
65 | ```
66 |
67 | ### From the source code
68 |
69 | You can download the source code, then build the docker images
70 |
71 | #### Build
72 |
73 | Build the image
74 |
75 | ```shell
76 | docker-compose build
77 | ```
78 |
79 | #### CLI usage
80 |
81 | ```shell
82 | docker-compose run --rm phoneinfoga --help
83 | ```
84 |
85 | #### Run web services
86 |
87 | ```shell
88 | docker-compose up -d
89 | ```
90 |
91 | ##### Disable web client
92 |
93 | Edit `docker-compose.yml` and add the `--no-client` option
94 |
95 | ```yaml
96 | # docker-compose.yml
97 | command: "serve --no-client"
98 | ```
99 |
100 | #### Troubleshooting
101 |
102 | All output is sent to stdout so it can be inspected by running:
103 |
104 | ```shell
105 | docker logs -f
106 | ```
107 |
--------------------------------------------------------------------------------
/docs/jetbrains.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
67 |
--------------------------------------------------------------------------------
/docs/resources.md:
--------------------------------------------------------------------------------
1 | # Resources
2 |
3 | ### Inderstanding phone numbers
4 |
5 | - [whitepages.fr/phonesystem](http://whitepages.fr/phonesystem/)
6 | - [Formatting-International-Phone-Numbers](https://support.twilio.com/hc/en-us/articles/223183008-Formatting-International-Phone-Numbers)
7 | - [National_conventions_for_writing_telephone_numbers](https://en.wikipedia.org/wiki/National_conventions_for_writing_telephone_numbers)
8 |
9 | ### Open data
10 |
11 | - [api.ovh.com/console/#/telephony](https://api.ovh.com/console/#/telephony)
12 | - [countrycode.org](https://countrycode.org/)
13 | - [countryareacode.net](http://www.countryareacode.net/en/)
14 | - [directory.didww.com/area-prefixes](http://directory.didww.com/area-prefixes)
15 | - [numinfo.net](http://www.numinfo.net/)
16 | - [gist.github.com/Goles/3196253](https://gist.github.com/Goles/3196253)
17 |
18 | ## Footprinting
19 |
20 | !!! info
21 | Both free and premium resources are included. Be careful, the listing of a data source here does not mean it has been verified or is used in the tool. Data might be false. Use it as an OSINT framework.
22 |
23 | ### Reputation / fraud
24 |
25 | - scamcallfighters.com
26 | - signal-arnaques.com
27 | - whosenumber.info
28 | - findwhocallsme.com
29 | - yellowpages.ca
30 | - phonenumbers.ie
31 | - who-calledme.com
32 | - usphonesearch.net
33 | - whocalled.us
34 | - quinumero.info
35 |
36 | ### Disposable numbers
37 |
38 | - receive-sms-online.com
39 | - receive-sms-now.com
40 | - hs3x.com
41 | - twilio.com
42 | - freesmsverification.com
43 | - freeonlinephone.org
44 | - sms-receive.net
45 | - smsreceivefree.com
46 | - receive-a-sms.com
47 | - receivefreesms.com
48 | - freephonenum.com
49 | - receive-smss.com
50 | - receivetxt.com
51 | - temp-mails.com
52 | - receive-sms.com
53 | - receivesmsonline.net
54 | - receivefreesms.com
55 | - sms-receive.net
56 | - pinger.com (=> textnow.com)
57 | - receive-a-sms.com
58 | - k7.net
59 | - kall8.com
60 | - faxaway.com
61 | - receivesmsonline.com
62 | - receive-sms-online.info
63 | - sellaite.com
64 | - getfreesmsnumber.com
65 | - smsreceiving.com
66 | - smstibo.com
67 | - catchsms.com
68 | - freesmscode.com
69 | - smsreceiveonline.com
70 | - smslisten.com
71 | - sms.sellaite.com
72 | - smslive.co
73 |
74 | ### Individuals
75 |
76 | - Facebook
77 | - Twitter
78 | - Instagram
79 | - Linkedin
80 | - True People
81 | - Fast People
82 | - Background Check
83 | - Pipl
84 | - Spytox
85 | - Makelia
86 | - IvyCall
87 | - PhoneSearch
88 | - 411
89 | - USPhone
90 | - WP Plus
91 | - Thats Them
92 | - True Caller
93 | - Sync.me
94 | - WhoCallsMe
95 | - ZabaSearch
96 | - DexKnows
97 | - WeLeakInfo
98 | - OK Caller
99 | - SearchBug
100 | - numinfo.net
101 |
102 | ### Google dork examples
103 |
104 | ```
105 | insubject:"+XXXXXXXXX" OR insubject:"+XXXXX" OR insubject:"XXXXX XXX XXX"
106 | insubject:"XXXXXXXXX" OR intitle:"XXXXXXXXX"
107 | intext:"XXXXXXXXX" AND (ext:doc OR ext:docx OR ext:odt OR ext:pdf OR ext:rtf OR ext:sxw OR ext:psw OR ext:ppt OR ext:pptx OR ext:pps OR ext:csv OR ext:txt OR ext:html)
108 | site:"hs3x.com" "+XXXXXXXXX"
109 | site:signal-arnaques.com intext:"XXXXXXXXX" intitle:" | Phone Fraud"
110 | ```
--------------------------------------------------------------------------------
/docs/usage.md:
--------------------------------------------------------------------------------
1 | Here is the documentation for CLI usage.
2 |
3 | ```shell
4 | $ phoneinfoga
5 |
6 | PhoneInfoga is one of the most advanced tools to scan phone numbers using only free resources.
7 |
8 | Usage:
9 | phoneinfoga [command]
10 |
11 | Examples:
12 | phoneinfoga scan -n
13 |
14 | Available Commands:
15 | help Help about any command
16 | scan Scan a phone number
17 | serve Serve web client
18 | version Print current version of the tool
19 |
20 | Flags:
21 | -h, --help help for phoneinfoga
22 |
23 | Use "phoneinfoga [command] --help" for more information about a command.
24 | ```
25 |
26 | ### Running a scan
27 |
28 | Use the `scan` command with the `-n` (or `--number`) option.
29 |
30 | ```
31 | phoneinfoga scan -n "+1 (555) 444-1212"
32 | phoneinfoga scan -n "+33 06 79368229"
33 | phoneinfoga scan -n "33679368229"
34 | ```
35 |
36 | Special chars such as `( ) - +` will be escaped so typing US-based numbers stay easy :
37 |
38 | ```
39 | phoneinfoga scan -n "+1 555-444-3333"
40 | ```
41 |
42 | !!! note "Note that the country code is essential. You don't know which country code to use ? [Find it here](https://www.countrycode.org/)"
43 |
44 |
69 |
70 | ## Available scanners
71 |
72 | - Numverify
73 | - Google search
74 | - OVH
75 |
76 | **Numverify** provide standard but useful informations such as number's country code, location, line type and carrier.
77 |
78 | **OVH** is, besides being a web and cloud hosting company, a telecom provider with several VoIP numbers in Europe. Thanks to their API-key free [REST API](https://api.ovh.com/), we are able to tell if a number is owned by OVH Telecom or not.
79 |
80 | **Google search** uses Google search engine and [Google Dorks](https://en.wikipedia.org/wiki/Google_hacking) to search phone number's footprints everywhere on the web. It allows you to search for scam reports, social media profiles, documents and more. **This scanner does only one thing:** generating several Google search links from a given phone number. You then have to manually open them in your browser to see results. You may therefore have links that do not return any results.
81 |
82 | ## Launch web client & REST API
83 |
84 | Run the tool through a REST API with a web client. The API has been written in Go and web client in Vue.js.
85 |
86 | ```shell
87 | phoneinfoga serve
88 | phoneinfoga serve -p 8080 # default port is 5000
89 | ```
90 |
91 | You should then be able to see the web client at `http://localhost:`.
92 |
93 | 
94 |
95 | ### Run the REST API only
96 |
97 | You can choose to only run the REST API without the web client :
98 |
99 | ```
100 | phoneinfoga serve --no-client
101 | ```
102 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module gopkg.in/sundowndev/phoneinfoga.v2
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/PuerkitoBio/goquery v1.5.1
7 | github.com/fatih/color v1.9.0
8 | github.com/gin-gonic/gin v1.6.3
9 | github.com/golang/protobuf v1.3.4 // indirect
10 | github.com/hashicorp/golang-lru v0.5.4 // indirect
11 | github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15
12 | github.com/mattn/go-colorable v0.1.6 // indirect
13 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
14 | github.com/modern-go/reflect2 v1.0.1 // indirect
15 | github.com/nyaruka/phonenumbers v1.0.56
16 | github.com/onlinecity/go-phone-iso3166 v0.0.1
17 | github.com/spf13/cobra v1.0.0
18 | github.com/spf13/pflag v1.0.5 // indirect
19 | github.com/stretchr/testify v1.6.1
20 | github.com/sundowndev/dorkgen v1.2.0
21 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a // indirect
22 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect
23 | gopkg.in/h2non/gock.v1 v1.0.15
24 | )
25 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "gopkg.in/sundowndev/phoneinfoga.v2/cmd"
5 | )
6 |
7 | func main() {
8 | cmd.Execute()
9 | }
10 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: PhoneInfoga
2 | repo_name: 'sundowndev/PhoneInfoga'
3 | repo_url: 'https://github.com/sundowndev/PhoneInfoga'
4 | site_description: 'Advanced information gathering & OSINT tool for phone numbers.'
5 | site_author: 'Sundowndev'
6 | copyright: 'PhoneInfoga was developed by sundowndev and is licensed under GPL-3.0.'
7 | nav:
8 | - 'Welcome': index.md
9 | - 'Installation': install.md
10 | - 'Getting started': usage.md
11 | - 'Formatting phone numbers': formatting.md
12 | - 'Go module usage': go-module-usage.md
13 | - 'Resources': resources.md
14 | - 'Contribute': contribute.md
15 | theme:
16 | name: material
17 | logo: './images/logo_white.svg'
18 | favicon: './images/logo.svg'
19 | palette:
20 | primary: 'blue grey'
21 | accent: 'brown'
22 | extra:
23 | social:
24 | - icon: fontawesome/brands/github-alt
25 | link: 'https://github.com/sundowndev/PhoneInfoga'
26 | - icon: fontawesome/brands/twitter
27 | link: 'https://twitter.com/sundowndev'
28 |
29 | # Extensions
30 | markdown_extensions:
31 | - markdown.extensions.admonition
32 | plugins:
33 | - search
34 | - minify:
35 | minify_html: true
36 |
--------------------------------------------------------------------------------
/pkg/config/config_test.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "regexp"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestConfig(t *testing.T) {
11 | assert := assert.New(t)
12 |
13 | t.Run("Version", func(t *testing.T) {
14 | t.Run("version should be use semver format", func(t *testing.T) {
15 | matched, err := regexp.MatchString(`^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$`, Version)
16 |
17 | assert.Equal(matched, true, "should be equal")
18 | assert.Equal(err, nil, "should be equal")
19 | })
20 | })
21 | }
22 |
--------------------------------------------------------------------------------
/pkg/config/version.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | // Version is the tool's version number as a string
4 | var Version string = "v2.3.0"
5 |
--------------------------------------------------------------------------------
/pkg/scanners/google.go:
--------------------------------------------------------------------------------
1 | package scanners
2 |
3 | import (
4 | "github.com/sundowndev/dorkgen"
5 | )
6 |
7 | // GoogleSearchDork is the common format for dork requests
8 | type GoogleSearchDork struct {
9 | Number string `json:"number"`
10 | Dork string `json:"dork"`
11 | URL string
12 | }
13 |
14 | // GoogleSearchResponse is the output of Google search scanner.
15 | // It contains all dorks created ordered by types.
16 | type GoogleSearchResponse struct {
17 | SocialMedia []*GoogleSearchDork `json:"socialMedia"`
18 | DisposableProviders []*GoogleSearchDork `json:"disposableProviders"`
19 | Reputation []*GoogleSearchDork `json:"reputation"`
20 | Individuals []*GoogleSearchDork `json:"individuals"`
21 | General []*GoogleSearchDork `json:"general"`
22 | }
23 |
24 | func getDisposableProvidersDorks(number *Number) (results []*GoogleSearchDork) {
25 | var dorks = []*dorkgen.GoogleSearch{
26 | (&dorkgen.GoogleSearch{}).
27 | Site("hs3x.com").
28 | Intext(number.International),
29 | (&dorkgen.GoogleSearch{}).
30 | Site("receive-sms-now.com").
31 | Intext(number.International).
32 | Or().
33 | Intext(number.RawLocal),
34 | (&dorkgen.GoogleSearch{}).
35 | Site("smslisten.com").
36 | Intext(number.International).
37 | Or().
38 | Intext(number.RawLocal),
39 | (&dorkgen.GoogleSearch{}).
40 | Site("smsnumbersonline.com").
41 | Intext(number.International).
42 | Or().
43 | Intext(number.RawLocal),
44 | (&dorkgen.GoogleSearch{}).
45 | Site("freesmscode.com").
46 | Intext(number.International).
47 | Or().
48 | Intext(number.RawLocal),
49 | (&dorkgen.GoogleSearch{}).
50 | Site("catchsms.com").
51 | Intext(number.International).
52 | Or().
53 | Intext(number.RawLocal),
54 | (&dorkgen.GoogleSearch{}).
55 | Site("smstibo.com").
56 | Intext(number.International).
57 | Or().
58 | Intext(number.RawLocal),
59 | (&dorkgen.GoogleSearch{}).
60 | Site("smsreceiving.com").
61 | Intext(number.International).
62 | Or().
63 | Intext(number.RawLocal),
64 | (&dorkgen.GoogleSearch{}).
65 | Site("getfreesmsnumber.com").
66 | Intext(number.International).
67 | Or().
68 | Intext(number.RawLocal),
69 | (&dorkgen.GoogleSearch{}).
70 | Site("sellaite.com").
71 | Intext(number.International).
72 | Or().
73 | Intext(number.RawLocal),
74 | (&dorkgen.GoogleSearch{}).
75 | Site("receive-sms-online.info").
76 | Intext(number.International).
77 | Or().
78 | Intext(number.RawLocal),
79 | (&dorkgen.GoogleSearch{}).
80 | Site("receivesmsonline.com").
81 | Intext(number.International).
82 | Or().
83 | Intext(number.RawLocal),
84 | (&dorkgen.GoogleSearch{}).
85 | Site("receive-a-sms.com").
86 | Intext(number.International).
87 | Or().
88 | Intext(number.RawLocal),
89 | (&dorkgen.GoogleSearch{}).
90 | Site("sms-receive.net").
91 | Intext(number.International).
92 | Or().
93 | Intext(number.RawLocal),
94 | (&dorkgen.GoogleSearch{}).
95 | Site("receivefreesms.com").
96 | Intext(number.International).
97 | Or().
98 | Intext(number.RawLocal),
99 | (&dorkgen.GoogleSearch{}).
100 | Site("receive-sms.com").
101 | Intext(number.International).
102 | Or().
103 | Intext(number.RawLocal),
104 | (&dorkgen.GoogleSearch{}).
105 | Site("receivetxt.com").
106 | Intext(number.International).
107 | Or().
108 | Intext(number.RawLocal),
109 | (&dorkgen.GoogleSearch{}).
110 | Site("freephonenum.com").
111 | Intext(number.International).
112 | Or().
113 | Intext(number.RawLocal),
114 | (&dorkgen.GoogleSearch{}).
115 | Site("freesmsverification.com").
116 | Intext(number.International).
117 | Or().
118 | Intext(number.RawLocal),
119 | (&dorkgen.GoogleSearch{}).
120 | Site("receive-sms-online.com").
121 | Intext(number.International).
122 | Or().
123 | Intext(number.RawLocal),
124 | (&dorkgen.GoogleSearch{}).
125 | Site("smslive.co").
126 | Intext(number.International).
127 | Or().
128 | Intext(number.RawLocal),
129 | }
130 |
131 | for _, dork := range dorks {
132 | results = append(results, &GoogleSearchDork{
133 | Number: number.E164,
134 | Dork: dork.String(),
135 | URL: dork.ToURL(),
136 | })
137 | }
138 |
139 | return results
140 | }
141 |
142 | func getIndividualsDorks(number *Number, formats ...string) (results []*GoogleSearchDork) {
143 | var dorks = []*dorkgen.GoogleSearch{
144 | (&dorkgen.GoogleSearch{}).
145 | Site("numinfo.net").
146 | Intext(number.International).
147 | Or().
148 | Intext(number.E164).
149 | Or().
150 | Intext(number.RawLocal),
151 | (&dorkgen.GoogleSearch{}).
152 | Site("sync.me").
153 | Intext(number.International).
154 | Or().
155 | Intext(number.E164).
156 | Or().
157 | Intext(number.RawLocal),
158 | (&dorkgen.GoogleSearch{}).
159 | Site("whocallsyou.de").
160 | Intext(number.RawLocal),
161 | (&dorkgen.GoogleSearch{}).
162 | Site("pastebin.com").
163 | Intext(number.International).
164 | Or().
165 | Intext(number.E164).
166 | Or().
167 | Intext(number.RawLocal),
168 | (&dorkgen.GoogleSearch{}).
169 | Site("whycall.me").
170 | Intext(number.International).
171 | Or().
172 | Intext(number.E164).
173 | Or().
174 | Intext(number.RawLocal),
175 | (&dorkgen.GoogleSearch{}).
176 | Site("locatefamily.com").
177 | Intext(number.International).
178 | Or().
179 | Intext(number.E164).
180 | Or().
181 | Intext(number.RawLocal),
182 | (&dorkgen.GoogleSearch{}).
183 | Site("spytox.com").
184 | Intext(number.RawLocal),
185 | }
186 |
187 | for _, dork := range dorks {
188 | for _, f := range formats {
189 | dork.Or().Intext(f)
190 | }
191 |
192 | results = append(results, &GoogleSearchDork{
193 | Number: number.E164,
194 | Dork: dork.String(),
195 | URL: dork.ToURL(),
196 | })
197 | }
198 |
199 | return results
200 | }
201 |
202 | func getSocialMediaDorks(number *Number, formats ...string) (results []*GoogleSearchDork) {
203 | var dorks = []*dorkgen.GoogleSearch{
204 | (&dorkgen.GoogleSearch{}).
205 | Site("facebook.com").
206 | Intext(number.International).
207 | Or().
208 | Intext(number.E164).
209 | Or().
210 | Intext(number.RawLocal),
211 | (&dorkgen.GoogleSearch{}).
212 | Site("twitter.com").
213 | Intext(number.International).
214 | Or().
215 | Intext(number.E164).
216 | Or().
217 | Intext(number.RawLocal),
218 | (&dorkgen.GoogleSearch{}).
219 | Site("linkedin.com").
220 | Intext(number.International).
221 | Or().
222 | Intext(number.E164).
223 | Or().
224 | Intext(number.RawLocal),
225 | (&dorkgen.GoogleSearch{}).
226 | Site("instagram.com").
227 | Intext(number.International).
228 | Or().
229 | Intext(number.E164).
230 | Or().
231 | Intext(number.RawLocal),
232 | (&dorkgen.GoogleSearch{}).
233 | Site("vk.com").
234 | Intext(number.International).
235 | Or().
236 | Intext(number.E164).
237 | Or().
238 | Intext(number.RawLocal),
239 | }
240 |
241 | for _, dork := range dorks {
242 | for _, f := range formats {
243 | dork.Or().Intext(f)
244 | }
245 |
246 | results = append(results, &GoogleSearchDork{
247 | Number: number.E164,
248 | Dork: dork.String(),
249 | URL: dork.ToURL(),
250 | })
251 | }
252 |
253 | return results
254 | }
255 |
256 | func getReputationDorks(number *Number, formats ...string) (results []*GoogleSearchDork) {
257 | var dorks = []*dorkgen.GoogleSearch{
258 | (&dorkgen.GoogleSearch{}).
259 | Site("whosenumber.info").
260 | Intext(number.E164).
261 | Intitle("who called"),
262 | (&dorkgen.GoogleSearch{}).
263 | Intitle("Phone Fraud").
264 | Intext(number.International).
265 | Or().
266 | Intext(number.E164).
267 | Or().
268 | Intext(number.RawLocal),
269 | (&dorkgen.GoogleSearch{}).
270 | Site("findwhocallsme.com").
271 | Intext(number.E164).
272 | Or().
273 | Intext(number.International),
274 | (&dorkgen.GoogleSearch{}).
275 | Site("yellowpages.ca").
276 | Intext(number.E164),
277 | (&dorkgen.GoogleSearch{}).
278 | Site("phonenumbers.ie").
279 | Intext(number.E164),
280 | (&dorkgen.GoogleSearch{}).
281 | Site("who-calledme.com").
282 | Intext(number.E164),
283 | (&dorkgen.GoogleSearch{}).
284 | Site("usphonesearch.net").
285 | Intext(number.RawLocal),
286 | (&dorkgen.GoogleSearch{}).
287 | Site("whocalled.us").
288 | Inurl(number.RawLocal),
289 | (&dorkgen.GoogleSearch{}).
290 | Site("quinumero.info").
291 | Intext(number.RawLocal).
292 | Or().
293 | Intext(number.International),
294 | (&dorkgen.GoogleSearch{}).
295 | Site("uk.popularphotolook.com").
296 | Inurl(number.RawLocal),
297 | }
298 |
299 | for _, dork := range dorks {
300 | for _, f := range formats {
301 | dork.Or().Intext(f)
302 | }
303 |
304 | results = append(results, &GoogleSearchDork{
305 | Number: number.E164,
306 | Dork: dork.String(),
307 | URL: dork.ToURL(),
308 | })
309 | }
310 |
311 | return results
312 | }
313 |
314 | func getGeneralDorks(number *Number, formats ...string) (results []*GoogleSearchDork) {
315 | var dorks = []*dorkgen.GoogleSearch{
316 | (&dorkgen.GoogleSearch{}).
317 | Intext(number.International).
318 | Or().
319 | Intext(number.E164).
320 | Or().
321 | Intext(number.RawLocal).
322 | Or().
323 | Intext(number.Local),
324 | (&dorkgen.GoogleSearch{}).
325 | Group((&dorkgen.GoogleSearch{}).
326 | Ext("doc").
327 | Or().
328 | Ext("docx").
329 | Or().
330 | Ext("odt").
331 | Or().
332 | Ext("pdf").
333 | Or().
334 | Ext("rtf").
335 | Or().
336 | Ext("sxw").
337 | Or().
338 | Ext("psw").
339 | Or().
340 | Ext("ppt").
341 | Or().
342 | Ext("pptx").
343 | Or().
344 | Ext("pps").
345 | Or().
346 | Ext("csv").
347 | Or().
348 | Ext("txt").
349 | Or().
350 | Ext("xls")).
351 | Intext(number.International).
352 | Or().
353 | Intext(number.E164).
354 | Or().
355 | Intext(number.RawLocal),
356 | }
357 |
358 | for _, dork := range dorks {
359 | for _, f := range formats {
360 | dork.Or().Intext(f)
361 | }
362 |
363 | results = append(results, &GoogleSearchDork{
364 | Number: number.E164,
365 | Dork: dork.String(),
366 | URL: dork.ToURL(),
367 | })
368 | }
369 |
370 | return results
371 | }
372 |
373 | // GoogleSearchScan creates several Google requests to search footprints of
374 | // the number online through Google search.
375 | func GoogleSearchScan(number *Number, formats ...string) (results GoogleSearchResponse) {
376 | results.SocialMedia = getSocialMediaDorks(number, formats...)
377 | results.Reputation = getReputationDorks(number, formats...)
378 | results.Individuals = getIndividualsDorks(number, formats...)
379 | results.DisposableProviders = getDisposableProvidersDorks(number)
380 | results.General = getGeneralDorks(number, formats...)
381 |
382 | return results
383 | }
384 |
--------------------------------------------------------------------------------
/pkg/scanners/local.go:
--------------------------------------------------------------------------------
1 | package scanners
2 |
3 | import (
4 | "github.com/nyaruka/phonenumbers"
5 | "gopkg.in/sundowndev/phoneinfoga.v2/pkg/utils"
6 | )
7 |
8 | // LocalScan performs a local scan of a phone
9 | // number using phonenumbers library
10 | func LocalScan(number string) (res *Number, err error) {
11 | n := "+" + utils.FormatNumber(number)
12 | country := utils.ParseCountryCode(n)
13 |
14 | num, err := phonenumbers.Parse(n, country)
15 |
16 | if err != nil {
17 | return nil, err
18 | }
19 |
20 | res = &Number{
21 | RawLocal: utils.FormatNumber(phonenumbers.Format(num, phonenumbers.NATIONAL)),
22 | Local: phonenumbers.Format(num, phonenumbers.NATIONAL),
23 | E164: phonenumbers.Format(num, phonenumbers.E164),
24 | International: utils.FormatNumber(phonenumbers.Format(num, phonenumbers.E164)),
25 | CountryCode: num.GetCountryCode(),
26 | Country: country,
27 | Carrier: num.GetPreferredDomesticCarrierCode(),
28 | }
29 |
30 | return res, nil
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/scanners/local_test.go:
--------------------------------------------------------------------------------
1 | package scanners
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "gopkg.in/sundowndev/phoneinfoga.v2/pkg/utils"
8 | )
9 |
10 | func TestLocalScan(t *testing.T) {
11 | assert := assert.New(t)
12 |
13 | t.Run("should scan number", func(t *testing.T) {
14 | result, err := localScanCLI(utils.LoggerService, "+1 718-521-2994")
15 |
16 | expectedResult := &Number{
17 | RawLocal: "7185212994",
18 | Local: "(718) 521-2994",
19 | E164: "+17185212994",
20 | International: "17185212994",
21 | CountryCode: 1,
22 | Country: "US",
23 | Carrier: "",
24 | }
25 |
26 | assert.Equal(expectedResult, result, "they should be equal")
27 | assert.Nil(err, "they should be equal")
28 | })
29 |
30 | t.Run("should fail and return error", func(t *testing.T) {
31 | _, err := LocalScan("this is not a phone number")
32 |
33 | assert.Equal(err.Error(), "the phone number supplied is not a number", "they should be equal")
34 | })
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/scanners/numverify.go:
--------------------------------------------------------------------------------
1 | package scanners
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/hex"
6 | "encoding/json"
7 | "fmt"
8 | "net/http"
9 |
10 | "github.com/PuerkitoBio/goquery"
11 | )
12 |
13 | // NumverifyScannerResponse REST API response
14 | type NumverifyScannerResponse struct {
15 | Valid bool `json:"valid"`
16 | Number string `json:"number"`
17 | LocalFormat string `json:"local_format"`
18 | InternationalFormat string `json:"international_format"`
19 | CountryPrefix string `json:"country_prefix"`
20 | CountryCode string `json:"country_code"`
21 | CountryName string `json:"country_name"`
22 | Location string `json:"location"`
23 | Carrier string `json:"carrier"`
24 | LineType string `json:"line_type"`
25 | }
26 |
27 | // NumverifyScan fetches Numverify's API
28 | func NumverifyScan(number *Number) (res *NumverifyScannerResponse, err error) {
29 | html, err := http.Get("http://numverify.com/")
30 | if err != nil {
31 | return nil, err
32 | }
33 | defer html.Body.Close()
34 |
35 | // Load the HTML document
36 | doc, err := goquery.NewDocumentFromReader(html.Body)
37 | if err != nil {
38 | return nil, err
39 | }
40 |
41 | secret, _ := doc.Find("[name=\"scl_request_secret\"]").Attr("value")
42 |
43 | // Then fetch REST API
44 | safeNumber := number.International
45 | apiKey := md5.Sum([]byte(safeNumber + secret))
46 |
47 | url := fmt.Sprintf("https://numverify.com/php_helper_scripts/phone_api.php?secret_key=%s&number=%s", hex.EncodeToString(apiKey[:]), safeNumber)
48 |
49 | // Build the request
50 | response, err := http.Get(url)
51 | if err != nil {
52 | return nil, err
53 | }
54 | defer response.Body.Close()
55 |
56 | // Fill the response with the data from the JSON
57 | var result NumverifyScannerResponse
58 |
59 | // Use json.Decode for reading streams of JSON data
60 | if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
61 | return nil, err
62 | }
63 |
64 | res = &NumverifyScannerResponse{
65 | Valid: result.Valid,
66 | Number: result.Number,
67 | LocalFormat: result.LocalFormat,
68 | InternationalFormat: result.InternationalFormat,
69 | CountryPrefix: result.CountryPrefix,
70 | CountryCode: result.CountryCode,
71 | CountryName: result.CountryName,
72 | Location: result.Location,
73 | Carrier: result.Carrier,
74 | LineType: result.LineType,
75 | }
76 |
77 | return res, nil
78 | }
79 |
--------------------------------------------------------------------------------
/pkg/scanners/numverify_test.go:
--------------------------------------------------------------------------------
1 | package scanners
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | gock "gopkg.in/h2non/gock.v1"
8 | "gopkg.in/sundowndev/phoneinfoga.v2/pkg/utils"
9 | )
10 |
11 | func TestNumverifyScanner(t *testing.T) {
12 | assert := assert.New(t)
13 |
14 | t.Run("should succeed", func(t *testing.T) {
15 | defer gock.Off() // Flush pending mocks after test execution
16 |
17 | expectedResult := NumverifyScannerResponse{
18 | Valid: true,
19 | Number: "79516566591",
20 | LocalFormat: "9516566591",
21 | InternationalFormat: "+79516566591",
22 | CountryPrefix: "+7",
23 | CountryCode: "RU",
24 | CountryName: "Russian Federation",
25 | Location: "Saint Petersburg and Leningrad Oblast",
26 | Carrier: "OJSC St. Petersburg Telecom (OJSC Tele2-Saint-Petersburg)",
27 | LineType: "mobile",
28 | }
29 |
30 | number, _ := LocalScan("+79516566591")
31 |
32 | gock.New("http://numverify.com").
33 | Get("/").
34 | Reply(200).BodyString(``)
35 |
36 | gock.New("https://numverify.com").
37 | Get("/php_helper_scripts/phone_api.php").
38 | MatchParam("secret_key", "5ad5554ac240e4d3d31107941b35a5eb").
39 | MatchParam("number", number.International).
40 | Reply(200).
41 | JSON(expectedResult)
42 |
43 | result, err := numverifyScanCLI(utils.LoggerService, number)
44 |
45 | assert.Nil(err, "they should be equal")
46 | assert.Equal(result, &expectedResult, "they should be equal")
47 |
48 | assert.Equal(gock.IsDone(), true, "there should have no pending mocks")
49 | })
50 |
51 | t.Run("should return invalid number", func(t *testing.T) {
52 | defer gock.Off() // Flush pending mocks after test execution
53 |
54 | expectedResult := NumverifyScannerResponse{
55 | Valid: false,
56 | Number: "",
57 | LocalFormat: "",
58 | InternationalFormat: "",
59 | CountryPrefix: "",
60 | CountryCode: "",
61 | CountryName: "",
62 | Location: "",
63 | Carrier: "",
64 | LineType: "",
65 | }
66 |
67 | number, _ := LocalScan("+123456789")
68 |
69 | gock.New("http://numverify.com").
70 | Get("/").
71 | Reply(200).BodyString(``)
72 |
73 | gock.New("https://numverify.com").
74 | Get("/php_helper_scripts/phone_api.php").
75 | MatchParam("secret_key", "7ccde16e862dfe7681297713e9e9cadb").
76 | MatchParam("number", number.International).
77 | Reply(200).
78 | JSON(expectedResult)
79 |
80 | result, err := numverifyScanCLI(utils.LoggerService, number)
81 |
82 | assert.Nil(err, "they should be equal")
83 | assert.Equal(result, &expectedResult, "they should be equal")
84 |
85 | assert.Equal(gock.IsDone(), true, "there should have no pending mocks")
86 | })
87 |
88 | t.Run("should return empty response and handle error properly", func(t *testing.T) {
89 | defer gock.Off() // Flush pending mocks after test execution
90 |
91 | number, _ := LocalScan("+123456789")
92 |
93 | gock.New("http://numverify.com").
94 | Get("/").
95 | Reply(200).BodyString(``)
96 |
97 | gock.New("https://numverify.com").
98 | Get("/php_helper_scripts/phone_api.php").
99 | MatchParam("secret_key", "7ccde16e862dfe7681297713e9e9cadb").
100 | MatchParam("number", number.International).
101 | Reply(200)
102 |
103 | result, err := numverifyScanCLI(utils.LoggerService, number)
104 |
105 | assert.EqualError(err, "EOF", "they should be equal")
106 | assert.Nil(result, "they should be equal")
107 |
108 | assert.Equal(gock.IsDone(), true, "there should have no pending mocks")
109 | })
110 | }
111 |
--------------------------------------------------------------------------------
/pkg/scanners/ovh.go:
--------------------------------------------------------------------------------
1 | package scanners
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 | "reflect"
8 | "strings"
9 | )
10 |
11 | // OVHAPIResponseNumber is a type that describes an OVH number range
12 | type OVHAPIResponseNumber struct {
13 | MatchingCriteria interface{} `json:"matchingCriteria"`
14 | City string `json:"city"`
15 | ZneList []string `json:"zneList"`
16 | InternationalNumber string `json:"internationalNumber"`
17 | Country string `json:"country"`
18 | AskedCity interface{} `json:"askedCity"`
19 | ZipCode string `json:"zipCode"`
20 | Number string `json:"number"`
21 | Prefix int `json:"prefix"`
22 | }
23 |
24 | // OVHScannerResponse is the OVH scanner response
25 | type OVHScannerResponse struct {
26 | Found bool `json:"found"`
27 | NumberRange string `json:"numberRange"`
28 | City string `json:"city"`
29 | ZipCode string `json:"zipCode"`
30 | }
31 |
32 | // OVHScan fetches OVH's REST API
33 | func OVHScan(number *Number) (res *OVHScannerResponse, err error) {
34 | countryCode := strings.ToLower(number.Country)
35 | url := fmt.Sprintf("https://api.ovh.com/1.0/telephony/number/detailedZones?country=%s", countryCode)
36 |
37 | // Build the request
38 | response, err := http.Get(url)
39 | if err != nil {
40 | return nil, err
41 | }
42 | defer response.Body.Close()
43 |
44 | // Fill the response with the data from the JSON
45 | var results []OVHAPIResponseNumber
46 |
47 | // Use json.Decode for reading streams of JSON data
48 | json.NewDecoder(response.Body).Decode(&results)
49 |
50 | var foundNumber OVHAPIResponseNumber
51 |
52 | rt := reflect.TypeOf(results)
53 | if rt.Kind() == reflect.Slice && len(number.RawLocal) > 6 {
54 | askedNumber := number.RawLocal[0:6] + "xxxx"
55 |
56 | for _, result := range results {
57 | if result.Number == askedNumber {
58 | foundNumber = result
59 | }
60 | }
61 | }
62 |
63 | res = &OVHScannerResponse{
64 | Found: len(foundNumber.Number) > 0,
65 | NumberRange: foundNumber.Number,
66 | City: foundNumber.City,
67 | ZipCode: foundNumber.ZipCode,
68 | }
69 |
70 | return res, nil
71 | }
72 |
--------------------------------------------------------------------------------
/pkg/scanners/ovh_test.go:
--------------------------------------------------------------------------------
1 | package scanners
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | gock "gopkg.in/h2non/gock.v1"
8 | "gopkg.in/sundowndev/phoneinfoga.v2/pkg/utils"
9 | )
10 |
11 | func TestOVHScanner(t *testing.T) {
12 | assert := assert.New(t)
13 |
14 | t.Run("should find number on OVH", func(t *testing.T) {
15 | defer gock.Off() // Flush pending mocks after test execution
16 |
17 | gock.New("https://api.ovh.com").
18 | Get("/1.0/telephony/number/detailedZones").
19 | MatchParam("country", "fr").
20 | Reply(200).
21 | JSON([]OVHAPIResponseNumber{
22 | {
23 | ZneList: []string{},
24 | MatchingCriteria: "",
25 | Prefix: 33,
26 | InternationalNumber: "003336517xxxx",
27 | Country: "fr",
28 | ZipCode: "",
29 | Number: "036517xxxx",
30 | City: "Abbeville",
31 | AskedCity: "",
32 | },
33 | })
34 |
35 | number, _ := LocalScan("+33 0365179268")
36 |
37 | result, err := ovhScanCLI(utils.LoggerService, number)
38 |
39 | assert.Equal(result, &OVHScannerResponse{
40 | Found: true,
41 | NumberRange: "036517xxxx",
42 | City: "Abbeville",
43 | ZipCode: "",
44 | }, "they should be equal")
45 |
46 | assert.Equal(gock.IsDone(), true, "there should have no pending mocks")
47 | assert.Nil(err, "they should be equal")
48 | })
49 |
50 | t.Run("should not find number on OVH", func(t *testing.T) {
51 | defer gock.Off() // Flush pending mocks after test execution
52 |
53 | gock.New("https://api.ovh.com").
54 | Get("/1.0/telephony/number/detailedZones").
55 | MatchParam("country", "us").
56 | Reply(200).
57 | JSON([]OVHAPIResponseNumber{
58 | {
59 | ZneList: []string{},
60 | MatchingCriteria: "",
61 | Prefix: 33,
62 | InternationalNumber: "003336517xxxx",
63 | Country: "fr",
64 | ZipCode: "",
65 | Number: "036517xxxx",
66 | City: "Abbeville",
67 | AskedCity: "",
68 | },
69 | })
70 |
71 | number, _ := LocalScan("+1 718-521-2994")
72 |
73 | result, err := OVHScan(number)
74 |
75 | assert.Equal(err, nil, "should not be errored")
76 | assert.Equal(result, &OVHScannerResponse{
77 | Found: false,
78 | NumberRange: "",
79 | City: "",
80 | ZipCode: "",
81 | }, "they should be equal")
82 |
83 | assert.Equal(gock.IsDone(), true, "there should have no pending mocks")
84 | })
85 |
86 | t.Run("should not find country code on OVH", func(t *testing.T) {
87 | defer gock.Off() // Flush pending mocks after test execution
88 |
89 | gock.New("https://api.ovh.com").
90 | Get("/1.0/telephony/number/detailedZones").
91 | MatchParam("country", "us").
92 | Reply(200).
93 | JSON(map[string]string{
94 | "message": "[country] Given data (us) does not belong to the NumberCountryEnum enumeration",
95 | })
96 |
97 | number, _ := LocalScan("+1 718-521-2994")
98 |
99 | result, err := OVHScan(number)
100 |
101 | assert.Equal(err, nil, "should not be errored")
102 | assert.Equal(result, &OVHScannerResponse{
103 | Found: false,
104 | NumberRange: "",
105 | City: "",
106 | ZipCode: "",
107 | }, "they should be equal")
108 |
109 | assert.Equal(gock.IsDone(), true, "there should have no pending mocks")
110 | })
111 | }
112 |
--------------------------------------------------------------------------------
/pkg/scanners/scanners.go:
--------------------------------------------------------------------------------
1 | package scanners
2 |
3 | import (
4 | "log"
5 | "os"
6 |
7 | "gopkg.in/sundowndev/phoneinfoga.v2/pkg/utils"
8 | )
9 |
10 | // Number is a phone number
11 | type Number struct {
12 | RawLocal string `json:"rawLocal"`
13 | Local string `json:"local"`
14 | E164 string `json:"E164"`
15 | International string `json:"international"`
16 | CountryCode int32 `json:"countryCode"`
17 | Country string `json:"country"`
18 | Carrier string `json:"carrier"`
19 | }
20 |
21 | func localScanCLI(l *utils.Logger, number string) (*Number, error) {
22 | l.Infoln("Running local scan...")
23 |
24 | scan, err := LocalScan(number)
25 |
26 | if err != nil {
27 | l.Errorln("An error occurred:", err.Error())
28 | return nil, err
29 | }
30 |
31 | l.Successln("Local format:", scan.Local)
32 | l.Successln("E164 format:", scan.E164)
33 | l.Successln("International format:", scan.International)
34 | l.Successf("Country found: +%v (%v)", scan.CountryCode, scan.Country)
35 | l.Successln("Carrier:", scan.Carrier)
36 |
37 | return scan, nil
38 | }
39 |
40 | func numverifyScanCLI(l *utils.Logger, number *Number) (*NumverifyScannerResponse, error) {
41 | l.Infoln("Running Numverify.com scan...")
42 |
43 | scan, err := NumverifyScan(number)
44 |
45 | if err != nil {
46 | l.Errorln("An error occurred:", err.Error())
47 | l.Errorln("It may be Numverify not finding information about that number, or the number being invalid.")
48 | return nil, err
49 | }
50 |
51 | l.Successf(`Valid: %v`, scan.Valid)
52 | l.Successln("Number:", scan.Number)
53 | l.Successln("Local format:", scan.LocalFormat)
54 | l.Successln("International format:", scan.InternationalFormat)
55 | l.Successf("Country code: %v (%v)", scan.CountryCode, scan.CountryPrefix)
56 | l.Successln("Country:", scan.CountryName)
57 | l.Successln("Location:", scan.Location)
58 | l.Successln("Carrier:", scan.Carrier)
59 | l.Successln("Line type:", scan.LineType)
60 |
61 | return scan, nil
62 | }
63 |
64 | func googlesearchScanCLI(l *utils.Logger, number *Number, formats ...string) GoogleSearchResponse {
65 | l.Infoln("Generating Google search dork requests...")
66 |
67 | scan := GoogleSearchScan(number, formats...)
68 |
69 | l.Infoln("Social media footprints")
70 | for _, dork := range scan.SocialMedia {
71 | l.Successf(`Link: %v`, dork.URL)
72 | }
73 |
74 | l.Infoln("Individual footprints")
75 | for _, dork := range scan.Individuals {
76 | l.Successf(`Link: %v`, dork.URL)
77 | }
78 |
79 | l.Infoln("Reputation footprints")
80 | for _, dork := range scan.Reputation {
81 | l.Successf(`Link: %v`, dork.URL)
82 | }
83 |
84 | l.Infoln("Temporary number providers footprints")
85 | for _, dork := range scan.DisposableProviders {
86 | l.Successf(`Link: %v`, dork.URL)
87 | }
88 |
89 | return scan
90 | }
91 |
92 | func ovhScanCLI(l *utils.Logger, number *Number) (*OVHScannerResponse, error) {
93 | l.Infoln("Running OVH API scan...")
94 |
95 | scan, err := OVHScan(number)
96 |
97 | if err != nil {
98 | l.Errorln("An error occurred:", err.Error())
99 | return nil, err
100 | }
101 |
102 | l.Successf(`Found: %v`, scan.Found)
103 | l.Successf(`Number range: %v`, scan.NumberRange)
104 | l.Successln("City:", scan.City)
105 | l.Successln("Zip code:", scan.ZipCode)
106 |
107 | return scan, nil
108 | }
109 |
110 | // ScanCLI Run scans with CLI output
111 | func ScanCLI(number string) {
112 | num, err := localScanCLI(utils.LoggerService, number)
113 |
114 | if err != nil {
115 | log.Fatal(err)
116 | os.Exit(1)
117 | }
118 |
119 | numverifyScanCLI(utils.LoggerService, num)
120 | googlesearchScanCLI(utils.LoggerService, num)
121 | ovhScanCLI(utils.LoggerService, num)
122 | }
123 |
--------------------------------------------------------------------------------
/pkg/utils/logger.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/fatih/color"
7 | )
8 |
9 | // Logger allows you to log messages in the terminal
10 | type Logger struct {
11 | NewColor func(value ...color.Attribute) colorLogger
12 | }
13 |
14 | type colorLogger interface {
15 | Println(a ...interface{}) (int, error)
16 | Printf(format string, a ...interface{}) (int, error)
17 | }
18 |
19 | // Infoln logs an info message
20 | func (l *Logger) Infoln(s ...string) {
21 | l.NewColor(color.FgCyan).Println("[i]", strings.Join(s, " "))
22 | }
23 |
24 | // Warnln logs an warning message
25 | func (l *Logger) Warnln(s ...string) {
26 | l.NewColor(color.FgYellow).Println("[*]", strings.Join(s, " "))
27 | }
28 |
29 | // Errorln logs an error message
30 | func (l *Logger) Errorln(s ...string) {
31 | l.NewColor(color.FgRed).Println("[!]", strings.Join(s, " "))
32 | }
33 |
34 | // Successln logs a success message
35 | func (l *Logger) Successln(s ...string) {
36 | l.NewColor(color.FgGreen).Println("[+]", strings.Join(s, " "))
37 | }
38 |
39 | // Successf logs a success message
40 | func (l *Logger) Successf(format string, messages ...interface{}) {
41 | l.NewColor(color.FgGreen).Printf("[+] "+format, messages...)
42 | l.NewColor().Printf("\n")
43 | }
44 |
45 | // LoggerService is the default logger instance
46 | var LoggerService = &Logger{
47 | NewColor: func(value ...color.Attribute) colorLogger {
48 | return color.New(value...)
49 | },
50 | }
51 |
--------------------------------------------------------------------------------
/pkg/utils/mocks/Color.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v1.0.0. DO NOT EDIT.
2 |
3 | package mocks
4 |
5 | import mock "github.com/stretchr/testify/mock"
6 |
7 | // Color is an autogenerated mock type for the Color type
8 | type Color struct {
9 | mock.Mock
10 | }
11 |
12 | // Printf provides a mock function with given fields: format, a
13 | func (_m *Color) Printf(format string, a ...interface{}) (int, error) {
14 | var _ca []interface{}
15 | _ca = append(_ca, format)
16 | _ca = append(_ca, a...)
17 | ret := _m.Called(_ca...)
18 |
19 | var r0 int
20 | if rf, ok := ret.Get(0).(func(string, ...interface{}) int); ok {
21 | r0 = rf(format, a...)
22 | } else {
23 | r0 = ret.Get(0).(int)
24 | }
25 |
26 | var r1 error
27 | if rf, ok := ret.Get(1).(func(string, ...interface{}) error); ok {
28 | r1 = rf(format, a...)
29 | } else {
30 | r1 = ret.Error(1)
31 | }
32 |
33 | return r0, r1
34 | }
35 |
36 | // Println provides a mock function with given fields: a
37 | func (_m *Color) Println(a ...interface{}) (int, error) {
38 | var _ca []interface{}
39 | _ca = append(_ca, a...)
40 | ret := _m.Called(_ca...)
41 |
42 | var r0 int
43 | if rf, ok := ret.Get(0).(func(...interface{}) int); ok {
44 | r0 = rf(a...)
45 | } else {
46 | r0 = ret.Get(0).(int)
47 | }
48 |
49 | var r1 error
50 | if rf, ok := ret.Get(1).(func(...interface{}) error); ok {
51 | r1 = rf(a...)
52 | } else {
53 | r1 = ret.Error(1)
54 | }
55 |
56 | return r0, r1
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "regexp"
5 | "strconv"
6 |
7 | phoneiso3166 "github.com/onlinecity/go-phone-iso3166"
8 | )
9 |
10 | // FormatNumber formats a phone number to remove
11 | // unnecessary chars and avoid dealing with unwanted input.
12 | func FormatNumber(n string) string {
13 | re := regexp.MustCompile(`[_\W]+`)
14 | number := re.ReplaceAllString(n, "")
15 |
16 | return number
17 | }
18 |
19 | // ParseCountryCode parses a phone number and returns ISO country code.
20 | // This is required in order to use the phonenumbers library.
21 | func ParseCountryCode(n string) string {
22 | var number uint64
23 | number, _ = strconv.ParseUint(FormatNumber(n), 10, 64)
24 |
25 | return phoneiso3166.E164.Lookup(number)
26 | }
27 |
28 | // IsValid indicate if a phone number has a valid format.
29 | func IsValid(number string) bool {
30 | number = FormatNumber(number)
31 |
32 | re := regexp.MustCompile("^[0-9]+$")
33 |
34 | return len(re.FindString(number)) != 0
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/utils/utils_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/fatih/color"
7 | "github.com/stretchr/testify/assert"
8 | "gopkg.in/sundowndev/phoneinfoga.v2/pkg/utils/mocks"
9 | )
10 |
11 | func TestUtils(t *testing.T) {
12 | assert := assert.New(t)
13 |
14 | t.Run("FormatNumber", func(t *testing.T) {
15 | t.Run("should format number correctly", func(t *testing.T) {
16 | result := FormatNumber("+1 555-444-2222")
17 |
18 | assert.Equal(result, "15554442222", "they should be equal")
19 | })
20 |
21 | t.Run("should format number correctly", func(t *testing.T) {
22 | result := FormatNumber("+1 (315) 284-1580")
23 |
24 | assert.Equal(result, "13152841580", "they should be equal")
25 | })
26 | })
27 |
28 | t.Run("ParseCountryCode", func(t *testing.T) {
29 | t.Run("should parse country code correctly", func(t *testing.T) {
30 | result := ParseCountryCode("+33 679368229")
31 |
32 | assert.Equal(result, "FR", "they should be equal")
33 | })
34 |
35 | t.Run("should parse country code correctly", func(t *testing.T) {
36 | result := ParseCountryCode("+1 315-284-1580")
37 |
38 | assert.Equal(result, "US", "they should be equal")
39 | })
40 |
41 | t.Run("should parse country code correctly", func(t *testing.T) {
42 | result := ParseCountryCode("4566118311")
43 |
44 | assert.Equal(result, "DK", "they should be equal")
45 | })
46 | })
47 |
48 | t.Run("IsValid", func(t *testing.T) {
49 | t.Run("should validate phone number", func(t *testing.T) {
50 | result := IsValid("+1 315-284-1580")
51 |
52 | assert.Equal(result, true, "they should be equal")
53 | })
54 |
55 | t.Run("should validate phone number", func(t *testing.T) {
56 | result := IsValid("P+1 315-284-1580A")
57 |
58 | assert.Equal(result, false, "they should be equal")
59 | })
60 | })
61 |
62 | t.Run("Logger", func(t *testing.T) {
63 | t.Run("Infoln", func(t *testing.T) {
64 | mLogger := new(mocks.Color)
65 |
66 | mLogger.On("Println", "[i]", "test").Once().Return(0, nil)
67 |
68 | log := &Logger{
69 | NewColor: func(value ...color.Attribute) colorLogger {
70 | assert.Equal([]color.Attribute([]color.Attribute{36}), value, "they should be equal")
71 |
72 | return mLogger
73 | },
74 | }
75 |
76 | log.Infoln("test")
77 |
78 | mLogger.AssertExpectations(t)
79 | })
80 |
81 | t.Run("Warnln", func(t *testing.T) {
82 | mLogger := new(mocks.Color)
83 |
84 | mLogger.On("Println", "[*]", "test").Once().Return(0, nil)
85 |
86 | log := &Logger{
87 | NewColor: func(value ...color.Attribute) colorLogger {
88 | assert.Equal([]color.Attribute([]color.Attribute{33}), value, "they should be equal")
89 |
90 | return mLogger
91 | },
92 | }
93 |
94 | log.Warnln("test")
95 |
96 | mLogger.AssertExpectations(t)
97 | })
98 |
99 | t.Run("Errorln", func(t *testing.T) {
100 | mLogger := new(mocks.Color)
101 |
102 | mLogger.On("Println", "[!]", "test").Once().Return(0, nil)
103 |
104 | log := &Logger{
105 | NewColor: func(value ...color.Attribute) colorLogger {
106 | assert.Equal([]color.Attribute([]color.Attribute{31}), value, "they should be equal")
107 |
108 | return mLogger
109 | },
110 | }
111 |
112 | log.Errorln("test")
113 |
114 | mLogger.AssertExpectations(t)
115 | })
116 |
117 | t.Run("Successln", func(t *testing.T) {
118 | mLogger := new(mocks.Color)
119 |
120 | mLogger.On("Println", "[+]", "test").Once().Return(0, nil)
121 |
122 | log := &Logger{
123 | NewColor: func(value ...color.Attribute) colorLogger {
124 | assert.Equal([]color.Attribute([]color.Attribute{32}), value, "they should be equal")
125 |
126 | return mLogger
127 | },
128 | }
129 |
130 | log.Successln("test")
131 |
132 | mLogger.AssertExpectations(t)
133 | })
134 |
135 | t.Run("Successf", func(t *testing.T) {
136 | var ColorNumberOfCalls int
137 |
138 | mLogger := new(mocks.Color)
139 |
140 | mLogger.On("Printf", "[+] %s", "test").Once().Return(0, nil)
141 | mLogger.On("Printf", "\n").Once().Return(0, nil)
142 |
143 | log := &Logger{
144 | NewColor: func(value ...color.Attribute) colorLogger {
145 | if ColorNumberOfCalls == 0 {
146 | assert.Equal([]color.Attribute([]color.Attribute{32}), value, "they should be equal")
147 | ColorNumberOfCalls++
148 | }
149 |
150 | return mLogger
151 | },
152 | }
153 |
154 | log.Successf("%s", "test")
155 |
156 | mLogger.AssertNumberOfCalls(t, "Printf", 2)
157 | mLogger.AssertExpectations(t)
158 | })
159 | })
160 | }
161 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ],
5 | "labels": [
6 | "dependencies",
7 | "renovate"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/scripts/docker_push.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin sh
2 |
3 | # This script is used to push the image to Docker hub
4 | docker login
5 | docker build --rm=true -t sundowndev/phoneinfoga:latest .
6 | docker tag $(docker images -q sundowndev/phoneinfoga) sundowndev/phoneinfoga:latest
7 | docker push sundowndev/phoneinfoga
8 |
9 | echo 'Script executed.'
--------------------------------------------------------------------------------
/scripts/format.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "Formatting Go files..."
4 |
5 | gofmt -s -w -l .
6 |
7 | echo "Done."
8 |
--------------------------------------------------------------------------------