iporigin 1.1.1__tar.gz → 1.1.2__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.
- {iporigin-1.1.1 → iporigin-1.1.2}/NOTICE +8 -0
- {iporigin-1.1.1 → iporigin-1.1.2}/PKG-INFO +7 -3
- {iporigin-1.1.1 → iporigin-1.1.2}/README.md +6 -2
- {iporigin-1.1.1 → iporigin-1.1.2}/pyproject.toml +1 -1
- {iporigin-1.1.1 → iporigin-1.1.2}/src/iporigin/__init__.py +1 -1
- iporigin-1.1.2/src/iporigin/data/ranges.bin +0 -0
- {iporigin-1.1.1 → iporigin-1.1.2}/src/iporigin.egg-info/PKG-INFO +7 -3
- {iporigin-1.1.1 → iporigin-1.1.2}/tests/test_iporigin.py +55 -0
- {iporigin-1.1.1 → iporigin-1.1.2}/tools/sources.py +41 -0
- iporigin-1.1.1/src/iporigin/data/ranges.bin +0 -0
- {iporigin-1.1.1 → iporigin-1.1.2}/CHANGELOG.md +0 -0
- {iporigin-1.1.1 → iporigin-1.1.2}/LICENSE +0 -0
- {iporigin-1.1.1 → iporigin-1.1.2}/MANIFEST.in +0 -0
- {iporigin-1.1.1 → iporigin-1.1.2}/setup.cfg +0 -0
- {iporigin-1.1.1 → iporigin-1.1.2}/src/iporigin/_data.py +0 -0
- {iporigin-1.1.1 → iporigin-1.1.2}/src/iporigin/cli.py +0 -0
- {iporigin-1.1.1 → iporigin-1.1.2}/src/iporigin/core.py +0 -0
- {iporigin-1.1.1 → iporigin-1.1.2}/src/iporigin/online.py +0 -0
- {iporigin-1.1.1 → iporigin-1.1.2}/src/iporigin/py.typed +0 -0
- {iporigin-1.1.1 → iporigin-1.1.2}/src/iporigin.egg-info/SOURCES.txt +0 -0
- {iporigin-1.1.1 → iporigin-1.1.2}/src/iporigin.egg-info/dependency_links.txt +0 -0
- {iporigin-1.1.1 → iporigin-1.1.2}/src/iporigin.egg-info/entry_points.txt +0 -0
- {iporigin-1.1.1 → iporigin-1.1.2}/src/iporigin.egg-info/requires.txt +0 -0
- {iporigin-1.1.1 → iporigin-1.1.2}/src/iporigin.egg-info/top_level.txt +0 -0
- {iporigin-1.1.1 → iporigin-1.1.2}/tools/build_dataset.py +0 -0
- {iporigin-1.1.1 → iporigin-1.1.2}/tools/bump_version.py +0 -0
|
@@ -28,6 +28,14 @@ consumed programmatically. No separate permission is needed or claimed.
|
|
|
28
28
|
|
|
29
29
|
CC0 is a public-domain dedication, so these carry no conditions.
|
|
30
30
|
|
|
31
|
+
The AI-company bot slugs probed for outbound crawler coverage
|
|
32
|
+
(OpenAI, Perplexity AI, DuckAssistBot, Apple Intelligence Proxy,
|
|
33
|
+
Meta) come from these same two repositories. The set above is
|
|
34
|
+
what was probed; only Meta adds ranges the disjoint sweep does not
|
|
35
|
+
collapse into an existing bot or vpn label, and the rest overlap
|
|
36
|
+
exactly with GPTBot / PerplexityBot / DuckDuckBot / Apple Private
|
|
37
|
+
Relay and answer under those provider names.
|
|
38
|
+
|
|
31
39
|
|
|
32
40
|
3. Used with the maintainer's permission
|
|
33
41
|
----------------------------------------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iporigin
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: Is this IP a datacenter, VPN, Tor exit or a home connection? Offline lookup, no API key.
|
|
5
5
|
Author-email: Yuix Networks <info@yuix.org>
|
|
6
6
|
License: MIT
|
|
@@ -91,7 +91,7 @@ origin.is_datacenter # True
|
|
|
91
91
|
| `cdn` | edge infrastructure fronting other people's sites | Cloudflare, Akamai |
|
|
92
92
|
| `vpn` | a consumer VPN or private relay exit | Mullvad, ProtonVPN, Apple Private Relay |
|
|
93
93
|
| `tor` | a Tor exit node | |
|
|
94
|
-
| `bot` | a declared crawler | Googlebot, GPTBot,
|
|
94
|
+
| `bot` | a declared crawler or an AI company's outbound IP | Googlebot, GPTBot, GPTBot (OpenAI's crawler), Meta, ... |
|
|
95
95
|
| `reserved` | private, loopback, link-local, documentation | `127.0.0.1`, `10.0.0.0/8` |
|
|
96
96
|
| `unknown` | in none of our lists — most often a residential ISP | |
|
|
97
97
|
|
|
@@ -185,7 +185,11 @@ and are second-hand by definition:
|
|
|
185
185
|
|
|
186
186
|
Covering Hetzner, OVHcloud, Scaleway, Alibaba Cloud, Leaseweb, UpCloud, IBM
|
|
187
187
|
Cloud, Huawei Cloud, Tencent Cloud, Rackspace, Akamai, Gcore, Mullvad,
|
|
188
|
-
ProtonVPN, Apple Private Relay, Tor, and ten declared crawlers.
|
|
188
|
+
ProtonVPN, Apple Private Relay, Tor, and ten declared crawlers. AI-company
|
|
189
|
+
outbound ranges (OpenAI, Perplexity AI, DuckAssistBot, Apple Intelligence
|
|
190
|
+
Proxy, Meta) come from these same two repositories. Most of them overlap
|
|
191
|
+
exactly with an existing bot label, so they contribute label diversity
|
|
192
|
+
rather than new bot coverage — only Meta adds distinct segments.
|
|
189
193
|
|
|
190
194
|
Both are CC0, a public-domain dedication, so they carry no conditions.
|
|
191
195
|
|
|
@@ -56,7 +56,7 @@ origin.is_datacenter # True
|
|
|
56
56
|
| `cdn` | edge infrastructure fronting other people's sites | Cloudflare, Akamai |
|
|
57
57
|
| `vpn` | a consumer VPN or private relay exit | Mullvad, ProtonVPN, Apple Private Relay |
|
|
58
58
|
| `tor` | a Tor exit node | |
|
|
59
|
-
| `bot` | a declared crawler | Googlebot, GPTBot,
|
|
59
|
+
| `bot` | a declared crawler or an AI company's outbound IP | Googlebot, GPTBot, GPTBot (OpenAI's crawler), Meta, ... |
|
|
60
60
|
| `reserved` | private, loopback, link-local, documentation | `127.0.0.1`, `10.0.0.0/8` |
|
|
61
61
|
| `unknown` | in none of our lists — most often a residential ISP | |
|
|
62
62
|
|
|
@@ -150,7 +150,11 @@ and are second-hand by definition:
|
|
|
150
150
|
|
|
151
151
|
Covering Hetzner, OVHcloud, Scaleway, Alibaba Cloud, Leaseweb, UpCloud, IBM
|
|
152
152
|
Cloud, Huawei Cloud, Tencent Cloud, Rackspace, Akamai, Gcore, Mullvad,
|
|
153
|
-
ProtonVPN, Apple Private Relay, Tor, and ten declared crawlers.
|
|
153
|
+
ProtonVPN, Apple Private Relay, Tor, and ten declared crawlers. AI-company
|
|
154
|
+
outbound ranges (OpenAI, Perplexity AI, DuckAssistBot, Apple Intelligence
|
|
155
|
+
Proxy, Meta) come from these same two repositories. Most of them overlap
|
|
156
|
+
exactly with an existing bot label, so they contribute label diversity
|
|
157
|
+
rather than new bot coverage — only Meta adds distinct segments.
|
|
154
158
|
|
|
155
159
|
Both are CC0, a public-domain dedication, so they carry no conditions.
|
|
156
160
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "iporigin"
|
|
7
|
-
version = "1.1.
|
|
7
|
+
version = "1.1.2"
|
|
8
8
|
description = "Is this IP a datacenter, VPN, Tor exit or a home connection? Offline lookup, no API key."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iporigin
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: Is this IP a datacenter, VPN, Tor exit or a home connection? Offline lookup, no API key.
|
|
5
5
|
Author-email: Yuix Networks <info@yuix.org>
|
|
6
6
|
License: MIT
|
|
@@ -91,7 +91,7 @@ origin.is_datacenter # True
|
|
|
91
91
|
| `cdn` | edge infrastructure fronting other people's sites | Cloudflare, Akamai |
|
|
92
92
|
| `vpn` | a consumer VPN or private relay exit | Mullvad, ProtonVPN, Apple Private Relay |
|
|
93
93
|
| `tor` | a Tor exit node | |
|
|
94
|
-
| `bot` | a declared crawler | Googlebot, GPTBot,
|
|
94
|
+
| `bot` | a declared crawler or an AI company's outbound IP | Googlebot, GPTBot, GPTBot (OpenAI's crawler), Meta, ... |
|
|
95
95
|
| `reserved` | private, loopback, link-local, documentation | `127.0.0.1`, `10.0.0.0/8` |
|
|
96
96
|
| `unknown` | in none of our lists — most often a residential ISP | |
|
|
97
97
|
|
|
@@ -185,7 +185,11 @@ and are second-hand by definition:
|
|
|
185
185
|
|
|
186
186
|
Covering Hetzner, OVHcloud, Scaleway, Alibaba Cloud, Leaseweb, UpCloud, IBM
|
|
187
187
|
Cloud, Huawei Cloud, Tencent Cloud, Rackspace, Akamai, Gcore, Mullvad,
|
|
188
|
-
ProtonVPN, Apple Private Relay, Tor, and ten declared crawlers.
|
|
188
|
+
ProtonVPN, Apple Private Relay, Tor, and ten declared crawlers. AI-company
|
|
189
|
+
outbound ranges (OpenAI, Perplexity AI, DuckAssistBot, Apple Intelligence
|
|
190
|
+
Proxy, Meta) come from these same two repositories. Most of them overlap
|
|
191
|
+
exactly with an existing bot label, so they contribute label diversity
|
|
192
|
+
rather than new bot coverage — only Meta adds distinct segments.
|
|
189
193
|
|
|
190
194
|
Both are CC0, a public-domain dedication, so they carry no conditions.
|
|
191
195
|
|
|
@@ -175,6 +175,61 @@ def test_a_known_bot_range_is_classified_as_bot():
|
|
|
175
175
|
pytest.fail("no bot ranges in the dataset")
|
|
176
176
|
|
|
177
177
|
|
|
178
|
+
@pytest.mark.parametrize(
|
|
179
|
+
"provider",
|
|
180
|
+
[
|
|
181
|
+
# Updated whenever a new bot provider is added in tools/sources.py.
|
|
182
|
+
# Slugs that returned 404 upstream are omitted from the dataset
|
|
183
|
+
# and skipped here. Slugs whose ranges overlap exactly with an
|
|
184
|
+
# existing bot label are also skipped (they add label diversity
|
|
185
|
+
# without contributing distinct segments to the disjoint sweep).
|
|
186
|
+
"OpenAI",
|
|
187
|
+
"Perplexity AI",
|
|
188
|
+
"DuckAssistBot",
|
|
189
|
+
"Apple Intelligence Proxy",
|
|
190
|
+
"Meta",
|
|
191
|
+
],
|
|
192
|
+
)
|
|
193
|
+
def test_known_ai_bot_provider_is_classified_as_bot(provider):
|
|
194
|
+
"""One sample per AI-company bot provider, taken from the dataset.
|
|
195
|
+
|
|
196
|
+
Sampled rather than hardcoded because ranges move with the feeds.
|
|
197
|
+
"""
|
|
198
|
+
data = _data.dataset()
|
|
199
|
+
for start, label_idx in zip(data.v4_starts, data.v4_labels):
|
|
200
|
+
if data.labels[label_idx][0] == provider:
|
|
201
|
+
origin = iporigin.classify(str(ipaddress.ip_address(start)))
|
|
202
|
+
assert origin.kind == core.BOT
|
|
203
|
+
assert origin.provider == provider
|
|
204
|
+
assert origin.is_datacenter
|
|
205
|
+
return
|
|
206
|
+
pytest.skip("provider %r has no ranges in this build" % provider)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
_AI_BOT_PROVIDERS = [
|
|
210
|
+
"OpenAI",
|
|
211
|
+
"Perplexity AI",
|
|
212
|
+
"DuckAssistBot",
|
|
213
|
+
"Apple Intelligence Proxy",
|
|
214
|
+
"Meta",
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def test_at_least_one_ai_bot_provider_contributes_ranges():
|
|
219
|
+
"""Catches a future regression where every new AI-bot slug turns
|
|
220
|
+
out to be a strict subset of an existing bot label — the
|
|
221
|
+
parametrized test above would quietly skip every case. At least
|
|
222
|
+
one of the probed providers must add distinct segments.
|
|
223
|
+
"""
|
|
224
|
+
data = _data.dataset()
|
|
225
|
+
bot_providers = {p for p, _ in data.labels if p in _AI_BOT_PROVIDERS}
|
|
226
|
+
assert bot_providers, (
|
|
227
|
+
"no AI-company bot provider contributed distinct ranges "
|
|
228
|
+
"to this build — every probed slug was absorbed into an "
|
|
229
|
+
"existing label"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
178
233
|
def test_a_known_vpn_range_is_classified_as_vpn():
|
|
179
234
|
data = _data.dataset()
|
|
180
235
|
for start, label in zip(data.v4_starts, data.v4_labels):
|
|
@@ -14,6 +14,7 @@ import csv
|
|
|
14
14
|
import io
|
|
15
15
|
import json
|
|
16
16
|
import re
|
|
17
|
+
import sys
|
|
17
18
|
import urllib.request
|
|
18
19
|
|
|
19
20
|
USER_AGENT = "iporigin-dataset-builder/1.0 (+https://github.com/Yuix-Networks/iporigin)"
|
|
@@ -214,6 +215,41 @@ def _lord_alfred(slug):
|
|
|
214
215
|
return fetch
|
|
215
216
|
|
|
216
217
|
|
|
218
|
+
def _optional_rezmoss(slug):
|
|
219
|
+
"""Like _rezmoss, but a missing upstream file is logged and skipped,
|
|
220
|
+
not raised. Use for community slugs whose availability is not certain."""
|
|
221
|
+
def fetch():
|
|
222
|
+
for version in (4, 6):
|
|
223
|
+
url = REZMOSS % (slug, slug, version)
|
|
224
|
+
try:
|
|
225
|
+
text = _get_text(url)
|
|
226
|
+
except Exception as exc:
|
|
227
|
+
print(" !! %-18s skipped: %s" % (slug, exc), file=sys.stderr)
|
|
228
|
+
continue
|
|
229
|
+
for line in text.splitlines():
|
|
230
|
+
line = line.strip()
|
|
231
|
+
if line and not line.startswith("#") and "/" in line:
|
|
232
|
+
yield line
|
|
233
|
+
return fetch
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _optional_lord_alfred(slug):
|
|
237
|
+
"""Like _lord_alfred, but a missing upstream file is logged and skipped."""
|
|
238
|
+
def fetch():
|
|
239
|
+
for version in (4, 6):
|
|
240
|
+
url = LORD_ALFRED % (slug, version)
|
|
241
|
+
try:
|
|
242
|
+
text = _get_text(url)
|
|
243
|
+
except Exception as exc:
|
|
244
|
+
print(" !! %-18s skipped: %s" % (slug, exc), file=sys.stderr)
|
|
245
|
+
continue
|
|
246
|
+
for line in text.splitlines():
|
|
247
|
+
line = line.strip()
|
|
248
|
+
if line and not line.startswith("#") and "/" in line:
|
|
249
|
+
yield line
|
|
250
|
+
return fetch
|
|
251
|
+
|
|
252
|
+
|
|
217
253
|
COMMUNITY = {
|
|
218
254
|
# Hosting with no official feed.
|
|
219
255
|
"Hetzner": ("hosting", _rezmoss("hetzner")),
|
|
@@ -249,6 +285,11 @@ COMMUNITY = {
|
|
|
249
285
|
"Applebot": ("bot", _rezmoss("applebot")),
|
|
250
286
|
"Common Crawl": ("bot", _rezmoss("commoncrawl")),
|
|
251
287
|
"Internet Archive": ("bot", _rezmoss("internetarchive")),
|
|
288
|
+
"OpenAI": ("bot", _optional_lord_alfred("openai")),
|
|
289
|
+
"Perplexity AI": ("bot", _optional_lord_alfred("perplexity")),
|
|
290
|
+
"DuckAssistBot": ("bot", _optional_lord_alfred("duckassistbot")),
|
|
291
|
+
"Apple Intelligence Proxy": ("bot", _optional_lord_alfred("apple-proxy")),
|
|
292
|
+
"Meta": ("bot", _optional_rezmoss("meta")),
|
|
252
293
|
}
|
|
253
294
|
|
|
254
295
|
SOURCES.update(COMMUNITY)
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|