GPAL - Generally Positive Automation Library v1.0
GPAL The Fluent Automation LIbrary
Loading...
Searching...
No Matches
GoogleDorking.cs
1// =============================================================================
2// GPAL - Generally Positive Automation Library
3// Copyright © 2026 Software Decisions, Inc. All rights reserved.
4//
5// This file is part of GPAL.
6// Licensed under the Business Source License 1.1
7//
8// Primary development, architecture, and vision by Michael B. Vederman,
9// CEO of Software Decisions, Inc., Texas.
10//
11// Internal development maintained privately.
12// Public releases appear on GitHub: https://github.com/SoftwareDecisionsInc/GPAL.
13//
14// See LICENSE for full terms, including Additional Use Grant.
15// =============================================================================
16
17using System;
18using System.Collections.Generic;
19using System.IO;
20using System.Linq;
22using static GenerallyPositive.Enums;
23
24namespace GenerallyPositive
25{
27 {
28 private static string ConfigFilePath = "./googleDorkingConfig.json";
29
30 public string SearchApiBase { get; set; } = "https://www.googleapis.com/customsearch";
31 public string SearchEndpoint { get; set; } = "/v1";
32 public string QueryFields { get; set; } = "items(title,link,snippet)";
33 public string ApiKey { get; set; }
34 public string EngineId { get; set; } // Added to store EngineId
35
40 public static GoogleDorkingConfig Load(GPALFile file = null)
41 {
42 if (file != null) ConfigFilePath = file.Filename;
43 if (true == File.Exists(ConfigFilePath))
44 {
45 try
46 {
47 var loaded = new GoogleDorkingConfig();
49 .WithInput((GPALFile)GPAL.File.WithFileName(ConfigFilePath))
50 .SaveTo(ref loaded);
51 return loaded;
52 }
53 catch (Exception ex)
54 {
55 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Failed to load config from file", null, GPALObjectType.None, ex);
56 }
57 }
58 else
60
61 return new GoogleDorkingConfig();
62 }
63
68 public void Save(GPALFile file = null)
69 {
70 if (file != null) ConfigFilePath = file.Filename;
72 .WithInput(this)
73 .SaveTo((GPALFile)GPAL.File.WithFileName(ConfigFilePath));
74 }
75 }
76 public class GoogleDorking : IGoogleDorking
77 {
78 public GoogleDorkingConfig Config { get; private set; }
79 internal IRESTClient SearchRESTClient { get; private set; }
80 public string ApiKey { get; private set; }
81 public string EngineId { get; private set; }
82 internal List<string> QueryParts { get; private set; }
83 internal int ResultsPerPage { get; private set; } = 10;
84 internal string DateRestrict { get; private set; }
85 internal string SortBy { get; private set; }
86 internal bool IsExactPhrase { get; private set; }
87 private ICredentials Credentials { get; set; }
88 private string ProjectName { get; set; }
89 private string ProjectId { get; set; }
90
91 public GoogleDorking()
92 {
93 Config = GoogleDorkingConfig.Load();
94 SearchRESTClient = GPAL.RESTClient.WithAPIBase(Config.SearchApiBase).ToGPALObject();
95 QueryParts = new List<string>();
96 ApiKey = Config.ApiKey;
97 EngineId = Config.EngineId;
98 }
99
100 public IAllowDorkingEngineSelection WithApiKey(string apiKey)
101 {
102 if (true == string.IsNullOrEmpty(apiKey) || false == IsValidApiKeyFormat(apiKey))
103 {
104 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Invalid or empty API key format. Expected format: AIzaSy followed by ~39 characters.", null, GPALObjectType.None);
105 return this;
106 }
107
108 ApiKey = apiKey;
109 Config.ApiKey = apiKey;
110 Config.Save();
111 return this;
112 }
113
114 public IAllowGenerateAPIKey WithCredentials(ICredentials credentials)
115 {
116 Credentials = credentials;
117 return this;
118 }
119
120 public IAllowGenerateAPIKey WithProjectName(string projectName)
121 {
122 ProjectName = projectName;
123 return this;
124 }
125
126 public IAllowGenerateAPIKey WithProjectId(string projectId)
127 {
128 ProjectId = projectId;
129 return this;
130 }
131
132 public IAllowDorkingApiKey GenerateApiKey(out string apiKey)
133 {
134 apiKey = string.Empty;
135 bool existingPublishToConsole = GPAL.PublishToConsole;
136
137 if (null == Credentials)
138 {
139 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "No credentials provided for API key generation.", null, GPALObjectType.None);
140 return this;
141 }
142
143 var creds = (Credentials)Credentials;
144 if (CredentialServiceType.Google != creds.ServiceType)
145 {
146 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Credentials must be for Google service.", null, GPALObjectType.None);
147 return this;
148 }
149
150 var googleCloud = new GoogleCloud(Credentials);
151 if (false == existingPublishToConsole)
153
154 GPAL.PublishSimpleEvent(GPALEventType.INFO, "Ensure Cloud Resource Manager API is enabled: https://console.cloud.google.com/apis/library/cloudresourcemanager.googleapis.com", null, GPALObjectType.None);
155
156 if (false == existingPublishToConsole)
157 GPAL.WithPublishToConsole(GPALEventType.NONE);
158
159 string googleProjectId = googleCloud.CreateProject(ProjectName, ProjectId);
160
161 if (true == string.IsNullOrEmpty(googleProjectId))
162 {
163 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"PROJECT NOT CREATED:Project Name [{ProjectName ?? googleProjectId}]\nProjectId [{ProjectId ?? googleProjectId}] Google ProjectId [{googleProjectId}]", this, GPALObjectType.GoogleDorking);
164 return this;
165 }
166
167 bool success = googleCloud.EnableApi(googleProjectId, GoogleApi.ServiceUsage) &&
168 googleCloud.EnableApi(googleProjectId, GoogleApi.ApiKeys) &&
169 googleCloud.EnableApi(googleProjectId, GoogleApi.CustomSearch);
170 //&&
171 // googleCloud.EnableApi(googleProjectId, GoogleApi.Sheets) &&
172 // googleCloud.EnableApi(googleProjectId, GoogleApi.Drive);
173
174
175 apiKey = googleCloud.GenerateApiKey(googleProjectId);
176
177 if (true != string.IsNullOrEmpty(apiKey))
178 {
179 if (false == existingPublishToConsole)
180 {
182 }
183 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"Write this information down:\nAPI KEY: [{apiKey}]\nProject Name [{ProjectName ?? googleProjectId}]\nProjectId [{ProjectId ?? googleProjectId}] Google ProjectId [{googleProjectId}]", this, GPALObjectType.GoogleDorking);
184 if (false == existingPublishToConsole)
185 {
186 GPAL.WithPublishToConsole(GPALEventType.NONE);
187 }
188 }
189
190 return this;
191 }
192
193 public IAllowDorkingQueryBuilding WithDateRestriction(DateRestriction restriction, int? customValue = null)
194 {
195 DateRestrict = restriction switch
196 {
197 DateRestriction.PastDay => "d1",
198 DateRestriction.PastWeek => "w1",
199 DateRestriction.PastMonth => "m1",
200 DateRestriction.PastYear => "y1",
201 DateRestriction.Custom when customValue.HasValue => $"m{customValue.Value}",
202 _ => null
203 };
204 if (true == DateRestriction.Custom.Equals(restriction) && false == customValue.HasValue)
205 {
206 GPAL.PublishSimpleEvent(GPALEventType.WARNING, "Custom date restriction requires a value", null, GPALObjectType.None);
207 }
208 return this;
209 }
210
211 public IAllowDorkingQueryBuilding WithSortBy(SortOption sortBy)
212 {
213 SortBy = sortBy switch
214 {
215 SortOption.Date => "date",
216 SortOption.Relevance => "",
217 _ => ""
218 };
219 return this;
220 }
221
222 public IAllowDorkingQueryExecution SearchFor(string searchTerm)
223 {
224 if (false == string.IsNullOrEmpty(searchTerm))
225 {
226 QueryParts.Insert(0, true == IsExactPhrase ? $"\"{Uri.EscapeDataString(searchTerm)}\"" : Uri.EscapeDataString(searchTerm));
227 }
228 else
229 {
230 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "No search term specified", null, GPALObjectType.None);
231 }
232 IsExactPhrase = false;
233 return this;
234 }
235
236 public IAllowDorkingQueryBuilding GetResults(out IGPALGrid<string> results)
237 {
238 results = GPAL.GridForType<string>();
239
240 if (true == string.IsNullOrEmpty(ApiKey) || true == string.IsNullOrEmpty(EngineId) || false == QueryParts.Any())
241 {
242 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Missing required setup (API key, engine ID, or query)", null, GPALObjectType.None);
243 ResetQuery();
244 return this;
245 }
246
247 try
248 {
249 var queryString = string.Join(" ", QueryParts);
250 var parameters = new Dictionary<string, string>
251 {
252 { "q", queryString },
253 { "cx", Uri.EscapeDataString(EngineId) },
254 { "fields", Uri.EscapeDataString(Config.QueryFields) },
255 { "key", Uri.EscapeDataString(ApiKey) }
256 };
257
258 if (0 < ResultsPerPage)
259 {
260 parameters.Add("num", ResultsPerPage.ToString());
261 }
262 if (false == string.IsNullOrEmpty(DateRestrict))
263 {
264 parameters.Add("dateRestrict", DateRestrict);
265 }
266 if (false == string.IsNullOrEmpty(SortBy))
267 {
268 parameters.Add("sort", SortBy);
269 }
270
271 var endpoint = $"{Config.SearchEndpoint}?{string.Join("&", parameters.Select(p => $"{p.Key}={p.Value}"))}";
272
273 var response = SearchRESTClient
274 .WithEndpoint(endpoint)
275 .Execute<SearchResponse>();
276
277 if (null != response?.Items)
278 {
279 foreach (var item in response.Items)
280 {
281 results.AddRow(new List<string> { item.Title, item.Link, item.Snippet });
282 }
283 }
284 else
285 {
286 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Failed to retrieve search results", null, GPALObjectType.None);
287 }
288 }
289 catch (Exception ex)
290 {
291 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Failed to retrieve search results", null, GPALObjectType.None, ex);
292 }
293
294 ResetQuery();
295 return this;
296 }
297
298 public IGoogleDorking ToGPALObject()
299 {
300 return this;
301 }
302
303 public static bool IsValidApiKeyFormat(string apiKey)
304 {
305 return false == string.IsNullOrEmpty(apiKey) && true == apiKey.StartsWith("AIzaSy") && 30 <= apiKey.Length && 50 >= apiKey.Length;
306 }
307
308 private void ResetQuery()
309 {
310 QueryParts.Clear();
311 DateRestrict = null;
312 SortBy = null;
313 IsExactPhrase = false;
314 }
315
316 public IAllowDorkingQueryBuilding WithEngineId(string engineId)
317 {
318 if (true == string.IsNullOrEmpty(engineId))
319 {
320 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Engine ID cannot be empty.", null, GPALObjectType.None);
321 return this;
322 }
323 EngineId = engineId;
324 Config.EngineId = engineId;
325 Config.Save();
326 return this;
327 }
328
329 public IAllowDorkingQueryBuilding WithExcludeTerm(string term)
330 {
331 if (false == string.IsNullOrEmpty(term))
332 {
333 QueryParts.Add($"-{Uri.EscapeDataString(term)}");
334 }
335 return this;
336 }
337
338 public IAllowDorkingQueryBuilding WithSite(string site)
339 {
340 if (false == string.IsNullOrEmpty(site))
341 {
342 QueryParts.Add($"site:{Uri.EscapeDataString(site)}");
343 }
344 return this;
345 }
346
347 public IAllowDorkingQueryBuilding WithFileType(FileType fileType)
348 {
349 string fileTypeStr = fileType switch
350 {
351 FileType.Pdf => "pdf",
352 FileType.Doc => "doc",
353 FileType.Docx => "docx",
354 FileType.Txt => "txt",
355 FileType.Xls => "xls",
356 FileType.Xlsx => "xlsx",
357 _ => ""
358 };
359 if (false == string.IsNullOrEmpty(fileTypeStr))
360 {
361 QueryParts.Add($"filetype:{fileTypeStr}");
362 }
363 return this;
364 }
365
366 public IAllowDorkingQueryBuilding WithInTitle(string term)
367 {
368 if (false == string.IsNullOrEmpty(term))
369 {
370 QueryParts.Add($"intitle:{Uri.EscapeDataString(term)}");
371 }
372 return this;
373 }
374
375 public IAllowDorkingQueryBuilding WithInUrl(string term)
376 {
377 if (false == string.IsNullOrEmpty(term))
378 {
379 QueryParts.Add($"inurl:{Uri.EscapeDataString(term)}");
380 }
381 return this;
382 }
383
384 public IAllowDorkingQueryBuilding WithResultsPerPageUpTo10(int num)
385 {
386 if (1 <= num && 10 >= num)
387 {
388 ResultsPerPage = num;
389 }
390 else
391 {
392 GPAL.PublishSimpleEvent(GPALEventType.WARNING, "Results per page must be between 1 and 10.", null, GPALObjectType.None);
393 }
394 return this;
395 }
396
397 public IAllowDorkingQueryExecution SearchForExactPhrase(string searchTerm)
398 {
399 IsExactPhrase = true;
400 return SearchFor(searchTerm);
401 }
402 }
403
404 public class SearchResponse
405 {
406 public List<SearchItem> Items { get; set; }
407 }
408
409 public class SearchItem
410 {
411 public string Title { get; set; }
412 public string Link { get; set; }
413 public string Snippet { get; set; }
414 }
415}
GPAL File object instantied with GPAL.File Used to load tokens into a GPALGrid [rows/columns].
Definition GPALFile.cs:36
Everything starts here, all the GPAL controls and global settings using fluent syntax are here....
Definition GPAL.cs:49
static IAllowConverterInput Converter
New GPAL Convertor.
Definition GPAL.cs:560
static IAllowRESTEndpoint RESTClient
Instantiate a new fluent RESTClient.
Definition GPAL.cs:914
static IAllowGPALSettings WithPublishToConsole(GPALEventType eventType=GPALEventType.INFO|GPALEventType.WARNING|GPALEventType.ERROR|GPALEventType.EXCEPTION|GPALEventType.NOTICE|GPALEventType.CAUTION|GPALEventType.FAILURE)
Print the published information or exception messages to the console.
Definition GPAL.cs:2698
static void PublishSimpleEvent(GPALEventType gPALEventType, string msg, dynamic gPALObject=null, Enums.GPALObjectType gPALObjectType=GPALObjectType.None, Exception ex=null)
Publish a message to either the information channel or exception channel (if exception passed in) Pub...
Definition GPAL.cs:2406
static IAllowFileName File
Instantiates a new fluent File SETTINGS object. This data object defines settings for use by the ....
Definition GPAL.cs:508
Handles Google Cloud operations such as project creation, API enabling, and API key generation.
static GoogleDorkingConfig Load(GPALFile file=null)
Loads the Google Dorking config from file, falling back to defaults if missing or invalid....
void Save(GPALFile file=null)
Saves this configuration to file. If file is provided it becomes the new default path for this sessi...