├── LICENSE.md ├── README.md └── convert ├── jsons └── Cac │ ├── 1 │ ├── 2 │ ├── 3 │ ├── 4 │ └── 5 ├── conv.py └── generated └── cac_autogenerated.py /LICENSE.md: -------------------------------------------------------------------------------- 1 | # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | 3 | _Version 2, December 2004_ 4 | 5 | _Copyright (C) 2004 Sam Hocevar _ 6 | 7 | Everyone is permitted to copy and distribute verbatim or modified 8 | copies of this license document, and changing it is allowed as long 9 | as the name is changed. 10 | 11 | ## DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 12 | ### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 13 | 14 | 0. You just DO WHAT THE FUCK YOU WANT TO. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GHunt Research & Development Toolkit 2 | 3 | Currently, only one module is available : Autoparse. 4 | 5 | ## Autoparse 6 | It basically takes all the JSON data you put in the `jsons` folder (one JSON structure per file, take an example on `jsons\Cac` (for ClientAuthConfig)), and it will automatically writes Python classes to parse the JSON output into Python objects, with type definition.\ 7 | The auto generated Python files will be wrote in the `generated` folder. 8 | 9 | PS : It was made to automatically generate Python classes for GHunt's parsers, so it will import and use the parent class `ghunt.objects.apis.Parser` (which auto manages [slots](https://betterprogramming.pub/optimize-your-python-programs-for-free-with-slots-4ff4e1611d9d)) but you can easily edit the script for your needs. -------------------------------------------------------------------------------- /convert/jsons/Cac/2: -------------------------------------------------------------------------------- 1 | HTTP/2 200 OK 2 | Content-Type: application/json; charset=UTF-8 3 | Vary: X-Origin 4 | Vary: Referer 5 | Vary: Origin,Accept-Encoding 6 | Date: Thu, 31 Mar 2022 21:38:29 GMT 7 | Server: ESF 8 | Cache-Control: private 9 | X-Xss-Protection: 0 10 | X-Frame-Options: SAMEORIGIN 11 | X-Content-Type-Options: nosniff 12 | Access-Control-Allow-Origin: https://console.cloud.google.com 13 | Access-Control-Allow-Credentials: true 14 | Access-Control-Expose-Headers: vary,vary,vary,content-encoding,date,server,content-length 15 | Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43" 16 | Accept-Ranges: none 17 | 18 | { 19 | "brandId": "873161391764", 20 | "projectIds": [], 21 | "projectNumbers": [ 22 | "873161391764" 23 | ], 24 | "displayName": "Unknown", 25 | "iconUrl": "", 26 | "storedIconUrl": "", 27 | "supportEmail": "unknown@gmail.com", 28 | "homePageUrl": "", 29 | "termsOfServiceUrls": [], 30 | "privacyPolicyUrls": [], 31 | "directNoticeToParentsUrl": "", 32 | "brandState": { 33 | "state": "ENABLED", 34 | "adminId": "1078053568452", 35 | "reason": "Enabled for creation", 36 | "limits": { 37 | "approvalQuotaMultiplier": 0, 38 | "maxDomainCount": 0, 39 | "defaultMaxClientCount": 36 40 | }, 41 | "brandSetup": "COMPLETED", 42 | "creationFlow": "CREATION_FLOW_UNSPECIFIED", 43 | "updateTimestamp": "2021-10-25T09:29:36.671Z" 44 | }, 45 | "clients": [], 46 | "review": { 47 | "hasAbuseVerdict": false, 48 | "isPublished": false, 49 | "reviewState": "UNREVIEWED", 50 | "highRiskScopesPrivilege": "DISABLED", 51 | "lowRiskScopes": [], 52 | "pendingScopes": [], 53 | "exemptScopes": [], 54 | "approvedScopes": [], 55 | "historicalApprovedScopes": [], 56 | "pendingDomains": [], 57 | "approvedDomains": [], 58 | "enforceRequestScopes": false, 59 | "category": [] 60 | }, 61 | "isOrgInternal": false, 62 | "consistencyToken": "2021-10-25T09:29:36.799776Z", 63 | "creationTime": "2021-10-25T09:29:36.411Z" 64 | } 65 | -------------------------------------------------------------------------------- /convert/jsons/Cac/5: -------------------------------------------------------------------------------- 1 | HTTP/2 200 OK 2 | Content-Type: application/json; charset=UTF-8 3 | Vary: X-Origin 4 | Vary: Referer 5 | Vary: Origin,Accept-Encoding 6 | Date: Sun, 13 Feb 2022 16:14:49 GMT 7 | Server: ESF 8 | Cache-Control: private 9 | X-Xss-Protection: 0 10 | X-Frame-Options: SAMEORIGIN 11 | X-Content-Type-Options: nosniff 12 | Access-Control-Allow-Origin: https://console.cloud.google.com 13 | Access-Control-Allow-Credentials: true 14 | Access-Control-Expose-Headers: vary,vary,vary,content-encoding,date,server,content-length 15 | Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43" 16 | Accept-Ranges: none 17 | 18 | { 19 | "brandId": "747654520220", 20 | "projectIds": [], 21 | "projectNumbers": [ 22 | "747654520220" 23 | ], 24 | "displayName": "project-747654520220", 25 | "iconUrl": "", 26 | "storedIconUrl": "", 27 | "supportEmail": "unconfiguredapp@google.com", 28 | "homePageUrl": "", 29 | "termsOfServiceUrls": [], 30 | "privacyPolicyUrls": [], 31 | "directNoticeToParentsUrl": "", 32 | "brandState": { 33 | "state": "ENABLED", 34 | "adminId": "1078053568452", 35 | "reason": "Enabled for creation", 36 | "limits": { 37 | "approvalQuotaMultiplier": 0, 38 | "maxDomainCount": 0, 39 | "defaultMaxClientCount": 36 40 | }, 41 | "brandSetup": "DEFERRED", 42 | "creationFlow": "FIREBASE", 43 | "updateTimestamp": "2020-01-30T01:23:42.386Z" 44 | }, 45 | "clients": [], 46 | "review": { 47 | "hasAbuseVerdict": false, 48 | "isPublished": false, 49 | "reviewState": "UNREVIEWED", 50 | "highRiskScopesPrivilege": "DISABLED", 51 | "lowRiskScopes": [], 52 | "pendingScopes": [], 53 | "exemptScopes": [], 54 | "approvedScopes": [], 55 | "historicalApprovedScopes": [], 56 | "pendingDomains": [ 57 | "chime-sdk.firebaseapp.com" 58 | ], 59 | "approvedDomains": [], 60 | "enforceRequestScopes": false, 61 | "category": [] 62 | }, 63 | "isOrgInternal": false, 64 | "consistencyToken": "2020-12-03T09:27:05.445640Z", 65 | "creationTime": "2020-01-30T01:23:42.386Z" 66 | } 67 | -------------------------------------------------------------------------------- /convert/jsons/Cac/1: -------------------------------------------------------------------------------- 1 | HTTP/2 200 OK 2 | Content-Type: application/json; charset=UTF-8 3 | Vary: X-Origin 4 | Vary: Referer 5 | Vary: Origin,Accept-Encoding 6 | Date: Sat, 26 Mar 2022 17:53:39 GMT 7 | Server: ESF 8 | Cache-Control: private 9 | X-Xss-Protection: 0 10 | X-Frame-Options: SAMEORIGIN 11 | X-Content-Type-Options: nosniff 12 | Access-Control-Allow-Origin: https://console.cloud.google.com 13 | Access-Control-Allow-Credentials: true 14 | Access-Control-Expose-Headers: vary,vary,vary,content-encoding,date,server,content-length 15 | Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43" 16 | Accept-Ranges: none 17 | 18 | { 19 | "brandId": "425112442765", 20 | "projectIds": [], 21 | "projectNumbers": [ 22 | "425112442765" 23 | ], 24 | "displayName": "Jodel", 25 | "iconUrl": "https://lh3.googleusercontent.com/L0EleY8LGmj5VR1h3XJIB08ESCfNfnPhNtK5xxMw-eowtNozSMgSPYDFZoGJOcty3w", 26 | "storedIconUrl": "https://lh3.googleusercontent.com/L0EleY8LGmj5VR1h3XJIB08ESCfNfnPhNtK5xxMw-eowtNozSMgSPYDFZoGJOcty3w", 27 | "supportEmail": "android@jodel.com", 28 | "homePageUrl": "https://www.jodel.com", 29 | "termsOfServiceUrls": [ 30 | "https://jodel.com/terms/" 31 | ], 32 | "privacyPolicyUrls": [ 33 | "https://jodel.com/app-privacy/" 34 | ], 35 | "directNoticeToParentsUrl": "", 36 | "brandState": { 37 | "state": "ENABLED", 38 | "adminId": "1078053568452", 39 | "reason": "Update BrandSetup", 40 | "limits": { 41 | "approvalQuotaMultiplier": 0, 42 | "maxDomainCount": 0, 43 | "defaultMaxClientCount": 36 44 | }, 45 | "brandSetup": "COMPLETED", 46 | "creationFlow": "CREATION_FLOW_UNSPECIFIED", 47 | "updateTimestamp": "2021-02-12T13:12:51.399Z" 48 | }, 49 | "clients": [], 50 | "review": { 51 | "hasAbuseVerdict": false, 52 | "isPublished": false, 53 | "reviewState": "PENDING", 54 | "highRiskScopesPrivilege": "DISABLED", 55 | "lowRiskScopes": [], 56 | "pendingScopes": [], 57 | "exemptScopes": [], 58 | "approvedScopes": [], 59 | "historicalApprovedScopes": [], 60 | "pendingDomains": [ 61 | "tellm-android.firebaseapp.com", 62 | "jodel.com", 63 | "cloudflareaccess.com", 64 | "jodel.dev", 65 | "jodel.live" 66 | ], 67 | "approvedDomains": [], 68 | "enforceRequestScopes": false, 69 | "category": [] 70 | }, 71 | "isOrgInternal": false, 72 | "riscConfiguration": { 73 | "enabled": true, 74 | "deliveryMethod": "FIREBEAR", 75 | "receiverSupportedEventType": [], 76 | "legalAgreement": [ 77 | "INTERNAL" 78 | ] 79 | }, 80 | "consistencyToken": "2021-02-12T13:12:52.427745Z" 81 | } 82 | -------------------------------------------------------------------------------- /convert/jsons/Cac/3: -------------------------------------------------------------------------------- 1 | HTTP/2 200 OK 2 | Content-Type: application/json; charset=UTF-8 3 | Vary: X-Origin 4 | Vary: Referer 5 | Vary: Origin,Accept-Encoding 6 | Date: Mon, 14 Feb 2022 00:23:58 GMT 7 | Server: ESF 8 | Cache-Control: private 9 | X-Xss-Protection: 0 10 | X-Frame-Options: SAMEORIGIN 11 | X-Content-Type-Options: nosniff 12 | Access-Control-Allow-Origin: https://console.cloud.google.com 13 | Access-Control-Allow-Credentials: true 14 | Access-Control-Expose-Headers: vary,vary,vary,content-encoding,date,server,content-length 15 | Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43" 16 | Accept-Ranges: none 17 | 18 | { 19 | "brandId": "1070009224336", 20 | "projectIds": [], 21 | "projectNumbers": [ 22 | "1070009224336" 23 | ], 24 | "displayName": "Android device", 25 | "iconUrl": "https://lh3.googleusercontent.com/G44E9KQbLv-6Li13ushW8nwkVypPCegUy5nDY00g7TBWCwUzBxFH6_l2DewpOKC6SLc", 26 | "storedIconUrl": "https://lh3.googleusercontent.com/G44E9KQbLv-6Li13ushW8nwkVypPCegUy5nDY00g7TBWCwUzBxFH6_l2DewpOKC6SLc", 27 | "supportEmail": "ukode@google.com", 28 | "homePageUrl": "", 29 | "termsOfServiceUrls": [], 30 | "privacyPolicyUrls": [], 31 | "directNoticeToParentsUrl": "", 32 | "brandState": { 33 | "state": "ENABLED", 34 | "adminId": "0", 35 | "reason": "", 36 | "limits": { 37 | "approvalQuotaMultiplier": 0, 38 | "maxDomainCount": 0, 39 | "defaultMaxClientCount": 36 40 | }, 41 | "brandSetup": "COMPLETED", 42 | "creationFlow": "CREATION_FLOW_UNSPECIFIED" 43 | }, 44 | "clients": [], 45 | "verifiedBrand": { 46 | "displayName": { 47 | "value": "Android device", 48 | "reason": "APPEALED" 49 | }, 50 | "storedIconUrl": { 51 | "value": "https://lh3.googleusercontent.com/G44E9KQbLv-6Li13ushW8nwkVypPCegUy5nDY00g7TBWCwUzBxFH6_l2DewpOKC6SLc", 52 | "reason": "APPEALED" 53 | }, 54 | "supportEmail": { 55 | "value": "ukode@google.com", 56 | "reason": "APPEALED" 57 | } 58 | }, 59 | "review": { 60 | "hasAbuseVerdict": false, 61 | "isPublished": true, 62 | "reviewState": "APPROVED", 63 | "highRiskScopesPrivilege": "ENABLED", 64 | "lowRiskScopes": [], 65 | "pendingScopes": [], 66 | "exemptScopes": [], 67 | "approvedScopes": [ 68 | 310 69 | ], 70 | "historicalApprovedScopes": [ 71 | 310 72 | ], 73 | "pendingDomains": [], 74 | "approvedDomains": [], 75 | "enforceRequestScopes": false, 76 | "category": [], 77 | "decisionTimestamp": "2020-11-19T00:42:31.926Z" 78 | }, 79 | "isOrgInternal": false, 80 | "consistencyToken": "2020-12-03T01:52:22.086324Z" 81 | } 82 | -------------------------------------------------------------------------------- /convert/jsons/Cac/4: -------------------------------------------------------------------------------- 1 | HTTP/2 200 OK 2 | Content-Type: application/json; charset=UTF-8 3 | Vary: X-Origin 4 | Vary: Referer 5 | Vary: Origin,Accept-Encoding 6 | Date: Mon, 14 Feb 2022 00:22:59 GMT 7 | Server: ESF 8 | Cache-Control: private 9 | X-Xss-Protection: 0 10 | X-Frame-Options: SAMEORIGIN 11 | X-Content-Type-Options: nosniff 12 | Access-Control-Allow-Origin: https://console.cloud.google.com 13 | Access-Control-Allow-Credentials: true 14 | Access-Control-Expose-Headers: vary,vary,vary,content-encoding,date,server,content-length 15 | Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43" 16 | Accept-Ranges: none 17 | 18 | { 19 | "brandId": "745476177629", 20 | "projectIds": [], 21 | "projectNumbers": [ 22 | "745476177629" 23 | ], 24 | "displayName": "Google Play services", 25 | "iconUrl": "", 26 | "storedIconUrl": "", 27 | "supportEmail": "apps-help@google.com", 28 | "homePageUrl": "https://developers.google.com/android/guides/overview", 29 | "termsOfServiceUrls": [ 30 | "https://policies.google.com/terms" 31 | ], 32 | "privacyPolicyUrls": [ 33 | "https://policies.google.com/privacy" 34 | ], 35 | "directNoticeToParentsUrl": "", 36 | "brandState": { 37 | "state": "ENABLED", 38 | "adminId": "1078053568452", 39 | "reason": "Enabled through UpdateBrandAsAdmin.", 40 | "limits": { 41 | "approvalQuotaMultiplier": 0, 42 | "maxDomainCount": 0, 43 | "defaultMaxClientCount": 36 44 | }, 45 | "brandSetup": "COMPLETED", 46 | "creationFlow": "CREATION_FLOW_UNSPECIFIED", 47 | "updateTimestamp": "2020-11-02T21:33:34.634Z" 48 | }, 49 | "clients": [], 50 | "verifiedBrand": { 51 | "displayName": { 52 | "value": "Google Play services", 53 | "reason": "APPEALED" 54 | }, 55 | "supportEmail": { 56 | "value": "apps-help@google.com", 57 | "reason": "APPEALED" 58 | }, 59 | "homePageUrl": { 60 | "value": "https://developers.google.com/android/guides/overview", 61 | "reason": "APPEALED" 62 | }, 63 | "privacyPolicyUrl": { 64 | "value": "https://policies.google.com/privacy", 65 | "reason": "APPEALED" 66 | }, 67 | "termsOfServiceUrl": { 68 | "value": "https://policies.google.com/terms", 69 | "reason": "APPEALED" 70 | } 71 | }, 72 | "review": { 73 | "hasAbuseVerdict": false, 74 | "isPublished": true, 75 | "reviewState": "APPROVED", 76 | "highRiskScopesPrivilege": "ENABLED", 77 | "lowRiskScopes": [ 78 | 8200, 79 | 42001, 80 | 5400, 81 | 63907, 82 | 63913, 83 | 54700, 84 | 4400, 85 | 11200, 86 | 68800, 87 | 54600, 88 | 202, 89 | 204, 90 | 4300, 91 | 7500, 92 | 68300, 93 | 15700, 94 | 77400, 95 | 9309, 96 | 9310, 97 | 50400, 98 | 9315, 99 | 9316, 100 | 9319, 101 | 1903, 102 | 11507, 103 | 42100, 104 | 9333, 105 | 103412, 106 | 9334, 107 | 43000, 108 | 1913, 109 | 9337, 110 | 9340, 111 | 21500, 112 | 9343 113 | ], 114 | "pendingScopes": [ 115 | 57003, 116 | 57004, 117 | 57005, 118 | 57007, 119 | 26800, 120 | 57008 121 | ], 122 | "exemptScopes": [], 123 | "approvedScopes": [ 124 | 2600, 125 | 300, 126 | 310, 127 | 9311, 128 | 9312, 129 | 1905, 130 | 9332, 131 | 1911, 132 | 9338 133 | ], 134 | "historicalApprovedScopes": [ 135 | 2600, 136 | 300, 137 | 310, 138 | 9311, 139 | 9312, 140 | 1905, 141 | 9332, 142 | 1911, 143 | 9338 144 | ], 145 | "pendingDomains": [], 146 | "approvedDomains": [ 147 | "google.com" 148 | ], 149 | "enforceRequestScopes": false, 150 | "category": [ 151 | "OTHER_CATEGORY" 152 | ], 153 | "decisionTimestamp": "2020-11-02T22:24:43.650Z" 154 | }, 155 | "isOrgInternal": false, 156 | "consistencyToken": "2020-12-09T20:02:24.881779Z" 157 | } 158 | -------------------------------------------------------------------------------- /convert/conv.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import * 3 | import re 4 | import hashlib 5 | from glob import glob 6 | import json 7 | 8 | import inflection 9 | 10 | 11 | initial_values = { 12 | "str": '""', 13 | "int": "0", 14 | "float": "0.0", 15 | "dict": "{}", 16 | "list": "[]", 17 | "bool": "False", 18 | } 19 | 20 | def extract_json(raw: str): 21 | splited = raw.split("{") 22 | if len(splited) <= 1: 23 | return False 24 | rebuilt = "{" + '{'.join(splited[1:]) 25 | 26 | data = "" 27 | try: 28 | data = json.loads(rebuilt) 29 | except: 30 | return False 31 | return data 32 | 33 | class Value(): 34 | def __init__(self): 35 | self.type: str = "" 36 | self.list_of: str = "" 37 | 38 | class Model(): 39 | def __init__(self): 40 | self.is_parent: bool = False 41 | self.keys_type: str = "str" 42 | self.values_type: str = "str" 43 | self.args: Dict[str, Value] = {} 44 | 45 | def calculate_dict_hash(data: Dict) -> str: 46 | txt = "" 47 | for key in sorted(data.keys()): 48 | txt += f"${key}" 49 | for value in [type(x).__name__ for x in data.values()]: 50 | txt += f"@{value}" 51 | return hashlib.md5(txt.encode()).hexdigest() 52 | 53 | def parse(data, name="", first=False): 54 | global unknown_model_count 55 | 56 | if isinstance(data, dict) and data: 57 | name = inflection.camelize(name) 58 | if "kind" in data: 59 | name = ''.join([inflection.camelize(x) for x in data["kind"].split("#")]) 60 | elif first: 61 | name = "BaseModel" 62 | elif not name: 63 | hash = calculate_dict_hash(data) 64 | if hash not in unknown_cache: 65 | unknown_cache[hash] = unknown_model_count 66 | unknown_model_count += 1 67 | name = f"UnknownModel{unknown_cache[hash]}" 68 | 69 | first = False 70 | 71 | if not name.startswith(prefix): 72 | name = inflection.camelize(prefix+name) 73 | if name not in known_models: 74 | model = Model() 75 | known_models[name] = model 76 | 77 | keys_type = type(list(data.keys())[0]).__name__ 78 | values_type = type(list(data.values())[0]).__name__ 79 | is_parent = False 80 | for key, val in data.items(): 81 | new_key_type = type(key).__name__ 82 | new_val_type = type(val).__name__ 83 | 84 | if isinstance(key, str) and key.isnumeric(): 85 | key = "digits_field" 86 | 87 | known_models[name].args[key] = Value() 88 | 89 | type_of_list, new_name = parse(val, key) 90 | if new_val_type == "dict": 91 | if new_name: 92 | new_val_type = new_name 93 | is_parent = True 94 | elif new_val_type == "list": 95 | known_models[name].args[key].list_of = type_of_list 96 | 97 | known_models[name].args[key].type = new_val_type 98 | 99 | if new_key_type != keys_type: 100 | keys_type = "any" 101 | if new_val_type != values_type: 102 | values_type = "any" 103 | 104 | known_models[name].keys_type = keys_type 105 | known_models[name].values_type = values_type 106 | known_models[name].is_parent = is_parent 107 | elif isinstance(data, list) and data: 108 | fetched_models = set() 109 | first = False 110 | for item in data: 111 | typeof, new_name = parse(item) 112 | if not new_name: 113 | new_name = typeof 114 | fetched_models.add(new_name) 115 | if len(fetched_models) > 1: 116 | return "any", name 117 | else: 118 | return list(fetched_models)[0], name 119 | 120 | return type(data).__name__, name 121 | 122 | def output(): 123 | out = "from typing import *\nfrom ghunt.objects.apis import Parser\n\n\n" 124 | for name, model in known_models.items(): 125 | out += f"class {name}(Parser):\n" 126 | out += "\tdef __init__(self):\n" 127 | for key, val in model.args.items(): 128 | field_name = inflection.underscore("".join([ch for ch in key if ch.isalpha()])) 129 | if val.type in initial_values: 130 | if val.type == "list": 131 | out += f"\t\tself.{field_name}: List[{val.list_of}] = {initial_values[val.type]}\n" 132 | else: 133 | out += f"\t\tself.{field_name}: {val.type} = {initial_values[val.type]}\n" 134 | else: 135 | out += f"\t\tself.{field_name}: {val.type} = {val.type}()\n" 136 | out += "\n" 137 | 138 | data_name = re.sub(f"^{prefix.lower()}_", "", inflection.underscore(name)) 139 | data_fullname = f"{data_name}_data" 140 | out += f"\tdef _scrape(self, {data_fullname}: Dict[{model.keys_type}, {model.values_type}]):\n" 141 | for key, val in model.args.items(): 142 | field_name = inflection.underscore("".join([ch for ch in key if ch.isalpha()])) 143 | if val.type in initial_values and not (val.type == "list" and (val.list_of not in initial_values)): 144 | out += f"\t\tself.{field_name} = {data_fullname}.get('{key}')\n" 145 | else: 146 | child_data_name = field_name 147 | child_data_fullname = f"{child_data_name}_data" 148 | child_data_item = f"{child_data_name}_data_item" 149 | child_item = f"{child_data_name}_item" 150 | if val.type == "list": 151 | out += f"\t\tif ({child_data_fullname} := {data_fullname}.get('{key}')):\n" 152 | out += f"\t\t\tfor {child_data_item} in {child_data_fullname}:\n" 153 | out += f"\t\t\t\t{child_item} = {val.list_of}()\n" 154 | out += f"\t\t\t\t{child_item}._scrape({child_data_item})\n" 155 | out += f"\t\t\t\tself.{child_data_name}.append({child_item})\n" 156 | else: 157 | out += f"\t\tif ({child_data_fullname} := {data_fullname}.get('{key}')):\n" 158 | out += f"\t\t\tself.{child_data_name}._scrape({child_data_fullname})\n" 159 | 160 | out += "\n" 161 | 162 | with open(f"convert/generated/{prefix.lower()}_autogenerated.py", "w", encoding="utf8") as f: 163 | f.write(out) 164 | 165 | folders = glob("convert/jsons/*") 166 | for folder in folders: 167 | prefix = Path(folder).name 168 | known_models: Dict[str, Model] = {} 169 | unknown_cache: Dict[str, int] = {} 170 | unknown_model_count = 1 171 | datas = [] 172 | files = glob(f"{folder}/*") 173 | for file in files: 174 | with open(file, "r", encoding="utf-8") as f: 175 | data = extract_json(f.read()) 176 | if data: 177 | datas.append(data) 178 | 179 | for item in datas: 180 | parse(item, first=True) 181 | output() -------------------------------------------------------------------------------- /convert/generated/cac_autogenerated.py: -------------------------------------------------------------------------------- 1 | from typing import * 2 | from ghunt.objects.apis import Parser 3 | 4 | 5 | class CacBaseModel(Parser): 6 | def __init__(self): 7 | self.brand_id: str = "" 8 | self.project_ids: List[list] = [] 9 | self.project_numbers: List[str] = [] 10 | self.display_name: str = "" 11 | self.icon_url: str = "" 12 | self.stored_icon_url: str = "" 13 | self.support_email: str = "" 14 | self.home_page_url: str = "" 15 | self.terms_of_service_urls: List[list] = [] 16 | self.privacy_policy_urls: List[list] = [] 17 | self.direct_notice_to_parents_url: str = "" 18 | self.brand_state: CacBrandState = CacBrandState() 19 | self.clients: List[list] = [] 20 | self.review: CacReview = CacReview() 21 | self.is_org_internal: bool = False 22 | self.risc_configuration: CacRiscConfiguration = CacRiscConfiguration() 23 | self.consistency_token: str = "" 24 | self.creation_time: str = "" 25 | self.verified_brand: CacVerifiedBrand = CacVerifiedBrand() 26 | 27 | def _scrape(self, base_model_data: Dict[str, any]): 28 | self.brand_id = base_model_data.get('brandId') 29 | self.project_ids = base_model_data.get('projectIds') 30 | self.project_numbers = base_model_data.get('projectNumbers') 31 | self.display_name = base_model_data.get('displayName') 32 | self.icon_url = base_model_data.get('iconUrl') 33 | self.stored_icon_url = base_model_data.get('storedIconUrl') 34 | self.support_email = base_model_data.get('supportEmail') 35 | self.home_page_url = base_model_data.get('homePageUrl') 36 | self.terms_of_service_urls = base_model_data.get('termsOfServiceUrls') 37 | self.privacy_policy_urls = base_model_data.get('privacyPolicyUrls') 38 | self.direct_notice_to_parents_url = base_model_data.get('directNoticeToParentsUrl') 39 | if (brand_state_data := base_model_data.get('brandState')): 40 | self.brand_state._scrape(brand_state_data) 41 | self.clients = base_model_data.get('clients') 42 | if (review_data := base_model_data.get('review')): 43 | self.review._scrape(review_data) 44 | self.is_org_internal = base_model_data.get('isOrgInternal') 45 | if (risc_configuration_data := base_model_data.get('riscConfiguration')): 46 | self.risc_configuration._scrape(risc_configuration_data) 47 | self.consistency_token = base_model_data.get('consistencyToken') 48 | self.creation_time = base_model_data.get('creationTime') 49 | if (verified_brand_data := base_model_data.get('verifiedBrand')): 50 | self.verified_brand._scrape(verified_brand_data) 51 | 52 | class CacBrandState(Parser): 53 | def __init__(self): 54 | self.state: str = "" 55 | self.admin_id: str = "" 56 | self.reason: str = "" 57 | self.limits: CacLimits = CacLimits() 58 | self.brand_setup: str = "" 59 | self.creation_flow: str = "" 60 | self.update_timestamp: str = "" 61 | 62 | def _scrape(self, brand_state_data: Dict[str, any]): 63 | self.state = brand_state_data.get('state') 64 | self.admin_id = brand_state_data.get('adminId') 65 | self.reason = brand_state_data.get('reason') 66 | if (limits_data := brand_state_data.get('limits')): 67 | self.limits._scrape(limits_data) 68 | self.brand_setup = brand_state_data.get('brandSetup') 69 | self.creation_flow = brand_state_data.get('creationFlow') 70 | self.update_timestamp = brand_state_data.get('updateTimestamp') 71 | 72 | class CacLimits(Parser): 73 | def __init__(self): 74 | self.approval_quota_multiplier: int = 0 75 | self.max_domain_count: int = 0 76 | self.default_max_client_count: int = 0 77 | 78 | def _scrape(self, limits_data: Dict[str, int]): 79 | self.approval_quota_multiplier = limits_data.get('approvalQuotaMultiplier') 80 | self.max_domain_count = limits_data.get('maxDomainCount') 81 | self.default_max_client_count = limits_data.get('defaultMaxClientCount') 82 | 83 | class CacReview(Parser): 84 | def __init__(self): 85 | self.has_abuse_verdict: bool = False 86 | self.is_published: bool = False 87 | self.review_state: str = "" 88 | self.high_risk_scopes_privilege: str = "" 89 | self.low_risk_scopes: List[list] = [] 90 | self.pending_scopes: List[list] = [] 91 | self.exempt_scopes: List[list] = [] 92 | self.approved_scopes: List[list] = [] 93 | self.historical_approved_scopes: List[list] = [] 94 | self.pending_domains: List[str] = [] 95 | self.approved_domains: List[list] = [] 96 | self.enforce_request_scopes: bool = False 97 | self.category: List[list] = [] 98 | self.decision_timestamp: str = "" 99 | 100 | def _scrape(self, review_data: Dict[str, any]): 101 | self.has_abuse_verdict = review_data.get('hasAbuseVerdict') 102 | self.is_published = review_data.get('isPublished') 103 | self.review_state = review_data.get('reviewState') 104 | self.high_risk_scopes_privilege = review_data.get('highRiskScopesPrivilege') 105 | self.low_risk_scopes = review_data.get('lowRiskScopes') 106 | self.pending_scopes = review_data.get('pendingScopes') 107 | self.exempt_scopes = review_data.get('exemptScopes') 108 | self.approved_scopes = review_data.get('approvedScopes') 109 | self.historical_approved_scopes = review_data.get('historicalApprovedScopes') 110 | self.pending_domains = review_data.get('pendingDomains') 111 | self.approved_domains = review_data.get('approvedDomains') 112 | self.enforce_request_scopes = review_data.get('enforceRequestScopes') 113 | self.category = review_data.get('category') 114 | self.decision_timestamp = review_data.get('decisionTimestamp') 115 | 116 | class CacRiscConfiguration(Parser): 117 | def __init__(self): 118 | self.enabled: bool = False 119 | self.delivery_method: str = "" 120 | self.receiver_supported_event_type: List[list] = [] 121 | self.legal_agreement: List[str] = [] 122 | 123 | def _scrape(self, risc_configuration_data: Dict[str, any]): 124 | self.enabled = risc_configuration_data.get('enabled') 125 | self.delivery_method = risc_configuration_data.get('deliveryMethod') 126 | self.receiver_supported_event_type = risc_configuration_data.get('receiverSupportedEventType') 127 | self.legal_agreement = risc_configuration_data.get('legalAgreement') 128 | 129 | class CacVerifiedBrand(Parser): 130 | def __init__(self): 131 | self.display_name: CacDisplayName = CacDisplayName() 132 | self.stored_icon_url: CacStoredIconUrl = CacStoredIconUrl() 133 | self.support_email: CacSupportEmail = CacSupportEmail() 134 | self.home_page_url: CacHomePageUrl = CacHomePageUrl() 135 | self.privacy_policy_url: CacPrivacyPolicyUrl = CacPrivacyPolicyUrl() 136 | self.terms_of_service_url: CacTermsOfServiceUrl = CacTermsOfServiceUrl() 137 | 138 | def _scrape(self, verified_brand_data: Dict[str, any]): 139 | if (display_name_data := verified_brand_data.get('displayName')): 140 | self.display_name._scrape(display_name_data) 141 | if (stored_icon_url_data := verified_brand_data.get('storedIconUrl')): 142 | self.stored_icon_url._scrape(stored_icon_url_data) 143 | if (support_email_data := verified_brand_data.get('supportEmail')): 144 | self.support_email._scrape(support_email_data) 145 | if (home_page_url_data := verified_brand_data.get('homePageUrl')): 146 | self.home_page_url._scrape(home_page_url_data) 147 | if (privacy_policy_url_data := verified_brand_data.get('privacyPolicyUrl')): 148 | self.privacy_policy_url._scrape(privacy_policy_url_data) 149 | if (terms_of_service_url_data := verified_brand_data.get('termsOfServiceUrl')): 150 | self.terms_of_service_url._scrape(terms_of_service_url_data) 151 | 152 | class CacDisplayName(Parser): 153 | def __init__(self): 154 | self.value: str = "" 155 | self.reason: str = "" 156 | 157 | def _scrape(self, display_name_data: Dict[str, str]): 158 | self.value = display_name_data.get('value') 159 | self.reason = display_name_data.get('reason') 160 | 161 | class CacStoredIconUrl(Parser): 162 | def __init__(self): 163 | self.value: str = "" 164 | self.reason: str = "" 165 | 166 | def _scrape(self, stored_icon_url_data: Dict[str, str]): 167 | self.value = stored_icon_url_data.get('value') 168 | self.reason = stored_icon_url_data.get('reason') 169 | 170 | class CacSupportEmail(Parser): 171 | def __init__(self): 172 | self.value: str = "" 173 | self.reason: str = "" 174 | 175 | def _scrape(self, support_email_data: Dict[str, str]): 176 | self.value = support_email_data.get('value') 177 | self.reason = support_email_data.get('reason') 178 | 179 | class CacHomePageUrl(Parser): 180 | def __init__(self): 181 | self.value: str = "" 182 | self.reason: str = "" 183 | 184 | def _scrape(self, home_page_url_data: Dict[str, str]): 185 | self.value = home_page_url_data.get('value') 186 | self.reason = home_page_url_data.get('reason') 187 | 188 | class CacPrivacyPolicyUrl(Parser): 189 | def __init__(self): 190 | self.value: str = "" 191 | self.reason: str = "" 192 | 193 | def _scrape(self, privacy_policy_url_data: Dict[str, str]): 194 | self.value = privacy_policy_url_data.get('value') 195 | self.reason = privacy_policy_url_data.get('reason') 196 | 197 | class CacTermsOfServiceUrl(Parser): 198 | def __init__(self): 199 | self.value: str = "" 200 | self.reason: str = "" 201 | 202 | def _scrape(self, terms_of_service_url_data: Dict[str, str]): 203 | self.value = terms_of_service_url_data.get('value') 204 | self.reason = terms_of_service_url_data.get('reason') 205 | 206 | --------------------------------------------------------------------------------