leadminer 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: leadminer
3
+ Version: 0.1.0
4
+ Summary: CLI tool for finding local business leads
5
+ Requires-Dist: requests
6
+ Requires-Dist: python-dotenv
7
+ Requires-Dist: rich
8
+ Requires-Dist: typer
@@ -0,0 +1,52 @@
1
+ import database
2
+ from database import *
3
+ from dotenv import load_dotenv, set_key, get_key
4
+ import os
5
+ import typer
6
+
7
+
8
+ def campaign(command: str =typer.Argument(...), option: str | None = typer.Argument(None)):
9
+
10
+ if command == "create":
11
+ #check valid name
12
+
13
+ make_campaign(option)
14
+
15
+ elif command == "select":
16
+ campaign_id = get_campaign_id(option)
17
+ if campaign_id:
18
+ load_dotenv(".env")
19
+ set_key(".env", "ACTIVE_CAMPAIGN_NAME", option)
20
+ set_key(".env", "ACTIVE_CAMPAIGN_ID", str(campaign_id))
21
+ else:
22
+ print("That campaign does not exist")
23
+
24
+ elif command == "list":
25
+ if option:
26
+ campaign_id = get_campaign_id(option)
27
+ if campaign_id:
28
+ #handle listing the data from campaign
29
+ list_campaign_data(option)
30
+ else:
31
+ print("That campaign does not exist")
32
+ else:
33
+ # list all the campaigns
34
+ list_campaigns()
35
+
36
+ elif command == "remove":
37
+ campaign_id = get_campaign_id(option)
38
+ if campaign_id:
39
+ # remove the campaign
40
+ remove_campaign(option)
41
+ else: print("That campaign does not exist")
42
+
43
+ elif command == "disconnect":
44
+ load_dotenv(".env")
45
+ exists = get_key(".env", "ACTIVE_CAMPAIGN_NAME")
46
+ if exists:
47
+ set_key(".env", "ACTIVE_CAMPAIGN_NAME", "")
48
+ set_key(".env", "ACTIVE_CAMPAIGN_ID", "")
49
+ else: print("You are not currently connected to a campaign")
50
+
51
+ else:
52
+ print(f"\"{option}\" is not a recognised command")
@@ -0,0 +1,18 @@
1
+ """place["id"],
2
+ place["displayName"]["text"],
3
+ place["formattedAddress"],
4
+ place.get("rating"),
5
+ place.get("userRatingCount"),"""
6
+ from numbers import Real
7
+
8
+
9
+ class Lead:
10
+ def __init__(self, place_id: str, name: str, address: str, phone: str, website: str, rating: float, reviews: int):
11
+ self.placeId: str = place_id
12
+ self.name: str = name
13
+ self.address: str = address
14
+ self.phone: str = phone
15
+ self.website: str = website
16
+ self.rating: float = rating
17
+ self.reviews: int = reviews
18
+
@@ -0,0 +1,148 @@
1
+ import sqlite3
2
+ from pathlib import Path
3
+ import classes
4
+
5
+ from output import output_cli
6
+
7
+ database_path = Path.home() / ".leadminer" / "leadminer.db"
8
+
9
+ def sort_database():
10
+ database_path.parent.mkdir(exist_ok=True)
11
+ connection = sqlite3.connect(database_path)
12
+ return connection
13
+
14
+
15
+ def make_tables():
16
+ connection = sort_database()
17
+ cursor = connection.cursor()
18
+
19
+ cursor.execute("""
20
+ CREATE TABLE IF NOT EXISTS campaigns (
21
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
22
+ name TEXT UNIQUE
23
+ )
24
+ """)
25
+
26
+ cursor.execute("""
27
+ CREATE TABLE IF NOT EXISTS leads (
28
+ place_id TEXT PRIMARY KEY,
29
+ name TEXT,
30
+ address TEXT,
31
+ website TEXT,
32
+ phone TEXT,
33
+ rating REAL,
34
+ reviews INTEGER,
35
+ campaign_id INTEGER,
36
+ FOREIGN KEY (campaign_id) REFERENCES campaigns(id)
37
+ )
38
+ """)
39
+
40
+ connection.commit()
41
+ connection.close()
42
+
43
+ def make_campaign(name: str):
44
+ connection = sort_database()
45
+ cursor = connection.cursor()
46
+
47
+ cursor.execute(
48
+ "INSERT INTO campaigns (name) VALUES (?)",
49
+ (name,)
50
+ )
51
+
52
+ connection.commit()
53
+ connection.close()
54
+
55
+ def remove_campaign(name: str):
56
+ connection = sort_database()
57
+ cursor = connection.cursor()
58
+
59
+ cursor.execute(
60
+ "DELETE FROM campaigns WHERE name = ?", (name,)
61
+ )
62
+
63
+ connection.commit()
64
+ connection.close()
65
+
66
+ def list_campaigns():
67
+ connection = sort_database()
68
+ cursor = connection.cursor()
69
+
70
+ cursor.execute(
71
+ "SELECT name from campaigns"
72
+ )
73
+
74
+ rows = cursor.fetchall()
75
+
76
+ for row in rows:
77
+ print(row[0])
78
+
79
+ connection.close()
80
+
81
+ def get_campaign_id(name: str):
82
+ connection = sort_database()
83
+ cursor = connection.cursor()
84
+
85
+ cursor.execute(
86
+ "SELECT id FROM campaigns WHERE name = ?",
87
+ (name,)
88
+ )
89
+
90
+ result = cursor.fetchone()
91
+ connection.close()
92
+
93
+ if result is not None:
94
+ return result[0]
95
+ else:
96
+ return None
97
+
98
+ def save_lead(place, campaign_name):
99
+
100
+ campaign_id = get_campaign_id(campaign_name)
101
+ connection = sort_database()
102
+ cursor = connection.cursor()
103
+
104
+ cursor.execute("""
105
+ INSERT OR IGNORE INTO leads
106
+ (place_id, name, address, phone, website, rating, reviews, campaign_id)
107
+ VALUES (?,?,?,?,?,?,?,?)
108
+ """, (
109
+ place.placeId,
110
+ place.name,
111
+ place.address,
112
+ place.phone,
113
+ place.website,
114
+ place.rating,
115
+ place.reviews,
116
+ campaign_id
117
+ ))
118
+
119
+ connection.commit()
120
+ connection.close()
121
+
122
+ def list_campaign_data(name: str):
123
+ connection = sort_database()
124
+ cursor = connection.cursor()
125
+
126
+ cursor.execute("""
127
+ SELECT leads.name, leads.address, leads.phone, leads.website, leads.rating, leads.reviews
128
+ FROM leads
129
+ JOIN campaigns ON leads.campaign_id = campaigns.id
130
+ WHERE campaigns.name = ?
131
+ """, (name,))
132
+
133
+ rows = cursor.fetchall()
134
+ output = []
135
+ for row in rows:
136
+ row_name = row[0]
137
+ row_address = row[1]
138
+ row_phone = row[2]
139
+ row_site = row[3]
140
+ row_rating = row[4]
141
+ row_reviews = row[5]
142
+ temp = classes.Lead("n/a", row_name, row_address, row_phone, row_site, row_rating, row_reviews)
143
+ output.append(temp)
144
+ output_cli(output)
145
+
146
+ connection.close()
147
+
148
+ make_tables()
@@ -0,0 +1,47 @@
1
+ def help():
2
+ print("""
3
+ Search
4
+
5
+ Used to search for businesses.
6
+
7
+ Default Usage:
8
+ search <business-type> <area> for default search e.g. \"search Barber London\"
9
+ Additional Filters:
10
+ - website (y / n)
11
+ - min-rating (0-5)
12
+ - max-rating (0-5)
13
+ - min-reviews
14
+ - max-reviews
15
+ - output-format (cli / csv)
16
+ Examples:
17
+ search electrician london --output-format csv
18
+ search restaurant croydon --website n
19
+ search cafe manchester --min-rating 0 --max-rating 3
20
+
21
+ Campaigns
22
+
23
+ Used to store businesses that you've found under categories
24
+
25
+ Create campaign:
26
+ campaign create <name>
27
+ Select campaign (searches will be stored under this campaign):
28
+ campaign select <name>
29
+ Stop storing searches:
30
+ campaign disconnect
31
+ Delete campaign:
32
+ campaign remove <name>
33
+ List all campaigns:
34
+ campaign list
35
+ List all data within a campaign:
36
+ campaign list <name>
37
+
38
+ Setup
39
+
40
+ Set API key:
41
+ setup key <api_key>
42
+ Learn how to find your API key:
43
+ setup key
44
+ Set limit of businesses returned to you per search:
45
+ setup limit <limit>
46
+
47
+ """)
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: leadminer
3
+ Version: 0.1.0
4
+ Summary: CLI tool for finding local business leads
5
+ Requires-Dist: requests
6
+ Requires-Dist: python-dotenv
7
+ Requires-Dist: rich
8
+ Requires-Dist: typer
@@ -0,0 +1,15 @@
1
+ campaigns.py
2
+ classes.py
3
+ database.py
4
+ help.py
5
+ main.py
6
+ output.py
7
+ pyproject.toml
8
+ search.py
9
+ setup_cmd.py
10
+ leadminer.egg-info/PKG-INFO
11
+ leadminer.egg-info/SOURCES.txt
12
+ leadminer.egg-info/dependency_links.txt
13
+ leadminer.egg-info/entry_points.txt
14
+ leadminer.egg-info/requires.txt
15
+ leadminer.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ leadminer = main:app
@@ -0,0 +1,4 @@
1
+ requests
2
+ python-dotenv
3
+ rich
4
+ typer
@@ -0,0 +1,8 @@
1
+ campaigns
2
+ classes
3
+ database
4
+ help
5
+ main
6
+ output
7
+ search
8
+ setup_cmd
@@ -0,0 +1,37 @@
1
+ import typer
2
+ from dotenv import load_dotenv, set_key
3
+ import os
4
+ from pathlib import Path
5
+
6
+ from search import search
7
+ from output import output
8
+ from setup_cmd import setup
9
+ from help import help
10
+ from campaigns import campaign
11
+
12
+ def nit():
13
+ # check that .env exists otherwise create
14
+ if not os.path.exists(".env"):
15
+ Path(".env").touch()
16
+
17
+ load_dotenv(".env")
18
+
19
+ searchLimit = os.getenv("LIMIT")
20
+ places_api_key = os.getenv("API_KEY")
21
+
22
+ # if no limit set
23
+ if searchLimit is None:
24
+ set_key(".env", "LIMIT", "100")
25
+
26
+ app = typer.Typer()
27
+
28
+ # search
29
+ app.command()(search)
30
+ app.command()(output)
31
+ app.command()(help)
32
+ app.command()(campaign)
33
+ app.command()(setup)
34
+
35
+ nit()
36
+ if __name__ == "__main__":
37
+ app()
@@ -0,0 +1,66 @@
1
+ import csv
2
+ import rich
3
+ from rich.table import Table
4
+ from rich.console import Console
5
+ from pathlib import Path
6
+
7
+ def output_cli(leads):
8
+ console = Console()
9
+
10
+ table = Table(title="Leads")
11
+
12
+ table.add_column("Name", style="cyan")
13
+ table.add_column("Address", style="green")
14
+ table.add_column("Phone")
15
+ table.add_column("Website")
16
+ table.add_column("Rating")
17
+ table.add_column("Reviews")
18
+
19
+ for lead in leads:
20
+ table.add_row(
21
+ lead.name,
22
+ lead.address,
23
+ str(lead.phone or ""),
24
+ str(lead.website or ""),
25
+ str(lead.rating or ""),
26
+ str(lead.reviews or "")
27
+ )
28
+
29
+ console.print(table)
30
+
31
+ def output_csv(leads, filename="leads.csv"):
32
+
33
+ export_dir = Path.home() / ".leadminer" / "exports"
34
+ export_dir.mkdir(exist_ok=True)
35
+
36
+ filepath = export_dir / filename
37
+
38
+ with open(filepath, "w", newline="") as f:
39
+ writer = csv.writer(f)
40
+
41
+ writer.writerow([
42
+ "name",
43
+ "address",
44
+ "phone",
45
+ "website",
46
+ "rating",
47
+ "reviews"
48
+ ])
49
+
50
+ for lead in leads:
51
+ writer.writerow([
52
+ lead.name,
53
+ lead.address,
54
+ lead.phone,
55
+ lead.website,
56
+ lead.rating,
57
+ lead.reviews
58
+ ])
59
+ print(f"Exported CSV to {filepath}")
60
+
61
+ def output(option: str, leads):
62
+ if option == "csv":
63
+ output_csv(leads)
64
+
65
+ elif option == "cli":
66
+ output_cli(leads)
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "leadminer"
7
+ version = "0.1.0"
8
+ description = "CLI tool for finding local business leads"
9
+ dependencies = [
10
+ "requests",
11
+ "python-dotenv",
12
+ "rich",
13
+ "typer"
14
+ ]
15
+
16
+ [project.scripts]
17
+ leadminer = "main:app"
18
+
19
+ [tool.setuptools]
20
+ py-modules = [
21
+ "main",
22
+ "search",
23
+ "output",
24
+ "setup_cmd",
25
+ "help",
26
+ "campaigns",
27
+ "database",
28
+ "classes",
29
+ ]
@@ -0,0 +1,89 @@
1
+ import requests, os
2
+ from dotenv import load_dotenv, get_key
3
+ import classes
4
+ from database import save_lead
5
+ import output as output_functions
6
+
7
+ url = "https://places.googleapis.com/v1/places:searchText"
8
+
9
+ load_dotenv(".env")
10
+ searchLimit = os.getenv("LIMIT")
11
+ places_api_key = os.getenv("API_KEY")
12
+
13
+ def search(
14
+ business_type: str,
15
+ location: str,
16
+ website: str = "a",
17
+ min_rating: float = 0,
18
+ max_rating: float = 5,
19
+ limit: int | None = searchLimit,
20
+ min_reviews: int = 0,
21
+ max_reviews: int = 999999999,
22
+ output_format: str = "cli",
23
+ api_key: str | None = places_api_key):
24
+
25
+ # print(limit, api_key)
26
+
27
+ all_good = True
28
+ while all_good:
29
+ if api_key is None:
30
+ print("You have not set an API key, please set one using 'setup key <api_key>'")
31
+ all_good = False
32
+ if limit is None:
33
+ print("You have not set a search limit, please set one using 'setup limit <limit>'")
34
+ all_good = False
35
+
36
+ query = f"{business_type} in {location}"
37
+
38
+ headers = {
39
+ "Content-Type": "application/json",
40
+ "X-Goog-Api-Key": api_key,
41
+ "X-Goog-FieldMask": "places.id,places.displayName,places.formattedAddress,places.rating,places.userRatingCount,places.websiteUri,places.nationalPhoneNumber"
42
+ }
43
+
44
+ data = {
45
+ "textQuery": query,
46
+ "pageSize": limit
47
+ }
48
+
49
+ response = requests.post(url, headers=headers, json=data)
50
+ results = response.json()
51
+
52
+ object_results = []
53
+ campaign_active = get_key(".env", "ACTIVE_CAMPAIGN_NAME")
54
+
55
+ for place in results["places"]:
56
+ place_id = place["id"]
57
+ place_name = place["displayName"]["text"]
58
+ place_address = place["formattedAddress"]
59
+ place_rating = place.get("rating")
60
+ place_reviews = place.get("userRatingCount")
61
+ websiteUrl = place.get("websiteUri")
62
+ place_phone = place.get("nationalPhoneNumber")
63
+
64
+ temp = classes.Lead(place_id, place_name, place_address, place_phone, websiteUrl, place_rating, place_reviews)
65
+ object_results.append(temp)
66
+ output = []
67
+
68
+ for lead in object_results:
69
+ valid = True
70
+ if not (website == "a" or (website == "y" and lead.website) or (website == "n" and not lead.website)):
71
+ valid = False
72
+ if min_rating > lead.rating:
73
+ valid = False
74
+ if max_rating < lead.rating:
75
+ valid = False
76
+ if min_reviews > lead.reviews:
77
+ valid = False
78
+ if max_reviews < lead.reviews:
79
+ valid = False
80
+ if valid:
81
+ output.append(lead)
82
+ if campaign_active:
83
+ save_lead(lead, campaign_active)
84
+
85
+ if output_format == "cli":
86
+ output_functions.output_cli(output)
87
+ elif output_format == "csv":
88
+ output_functions.output_csv(output)
89
+ all_good = False
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,30 @@
1
+ import typer
2
+ from dotenv import load_dotenv, set_key
3
+
4
+ # setup command
5
+ # setup places api key
6
+ # set max limit
7
+
8
+
9
+
10
+ def setup(choice: str = typer.Argument(...), value: str | None = typer.Argument(None)):
11
+
12
+ if choice == "key" and value:
13
+ set_key(".env", "API_KEY", value)
14
+ elif choice == "limit":
15
+ set_key(".env", "LIMIT", value)
16
+
17
+ elif choice == "key" and not value:
18
+ print("""
19
+ To use leadminer, you need a Google Places API key.
20
+
21
+ 1. Go to this link, sign in, and click "enable":
22
+ https://console.cloud.google.com/apis/library/places-backend.googleapis.com
23
+
24
+ 2. Create an API key:
25
+ https://console.cloud.google.com/apis/credentials
26
+
27
+ 3. Copy the key and run:
28
+ leadminer setup key <YOUR_API_KEY>
29
+ """)
30
+