Compare commits
6 commits
0065c7f4f5
...
cc4a8cca8e
Author | SHA1 | Date | |
---|---|---|---|
Chandler Swift | cc4a8cca8e | ||
Chandler Swift | a0675bc376 | ||
Chandler Swift | c37ffbb353 | ||
Chandler Swift | 8f5803b5bc | ||
Chandler Swift | c125525413 | ||
Chandler Swift | a44f8c23b6 |
|
@ -5,64 +5,71 @@ Many states are through one of a few providers:
|
||||||
* Castle Rock: https://www.castlerockits.com/oneweb
|
* Castle Rock: https://www.castlerockits.com/oneweb
|
||||||
* IBI Travel IQ: https://www.ibigroup.com/ibi-products/traveliq/
|
* IBI Travel IQ: https://www.ibigroup.com/ibi-products/traveliq/
|
||||||
|
|
||||||
| State | DOT Name[^name] | Online Service Name | Video? | Web provider | Link |
|
### State list
|
||||||
| ----- | --------------- | ------------------- | ------ | ------------ | ------------------------------- |
|
|
||||||
| AK | DOT&PF | | | Travel IQ | https://511.alaska.gov/ |
|
| State | DOT Name[^name] | Online Service Name | Still | Video | Web provider | Link |
|
||||||
| AL | ALDOT | | | | |
|
| ----- | --------------- | ------------------- | ----- | ----- | ------------ | --------------------------------- |
|
||||||
| AR | ARDOT | | | | |
|
| AK | DOT&PF | Alaska 511 | 111 | 0 | Travel IQ | https://511.alaska.gov/ |
|
||||||
| AZ | ADOT | | | | |
|
| AL | ALDOT | algotraffic | 0 | 568 | custom | https://algotraffic.com/cameras |
|
||||||
| CA | Caltrans | | | | |
|
| AR | ARDOT | | | | | |
|
||||||
| CO | CDOT | | | | |
|
| AZ | ADOT | AZ511 | 602 | 0 | Travel IQ | https://az511.com/ |
|
||||||
| CT | CTDOT | | | | |
|
| CA | Caltrans | | | | | |
|
||||||
| DE | DelDOT | | | | |
|
| CO | CDOT | COtrip | 832 | 686 | Castle Rock | https://maps.cotrip.org/ |
|
||||||
| FL | FDOT | | | | |
|
| CT | CTDOT | CTroads | 350 | 0 | Travel IQ | https://ctroads.org/ |
|
||||||
| GA | GDOT | | | | |
|
| DE | DelDOT | DelDOT Maps | 0 | 282 | Custom | https://deldot.gov/map/ |
|
||||||
| HI | HDOT | | | | |
|
| FL | FDOT | | | | | |
|
||||||
| IA | Iowa DOT | | | | |
|
| GA | GDOT | 511GA | 1 | 3416 | Travel IQ | https://511ga.org/ |
|
||||||
| ID | ITD | | | | |
|
| HI | HDOT | | | | | |
|
||||||
| IL | IDOT | | | | |
|
| IA | Iowa DOT | 511IA | 446 | 485 | Castle Rock | https://511ia.org/ |
|
||||||
| IN | IDOT | | | | |
|
| ID | ITD | Idaho 511 | 274 | 0 | Travel IQ | https://511.idaho.gov/ |
|
||||||
| KS | KDOT | | | | |
|
| IL | IDOT | | | | | |
|
||||||
| KY | KYTC | | | | |
|
| IN | IDOT | TrafficWise | 0 | 640 | Castle Rock | https://511in.org/ |
|
||||||
| LA | DOTD | | | | |
|
| KS | KDOT | KanDrive | 382 | 167 | Castle Rock | https://www.kandrive.gov/ |
|
||||||
| MA | MassDOT | | | | |
|
| KY | KYTC | | | | | |
|
||||||
| MD | MDOT | | | | |
|
| LA | DOTD | 511 | 0 | 288 | Travel IQ | https://www.511la.org/ |
|
||||||
| ME | MaineDOT | | | | |
|
| MA | MassDOT | Mass511 | 20 | 225 | Castle Rock | https://mass511.com/ |
|
||||||
| MI | MDOT | | | | |
|
| MD | MDOT | CHART | 0 | 538 | Custom | https://chart.maryland.gov/ |
|
||||||
| MN | MNDOT | 511MN | most | Castle Rock | https://511mn.org/ |
|
| ME | MaineDOT | | | | | |
|
||||||
| MO | MoDOT | | | | https://traveler.modot.org/map/ |
|
| MI | MDOT | | | | | |
|
||||||
| MS | MDOT | | | | |
|
| MN | MNDOT | 511MN | 647 | 1131 | Castle Rock | https://511mn.org/ |
|
||||||
| MT | MDT | | | | |
|
| MO | MoDOT | | | | | https://traveler.modot.org/map/ |
|
||||||
| NC | NCDOT | | | | |
|
| MS | MDOT | | | | | |
|
||||||
| ND | NDDOT | | | | |
|
| MT | MDT | | | | | |
|
||||||
| NE | NDOT | | | | |
|
| NC | NCDOT | | | | | |
|
||||||
| NH | NHDOT | | | | |
|
| ND | NDDOT | | | | | |
|
||||||
| NJ | NJDOT | | | | |
|
| NE | NDOT | Nebraska 511 | 919 | 0 | Castle Rock | https://new.511.nebraska.gov/ |
|
||||||
| NM | NMDOT | | | | |
|
| NH | NHDOT | | | | | |
|
||||||
| NV | NDOT | | | | |
|
| NJ | NJDOT | | | | | |
|
||||||
| NY | NYSDOT | | | | |
|
| NM | NMDOT | | | | | |
|
||||||
| OH | ODOT | | | | |
|
| NV | NDOT | Nevada 511 | 0 | 606 | Travel IQ | https://www.nvroads.com/ |
|
||||||
| OK | ODOT | | | | |
|
| NY | NYSDOT | 511NY | 369 | 1272 | Travel IQ | https://www.511ny.org/ |
|
||||||
| OR | ODOT | | | | |
|
| OH | ODOT | | | | | |
|
||||||
| PA | PennDOT | | | | |
|
| OK | ODOT | | | | | |
|
||||||
| RI | RIDOT | | | | |
|
| OR | ODOT | | | | | |
|
||||||
| SC | SCDOT | | | | |
|
| PA | PennDOT | | | | | |
|
||||||
| SD | SDDOT | | | | |
|
| RI | RIDOT | | | | | |
|
||||||
| TN | TDOT | | | | |
|
| SC | SCDOT | | | | | |
|
||||||
| TX | TxDOT | | | | |
|
| SD | SDDOT | | | | | |
|
||||||
| UT | UDOT | | | | |
|
| TN | TDOT | | | | | |
|
||||||
| VA | VDOT | | | | |
|
| TX | TxDOT | | | | | |
|
||||||
| VT | VTrans | | | | |
|
| UT | UDOT | UDOT Traffic | 0 | 1787 | Travel IQ | https://www.udottraffic.utah.gov/ |
|
||||||
| WA | WSDOT | | | | |
|
| VA | VDOT | | | | | |
|
||||||
| WI | WisDOT | | | | |
|
| VT | VTrans | | | | | |
|
||||||
| WV | WVDOT | | | | |
|
| WA | WSDOT | | | | | |
|
||||||
| WY | WYDOT | | | | |
|
| WI | WisDOT | 511WI | 0 | 468 | Travel IQ | https://511wi.gov/ |
|
||||||
|
| WV | WVDOT | | | | | |
|
||||||
|
| WY | WYDOT | | | | | |
|
||||||
|
|
||||||
|
|
||||||
[^name]: From https://en.wikipedia.org/wiki/Department_of_transportation#List_of_U.S._state_and_insular_area_departments_of_transportation
|
[^name]: From https://en.wikipedia.org/wiki/Department_of_transportation#List_of_U.S._state_and_insular_area_departments_of_transportation
|
||||||
|
|
||||||
|
|
||||||
|
### Non-State aligned
|
||||||
|
|
||||||
|
* New England 511 (https://newengland511.org/) consolidates Maine, New Hampshire, and Vermont into one site (341 photo/0 video/Travel IQ)
|
||||||
|
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
https://map.wyoroad.info/wtimap/index.html
|
https://map.wyoroad.info/wtimap/index.html
|
||||||
|
|
||||||
|
@ -115,8 +122,6 @@ https://www.511virginia.org/
|
||||||
|
|
||||||
https://wv511.org/
|
https://wv511.org/
|
||||||
|
|
||||||
https://chart.maryland.gov/TrafficCameras/GetTrafficCameras
|
|
||||||
|
|
||||||
http://goakamai.org/cameras
|
http://goakamai.org/cameras
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,14 +133,11 @@ https://dot.ca.gov/programs/traffic-operations/traveler-information/511
|
||||||
|
|
||||||
"rr": "https://riverregion511.org" castlerock
|
"rr": "https://riverregion511.org" castlerock
|
||||||
|
|
||||||
|
|
||||||
No video, only photos ??
|
|
||||||
"ak": "https://511.alaska.gov/",
|
|
||||||
"az": "https://az511.com/",
|
|
||||||
"ct": "https://ctroads.org/",
|
|
||||||
"new england": "https://newengland511.org/",
|
|
||||||
"id": "https://511.idaho.gov/",
|
|
||||||
"ut": "https://www.udottraffic.utah.gov/",
|
|
||||||
|
|
||||||
Some kind of signature needed:
|
Some kind of signature needed:
|
||||||
"fl": "https://fl511.com/",
|
"fl": "https://fl511.com/",
|
||||||
|
|
||||||
|
|
||||||
|
### Known data errors
|
||||||
|
- WI sure has a lot of cameras on null island
|
||||||
|
- Nebraska has one camera located in California
|
||||||
|
- Several workarounds documented in code
|
||||||
|
|
|
@ -65,7 +65,11 @@ for state, baseURL in states.items():
|
||||||
print(f"warn: Differing types detected: {c['views']}")
|
print(f"warn: Differing types detected: {c['views']}")
|
||||||
if view['category'] == 'VIDEO':
|
if view['category'] == 'VIDEO':
|
||||||
if state == "Nebraska":
|
if state == "Nebraska":
|
||||||
print(c)
|
# Nebraska has mislabeled a small amount of their data;
|
||||||
|
# there is one location with 4 views labeled as "VIDEO"
|
||||||
|
# which are not, in fact, video-containing views
|
||||||
|
view['category'] = "PHOTO"
|
||||||
|
continue
|
||||||
videoCount += 1
|
videoCount += 1
|
||||||
if len(view['sources']) != 1:
|
if len(view['sources']) != 1:
|
||||||
raise Exception(f"Unexpected number of sources ({len(view['sources'])})")
|
raise Exception(f"Unexpected number of sources ({len(view['sources'])})")
|
||||||
|
|
78
layers/dot-cams/de/get_data.py
Executable file
78
layers/dot-cams/de/get_data.py
Executable file
|
@ -0,0 +1,78 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
cameras = []
|
||||||
|
|
||||||
|
res = requests.get("https://tmc.deldot.gov/json/videocamera.json")
|
||||||
|
res.raise_for_status()
|
||||||
|
# {
|
||||||
|
# "timestamp": "2024-02-03T09:00:50Z",
|
||||||
|
# "timestampMS": "1706950850013",
|
||||||
|
# "version": "1.1",
|
||||||
|
# "uid": "-8569684966613574390",
|
||||||
|
# "status": "normal",
|
||||||
|
# "message": "",
|
||||||
|
# "cameraCount": 282,
|
||||||
|
# "cameraHash": "-1859423038",
|
||||||
|
# "videoCameras": [
|
||||||
|
# {
|
||||||
|
# "urls": {
|
||||||
|
# "rtmp": "rtmpt://167.21.72.35:80/live/KCAM001.stream",
|
||||||
|
# "rtsp": "rtsp://167.21.72.35:554/live/KCAM001.stream",
|
||||||
|
# "f4m": "http://167.21.72.35:1935/live/KCAM001.stream/manifest.f4m",
|
||||||
|
# "m3u8": "http://167.21.72.35:1935/live/KCAM001.stream/playlist.m3u8",
|
||||||
|
# "m3u8s": "https://video.deldot.gov:443/live/KCAM001.stream/playlist.m3u8",
|
||||||
|
# "mssmooth": "http://167.21.72.35:1935/live/KCAM001.stream/Manifest"
|
||||||
|
# },
|
||||||
|
# "id": "KCAM001",
|
||||||
|
# "title": "DE 1 @ MILFORD NECK ROAD (NORTH OFF)",
|
||||||
|
# "county": "Kent",
|
||||||
|
# "lat": 38.990771,
|
||||||
|
# "lon": -75.448487,
|
||||||
|
# "hash": "966393601",
|
||||||
|
# "enabled": true
|
||||||
|
# },
|
||||||
|
# ...
|
||||||
|
|
||||||
|
for c in res.json()['videoCameras']:
|
||||||
|
try:
|
||||||
|
if not c['enabled']:
|
||||||
|
print("warn: camera disabled; ignoring:", c)
|
||||||
|
continue
|
||||||
|
if not c['urls']['m3u8s'].startswith('https://'):
|
||||||
|
raise Exception("invalid hlsUrl")
|
||||||
|
cameras.append({
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [c['lon'], c['lat']], # yes, [lon, lat] since it's [x, y]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"name": c['title'],
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
'hasVideo': True,
|
||||||
|
'src': c['urls']['m3u8s'],
|
||||||
|
},
|
||||||
|
# {
|
||||||
|
# 'hasVideo': False,
|
||||||
|
# 'src': c['imageUrl'],
|
||||||
|
# }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
print(c)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
geojson = {
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": cameras,
|
||||||
|
}
|
||||||
|
|
||||||
|
with open("data.geojson", "w") as f:
|
||||||
|
f.write(json.dumps(geojson))
|
||||||
|
|
||||||
|
print(f"{len(cameras)} locations found")
|
|
@ -13,6 +13,8 @@ import pinVideo from './pin-video.svg?url'; // TODO: remove `?url`?
|
||||||
import castleRockStates from './castle-rock/data/states.js';
|
import castleRockStates from './castle-rock/data/states.js';
|
||||||
import travelIqStates from './travel-iq/data/states.js';
|
import travelIqStates from './travel-iq/data/states.js';
|
||||||
import al from './al/data.geojson?url';
|
import al from './al/data.geojson?url';
|
||||||
|
import de from './de/data.geojson?url';
|
||||||
|
import md from './md/data.geojson?url';
|
||||||
|
|
||||||
import dot_names from './layer_names.js';
|
import dot_names from './layer_names.js';
|
||||||
|
|
||||||
|
@ -20,6 +22,8 @@ const allStates = {
|
||||||
...castleRockStates,
|
...castleRockStates,
|
||||||
...travelIqStates,
|
...travelIqStates,
|
||||||
'Alabama': al,
|
'Alabama': al,
|
||||||
|
'Delaware': de,
|
||||||
|
'Maryland': md,
|
||||||
};
|
};
|
||||||
|
|
||||||
let dot_cams = {
|
let dot_cams = {
|
||||||
|
|
74
layers/dot-cams/md/get_data.py
Executable file
74
layers/dot-cams/md/get_data.py
Executable file
|
@ -0,0 +1,74 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
cameras = []
|
||||||
|
|
||||||
|
res = requests.get("https://chartexp1.sha.maryland.gov/CHARTExportClientService/getCameraMapDataJSON.do")
|
||||||
|
res.raise_for_status()
|
||||||
|
# {
|
||||||
|
# "error": null,
|
||||||
|
# "data": [
|
||||||
|
# {
|
||||||
|
# "description": "I-270 & Old Hundred Rd (MD 109)(CAM 165)",
|
||||||
|
# "lat": 39.2773,
|
||||||
|
# "lon": -77.3236,
|
||||||
|
# "cctvIp": "strmr5.sha.maryland.gov",
|
||||||
|
# "milePost": 22.27,
|
||||||
|
# "cameraCategories": [
|
||||||
|
# "Wash. DC"
|
||||||
|
# ],
|
||||||
|
# "routeNumber": 270,
|
||||||
|
# "commMode": "ONLINE",
|
||||||
|
# "routePrefix": "IS",
|
||||||
|
# "routeSuffix": "",
|
||||||
|
# "opStatus": "OK",
|
||||||
|
# "publicVideoURL": "https://chart.maryland.gov/Video/GetVideo/7a00a1dc01250075004d823633235daa",
|
||||||
|
# "lastCachedDataUpdateTime": 1706950514352,
|
||||||
|
# "name": "I-270 & Old Hundred Rd (MD 109)",
|
||||||
|
# "id": "7a00a1dc01250075004d823633235daa"
|
||||||
|
# },
|
||||||
|
|
||||||
|
for c in res.json()['data']:
|
||||||
|
try:
|
||||||
|
# # 50-ish are marginal; let's ignore that and put 'em on the map anyway
|
||||||
|
# if not c['commMode'] == "ONLINE":
|
||||||
|
# print("warn: camera offline; ignoring:", c)
|
||||||
|
# continue
|
||||||
|
# if not c['opStatus'] == "OK":
|
||||||
|
# print("warn: camera not ok; ignoring:", c)
|
||||||
|
# continue
|
||||||
|
cameras.append({
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [c['lon'], c['lat']], # yes, [lon, lat] since it's [x, y]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"name": c['description'],
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
'hasVideo': True,
|
||||||
|
'src': f"https://{c['cctvIp']}/rtplive/{c['id']}/playlist.m3u8",
|
||||||
|
},
|
||||||
|
# {
|
||||||
|
# 'hasVideo': False,
|
||||||
|
# 'src': c['imageUrl'],
|
||||||
|
# }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
print(c)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
geojson = {
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": cameras,
|
||||||
|
}
|
||||||
|
|
||||||
|
with open("data.geojson", "w") as f:
|
||||||
|
f.write(json.dumps(geojson))
|
||||||
|
|
||||||
|
print(f"{len(cameras)} locations found")
|
|
@ -4,10 +4,16 @@ import requests
|
||||||
import json
|
import json
|
||||||
|
|
||||||
states = {
|
states = {
|
||||||
|
"Alaska": "https://511.alaska.gov/",
|
||||||
|
"Arizona": "https://az511.com/",
|
||||||
|
"Connecticut": "https://ctroads.org/",
|
||||||
"Georgia": "https://511ga.org/",
|
"Georgia": "https://511ga.org/",
|
||||||
|
"Idaho": "https://511.idaho.gov/",
|
||||||
"Louisiana": "https://www.511la.org/",
|
"Louisiana": "https://www.511la.org/",
|
||||||
"Nevada": "https://www.nvroads.com/",
|
"Nevada": "https://www.nvroads.com/",
|
||||||
|
"NewEngland": "https://newengland511.org/",
|
||||||
"NewYork": "https://www.511ny.org/",
|
"NewYork": "https://www.511ny.org/",
|
||||||
|
"Utah": "https://www.udottraffic.utah.gov/",
|
||||||
"Wisconsin": "https://511wi.gov/"
|
"Wisconsin": "https://511wi.gov/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,10 +52,17 @@ for state, baseURL in states.items():
|
||||||
"length": 100,
|
"length": 100,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
video_camera_count = 0
|
||||||
|
still_camera_count = 0
|
||||||
|
|
||||||
cameras = []
|
cameras = []
|
||||||
available_cameras = 999_999 # lots
|
available_cameras = 999_999 # lots
|
||||||
|
|
||||||
while len(cameras) < available_cameras:
|
while len(cameras) < available_cameras:
|
||||||
|
if state == "Connecticut":
|
||||||
|
# gotta be a special snowflake I guess?
|
||||||
|
res = requests.post(f"{baseURL}/List/GetData/Cameras", json={"draw":1,"columns":[{"data":"sortId","name":"sortId","searchable":True,"orderable":True,"search":{"value":"","regex":False}},{"data":"cityName","name":"cityName","searchable":True,"orderable":True,"search":{"value":"","regex":False}},{"data":"roadway","name":"roadway","searchable":True,"orderable":True,"search":{"value":"","regex":False}},{"data":"sortIdDisplay","name":"sortIdDisplay","searchable":True,"orderable":True,"search":{"value":"","regex":False}},{"data":"description1","name":"description1","searchable":False,"orderable":True,"search":{"value":"","regex":False}},{"data":"direction","name":"direction","searchable":False,"orderable":False,"search":{"value":"","regex":False}},{"data":6,"name":"","searchable":False,"orderable":False,"search":{"value":"","regex":False}}],"order":[{"column":0,"dir":"asc"},{"column":1,"dir":"asc"}],"start":0,"length":10,"search":{"value":"","regex":False}})
|
||||||
|
else:
|
||||||
res = requests.get(f"{baseURL}/List/GetData/Cameras", {
|
res = requests.get(f"{baseURL}/List/GetData/Cameras", {
|
||||||
"query": json.dumps(query),
|
"query": json.dumps(query),
|
||||||
"lang": "en",
|
"lang": "en",
|
||||||
|
@ -58,6 +71,17 @@ for state, baseURL in states.items():
|
||||||
res = res.json()
|
res = res.json()
|
||||||
available_cameras = res['recordsTotal']
|
available_cameras = res['recordsTotal']
|
||||||
for c in res['data']:
|
for c in res['data']:
|
||||||
|
if 'videoUrl' in c and isinstance(c['videoUrl'], list): # LA returns multiple (identical?) streams
|
||||||
|
src = c['videoUrl'][0]
|
||||||
|
video_camera_count += 1
|
||||||
|
elif 'videoUrl' in c and c['videoUrl'] and c['videoUrl'] != '0':
|
||||||
|
# Yeah, Idaho has a single camera where videoURL = '0'. Nice.
|
||||||
|
src = c['videoUrl']
|
||||||
|
video_camera_count += 1
|
||||||
|
else:
|
||||||
|
src = baseURL + "map/Cctv/" + c['id']
|
||||||
|
still_camera_count += 1
|
||||||
|
|
||||||
cameras.append({
|
cameras.append({
|
||||||
"type": "Feature",
|
"type": "Feature",
|
||||||
"geometry": {
|
"geometry": {
|
||||||
|
@ -67,8 +91,8 @@ for state, baseURL in states.items():
|
||||||
"properties": {
|
"properties": {
|
||||||
'name': c['displayName'],
|
'name': c['displayName'],
|
||||||
'views': [{
|
'views': [{
|
||||||
'hasVideo': c['videoUrl'],
|
'hasVideo': 'videoUrl' in c and bool(c['videoUrl']),
|
||||||
'src': c['videoUrl'][0] if isinstance(c['videoUrl'], list) else c['videoUrl'], # LA returns multiple (identical?) streams
|
'src': src,
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -83,6 +107,7 @@ for state, baseURL in states.items():
|
||||||
f.write(json.dumps(geojson))
|
f.write(json.dumps(geojson))
|
||||||
|
|
||||||
print(f"{len(cameras)} locations found for {state}")
|
print(f"{len(cameras)} locations found for {state}")
|
||||||
|
print(f"{state}: {still_camera_count} photo + {video_camera_count} video cameras")
|
||||||
|
|
||||||
# hack hack hack
|
# hack hack hack
|
||||||
#
|
#
|
||||||
|
|
Loading…
Reference in a new issue