cloning over all the old files
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
# chapter 3
|
||||||
|
Locard's principle of transferrence
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import csv
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
# Replace with your CrowdStrike API credentials
|
||||||
|
CLIENT_ID = 'YOUR_CLIENT_ID'
|
||||||
|
CLIENT_SECRET = 'YOUR_CLIENT_SECRET'
|
||||||
|
|
||||||
|
# Base URL for the CrowdStrike Falcon API
|
||||||
|
BASE_URL = 'https://api.crowdstrike.com'
|
||||||
|
|
||||||
|
# Function to obtain an OAuth2 access token
|
||||||
|
def get_access_token():
|
||||||
|
url = f"{BASE_URL}/oauth2/token"
|
||||||
|
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
|
||||||
|
data = {
|
||||||
|
'client_id': CLIENT_ID,
|
||||||
|
'client_secret': CLIENT_SECRET,
|
||||||
|
'grant_type': 'client_credentials'
|
||||||
|
}
|
||||||
|
response = requests.post(url, headers=headers, data=data)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()['access_token']
|
||||||
|
|
||||||
|
# Function to fetch exposure management reports (V2 API)
|
||||||
|
def get_exposure_management_reports(access_token):
|
||||||
|
url = f"{BASE_URL}/spotlight/queries/reports/v2"
|
||||||
|
headers = {
|
||||||
|
'Authorization': f'Bearer {access_token}',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Calculate the timestamp for 24 hours ago
|
||||||
|
now = datetime.utcnow()
|
||||||
|
twenty_four_hours_ago = now - timedelta(hours=24)
|
||||||
|
timestamp_filter = twenty_four_hours_ago.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||||
|
|
||||||
|
# Filter for reports generated in the last 24 hours
|
||||||
|
params = {
|
||||||
|
'filter': f'created_on:>{timestamp_filter}'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get(url, headers=headers, params=params)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()['resources']
|
||||||
|
|
||||||
|
# Function to export a report as CSV (V2 API)
|
||||||
|
def export_report_to_csv(report_id, access_token):
|
||||||
|
url = f"{BASE_URL}/spotlight/entities/reports-executions/v1/{report_id}/download"
|
||||||
|
headers = {'Authorization': f'Bearer {access_token}'}
|
||||||
|
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# Assuming the report content is in CSV format
|
||||||
|
with open(f'{report_id}.csv', 'wb') as f:
|
||||||
|
f.write(response.content)
|
||||||
|
|
||||||
|
# Main script execution
|
||||||
|
if __name__ == '__main__':
|
||||||
|
access_token = get_access_token()
|
||||||
|
reports = get_exposure_management_reports(access_token)
|
||||||
|
|
||||||
|
if reports:
|
||||||
|
most_recent_report_id = reports[0]['id']
|
||||||
|
export_report_to_csv(most_recent_report_id, access_token)
|
||||||
|
print(f"Exported report {most_recent_report_id} to CSV")
|
||||||
|
else:
|
||||||
|
print("No exposure management reports found in the last 24 hours")
|
||||||
Submodule
+1
Submodule DavidsDevelopment added at db7cc92e66
@@ -0,0 +1,71 @@
|
|||||||
|
version: "3.2"
|
||||||
|
services:
|
||||||
|
|
||||||
|
outline:
|
||||||
|
image: docker.getoutline.com/outlinewiki/outline:latest
|
||||||
|
env_file: ./docker.env
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
volumes:
|
||||||
|
- storage-data:/var/lib/outline/data
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis
|
||||||
|
env_file: ./docker.env
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
volumes:
|
||||||
|
- ./redis.conf:/redis.conf
|
||||||
|
command: ["redis-server", "/redis.conf"]
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 30s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres
|
||||||
|
env_file: ./docker.env
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- database-data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "pg_isready", "-d", "outline", "-U", "user"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 3
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: 'user'
|
||||||
|
POSTGRES_PASSWORD: 'pass'
|
||||||
|
POSTGRES_DB: 'outline'
|
||||||
|
|
||||||
|
https-portal:
|
||||||
|
image: steveltn/https-portal
|
||||||
|
env_file: ./docker.env
|
||||||
|
ports:
|
||||||
|
- '80:80'
|
||||||
|
- '443:443'
|
||||||
|
links:
|
||||||
|
- outline
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- https-portal-data:/var/lib/https-portal
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "service", "nginx", "status"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 3
|
||||||
|
environment:
|
||||||
|
DOMAINS: 'docs.mycompany.com -> http://outline:3000'
|
||||||
|
STAGE: 'production'
|
||||||
|
WEBSOCKET: 'true'
|
||||||
|
CLIENT_MAX_BODY_SIZE: '0'
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
https-portal-data:
|
||||||
|
storage-data:
|
||||||
|
database-data:
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
,david,pop-os,16.06.2023 16:27,file:///home/david/.config/libreoffice/4;
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIGojCCBIqgAwIBAgIEAmKu/zANBgkqhkiG9w0BAQsFADB7MQswCQYDVQQGEwJS
|
||||||
|
TzESMBAGA1UEBxMJQnVjaGFyZXN0MRgwFgYDVQQKEw9DeWJlckdob3N0IFMuQS4x
|
||||||
|
GzAZBgNVBAMTEkN5YmVyR2hvc3QgUm9vdCBDQTEhMB8GCSqGSIb3DQEJARYSaW5m
|
||||||
|
b0BjeWJlcmdob3N0LnJvMB4XDTIzMDIxNzE1MTE0OFoXDTMzMDIxNDE1MTE0OFow
|
||||||
|
czELMAkGA1UEBhMCUk8xEjAQBgNVBAcMCUJ1Y2hhcmVzdDEYMBYGA1UECgwPQ3li
|
||||||
|
ZXJHaG9zdCBTLkEuMRMwEQYDVQQDDApYUEVqeGlmWDcyMSEwHwYJKoZIhvcNAQkB
|
||||||
|
FhJpbmZvQGN5YmVyZ2hvc3Qucm8wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
|
||||||
|
AoICAQC7tEeEtyGY/c6Xvev31b0pB5NxpZ33d+nw6hAV6UtZnbOnsaBMDSkXzppL
|
||||||
|
VaMWBayoBTmR0cwhkNayn3+BHjQzPxTBIxResxWVqf/Dm/5o4QTT5YqJ1OE/U8GY
|
||||||
|
dy4HY8TGdHXjwdt3QkPVGBQUkC2dlsKLMth8eWk7eXOcEC5cLWv37t3Df7WYAzyo
|
||||||
|
l1tPbzM1z2cL6GQrp6kuBqr78LuQ1QP9OyBoN14mpHKnzkADcw+gmvVrfpFTX/Yi
|
||||||
|
39lgoSZZZU/74GTHpc89o71B9pPkj19XgCIEimsdBuBrXYhfoCnrNh9ChsNPRd/8
|
||||||
|
Ba+rqEsTP/kXbxgWh24fO6fL86ZU6VslSHCHIZfoJhX5CLzthnIeMmPNMwJsPGVy
|
||||||
|
NN6SPxPPm8tKYAQjzpSbEXmiecO9VYVclU3KQTqjoGUNvUByCUae7wLF3PXIzLVQ
|
||||||
|
x4Z2hNpmCmT916TcWGPJDKXMjkNQfBF1o7RnYaIFf5Ne4trwgQo/stHNYAz0y0/U
|
||||||
|
A1gyYUs192vYnUTfIYhN5x8mfUWOFaK7RqT2RIidSlaFMhZ8tH3hASqqdo/b2e03
|
||||||
|
D8N1PM38Wq4R12Vfq3SlTqQobEWb/1qXusBZhaOFCttck5LauaWP+kIdvI5hKFiP
|
||||||
|
VEa7zF1UJuyUjm/ZmN4uYPTrPuvAfkupZybAwuGB1L5pDuRyiQIDAQABo4IBNDCC
|
||||||
|
ATAwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwNAYJYIZIAYb4QgENBCcWJUN5YmVy
|
||||||
|
R2hvc3QgR2VuZXJhdGVkIFVzZXIgQ2VydGlmaWNhdGUwEQYJYIZIAYb4QgEBBAQD
|
||||||
|
AgeAMB0GA1UdDgQWBBQy2dvEDGXhLUYzaV8/VPwiB9+J+jCBrQYDVR0jBIGlMIGi
|
||||||
|
gBTq10rWD8d7mrON4Cgzl4e3iKf2JaF/pH0wezELMAkGA1UEBhMCUk8xEjAQBgNV
|
||||||
|
BAcTCUJ1Y2hhcmVzdDEYMBYGA1UEChMPQ3liZXJHaG9zdCBTLkEuMRswGQYDVQQD
|
||||||
|
ExJDeWJlckdob3N0IFJvb3QgQ0ExITAfBgkqhkiG9w0BCQEWEmluZm9AY3liZXJn
|
||||||
|
aG9zdC5yb4IJAJxUG61mxDS7MA0GCSqGSIb3DQEBCwUAA4ICAQDVmu2F3OxvBbTA
|
||||||
|
yf3Wop9H7KaHtHu8dgEr5q3tL6neH2JuqYYnWMi3eMb/5kviFkJ98GQI/cDls/ae
|
||||||
|
NTuvxZ5Bz9lMQjmPvniqk8YgdIbyxkSa4Qfols08go7ZoAIHsNgPvradJoj/1hOg
|
||||||
|
1OCl+tlXanPPEDIsNL8S9MnSWUkWfIksq7+jjVXKZzGzrox3OsOSo2kLyoJwPvuH
|
||||||
|
SwsjGV8KDByhbBUTRNH6MnXY7PPBLdpsTtBmHCJvhNnC/j9xWTCutxifonZhCI2J
|
||||||
|
z4i1GcAuV4nNyBrj5h7uNjgQGKnP1wvxysRtrO3UNJqcJkJqZqOKKi9Sqmjh/NDv
|
||||||
|
2o7pwgfvsf+vAXCwVBmv/ea39jKltY7s6ZY3d8JHgfAzU2R8Q0JN+aQRh0L9jcu/
|
||||||
|
JC3jB6iYV+lgLfg6ZuBuneTV69yba4G9TRcdEdGpe0SNcj3gWEvDFnmKe3i8c8Px
|
||||||
|
RUHknJr6+9ielIZl7TeB3zRNWMBWxGfUmGdUx5/SVIfc8kOOWBBHWi3ice6uxa6C
|
||||||
|
/hQh15dy/Dmro3iF2HF3hxNV8C9VQcceZcS7wOF//mqK6721bjk5/fxNhtBh1CB9
|
||||||
|
9nxq719le9XCd0zFRPYMTtnVd6ELqHhOdI7Fw90dFeAOIbBDZ5D3AjbjASUaILR0
|
||||||
|
X+/aNMPxoNGpbkCY3G/lb0NzmV0FLQ==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
T1HH2ae+okFYaHnNKrA9DGZNOgzYGQ==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/cyberghost.md
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
gluetun:
|
||||||
|
image: qmcgaw/gluetun
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
devices:
|
||||||
|
- /dev/net/tun:/dev/net/tun
|
||||||
|
environment:
|
||||||
|
- VPN_SERVICE_PROVIDER=cyberghost
|
||||||
|
- OPENVPN_USER=pvDXWWNiJm
|
||||||
|
- OPENVPN_PASSWORD=WvCrDvzh8r
|
||||||
|
- SERVER_COUNTRIES=Netherlands
|
||||||
|
volumes:
|
||||||
|
- /mnt/storagepool/media/software/gluetun:/gluetun
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
client
|
||||||
|
remote 87-1-ca.cg-dialup.net 443
|
||||||
|
dev tun
|
||||||
|
proto udp
|
||||||
|
auth-user-pass
|
||||||
|
|
||||||
|
|
||||||
|
resolv-retry infinite
|
||||||
|
redirect-gateway def1
|
||||||
|
persist-key
|
||||||
|
persist-tun
|
||||||
|
nobind
|
||||||
|
cipher AES-256-CBC
|
||||||
|
ncp-disable
|
||||||
|
auth SHA256
|
||||||
|
ping 5
|
||||||
|
ping-exit 60
|
||||||
|
ping-timer-rem
|
||||||
|
explicit-exit-notify 2
|
||||||
|
script-security 2
|
||||||
|
remote-cert-tls server
|
||||||
|
route-delay 5
|
||||||
|
verb 4
|
||||||
|
|
||||||
|
pull-filter ignore "ifconfig-ipv6"
|
||||||
|
pull-filter ignore "route-ipv6"
|
||||||
|
|
||||||
|
<ca>
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
</ca>
|
||||||
|
|
||||||
|
<cert>
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIGojCCBIqgAwIBAgIEAmKu/zANBgkqhkiG9w0BAQsFADB7MQswCQYDVQQGEwJS
|
||||||
|
TzESMBAGA1UEBxMJQnVjaGFyZXN0MRgwFgYDVQQKEw9DeWJlckdob3N0IFMuQS4x
|
||||||
|
GzAZBgNVBAMTEkN5YmVyR2hvc3QgUm9vdCBDQTEhMB8GCSqGSIb3DQEJARYSaW5m
|
||||||
|
b0BjeWJlcmdob3N0LnJvMB4XDTIzMDIxNzE1MTE0OFoXDTMzMDIxNDE1MTE0OFow
|
||||||
|
czELMAkGA1UEBhMCUk8xEjAQBgNVBAcMCUJ1Y2hhcmVzdDEYMBYGA1UECgwPQ3li
|
||||||
|
ZXJHaG9zdCBTLkEuMRMwEQYDVQQDDApYUEVqeGlmWDcyMSEwHwYJKoZIhvcNAQkB
|
||||||
|
FhJpbmZvQGN5YmVyZ2hvc3Qucm8wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
|
||||||
|
AoICAQC7tEeEtyGY/c6Xvev31b0pB5NxpZ33d+nw6hAV6UtZnbOnsaBMDSkXzppL
|
||||||
|
VaMWBayoBTmR0cwhkNayn3+BHjQzPxTBIxResxWVqf/Dm/5o4QTT5YqJ1OE/U8GY
|
||||||
|
dy4HY8TGdHXjwdt3QkPVGBQUkC2dlsKLMth8eWk7eXOcEC5cLWv37t3Df7WYAzyo
|
||||||
|
l1tPbzM1z2cL6GQrp6kuBqr78LuQ1QP9OyBoN14mpHKnzkADcw+gmvVrfpFTX/Yi
|
||||||
|
39lgoSZZZU/74GTHpc89o71B9pPkj19XgCIEimsdBuBrXYhfoCnrNh9ChsNPRd/8
|
||||||
|
Ba+rqEsTP/kXbxgWh24fO6fL86ZU6VslSHCHIZfoJhX5CLzthnIeMmPNMwJsPGVy
|
||||||
|
NN6SPxPPm8tKYAQjzpSbEXmiecO9VYVclU3KQTqjoGUNvUByCUae7wLF3PXIzLVQ
|
||||||
|
x4Z2hNpmCmT916TcWGPJDKXMjkNQfBF1o7RnYaIFf5Ne4trwgQo/stHNYAz0y0/U
|
||||||
|
A1gyYUs192vYnUTfIYhN5x8mfUWOFaK7RqT2RIidSlaFMhZ8tH3hASqqdo/b2e03
|
||||||
|
D8N1PM38Wq4R12Vfq3SlTqQobEWb/1qXusBZhaOFCttck5LauaWP+kIdvI5hKFiP
|
||||||
|
VEa7zF1UJuyUjm/ZmN4uYPTrPuvAfkupZybAwuGB1L5pDuRyiQIDAQABo4IBNDCC
|
||||||
|
ATAwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwNAYJYIZIAYb4QgENBCcWJUN5YmVy
|
||||||
|
R2hvc3QgR2VuZXJhdGVkIFVzZXIgQ2VydGlmaWNhdGUwEQYJYIZIAYb4QgEBBAQD
|
||||||
|
AgeAMB0GA1UdDgQWBBQy2dvEDGXhLUYzaV8/VPwiB9+J+jCBrQYDVR0jBIGlMIGi
|
||||||
|
gBTq10rWD8d7mrON4Cgzl4e3iKf2JaF/pH0wezELMAkGA1UEBhMCUk8xEjAQBgNV
|
||||||
|
BAcTCUJ1Y2hhcmVzdDEYMBYGA1UEChMPQ3liZXJHaG9zdCBTLkEuMRswGQYDVQQD
|
||||||
|
ExJDeWJlckdob3N0IFJvb3QgQ0ExITAfBgkqhkiG9w0BCQEWEmluZm9AY3liZXJn
|
||||||
|
aG9zdC5yb4IJAJxUG61mxDS7MA0GCSqGSIb3DQEBCwUAA4ICAQDVmu2F3OxvBbTA
|
||||||
|
yf3Wop9H7KaHtHu8dgEr5q3tL6neH2JuqYYnWMi3eMb/5kviFkJ98GQI/cDls/ae
|
||||||
|
NTuvxZ5Bz9lMQjmPvniqk8YgdIbyxkSa4Qfols08go7ZoAIHsNgPvradJoj/1hOg
|
||||||
|
1OCl+tlXanPPEDIsNL8S9MnSWUkWfIksq7+jjVXKZzGzrox3OsOSo2kLyoJwPvuH
|
||||||
|
SwsjGV8KDByhbBUTRNH6MnXY7PPBLdpsTtBmHCJvhNnC/j9xWTCutxifonZhCI2J
|
||||||
|
z4i1GcAuV4nNyBrj5h7uNjgQGKnP1wvxysRtrO3UNJqcJkJqZqOKKi9Sqmjh/NDv
|
||||||
|
2o7pwgfvsf+vAXCwVBmv/ea39jKltY7s6ZY3d8JHgfAzU2R8Q0JN+aQRh0L9jcu/
|
||||||
|
JC3jB6iYV+lgLfg6ZuBuneTV69yba4G9TRcdEdGpe0SNcj3gWEvDFnmKe3i8c8Px
|
||||||
|
RUHknJr6+9ielIZl7TeB3zRNWMBWxGfUmGdUx5/SVIfc8kOOWBBHWi3ice6uxa6C
|
||||||
|
/hQh15dy/Dmro3iF2HF3hxNV8C9VQcceZcS7wOF//mqK6721bjk5/fxNhtBh1CB9
|
||||||
|
9nxq719le9XCd0zFRPYMTtnVd6ELqHhOdI7Fw90dFeAOIbBDZ5D3AjbjASUaILR0
|
||||||
|
X+/aNMPxoNGpbkCY3G/lb0NzmV0FLQ==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
</cert>
|
||||||
|
|
||||||
|
<key>
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
T1HH2ae+okFYaHnNKrA9DGZNOgzYGQ==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
</key>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
deluge:
|
||||||
|
container_name: deluge
|
||||||
|
image: linuxserver/deluge:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: service:gluetun # run on the vpn network
|
||||||
|
environment:
|
||||||
|
- PUID=1000 # default user id, defined in .env
|
||||||
|
- PGID=1000 # default group id, defined in .env
|
||||||
|
- America/New_York # timezone, defined in .env
|
||||||
|
volumes:
|
||||||
|
- /mnt/storagepool/media/downloads:/downloads # downloads folder
|
||||||
|
- /mnt/storagepool/media/software/deluge:/config # config files
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
gluetun:
|
||||||
|
container_name: gluetun
|
||||||
|
image: qmcgaw/gluetun
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
devices:
|
||||||
|
- /dev/net/tun:/dev/net/tun
|
||||||
|
environment:
|
||||||
|
- VPN_SERVICE_PROVIDER=cyberghost
|
||||||
|
- OPENVPN_USER=pvDXWWNiJm # Replace with your actual username
|
||||||
|
- OPENVPN_PASSWORD=WvCrDvzh8r # Replace with your actual password
|
||||||
|
- SERVER_COUNTRIES=Netherlands
|
||||||
|
- PUID=1000
|
||||||
|
- PGID=999
|
||||||
|
volumes:
|
||||||
|
- /mnt/storagepool/media/software/gluetun:/gluetun
|
||||||
|
ports:
|
||||||
|
- 8112:8112
|
||||||
|
restart: always
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-s", "--fail", "https://www.google.com"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
deluge:
|
||||||
|
container_name: deluge
|
||||||
|
image: linuxserver/deluge:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: service:gluetun
|
||||||
|
environment:
|
||||||
|
- PUID=1000
|
||||||
|
- PGID=999
|
||||||
|
- TZ=America/New_York
|
||||||
|
volumes:
|
||||||
|
- /mnt/storagepool/media/downloads:/downloads
|
||||||
|
- /mnt/storagepool/media/software/deluge:/config
|
||||||
|
depends_on:
|
||||||
|
gluetun:
|
||||||
|
condition: service_healthy
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
version: "2.1"
|
||||||
|
services:
|
||||||
|
whisperasr:
|
||||||
|
image: onerahmet/openai-whisper-asr-webservice:latest
|
||||||
|
environment:
|
||||||
|
- ASR_MODEL=small
|
||||||
|
- ASR_ENGINE=faster_whisper
|
||||||
|
ports:
|
||||||
|
- 9000:9000
|
||||||
|
restart: unless-stopped
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import os
|
||||||
|
from markdown import markdown
|
||||||
|
from docx import Document
|
||||||
|
|
||||||
|
def convert_md_to_docx(md_file_path, docx_file_path):
|
||||||
|
"""Converts a Markdown file to a Word document."""
|
||||||
|
with open(md_file_path, 'r', encoding='utf-8') as md_file:
|
||||||
|
md_content = md_file.read()
|
||||||
|
|
||||||
|
# Convert Markdown to HTML
|
||||||
|
html_content = markdown(md_content)
|
||||||
|
|
||||||
|
# Create a Word document and add the HTML content
|
||||||
|
doc = Document()
|
||||||
|
doc.add_paragraph(html_content)
|
||||||
|
doc.save(docx_file_path)
|
||||||
|
print(f"Converted: {md_file_path} -> {docx_file_path}")
|
||||||
|
|
||||||
|
def convert_all_md_to_docx(root_directory, output_directory):
|
||||||
|
"""Traverse the directory tree and convert all .md files to .docx, storing them in a mirrored structure."""
|
||||||
|
for dirpath, dirnames, filenames in os.walk(root_directory):
|
||||||
|
for filename in filenames:
|
||||||
|
if filename.endswith('.md'):
|
||||||
|
md_file_path = os.path.join(dirpath, filename)
|
||||||
|
|
||||||
|
# Create mirrored directory structure in the output directory
|
||||||
|
relative_path = os.path.relpath(dirpath, root_directory)
|
||||||
|
target_dir = os.path.join(output_directory, relative_path)
|
||||||
|
os.makedirs(target_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Define the output .docx file path
|
||||||
|
docx_file_path = os.path.join(target_dir, os.path.splitext(filename)[0] + '.docx')
|
||||||
|
|
||||||
|
try:
|
||||||
|
convert_md_to_docx(md_file_path, docx_file_path)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to convert {md_file_path}: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Prompt user for the root directory and output directory
|
||||||
|
root_dir = input("Enter the root directory to scan for Markdown files: ").strip()
|
||||||
|
output_dir = input("Enter the output directory to store converted files: ").strip()
|
||||||
|
|
||||||
|
if os.path.exists(root_dir):
|
||||||
|
os.makedirs(output_dir, exist_ok=True) # Ensure output directory exists
|
||||||
|
convert_all_md_to_docx(root_dir, output_dir)
|
||||||
|
else:
|
||||||
|
print("The specified root directory does not exist.")
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import os
|
||||||
|
from markdown import markdown
|
||||||
|
from docx import Document
|
||||||
|
|
||||||
|
def convert_md_to_docx(md_file_path, docx_file_path):
|
||||||
|
"""Converts a Markdown file to a Word document."""
|
||||||
|
with open(md_file_path, 'r', encoding='utf-8') as md_file:
|
||||||
|
md_content = md_file.read()
|
||||||
|
|
||||||
|
# Convert Markdown to HTML
|
||||||
|
html_content = markdown(md_content)
|
||||||
|
|
||||||
|
# Create a Word document and add the HTML content
|
||||||
|
doc = Document()
|
||||||
|
doc.add_paragraph(html_content)
|
||||||
|
doc.save(docx_file_path)
|
||||||
|
print(f"Converted: {md_file_path} -> {docx_file_path}")
|
||||||
|
|
||||||
|
def convert_all_md_to_docx(root_directory, output_directory):
|
||||||
|
"""Traverse the directory tree and convert all .md files to .docx, storing them in a mirrored structure."""
|
||||||
|
for dirpath, dirnames, filenames in os.walk(root_directory):
|
||||||
|
for filename in filenames:
|
||||||
|
if filename.endswith('.md'):
|
||||||
|
md_file_path = os.path.join(dirpath, filename)
|
||||||
|
|
||||||
|
# Create mirrored directory structure in the output directory
|
||||||
|
relative_path = os.path.relpath(dirpath, root_directory)
|
||||||
|
target_dir = os.path.join(output_directory, relative_path)
|
||||||
|
os.makedirs(target_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Define the output .docx file path
|
||||||
|
docx_file_path = os.path.join(target_dir, os.path.splitext(filename)[0] + '.docx')
|
||||||
|
|
||||||
|
try:
|
||||||
|
convert_md_to_docx(md_file_path, docx_file_path)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to convert {md_file_path}: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Prompt user for the root directory and output directory
|
||||||
|
root_dir = input("Enter the root directory to scan for Markdown files: ").strip()
|
||||||
|
output_dir = input("Enter the output directory to store converted files: ").strip()
|
||||||
|
|
||||||
|
if os.path.exists(root_dir):
|
||||||
|
os.makedirs(output_dir, exist_ok=True) # Ensure output directory exists
|
||||||
|
convert_all_md_to_docx(root_dir, output_dir)
|
||||||
|
else:
|
||||||
|
print("The specified root directory does not exist.")
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import pandas as pd
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
def get_latest_cves(days=3):
|
||||||
|
try:
|
||||||
|
end_date = datetime.now()
|
||||||
|
start_date = end_date - timedelta(days=days)
|
||||||
|
start_date_str = start_date.strftime("%Y-%m-%dT%H:%M:%S")
|
||||||
|
end_date_str = end_date.strftime("%Y-%m-%dT%H:%M:%S")
|
||||||
|
nvd_url = f"https://services.nvd.nist.gov/rest/json/cves/1.0?pubStartDate={start_date_str}&pubEndDate={end_date_str}"
|
||||||
|
response = requests.get(nvd_url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
print(f"Error: Unable to fetch CVE data. Status code: {response.status_code}")
|
||||||
|
print("Response content:", response.content)
|
||||||
|
return None
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"Error: Unable to fetch CVE data. {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def extract_cvss_data(cve_entry):
|
||||||
|
cvss_data = cve_entry.get("impact", {}).get("baseMetricV2", {}).get("cvssV2", {})
|
||||||
|
if cvss_data:
|
||||||
|
return {
|
||||||
|
"CVSS Version": "2.0",
|
||||||
|
"Vector String": cvss_data.get("vectorString", ""),
|
||||||
|
"Base Score": cvss_data.get("baseScore", ""),
|
||||||
|
"Base Severity": cvss_data.get("severity", ""),
|
||||||
|
"Access Vector": cvss_data.get("accessVector", ""),
|
||||||
|
"Access Complexity": cvss_data.get("accessComplexity", ""),
|
||||||
|
"Authentication": cvss_data.get("authentication", ""),
|
||||||
|
"Confidentiality Impact": cvss_data.get("confidentialityImpact", ""),
|
||||||
|
"Integrity Impact": cvss_data.get("integrityImpact", ""),
|
||||||
|
"Availability Impact": cvss_data.get("availabilityImpact", ""),
|
||||||
|
"Exploitability Score": cve_entry.get("exploitabilityScore", ""),
|
||||||
|
"Impact Score": cve_entry.get("impactScore", "")
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"CVSS Version": "",
|
||||||
|
"Vector String": "",
|
||||||
|
"Base Score": "",
|
||||||
|
"Base Severity": "",
|
||||||
|
"Access Vector": "",
|
||||||
|
"Access Complexity": "",
|
||||||
|
"Authentication": "",
|
||||||
|
"Confidentiality Impact": "",
|
||||||
|
"Integrity Impact": "",
|
||||||
|
"Availability Impact": "",
|
||||||
|
"Exploitability Score": "",
|
||||||
|
"Impact Score": ""
|
||||||
|
}
|
||||||
|
|
||||||
|
def generate_excel_report(cves, excel_filename):
|
||||||
|
try:
|
||||||
|
data = []
|
||||||
|
for vulnerability_entry in cves.get("result", {}).get("CVE_Items", []):
|
||||||
|
cve_entry = vulnerability_entry.get("cve", {})
|
||||||
|
cve_id = cve_entry.get("CVE_data_meta", {}).get("ID", "")
|
||||||
|
descriptions = cve_entry.get("description", {}).get("description_data", [])
|
||||||
|
english_descriptions = [desc.get("value", "") for desc in descriptions if desc.get("lang") == "en"]
|
||||||
|
description = " ".join(english_descriptions)
|
||||||
|
references = cve_entry.get("references", {}).get("reference_data", [])
|
||||||
|
reference_urls = [ref.get("url", "") for ref in references]
|
||||||
|
reference_sources = [ref.get("name", "") for ref in references]
|
||||||
|
reference_url = reference_urls[0] if reference_urls else ""
|
||||||
|
reference_source = reference_sources[0] if reference_sources else ""
|
||||||
|
cvss_data = extract_cvss_data(vulnerability_entry)
|
||||||
|
data.append({
|
||||||
|
"CVE ID": cve_id,
|
||||||
|
"Description": description,
|
||||||
|
"Reference URL": reference_url,
|
||||||
|
"Reference Source": reference_source,
|
||||||
|
**cvss_data
|
||||||
|
})
|
||||||
|
df = pd.DataFrame(data)
|
||||||
|
df.to_excel(excel_filename, index=False)
|
||||||
|
print(f"Excel report generated successfully: {excel_filename}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: Unable to generate Excel report. {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
latest_cves = get_latest_cves()
|
||||||
|
if latest_cves:
|
||||||
|
excel_timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
||||||
|
excel_filename = f"latest_cves_{excel_timestamp}.xlsx"
|
||||||
|
generate_excel_report(latest_cves, excel_filename)
|
||||||
|
else:
|
||||||
|
print("No CVEs found.")
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import csv
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
def get_nist_vulnerabilities(days=3, limit=5000): # Added limit parameter
|
||||||
|
"""Retrieves the latest vulnerabilities from the NIST NVD API v2.0 with a limit."""
|
||||||
|
|
||||||
|
end_date = datetime.date.today()
|
||||||
|
start_date = end_date - datetime.timedelta(days=days)
|
||||||
|
|
||||||
|
all_vulnerabilities = []
|
||||||
|
startIndex = 0
|
||||||
|
resultsPerPage = 2000
|
||||||
|
|
||||||
|
while True:
|
||||||
|
url = f"https://services.nvd.nist.gov/rest/json/cves/2.0?startIndex={startIndex}&resultsPerPage={resultsPerPage}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(url)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
vulnerabilities = data.get("vulnerabilities", [])
|
||||||
|
|
||||||
|
if not vulnerabilities:
|
||||||
|
break
|
||||||
|
|
||||||
|
all_vulnerabilities.extend(vulnerabilities)
|
||||||
|
startIndex += resultsPerPage
|
||||||
|
time.sleep(0.5) # Reduced sleep time slightly
|
||||||
|
|
||||||
|
if len(all_vulnerabilities) >= limit: #Enforce limit
|
||||||
|
print(f"Reached CVE limit of {limit}. Stopping retrieval.")
|
||||||
|
break
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"Error fetching data from NIST API: {e}")
|
||||||
|
return []
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print(f"Error decoding JSON response: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
filtered_vulnerabilities = [
|
||||||
|
cve for cve in all_vulnerabilities
|
||||||
|
if datetime.datetime.fromisoformat(cve.get('cve', {}).get('published', '').replace('Z', '+00:00')).date() >= start_date
|
||||||
|
]
|
||||||
|
return filtered_vulnerabilities
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def write_to_csv(vulnerabilities, filename):
|
||||||
|
# ... (This function is exactly the same as in my previous responses)
|
||||||
|
if not vulnerabilities:
|
||||||
|
print("No vulnerabilities found for the specified period.")
|
||||||
|
return
|
||||||
|
|
||||||
|
fieldnames = ["Product", "Vendor", "CVE ID", "Date Added", "Date Updated", "Description", "Severity"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
|
||||||
|
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
||||||
|
writer.writeheader()
|
||||||
|
for cve in vulnerabilities:
|
||||||
|
cve_data = cve.get('cve', {})
|
||||||
|
|
||||||
|
products = []
|
||||||
|
vendors = []
|
||||||
|
|
||||||
|
if 'cpeMatch' in cve_data.get('configurations', {}):
|
||||||
|
for match in cve_data['configurations']['cpeMatch']:
|
||||||
|
cpe_string = match.get('cpe23Uri', '')
|
||||||
|
parts = cpe_string.split(':')
|
||||||
|
if len(parts) >= 5:
|
||||||
|
vendors.append(parts[3])
|
||||||
|
products.append(parts[4])
|
||||||
|
else:
|
||||||
|
vendors.append("N/A")
|
||||||
|
products.append("N/A")
|
||||||
|
|
||||||
|
product_str = ", ".join(products)
|
||||||
|
vendor_str = ", ".join(vendors)
|
||||||
|
|
||||||
|
description = cve_data.get('descriptions', [{}])[0].get('value', 'N/A')
|
||||||
|
published_date = cve_data.get('published', 'N/A')
|
||||||
|
last_modified_date = cve_data.get('lastModified', 'N/A')
|
||||||
|
cve_id = cve_data.get('id', 'N/A')
|
||||||
|
|
||||||
|
severity = 'N/A'
|
||||||
|
metrics = cve_data.get('metrics', {})
|
||||||
|
if 'cvssMetricV31' in metrics:
|
||||||
|
severity = metrics['cvssMetricV31'][0].get('cvssData', {}).get('baseSeverity', 'N/A')
|
||||||
|
elif 'cvssMetricV30' in metrics:
|
||||||
|
severity = metrics['cvssMetricV30'][0].get('cvssData', {}).get('baseSeverity', 'N/A')
|
||||||
|
elif 'cvssMetricV2' in metrics:
|
||||||
|
severity = metrics['cvssMetricV2'][0].get('cvssData', {}).get('baseSeverity', 'N/A')
|
||||||
|
|
||||||
|
writer.writerow({
|
||||||
|
"Product": product_str,
|
||||||
|
"Vendor": vendor_str,
|
||||||
|
"CVE ID": cve_id,
|
||||||
|
"Date Added": published_date,
|
||||||
|
"Date Updated": last_modified_date,
|
||||||
|
"Description": description,
|
||||||
|
"Severity": severity
|
||||||
|
})
|
||||||
|
print(f"Vulnerabilities written to {filename}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error writing to CSV: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
vulnerabilities = get_nist_vulnerabilities()
|
||||||
|
|
||||||
|
if vulnerabilities:
|
||||||
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
filename = f"nist_vulnerabilities_{timestamp}.csv"
|
||||||
|
write_to_csv(vulnerabilities, filename)
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#a
|
||||||
|
print("Hello, world!")
|
||||||
|
#b
|
||||||
|
print("Hello", "World!")
|
||||||
|
#c
|
||||||
|
print("3")
|
||||||
|
#d
|
||||||
|
print("3.0")
|
||||||
|
#e
|
||||||
|
print(2 + 3)
|
||||||
|
#f
|
||||||
|
print("2.0 + 3.0")
|
||||||
|
#g
|
||||||
|
print("2" + "3")
|
||||||
|
#h
|
||||||
|
print("2 + 3 =", 2 + 3)
|
||||||
|
#i
|
||||||
|
print("2 * 3")
|
||||||
|
#j
|
||||||
|
print(2 ** 3)
|
||||||
|
#k
|
||||||
|
print(7 / 3)
|
||||||
|
#l
|
||||||
|
print(7 // 3)
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#a
|
||||||
|
print("Hello, world!")
|
||||||
|
#b
|
||||||
|
print("Hello", "World!")
|
||||||
|
#c
|
||||||
|
print("3")
|
||||||
|
#d
|
||||||
|
print("3.0")
|
||||||
|
#e
|
||||||
|
print(2 + 3)
|
||||||
|
#f
|
||||||
|
print("2.0 + 3.0")
|
||||||
|
#g
|
||||||
|
print("2" + "3")
|
||||||
|
#h
|
||||||
|
print("2 + 3 =", 2 + 3)
|
||||||
|
#i
|
||||||
|
print("2 * 3")
|
||||||
|
#j
|
||||||
|
print(2 ** 3)
|
||||||
|
#k
|
||||||
|
print(7 / 3)
|
||||||
|
#l
|
||||||
|
print(7 // 3)
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#performs the conversion and prints to the console
|
||||||
|
def ConvertToMiles():
|
||||||
|
kilometers = float(input("Please provide a number of kilometers that you would like to convert to miles: "))
|
||||||
|
converted_to_miles = kilometers * 0.62
|
||||||
|
print(kilometers, " kilometer(s) is/are equal to " , converted_to_miles , " miles.")
|
||||||
|
askaboutrepeating()
|
||||||
|
|
||||||
|
#Ask the users if they would like to do it again.
|
||||||
|
def askaboutrepeating():
|
||||||
|
print("Looks like that provided you a converted number. Do you want to convert another number? ")
|
||||||
|
answer = str(input("Please respond with yes (y) or no (n) :"))
|
||||||
|
if answer == "y":
|
||||||
|
ConvertToMiles()
|
||||||
|
if answer == "n":
|
||||||
|
exit
|
||||||
|
else:
|
||||||
|
print("Woah, something about what you typed was definitley not correct.")
|
||||||
|
askaboutrepeating()
|
||||||
|
|
||||||
|
# Defines what the script is going to do and calls upon the earlier defined Convert function
|
||||||
|
def main():
|
||||||
|
print("Welcome!\n\n This application was made by David Kifer to take a quantity of kilometers and convert them to miles. In the following prompt, you will be asked to provide the amount of kilometers\n\n ")
|
||||||
|
ConvertToMiles()
|
||||||
|
|
||||||
|
# Call the main app to kick off the app
|
||||||
|
main()
|
||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#Konditorei Coffee shop
|
||||||
|
def main():
|
||||||
|
pounds = float(input("How many pounds of coffee would you like to order?: "))
|
||||||
|
coffee_price = float(15.50)
|
||||||
|
shipping_price = float(0.99 * pounds + 4.50)
|
||||||
|
grand_total = coffee_price + shipping_price
|
||||||
|
print("That comes to a grand total of $" , grand_total)
|
||||||
|
askaboutrepeating()
|
||||||
|
|
||||||
|
# Ask about repeating
|
||||||
|
def askaboutrepeating():
|
||||||
|
print("Thanks for ordering with me! Would you like to order some more?")
|
||||||
|
answer = str(input("Please respond with yes (y) or no (n) :"))
|
||||||
|
if answer == "y":
|
||||||
|
main()
|
||||||
|
if answer == "n":
|
||||||
|
exit
|
||||||
|
else:
|
||||||
|
print("Woah, something about what you typed was definitley not correct.")
|
||||||
|
askaboutrepeating()
|
||||||
|
|
||||||
|
# Call the main app to kick off the app
|
||||||
|
main()
|
||||||
|
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
menu = {
|
||||||
|
1: {"name": 'espresso', "price": 1.99},
|
||||||
|
2: {"name": 'coffee', "price": 2.50},
|
||||||
|
3: {"name": 'cake', "price": 2.79},
|
||||||
|
4: {"name": 'soup', "price": 4.50},
|
||||||
|
5: {"name": 'sandwich', "price": 4.99}
|
||||||
|
}
|
||||||
|
|
||||||
|
def calculate_subtotal(order):
|
||||||
|
"""Calculates the subtotal of an order.
|
||||||
|
|
||||||
|
[IMPLEMENT ME]
|
||||||
|
1. Add up the prices of all items in the order.
|
||||||
|
2. Round the result to 2 decimal places and return it.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
order (list): A list of dicts, where each dict represents an item with a name and price.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: The subtotal of all item prices, rounded to 2 decimal places.
|
||||||
|
"""
|
||||||
|
print('Calculating bill subtotal...')
|
||||||
|
|
||||||
|
### WRITE SOLUTION HERE
|
||||||
|
subtotal_calc = float(0.00)
|
||||||
|
for item in order:
|
||||||
|
subtotal_calc += item['price']
|
||||||
|
return round(subtotal_calc, 2)
|
||||||
|
|
||||||
|
def calculate_tax(subtotal):
|
||||||
|
"""Calculates the tax of an order.
|
||||||
|
|
||||||
|
[IMPLEMENT ME]
|
||||||
|
1. Calculate 15% of the subtotal.
|
||||||
|
2. Round the tax to 2 decimal places and return it.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
subtotal (float): The subtotal amount.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: The calculated tax amount, rounded to 2 decimal places.
|
||||||
|
"""
|
||||||
|
print('Calculating tax from subtotal...')
|
||||||
|
### WRITE SOLUTION HERE
|
||||||
|
tax_rate = 0.15
|
||||||
|
tax = subtotal * tax_rate
|
||||||
|
return round(tax, 2)
|
||||||
|
|
||||||
|
|
||||||
|
def summarize_order(order):
|
||||||
|
"""Summarizes the order.
|
||||||
|
|
||||||
|
[IMPLEMENT ME]
|
||||||
|
1. Calculate subtotal and tax.
|
||||||
|
2. Compute the total (subtotal + tax) and round to 2 decimal places.
|
||||||
|
3. Extract the item names and return them with the total.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
order (list): A list of dicts, where each dict represents an item with a name and price.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: A list of item names and the total amount (rounded to 2 decimal places).
|
||||||
|
"""
|
||||||
|
|
||||||
|
### WRITE SOLUTION HERE
|
||||||
|
item_details = list()
|
||||||
|
for item in order:
|
||||||
|
item_details.append((item['name'], item['price']))
|
||||||
|
subtotal = calculate_subtotal(order)
|
||||||
|
subtotal = round(subtotal, 2)
|
||||||
|
tax = calculate_tax(subtotal)
|
||||||
|
total = round(subtotal + tax, 2)
|
||||||
|
names = [item['name'] for item in order]
|
||||||
|
return names, total
|
||||||
|
|
||||||
|
|
||||||
|
# This function is provided for you and will print out the items in an order
|
||||||
|
def print_order(order):
|
||||||
|
print('You have ordered ' + str(len(order)) + ' items')
|
||||||
|
items = [item["name"] for item in order]
|
||||||
|
print(items)
|
||||||
|
return order
|
||||||
|
|
||||||
|
# This function is provided for you and will display the menu
|
||||||
|
def display_menu():
|
||||||
|
print("------- Menu -------")
|
||||||
|
for selection in menu:
|
||||||
|
print(f"{selection}. {menu[selection]['name'] : <9} | {menu[selection]['price'] : >5}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# This function is provided for you and will create an order by prompting the user to select menu items
|
||||||
|
def take_order():
|
||||||
|
display_menu()
|
||||||
|
order = []
|
||||||
|
count = 1
|
||||||
|
for i in range(3):
|
||||||
|
item = input('Select menu item number ' + str(count) + ' (from 1 to 5): ')
|
||||||
|
count += 1
|
||||||
|
order.append(menu[int(item)])
|
||||||
|
return order
|
||||||
|
|
||||||
|
'''
|
||||||
|
Here are some sample function calls to help you test your implementations.
|
||||||
|
Feel free to change, uncomment, and add these as you wish.
|
||||||
|
'''
|
||||||
|
def main():
|
||||||
|
order = take_order()
|
||||||
|
print_order(order)
|
||||||
|
names, total = summarize_order(order)
|
||||||
|
|
||||||
|
|
||||||
|
print("Order summary:")
|
||||||
|
for name, price in [(item['name'], item['price']) for item in order]:
|
||||||
|
print(f" {name}: ${price:.2f}")
|
||||||
|
|
||||||
|
print(f"Total: ${total:.2f}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Given the initial statements
|
||||||
|
# s1 = "spam"
|
||||||
|
# s2 = "ni!"
|
||||||
|
|
||||||
|
# Show the result of evaluating each of the following string expressions.
|
||||||
|
|
||||||
|
# a) "The knights who say, " + s2
|
||||||
|
# b) 3 * s1 +2 * s2
|
||||||
|
# c) a1[1]
|
||||||
|
# d) s1[1:3]
|
||||||
|
# e) s1[2] + s2[:2]
|
||||||
|
# f) s1 + s2[-1]
|
||||||
|
# g) s1.upper()
|
||||||
|
# h) s1.upper().1just(4) * 3
|
||||||
|
|
||||||
|
## Initialization
|
||||||
|
s1 = "spam"
|
||||||
|
s2 = "ni!"
|
||||||
|
|
||||||
|
StringValues = {
|
||||||
|
"a": "The knights who say, " + s2,
|
||||||
|
"b": 3 * s1 + 2 * s2,
|
||||||
|
"c": s1[1],
|
||||||
|
"d": s1[1:3],
|
||||||
|
"e": s1[2] + s2[:2],
|
||||||
|
"f": s1 + s2[-1],
|
||||||
|
"g": s1.upper(),
|
||||||
|
"h": s1.upper().ljust(4) * 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
def main():
|
||||||
|
for key, value in StringValues.items():
|
||||||
|
print(f"{key}) {value}")
|
||||||
|
|
||||||
|
|
||||||
|
main()
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
s1 = "spam"
|
||||||
|
s2 = "eggs"
|
||||||
|
num1 = 10
|
||||||
|
num2 = 3.14159
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print(f"That is {num1} {s1} and {num1//2} {s2}")
|
||||||
|
print(f"That is {num1:5} {s1} and {num2:5.2} {s2}")
|
||||||
|
print(f"{num1:0.2f} {num2:0.2f}")
|
||||||
|
print(f"{num1:7.5f}{num2:7.5f}")
|
||||||
|
print(f"Time left {num1:02}:{num2:05.0f}")
|
||||||
|
|
||||||
|
main()
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
gradetable = {
|
||||||
|
"90-100": "A",
|
||||||
|
"80-89": "B",
|
||||||
|
"70-79": "C",
|
||||||
|
"60-69": "D",
|
||||||
|
"0-59": "F"
|
||||||
|
}
|
||||||
|
|
||||||
|
def main():
|
||||||
|
usercontinue = 'yes'
|
||||||
|
while usercontinue == 'yes':
|
||||||
|
try:
|
||||||
|
score = int(input("What was the exam score?: "))
|
||||||
|
|
||||||
|
if 90 <= score <= 100:
|
||||||
|
grade = gradetable["90-100"]
|
||||||
|
elif 80 <= score <= 89:
|
||||||
|
grade = gradetable["80-89"]
|
||||||
|
elif 70 <= score <= 79:
|
||||||
|
grade = gradetable["70-79"]
|
||||||
|
elif 60 <= score <= 69:
|
||||||
|
grade = gradetable["60-69"]
|
||||||
|
elif 0 <= score <= 59:
|
||||||
|
grade = gradetable["0-59"]
|
||||||
|
else:
|
||||||
|
grade = 'invalid score'
|
||||||
|
|
||||||
|
print(f"The letter score of score of the exam was {grade}")
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
print("Invalid input. Please enter a number.")
|
||||||
|
|
||||||
|
|
||||||
|
usercontinueprompt = input("Would you like to determine the letter grade of another score? (yes/no): ")
|
||||||
|
usercontinueprompt = usercontinueprompt.lower() #Convert to lowercase once
|
||||||
|
|
||||||
|
if usercontinueprompt == 'yes': #Corrected to assignment
|
||||||
|
usercontinue = 'yes'
|
||||||
|
elif usercontinueprompt == 'no':
|
||||||
|
print("Thanks, have a great day!")
|
||||||
|
usercontinue = 'no' # Corrected typo and assignment
|
||||||
|
else:
|
||||||
|
print("That wasn't a valid selection. Try again")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
a = "cool"
|
||||||
|
input=("")
|
||||||
|
|
||||||
|
print(a)
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
s1 = [2, 1, 4, 3]
|
||||||
|
s2 = ["c","a","b"]
|
||||||
|
print("------------Discussion Part 1----------")
|
||||||
|
# s1 + s2
|
||||||
|
print("-----s1 + s2-----")
|
||||||
|
print(s1 + s2)
|
||||||
|
|
||||||
|
|
||||||
|
#b) 3 * s1 + 2 * s2
|
||||||
|
print("--------3 * s1 + 2 * s2-----")
|
||||||
|
print(3 * s1 + 2 *s2)
|
||||||
|
print(f"This is the value of s1: {s1}")
|
||||||
|
print(f"This is the value of s2: {s2}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
#c) s1[1]
|
||||||
|
print("--------s1[1]-----")
|
||||||
|
print(s1[1])
|
||||||
|
print(f"This is the value of s1: {s1}")
|
||||||
|
print(f"This is the value of s2: {s2}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
#d) s1[1:3]
|
||||||
|
print("--------s1[1:3]-----")
|
||||||
|
print(s1[1:3])
|
||||||
|
print(f"This is the value of s1: {s1}")
|
||||||
|
print(f"This is the value of s2: {s2}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
#e) s1 + s2[-1]
|
||||||
|
print("--------s1[1:3]------")
|
||||||
|
print(s1 + [s2[-1]])
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("------------Discussion Part 2----------")
|
||||||
|
#a)s1.remove(2)
|
||||||
|
print(s1.remove(2))
|
||||||
|
print(f"This is the value of s1: {s1}")
|
||||||
|
print(f"This is the value of s2: {s2}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
#b)s1.sort()
|
||||||
|
print(s1.sort())
|
||||||
|
print(f"This is the value of s1: {s1}")
|
||||||
|
print(f"This is the value of s2: {s2}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
#c)s1.append([s2.index("b")]
|
||||||
|
print(s1.append([s2.index("b")]))
|
||||||
|
print(f"This is the value of s1: {s1}")
|
||||||
|
print(f"This is the value of s2: {s2}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
#d)s2.pop(s1.pop(0))
|
||||||
|
print(s2.pop(s1.pop(0)))
|
||||||
|
print(f"This is the value of s1: {s1}")
|
||||||
|
print(f"This is the value of s2: {s2}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
#e)s2.insert(s1[1], "d"
|
||||||
|
print(s2.insert(s1[1], "d"))
|
||||||
|
print(f"This is the value of s1: {s1}")
|
||||||
|
print(f"This is the value of s2: {s2}")
|
||||||
|
print()
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
def removeDuplicates(somelist):
|
||||||
|
seen = set()
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for i in somelist:
|
||||||
|
if i not in seen:
|
||||||
|
seen.add(i)
|
||||||
|
result.append(i)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def main():
|
||||||
|
#Create a list with duplicates
|
||||||
|
somelist = [1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9]
|
||||||
|
|
||||||
|
#Display the list
|
||||||
|
print(somelist)
|
||||||
|
|
||||||
|
#Use the remove duplicates function to remove duplicate values
|
||||||
|
list_parsed = removeDuplicates(somelist)
|
||||||
|
|
||||||
|
print(list_parsed)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
from graphics import *
|
||||||
|
|
||||||
|
win = GraphWin("My Window", 500, 500)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
key = win.checkKey()
|
||||||
|
if key == "q": #loop exit
|
||||||
|
break
|
||||||
|
if key == "r":
|
||||||
|
win.setBackground("red")
|
||||||
|
elif key == "g":
|
||||||
|
win.setBackground("green")
|
||||||
|
elif key == "b":
|
||||||
|
win.setBackground("blue")
|
||||||
|
elif key == "y":
|
||||||
|
win.setBackground("yellow")
|
||||||
|
elif key == "w":
|
||||||
|
win.setBackground("white")
|
||||||
|
elif key == "p":
|
||||||
|
win.setBackground("purple")
|
||||||
|
elif key == "o":
|
||||||
|
win.setBackground("orange")
|
||||||
|
elif key == "c":
|
||||||
|
win.setBackground("cyan")
|
||||||
|
else :
|
||||||
|
win.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import whois
|
||||||
|
from docx import Document
|
||||||
|
from fpdf import FPDF
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
def investigate_website(url):
|
||||||
|
"""Investigate the website to find the country of operation and past security breaches."""
|
||||||
|
try:
|
||||||
|
# Perform WHOIS lookup to determine country
|
||||||
|
domain = url.replace("https://", "").replace("http://", "").split("/")[0]
|
||||||
|
whois_data = whois.whois(domain)
|
||||||
|
country = whois_data.get("country", "Unknown")
|
||||||
|
|
||||||
|
# Check for breaches using Have I Been Pwned API (requires API key)
|
||||||
|
hibp_api_url = f"https://haveibeenpwned.com/api/v3/breachedaccount/{domain}"
|
||||||
|
headers = {"hibp-api-key": "YOUR_API_KEY_HERE"} # Replace with your HIBP API key
|
||||||
|
breaches = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
breach_response = requests.get(hibp_api_url, headers=headers)
|
||||||
|
if breach_response.status_code == 200:
|
||||||
|
breaches = breach_response.json()
|
||||||
|
elif breach_response.status_code == 404:
|
||||||
|
breaches = "No breaches found."
|
||||||
|
else:
|
||||||
|
breaches = f"Error: {breach_response.status_code}"
|
||||||
|
except Exception as e:
|
||||||
|
breaches = f"Error fetching breaches: {e}"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"country": country,
|
||||||
|
"breaches": breaches,
|
||||||
|
"whois_data": str(whois_data)
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"country": "Error determining country",
|
||||||
|
"breaches": f"Error fetching breaches: {e}",
|
||||||
|
"whois_data": f"Error: {e}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def analyze_headers(url):
|
||||||
|
"""Perform a header analysis to identify security weaknesses."""
|
||||||
|
try:
|
||||||
|
response = requests.get(url)
|
||||||
|
headers = response.headers
|
||||||
|
|
||||||
|
# Security headers to check
|
||||||
|
security_headers = {
|
||||||
|
"Content-Security-Policy": "Missing or insufficient Content-Security-Policy",
|
||||||
|
"Strict-Transport-Security": "Missing Strict-Transport-Security",
|
||||||
|
"X-Content-Type-Options": "Missing X-Content-Type-Options",
|
||||||
|
"X-Frame-Options": "Missing X-Frame-Options",
|
||||||
|
"X-XSS-Protection": "Missing or insufficient X-XSS-Protection",
|
||||||
|
"Referrer-Policy": "Missing Referrer-Policy",
|
||||||
|
"Permissions-Policy": "Missing Permissions-Policy"
|
||||||
|
}
|
||||||
|
|
||||||
|
weaknesses = []
|
||||||
|
for header, issue in security_headers.items():
|
||||||
|
if header not in headers:
|
||||||
|
weaknesses.append(issue)
|
||||||
|
|
||||||
|
grade = "A" if not weaknesses else "C" if len(weaknesses) < 3 else "F"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"grade": grade,
|
||||||
|
"weaknesses": weaknesses,
|
||||||
|
"headers": dict(headers)
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"grade": "Error",
|
||||||
|
"weaknesses": [f"Error analyzing headers: {e}"],
|
||||||
|
"headers": f"Error: {e}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def save_results_to_files(data, output_dir, domain):
|
||||||
|
"""Save investigation results to docx, pdf, and markdown files."""
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Generate unique filenames using timestamp and domain
|
||||||
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
# Ensure the domain is a valid filename
|
||||||
|
domain_safe = "".join(c if c.isalnum() else "_" for c in domain)
|
||||||
|
base_filename = f"{domain_safe}_{timestamp}"
|
||||||
|
docx_path = os.path.join(output_dir, f"{base_filename}.docx")
|
||||||
|
pdf_path = os.path.join(output_dir, f"{base_filename}.pdf")
|
||||||
|
md_path = os.path.join(output_dir, f"{base_filename}.md")
|
||||||
|
|
||||||
|
# Create a Word document
|
||||||
|
doc = Document()
|
||||||
|
doc.add_heading("Website Security Analysis", level=1)
|
||||||
|
|
||||||
|
doc.add_heading("1. Website Investigation", level=2)
|
||||||
|
doc.add_paragraph(f"Country of Operation: {data['investigation'].get('country', 'N/A')}")
|
||||||
|
doc.add_paragraph(f"Security Breaches: {data['investigation'].get('breaches', 'N/A')}")
|
||||||
|
|
||||||
|
doc.add_heading("2. Header Analysis", level=2)
|
||||||
|
doc.add_paragraph(f"Security Grade: {data['headers'].get('grade', 'N/A')}")
|
||||||
|
if data['headers'].get('weaknesses'):
|
||||||
|
doc.add_paragraph("Weaknesses:")
|
||||||
|
for weakness in data['headers']['weaknesses']:
|
||||||
|
doc.add_paragraph(f"- {weakness}", style="List Bullet")
|
||||||
|
|
||||||
|
# Add Appendix
|
||||||
|
doc.add_heading("Appendix", level=1)
|
||||||
|
doc.add_heading("Full WHOIS Data", level=2)
|
||||||
|
doc.add_paragraph(data['investigation'].get('whois_data', 'N/A'))
|
||||||
|
|
||||||
|
doc.add_heading("Full Header Analysis", level=2)
|
||||||
|
doc.add_paragraph(str(data['headers'].get('headers', 'N/A')))
|
||||||
|
|
||||||
|
doc.add_heading("Full Breach Data", level=2)
|
||||||
|
doc.add_paragraph(str(data['investigation'].get('breaches', 'N/A')))
|
||||||
|
|
||||||
|
doc.save(docx_path)
|
||||||
|
|
||||||
|
# Create a Markdown file
|
||||||
|
with open(md_path, "w") as md_file:
|
||||||
|
md_file.write("# Website Security Analysis\n\n")
|
||||||
|
md_file.write("## 1. Website Investigation\n")
|
||||||
|
md_file.write(f"- Country of Operation: {data['investigation'].get('country', 'N/A')}\n")
|
||||||
|
md_file.write(f"- Security Breaches: {data['investigation'].get('breaches', 'N/A')}\n\n")
|
||||||
|
md_file.write("## 2. Header Analysis\n")
|
||||||
|
md_file.write(f"- Security Grade: {data['headers'].get('grade', 'N/A')}\n")
|
||||||
|
if data['headers'].get('weaknesses'):
|
||||||
|
md_file.write("- Weaknesses:\n")
|
||||||
|
for weakness in data['headers']['weaknesses']:
|
||||||
|
md_file.write(f" - {weakness}\n")
|
||||||
|
|
||||||
|
md_file.write("\n# Appendix\n\n")
|
||||||
|
md_file.write("## Full WHOIS Data\n")
|
||||||
|
md_file.write(f"```\n{data['investigation'].get('whois_data', 'N/A')}\n```\n")
|
||||||
|
|
||||||
|
md_file.write("## Full Header Analysis\n")
|
||||||
|
md_file.write(f"```\n{data['headers'].get('headers', 'N/A')}\n```\n")
|
||||||
|
|
||||||
|
md_file.write("## Full Breach Data\n")
|
||||||
|
md_file.write(f"```\n{data['investigation'].get('breaches', 'N/A')}\n```\n")
|
||||||
|
|
||||||
|
# Create a PDF file using FPDF
|
||||||
|
pdf = FPDF()
|
||||||
|
pdf.set_auto_page_break(auto=True, margin=15)
|
||||||
|
pdf.add_page()
|
||||||
|
pdf.set_font("Arial", size=12)
|
||||||
|
|
||||||
|
pdf.cell(200, 10, txt="Website Security Analysis", ln=True, align='C')
|
||||||
|
|
||||||
|
pdf.ln(10)
|
||||||
|
pdf.set_font("Arial", style="B", size=12)
|
||||||
|
pdf.cell(0, 10, "1. Website Investigation", ln=True)
|
||||||
|
|
||||||
|
pdf.set_font("Arial", size=12)
|
||||||
|
pdf.multi_cell(0, 10, f"Country of Operation: {data['investigation'].get('country', 'N/A')}")
|
||||||
|
pdf.multi_cell(0, 10, f"Security Breaches: {data['investigation'].get('breaches', 'N/A')}")
|
||||||
|
|
||||||
|
pdf.ln(5)
|
||||||
|
pdf.set_font("Arial", style="B", size=12)
|
||||||
|
pdf.cell(0, 10, "2. Header Analysis", ln=True)
|
||||||
|
|
||||||
|
pdf.set_font("Arial", size=12)
|
||||||
|
pdf.multi_cell(0, 10, f"Security Grade: {data['headers'].get('grade', 'N/A')}")
|
||||||
|
if data['headers'].get('weaknesses'):
|
||||||
|
pdf.cell(0, 10, "Weaknesses:", ln=True)
|
||||||
|
for weakness in data['headers']['weaknesses']:
|
||||||
|
pdf.cell(0, 10, f"- {weakness}", ln=True)
|
||||||
|
|
||||||
|
pdf.add_page()
|
||||||
|
pdf.set_font("Arial", style="B", size=12)
|
||||||
|
pdf.cell(0, 10, "Appendix", ln=True)
|
||||||
|
|
||||||
|
pdf.set_font("Arial", style="B", size=12)
|
||||||
|
pdf.cell(0, 10, "Full WHOIS Data", ln=True)
|
||||||
|
pdf.set_font("Arial", size=10)
|
||||||
|
pdf.multi_cell(0, 10, data['investigation'].get('whois_data', 'N/A'))
|
||||||
|
|
||||||
|
pdf.set_font("Arial", style="B", size=12)
|
||||||
|
pdf.cell(0, 10, "Full Header Analysis", ln=True)
|
||||||
|
pdf.set_font("Arial", size=10)
|
||||||
|
pdf.multi_cell(0, 10, str(data['headers'].get('headers', 'N/A')))
|
||||||
|
|
||||||
|
pdf.set_font("Arial", style="B", size=12)
|
||||||
|
pdf.cell(0, 10, "Full Breach Data", ln=True)
|
||||||
|
pdf.set_font("Arial", size=10)
|
||||||
|
pdf.multi_cell(0, 10, str(data['investigation'].get('breaches', 'N/A')))
|
||||||
|
|
||||||
|
pdf.output(pdf_path)
|
||||||
|
|
||||||
|
print(f"Results saved to: {docx_path}, {md_path}, {pdf_path}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# User inputs
|
||||||
|
website_url = input("Enter the website URL (including https://): ").strip()
|
||||||
|
default_directory = os.getcwd()
|
||||||
|
print(f"Default save directory: {default_directory}")
|
||||||
|
use_default = input("Do you want to use the default directory? (y/n): ").strip().lower()
|
||||||
|
|
||||||
|
if use_default == 'y':
|
||||||
|
output_directory = default_directory
|
||||||
|
else:
|
||||||
|
output_directory = input("Enter the directory to save results: ").strip()
|
||||||
|
|
||||||
|
# Extract domain name for unique file naming
|
||||||
|
domain = website_url.replace("https://", "").replace("http://", "").split("/")[0]
|
||||||
|
|
||||||
|
# Perform analysis
|
||||||
|
investigation_results = investigate_website(website_url)
|
||||||
|
header_analysis_results = analyze_headers(website_url)
|
||||||
|
|
||||||
|
# Combine results
|
||||||
|
results = {
|
||||||
|
"investigation": investigation_results,
|
||||||
|
"headers": header_analysis_results
|
||||||
|
}
|
||||||
|
|
||||||
|
# Save to files
|
||||||
|
save_results_to_files(results, output_directory, domain)
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import whois
|
||||||
|
from docx import Document
|
||||||
|
from fpdf import FPDF
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
def investigate_website(url):
|
||||||
|
"""Investigate the website to find the country of operation and past security breaches."""
|
||||||
|
try:
|
||||||
|
# Perform WHOIS lookup to determine country
|
||||||
|
domain = url.replace("https://", "").replace("http://", "").split("/")[0]
|
||||||
|
whois_data = whois.whois(domain)
|
||||||
|
country = whois_data.get("country", "Unknown")
|
||||||
|
|
||||||
|
# Check for breaches using Have I Been Pwned API (requires API key)
|
||||||
|
hibp_api_url = f"https://haveibeenpwned.com/api/v3/breachedaccount/{domain}"
|
||||||
|
headers = {"hibp-api-key": "YOUR_API_KEY_HERE"} # Replace with your HIBP API key
|
||||||
|
breaches = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
breach_response = requests.get(hibp_api_url, headers=headers)
|
||||||
|
if breach_response.status_code == 200:
|
||||||
|
breaches = breach_response.json()
|
||||||
|
elif breach_response.status_code == 404:
|
||||||
|
breaches = "No breaches found."
|
||||||
|
else:
|
||||||
|
breaches = f"Error: {breach_response.status_code}"
|
||||||
|
except Exception as e:
|
||||||
|
breaches = f"Error fetching breaches: {e}"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"country": country,
|
||||||
|
"breaches": breaches,
|
||||||
|
"whois_data": str(whois_data)
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"country": "Error determining country",
|
||||||
|
"breaches": f"Error fetching breaches: {e}",
|
||||||
|
"whois_data": f"Error: {e}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def analyze_headers(url):
|
||||||
|
"""Perform a header analysis to identify security weaknesses."""
|
||||||
|
try:
|
||||||
|
response = requests.get(url)
|
||||||
|
headers = response.headers
|
||||||
|
|
||||||
|
# Security headers to check
|
||||||
|
security_headers = {
|
||||||
|
"Content-Security-Policy": "Missing or insufficient Content-Security-Policy",
|
||||||
|
"Strict-Transport-Security": "Missing Strict-Transport-Security",
|
||||||
|
"X-Content-Type-Options": "Missing X-Content-Type-Options",
|
||||||
|
"X-Frame-Options": "Missing X-Frame-Options",
|
||||||
|
"X-XSS-Protection": "Missing or insufficient X-XSS-Protection",
|
||||||
|
"Referrer-Policy": "Missing Referrer-Policy",
|
||||||
|
"Permissions-Policy": "Missing Permissions-Policy"
|
||||||
|
}
|
||||||
|
|
||||||
|
weaknesses = []
|
||||||
|
for header, issue in security_headers.items():
|
||||||
|
if header not in headers:
|
||||||
|
weaknesses.append(issue)
|
||||||
|
|
||||||
|
grade = "A" if not weaknesses else "C" if len(weaknesses) < 3 else "F"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"grade": grade,
|
||||||
|
"weaknesses": weaknesses,
|
||||||
|
"headers": dict(headers)
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"grade": "Error",
|
||||||
|
"weaknesses": [f"Error analyzing headers: {e}"],
|
||||||
|
"headers": f"Error: {e}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def save_results_to_files(data, output_dir, domain):
|
||||||
|
"""Save investigation results to docx, pdf, and markdown files."""
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Generate unique filenames using timestamp and domain
|
||||||
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
# Ensure the domain is a valid filename
|
||||||
|
domain_safe = "".join(c if c.isalnum() else "_" for c in domain)
|
||||||
|
base_filename = f"{domain_safe}_{timestamp}"
|
||||||
|
docx_path = os.path.join(output_dir, f"{base_filename}.docx")
|
||||||
|
pdf_path = os.path.join(output_dir, f"{base_filename}.pdf")
|
||||||
|
md_path = os.path.join(output_dir, f"{base_filename}.md")
|
||||||
|
|
||||||
|
# Create a Word document
|
||||||
|
doc = Document()
|
||||||
|
doc.add_heading("Website Security Analysis", level=1)
|
||||||
|
|
||||||
|
doc.add_heading("1. Website Investigation", level=2)
|
||||||
|
doc.add_paragraph(f"Country of Operation: {data['investigation'].get('country', 'N/A')}")
|
||||||
|
doc.add_paragraph(f"Security Breaches: {data['investigation'].get('breaches', 'N/A')}")
|
||||||
|
|
||||||
|
doc.add_heading("2. Header Analysis", level=2)
|
||||||
|
doc.add_paragraph(f"Security Grade: {data['headers'].get('grade', 'N/A')}")
|
||||||
|
if data['headers'].get('weaknesses'):
|
||||||
|
doc.add_paragraph("Weaknesses:")
|
||||||
|
for weakness in data['headers']['weaknesses']:
|
||||||
|
doc.add_paragraph(f"- {weakness}", style="List Bullet")
|
||||||
|
|
||||||
|
# Add Appendix
|
||||||
|
doc.add_heading("Appendix", level=1)
|
||||||
|
doc.add_heading("Full WHOIS Data", level=2)
|
||||||
|
doc.add_paragraph(data['investigation'].get('whois_data', 'N/A'))
|
||||||
|
|
||||||
|
doc.add_heading("Full Header Analysis", level=2)
|
||||||
|
doc.add_paragraph(str(data['headers'].get('headers', 'N/A')))
|
||||||
|
|
||||||
|
doc.add_heading("Full Breach Data", level=2)
|
||||||
|
doc.add_paragraph(str(data['investigation'].get('breaches', 'N/A')))
|
||||||
|
|
||||||
|
doc.save(docx_path)
|
||||||
|
|
||||||
|
# Create a Markdown file
|
||||||
|
with open(md_path, "w") as md_file:
|
||||||
|
md_file.write("# Website Security Analysis\n\n")
|
||||||
|
md_file.write("## 1. Website Investigation\n")
|
||||||
|
md_file.write(f"- Country of Operation: {data['investigation'].get('country', 'N/A')}\n")
|
||||||
|
md_file.write(f"- Security Breaches: {data['investigation'].get('breaches', 'N/A')}\n\n")
|
||||||
|
md_file.write("## 2. Header Analysis\n")
|
||||||
|
md_file.write(f"- Security Grade: {data['headers'].get('grade', 'N/A')}\n")
|
||||||
|
if data['headers'].get('weaknesses'):
|
||||||
|
md_file.write("- Weaknesses:\n")
|
||||||
|
for weakness in data['headers']['weaknesses']:
|
||||||
|
md_file.write(f" - {weakness}\n")
|
||||||
|
|
||||||
|
md_file.write("\n# Appendix\n\n")
|
||||||
|
md_file.write("## Full WHOIS Data\n")
|
||||||
|
md_file.write(f"```\n{data['investigation'].get('whois_data', 'N/A')}\n```\n")
|
||||||
|
|
||||||
|
md_file.write("## Full Header Analysis\n")
|
||||||
|
md_file.write(f"```\n{data['headers'].get('headers', 'N/A')}\n```\n")
|
||||||
|
|
||||||
|
md_file.write("## Full Breach Data\n")
|
||||||
|
md_file.write(f"```\n{data['investigation'].get('breaches', 'N/A')}\n```\n")
|
||||||
|
|
||||||
|
# Create a PDF file using FPDF
|
||||||
|
pdf = FPDF()
|
||||||
|
pdf.set_auto_page_break(auto=True, margin=15)
|
||||||
|
pdf.add_page()
|
||||||
|
pdf.set_font("Arial", size=12)
|
||||||
|
|
||||||
|
pdf.cell(200, 10, txt="Website Security Analysis", ln=True, align='C')
|
||||||
|
|
||||||
|
pdf.ln(10)
|
||||||
|
pdf.set_font("Arial", style="B", size=12)
|
||||||
|
pdf.cell(0, 10, "1. Website Investigation", ln=True)
|
||||||
|
|
||||||
|
pdf.set_font("Arial", size=12)
|
||||||
|
pdf.multi_cell(0, 10, f"Country of Operation: {data['investigation'].get('country', 'N/A')}")
|
||||||
|
pdf.multi_cell(0, 10, f"Security Breaches: {data['investigation'].get('breaches', 'N/A')}")
|
||||||
|
|
||||||
|
pdf.ln(5)
|
||||||
|
pdf.set_font("Arial", style="B", size=12)
|
||||||
|
pdf.cell(0, 10, "2. Header Analysis", ln=True)
|
||||||
|
|
||||||
|
pdf.set_font("Arial", size=12)
|
||||||
|
pdf.multi_cell(0, 10, f"Security Grade: {data['headers'].get('grade', 'N/A')}")
|
||||||
|
if data['headers'].get('weaknesses'):
|
||||||
|
pdf.cell(0, 10, "Weaknesses:", ln=True)
|
||||||
|
for weakness in data['headers']['weaknesses']:
|
||||||
|
pdf.cell(0, 10, f"- {weakness}", ln=True)
|
||||||
|
|
||||||
|
pdf.add_page()
|
||||||
|
pdf.set_font("Arial", style="B", size=12)
|
||||||
|
pdf.cell(0, 10, "Appendix", ln=True)
|
||||||
|
|
||||||
|
pdf.set_font("Arial", style="B", size=12)
|
||||||
|
pdf.cell(0, 10, "Full WHOIS Data", ln=True)
|
||||||
|
pdf.set_font("Arial", size=10)
|
||||||
|
pdf.multi_cell(0, 10, data['investigation'].get('whois_data', 'N/A'))
|
||||||
|
|
||||||
|
pdf.set_font("Arial", style="B", size=12)
|
||||||
|
pdf.cell(0, 10, "Full Header Analysis", ln=True)
|
||||||
|
pdf.set_font("Arial", size=10)
|
||||||
|
pdf.multi_cell(0, 10, str(data['headers'].get('headers', 'N/A')))
|
||||||
|
|
||||||
|
pdf.set_font("Arial", style="B", size=12)
|
||||||
|
pdf.cell(0, 10, "Full Breach Data", ln=True)
|
||||||
|
pdf.set_font("Arial", size=10)
|
||||||
|
pdf.multi_cell(0, 10, str(data['investigation'].get('breaches', 'N/A')))
|
||||||
|
|
||||||
|
pdf.output(pdf_path)
|
||||||
|
|
||||||
|
print(f"Results saved to: {docx_path}, {md_path}, {pdf_path}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# User inputs
|
||||||
|
website_url = input("Enter the website URL (including https://): ").strip()
|
||||||
|
default_directory = os.getcwd()
|
||||||
|
print(f"Default save directory: {default_directory}")
|
||||||
|
use_default = input("Do you want to use the default directory? (y/n): ").strip().lower()
|
||||||
|
|
||||||
|
if use_default == 'y':
|
||||||
|
output_directory = default_directory
|
||||||
|
else:
|
||||||
|
output_directory = input("Enter the directory to save results: ").strip()
|
||||||
|
|
||||||
|
# Extract domain name for unique file naming
|
||||||
|
domain = website_url.replace("https://", "").replace("http://", "").split("/")[0]
|
||||||
|
|
||||||
|
# Perform analysis
|
||||||
|
investigation_results = investigate_website(website_url)
|
||||||
|
header_analysis_results = analyze_headers(website_url)
|
||||||
|
|
||||||
|
# Combine results
|
||||||
|
results = {
|
||||||
|
"investigation": investigation_results,
|
||||||
|
"headers": header_analysis_results
|
||||||
|
}
|
||||||
|
|
||||||
|
# Save to files
|
||||||
|
save_results_to_files(results, output_directory, domain)
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import pandas as pd
|
||||||
|
import os
|
||||||
|
import whois
|
||||||
|
import socket
|
||||||
|
import requests
|
||||||
|
from ipwhois import IPWhois
|
||||||
|
|
||||||
|
def aggregate_csv_to_excel(folder_path, output_excel):
|
||||||
|
with pd.ExcelWriter(output_excel, engine='xlsxwriter') as writer:
|
||||||
|
for filename in os.listdir(folder_path):
|
||||||
|
if filename.endswith('.csv'):
|
||||||
|
file_path = os.path.join(folder_path, filename)
|
||||||
|
df = pd.read_csv(file_path)
|
||||||
|
|
||||||
|
# Filter relevant columns
|
||||||
|
columns = ['timestamp', 'client_ip', 'server_ip', 'server_port', 'direction', 'total_bytes', 'country', 'flow_state']
|
||||||
|
if 'dns' in df.columns:
|
||||||
|
columns.append('dns')
|
||||||
|
df = df[columns]
|
||||||
|
|
||||||
|
# Investigate each server IP
|
||||||
|
df['whois_info'] = df['server_ip'].apply(get_whois_info)
|
||||||
|
df['geo_info'] = df['server_ip'].apply(get_geo_info)
|
||||||
|
df['virus_total'] = df['server_ip'].apply(check_virus_total)
|
||||||
|
df['dns_lookup'] = df['server_ip'].apply(dns_lookup)
|
||||||
|
|
||||||
|
# Write to Excel
|
||||||
|
sheet_name = os.path.splitext(filename)[0]
|
||||||
|
df.to_excel(writer, sheet_name=sheet_name, index=False)
|
||||||
|
|
||||||
|
def get_whois_info(ip):
|
||||||
|
try:
|
||||||
|
w = whois.whois(ip)
|
||||||
|
return f"Registrar: {w.registrar}, Country: {w.country}"
|
||||||
|
except Exception as e:
|
||||||
|
return f"WHOIS Error: {str(e)}"
|
||||||
|
|
||||||
|
def get_geo_info(ip):
|
||||||
|
try:
|
||||||
|
obj = IPWhois(ip)
|
||||||
|
res = obj.lookup_rdap()
|
||||||
|
return f"Country: {res['asn_country_code']}, ASN: {res['asn']}"
|
||||||
|
except Exception as e:
|
||||||
|
return f"Geo Error: {str(e)}"
|
||||||
|
|
||||||
|
def check_virus_total(ip):
|
||||||
|
try:
|
||||||
|
url = f"https://www.virustotal.com/api/v3/ip_addresses/{ip}"
|
||||||
|
headers = {"x-apikey": "your-api-key-here"} # Replace with your VirusTotal API key
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
return f"Reputation: {data.get('data', {}).get('attributes', {}).get('reputation', 'N/A')}"
|
||||||
|
else:
|
||||||
|
return f"VT Error: {response.status_code}"
|
||||||
|
except Exception as e:
|
||||||
|
return f"VT Error: {str(e)}"
|
||||||
|
|
||||||
|
def dns_lookup(ip):
|
||||||
|
try:
|
||||||
|
return socket.gethostbyaddr(ip)[0]
|
||||||
|
except socket.herror:
|
||||||
|
return "DNS Lookup Error"
|
||||||
|
|
||||||
|
def main():
|
||||||
|
current_dir = os.getcwd()
|
||||||
|
user_input = input(f"Do you want to use the current directory ({current_dir}) to pull CSV files? (yes/no): ").strip().lower()
|
||||||
|
|
||||||
|
if user_input == 'yes':
|
||||||
|
folder_path = current_dir
|
||||||
|
else:
|
||||||
|
folder_path = input("Please enter the directory where the CSV files are located: ").strip()
|
||||||
|
|
||||||
|
output_excel = os.path.join(current_dir, 'aggregated_investigation.xlsx')
|
||||||
|
aggregate_csv_to_excel(folder_path, output_excel)
|
||||||
|
print(f"Aggregated investigation saved to {output_excel}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,340 @@
|
|||||||
|
import datetime
|
||||||
|
from docx import Document
|
||||||
|
from docx2pdf import convert
|
||||||
|
|
||||||
|
def create_assessment_doc(data):
|
||||||
|
"""Creates a Word document from the assessment data."""
|
||||||
|
|
||||||
|
document = Document()
|
||||||
|
document.add_heading('Software/Vendor Security Assessment Request', 0)
|
||||||
|
|
||||||
|
# 1. Requestor Information
|
||||||
|
document.add_heading('1. Requestor Information', level=1)
|
||||||
|
document.add_paragraph(f"Name: {data['requestor_name']}")
|
||||||
|
document.add_paragraph(f"Department: {data['department']}")
|
||||||
|
document.add_paragraph(f"Email Address: {data['email']}")
|
||||||
|
document.add_paragraph(f"Phone Number: {data['phone']}")
|
||||||
|
|
||||||
|
# 2. Request Type
|
||||||
|
document.add_heading('2. Request Type', level=1)
|
||||||
|
document.add_paragraph(data['request_type'])
|
||||||
|
|
||||||
|
# 3. Deployment Model
|
||||||
|
document.add_heading('3. Deployment Model', level=1)
|
||||||
|
document.add_paragraph(data['deployment_model'])
|
||||||
|
|
||||||
|
# 4. Software/Vendor Information
|
||||||
|
document.add_heading('4. Software/Vendor Information', level=1)
|
||||||
|
document.add_paragraph(f"Software/Vendor Name: {data['vendor_name']}")
|
||||||
|
document.add_paragraph(f"Software Name: {data['software_name']}")
|
||||||
|
document.add_paragraph(f"Software/Vendor Website: {data['vendor_website']}")
|
||||||
|
document.add_paragraph(f"Software/Vendor Contact Information: {data['vendor_contact']}")
|
||||||
|
document.add_paragraph(f"Brief Description of Software/Vendor and its Purpose: {data['vendor_description']}")
|
||||||
|
|
||||||
|
# 5. Intended Use
|
||||||
|
document.add_heading('5. Intended Use', level=1)
|
||||||
|
document.add_paragraph(f"Describe how the software/vendor will be used within the company: {data['intended_use']}")
|
||||||
|
document.add_paragraph(f"Specify the departments or teams that will be using the software/vendor: {data['departments_teams']}")
|
||||||
|
document.add_paragraph(f"Identify any critical business processes or data that will be impacted by the software/vendor: {data['critical_processes_data']}")
|
||||||
|
|
||||||
|
# 6. Prerequisites
|
||||||
|
document.add_heading('6. Prerequisites', level=1)
|
||||||
|
document.add_paragraph(f"Operating System Requirements: {data['os_requirements']}")
|
||||||
|
document.add_paragraph(f"Hardware Requirements (e.g., RAM, storage space): {data['hardware_requirements']}")
|
||||||
|
document.add_paragraph(f"Network Connectivity (e.g., bandwidth, VPN): {data['network_connectivity']}")
|
||||||
|
document.add_paragraph(f"Software Dependencies (e.g., libraries, frameworks): {data['software_dependencies']}")
|
||||||
|
document.add_paragraph(f"User Access Requirements (e.g., specific roles, permissions): {data['user_access_requirements']}")
|
||||||
|
document.add_paragraph(f"Other Prerequisites: {data['other_prerequisites']}")
|
||||||
|
|
||||||
|
# 7. Technical Information
|
||||||
|
document.add_heading('7. Technical Information', level=1)
|
||||||
|
document.add_paragraph(f"Hosting Environment (e.g., cloud provider, on-premise data center): {data['hosting_environment']}")
|
||||||
|
document.add_paragraph(f"Network Requirements (e.g., ports, protocols, firewall rules): {data['network_requirements']}")
|
||||||
|
document.add_paragraph(f"Data Storage Requirements (e.g., location, encryption, data retention policies): {data['data_storage_requirements']}")
|
||||||
|
document.add_paragraph(f"Integration with Existing Systems (if any): {data['integration_existing_systems']}")
|
||||||
|
document.add_paragraph(f"API Information (if applicable): {data['api_information']}")
|
||||||
|
|
||||||
|
# 8. Security Requirements
|
||||||
|
document.add_heading('8. Security Requirements', level=1)
|
||||||
|
document.add_paragraph(f"Data Security - How will data be protected in transit and at rest?: {data['data_protection']}")
|
||||||
|
document.add_paragraph(f"Data Security - Does the software/vendor comply with relevant data privacy regulations (e.g., GDPR, CCPA)?: {data['data_privacy_compliance']}")
|
||||||
|
document.add_paragraph(f"Data Security - What data encryption methods are used?: {data['data_encryption_methods']}")
|
||||||
|
document.add_paragraph(f"Access Control - How will access to the software/vendor be granted and managed?: {data['access_control_management']}")
|
||||||
|
document.add_paragraph(f"Access Control - Does the software/vendor support multi-factor authentication?: {data['multi_factor_authentication']}")
|
||||||
|
document.add_paragraph(f"Access Control - What user roles and permissions are available?: {data['user_roles_permissions']}")
|
||||||
|
document.add_paragraph(f"Vulnerability Management - Does the software/vendor have a vulnerability disclosure program?: {data['vulnerability_disclosure']}")
|
||||||
|
document.add_paragraph(f"Vulnerability Management - How are security patches and updates managed?: {data['security_patch_management']}")
|
||||||
|
document.add_paragraph(f"Vulnerability Management - Are regular security assessments and penetration testing conducted?: {data['security_assessments']}")
|
||||||
|
document.add_paragraph(f"Compliance - Does the software/vendor comply with relevant industry security standards (e.g., ISO 27001, SOC 2)?: {data['industry_security_standards']}")
|
||||||
|
document.add_paragraph(f"Compliance - Are there any third-party security certifications or audits available?: {data['third_party_certifications']}")
|
||||||
|
document.add_paragraph(f"Business Continuity and Disaster Recovery - What are the software/vendor's business continuity and disaster recovery plans?: {data['business_continuity_plans']}")
|
||||||
|
document.add_paragraph(f"Business Continuity and Disaster Recovery - What are the recovery time objectives (RTOs) and recovery point objectives (RPOs)?: {data['rto_rpo']}")
|
||||||
|
|
||||||
|
# 9. Privacy Requirements
|
||||||
|
document.add_heading('9. Privacy Requirements', level=1)
|
||||||
|
document.add_paragraph(f"Data Collection - What personal data will be collected by the software/vendor?: {data['personal_data_collected']}")
|
||||||
|
document.add_paragraph(f"Data Collection - What is the purpose of collecting this data?: {data['data_collection_purpose']}")
|
||||||
|
document.add_paragraph(f"Data Collection - Will data be shared with third parties? If so, with whom and for what purpose?: {data['data_sharing']}")
|
||||||
|
document.add_paragraph(f"Data Subject Rights - How does the software/vendor ensure data subject rights (e.g., access, rectification, erasure)?: {data['data_subject_rights']}")
|
||||||
|
document.add_paragraph(f"Data Subject Rights - Does the software/vendor provide a privacy policy that is readily accessible to users?: {data['privacy_policy']}")
|
||||||
|
|
||||||
|
# 10. Legal and Contractual Requirements
|
||||||
|
document.add_heading('10. Legal and Contractual Requirements', level=1)
|
||||||
|
document.add_paragraph(f"Software License Agreement: {data['license_agreement']}")
|
||||||
|
document.add_paragraph(f"Service Level Agreement (SLA): {data['sla']}")
|
||||||
|
document.add_paragraph(f"Data Processing Agreement (DPA): {data['dpa']}")
|
||||||
|
document.add_paragraph(f"Vendor Security Policies: {data['vendor_security_policies']}")
|
||||||
|
|
||||||
|
# 11. Risk Assessment
|
||||||
|
document.add_heading('11. Risk Assessment', level=1)
|
||||||
|
document.add_paragraph(f"Identify any potential security or privacy risks associated with the use of the software/vendor: {data['potential_risks']}")
|
||||||
|
document.add_paragraph(f"Describe the risk mitigation measures that will be implemented: {data['risk_mitigation']}")
|
||||||
|
|
||||||
|
# 12. Approval (This section will likely be filled later)
|
||||||
|
document.add_heading('12. Approval', level=1)
|
||||||
|
document.add_paragraph("Security Analyst Review:")
|
||||||
|
document.add_paragraph("Date of Review: ")
|
||||||
|
document.add_paragraph("Security Analyst Name: ")
|
||||||
|
document.add_paragraph("Security Assessment Findings: ")
|
||||||
|
document.add_paragraph("Approval Status (Approved, Conditionally Approved, Rejected): ")
|
||||||
|
document.add_paragraph("Management Approval (if required):")
|
||||||
|
document.add_paragraph("Date of Approval: ")
|
||||||
|
document.add_paragraph("Approving Manager Name: ")
|
||||||
|
|
||||||
|
# 13. Supporting Documentation
|
||||||
|
document.add_heading('13. Supporting Documentation', level=1)
|
||||||
|
document.add_paragraph("Attach any relevant supporting documentation, such as:")
|
||||||
|
document.add_paragraph("Vendor security questionnaires")
|
||||||
|
document.add_paragraph("Penetration testing reports")
|
||||||
|
document.add_paragraph("Security audit reports")
|
||||||
|
document.add_paragraph("Privacy impact assessments")
|
||||||
|
|
||||||
|
return document
|
||||||
|
|
||||||
|
def create_markdown_file(data, md_filename): # Receive filename as argument
|
||||||
|
"""Creates a Markdown file from the assessment data."""
|
||||||
|
|
||||||
|
with open(md_filename, "w") as f: # Use the provided filename
|
||||||
|
# Add all the headings and data from the form in Markdown format
|
||||||
|
f.write("# Software/Vendor Security Assessment Request\n\n")
|
||||||
|
|
||||||
|
f.write("## 1. Requestor Information\n")
|
||||||
|
f.write(f"Name: {data['requestor_name']}\n")
|
||||||
|
f.write(f"Department: {data['department']}\n")
|
||||||
|
f.write(f"Email Address: {data['email']}\n")
|
||||||
|
f.write(f"Phone Number: {data['phone']}\n")
|
||||||
|
|
||||||
|
f.write("\n## 2. Request Type\n")
|
||||||
|
f.write(f"{data['request_type']}\n")
|
||||||
|
|
||||||
|
f.write("\n## 3. Deployment Model\n")
|
||||||
|
f.write(f"{data['deployment_model']}\n")
|
||||||
|
|
||||||
|
f.write("\n## 4. Software/Vendor Information\n")
|
||||||
|
f.write(f"Software/Vendor Name: {data['vendor_name']}\n")
|
||||||
|
f.write(f"Software Name: {data['software_name']}\n")
|
||||||
|
f.write(f"Software/Vendor Website: {data['vendor_website']}\n")
|
||||||
|
f.write(f"Software/Vendor Contact Information: {data['vendor_contact']}\n")
|
||||||
|
f.write(f"Brief Description of Software/Vendor and its Purpose: {data['vendor_description']}\n")
|
||||||
|
|
||||||
|
f.write("\n## 5. Intended Use\n")
|
||||||
|
f.write(f"Describe how the software/vendor will be used within the company: {data['intended_use']}\n")
|
||||||
|
f.write(f"Specify the departments or teams that will be using the software/vendor: {data['departments_teams']}\n")
|
||||||
|
f.write(f"Identify any critical business processes or data that will be impacted by the software/vendor: {data['critical_processes_data']}\n")
|
||||||
|
|
||||||
|
f.write("\n## 6. Prerequisites\n")
|
||||||
|
f.write(f"Operating System Requirements: {data['os_requirements']}\n")
|
||||||
|
f.write(f"Hardware Requirements (e.g., RAM, storage space): {data['hardware_requirements']}\n")
|
||||||
|
f.write(f"Network Connectivity (e.g., bandwidth, VPN): {data['network_connectivity']}\n")
|
||||||
|
f.write(f"Software Dependencies (e.g., libraries, frameworks): {data['software_dependencies']}\n")
|
||||||
|
f.write(f"User Access Requirements (e.g., specific roles, permissions): {data['user_access_requirements']}\n")
|
||||||
|
f.write(f"Other Prerequisites: {data['other_prerequisites']}\n")
|
||||||
|
|
||||||
|
f.write("\n## 7. Technical Information\n")
|
||||||
|
f.write(f"Hosting Environment (e.g., cloud provider, on-premise data center): {data['hosting_environment']}\n")
|
||||||
|
f.write(f"Network Requirements (e.g., ports, protocols, firewall rules): {data['network_requirements']}\n")
|
||||||
|
f.write(f"Data Storage Requirements (e.g., location, encryption, data retention policies): {data['data_storage_requirements']}\n")
|
||||||
|
f.write(f"Integration with Existing Systems (if any): {data['integration_existing_systems']}\n")
|
||||||
|
f.write(f"API Information (if applicable): {data['api_information']}\n")
|
||||||
|
|
||||||
|
f.write("\n## 8. Security Requirements\n")
|
||||||
|
f.write(f"Data Security - How will data be protected in transit and at rest?: {data['data_protection']}\n")
|
||||||
|
f.write(f"Data Security - Does the software/vendor comply with relevant data privacy regulations (e.g., GDPR, CCPA)?: {data['data_privacy_compliance']}\n")
|
||||||
|
f.write(f"Data Security - What data encryption methods are used?: {data['data_encryption_methods']}\n")
|
||||||
|
f.write(f"Access Control - How will access to the software/vendor be granted and managed?: {data['access_control_management']}\n")
|
||||||
|
f.write(f"Access Control - Does the software/vendor support multi-factor authentication?: {data['multi_factor_authentication']}\n")
|
||||||
|
f.write(f"Access Control - What user roles and permissions are available?: {data['user_roles_permissions']}\n")
|
||||||
|
f.write(f"Vulnerability Management - Does the software/vendor have a vulnerability disclosure program?: {data['vulnerability_disclosure']}\n")
|
||||||
|
f.write(f"Vulnerability Management - How are security patches and updates managed?: {data['security_patch_management']}\n")
|
||||||
|
f.write(f"Vulnerability Management - Are regular security assessments and penetration testing conducted?: {data['security_assessments']}\n")
|
||||||
|
f.write(f"Compliance - Does the software/vendor comply with relevant industry security standards (e.g., ISO 27001, SOC 2)?: {data['industry_security_standards']}\n")
|
||||||
|
f.write(f"Compliance - Are there any third-party security certifications or audits available?: {data['third_party_certifications']}\n")
|
||||||
|
f.write(f"Business Continuity and Disaster Recovery - What are the software/vendor's business continuity and disaster recovery plans?: {data['business_continuity_plans']}\n")
|
||||||
|
f.write(f"Business Continuity and Disaster Recovery - What are the recovery time objectives (RTOs) and recovery point objectives (RPOs)?: {data['rto_rpo']}\n")
|
||||||
|
|
||||||
|
f.write("\n## 9. Privacy Requirements\n")
|
||||||
|
f.write(f"Data Collection - What personal data will be collected by the software/vendor?: {data['personal_data_collected']}\n")
|
||||||
|
f.write(f"Data Collection - What is the purpose of collecting this data?: {data['data_collection_purpose']}\n")
|
||||||
|
f.write(f"Data Collection - Will data be shared with third parties? If so, with whom and for what purpose?: {data['data_sharing']}\n")
|
||||||
|
f.write(f"Data Subject Rights - How does the software/vendor ensure data subject rights (e.g., access, rectification, erasure)?: {data['data_subject_rights']}\n")
|
||||||
|
f.write(f"Data Subject Rights - Does the software/vendor provide a privacy policy that is readily accessible to users?: {data['privacy_policy']}\n")
|
||||||
|
|
||||||
|
f.write("\n## 10. Legal and Contractual Requirements\n")
|
||||||
|
f.write(f"Software License Agreement: {data['license_agreement']}\n")
|
||||||
|
f.write(f"Service Level Agreement (SLA): {data['sla']}\n")
|
||||||
|
f.write(f"Data Processing Agreement (DPA): {data['dpa']}\n")
|
||||||
|
f.write(f"Vendor Security Policies: {data['vendor_security_policies']}\n")
|
||||||
|
|
||||||
|
f.write("\n## 11. Risk Assessment\n")
|
||||||
|
f.write(f"Identify any potential security or privacy risks associated with the use of the software/vendor: {data['potential_risks']}\n")
|
||||||
|
f.write(f"Describe the risk mitigation measures that will be implemented: {data['risk_mitigation']}\n")
|
||||||
|
|
||||||
|
f.write("\n## 12. Approval\n")
|
||||||
|
f.write("Security Analyst Review:\n")
|
||||||
|
f.write("Date of Review: \n")
|
||||||
|
f.write("Security Analyst Name: \n")
|
||||||
|
f.write("Security Assessment Findings: \n")
|
||||||
|
f.write("Approval Status (Approved, Conditionally Approved, Rejected): \n")
|
||||||
|
f.write("Management Approval (if required):\n")
|
||||||
|
f.write("Date of Approval: \n")
|
||||||
|
f.write("Approving Manager Name: \n")
|
||||||
|
|
||||||
|
f.write("\n## 13. Supporting Documentation\n")
|
||||||
|
f.write("Attach any relevant supporting documentation, such as:\n")
|
||||||
|
f.write("- Vendor security questionnaires\n")
|
||||||
|
f.write("- Penetration testing reports\n")
|
||||||
|
f.write("- Security audit reports\n")
|
||||||
|
f.write("- Privacy impact assessments\n")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Collects assessment data from user input and generates documents."""
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
# 1. Requestor Information
|
||||||
|
data['requestor_name'] = input("Enter your name: ")
|
||||||
|
data['department'] = input("Enter your department: ")
|
||||||
|
data['email'] = input("Enter your email address: ")
|
||||||
|
data['phone'] = input("Enter your phone number: ")
|
||||||
|
|
||||||
|
# 2. Request Type
|
||||||
|
print("\nRequest Type:")
|
||||||
|
print("1. New Software/Vendor")
|
||||||
|
print("2. Existing Software/Vendor (Renewal/Upgrade)")
|
||||||
|
print("3. Software/Vendor Decommissioning")
|
||||||
|
choice = input("Enter your choice (1-3): ")
|
||||||
|
request_types = {
|
||||||
|
"1": "New Software/Vendor",
|
||||||
|
"2": "Existing Software/Vendor (Renewal/Upgrade)",
|
||||||
|
"3": "Software/Vendor Decommissioning"
|
||||||
|
}
|
||||||
|
data['request_type'] = request_types.get(choice)
|
||||||
|
|
||||||
|
# 3. Deployment Model
|
||||||
|
print("\nDeployment Model:")
|
||||||
|
print("1. SaaS (Software as a Service)")
|
||||||
|
print("2. IaaS (Infrastructure as a Service)")
|
||||||
|
print("3. PaaS (Platform as a Service)")
|
||||||
|
print("4. On-Premises (Internally Hosted)")
|
||||||
|
print("5. Hybrid")
|
||||||
|
print("6. Cloud-Hosted")
|
||||||
|
print("7. Other (Please Specify)")
|
||||||
|
choice = input("Enter your choice (1-7): ")
|
||||||
|
if choice == "7":
|
||||||
|
data['deployment_model'] = input("Specify the deployment model: ")
|
||||||
|
else:
|
||||||
|
deployment_models = {
|
||||||
|
"1": "SaaS (Software as a Service)",
|
||||||
|
"2": "IaaS (Infrastructure as a Service)",
|
||||||
|
"3": "PaaS (Platform as a Service)",
|
||||||
|
"4": "On-Premises (Internally Hosted)",
|
||||||
|
"5": "Hybrid",
|
||||||
|
"6": "Cloud-Hosted"
|
||||||
|
}
|
||||||
|
data['deployment_model'] = deployment_models.get(choice)
|
||||||
|
|
||||||
|
# 4. Software/Vendor Information
|
||||||
|
data['vendor_name'] = input("Enter the Software/Vendor Name: ")
|
||||||
|
data['software_name'] = input("Enter the Software Name: ")
|
||||||
|
data['vendor_website'] = input("Enter the Software/Vendor Website: ")
|
||||||
|
data['vendor_contact'] = input("Enter the Software/Vendor Contact Information: ")
|
||||||
|
data['vendor_description'] = input("Enter a Brief Description of Software/Vendor and its Purpose: ")
|
||||||
|
|
||||||
|
# 5. Intended Use
|
||||||
|
data['intended_use'] = input("Describe how the software/vendor will be used within the company: ")
|
||||||
|
data['departments_teams'] = input("Specify the departments or teams that will be using the software/vendor: ")
|
||||||
|
data['critical_processes_data'] = input("Identify any critical business processes or data that will be impacted by the software/vendor: ")
|
||||||
|
|
||||||
|
# 6. Prerequisites
|
||||||
|
data['os_requirements'] = input("List any Operating System Requirements: ")
|
||||||
|
data['hardware_requirements'] = input("List any Hardware Requirements (e.g., RAM, storage space): ")
|
||||||
|
data['network_connectivity'] = input("List any Network Connectivity Requirements (e.g., bandwidth, VPN): ")
|
||||||
|
data['software_dependencies'] = input("List any Software Dependencies (e.g., libraries, frameworks): ")
|
||||||
|
data['user_access_requirements'] = input("List any User Access Requirements (e.g., specific roles, permissions): ")
|
||||||
|
data['other_prerequisites'] = input("List any Other Prerequisites: ")
|
||||||
|
|
||||||
|
# 7. Technical Information
|
||||||
|
data['hosting_environment'] = input("Provide the Hosting Environment (e.g., cloud provider, on-premise data center): ")
|
||||||
|
data['network_requirements'] = input("Provide the Network Requirements (e.g., ports, protocols, firewall rules): ")
|
||||||
|
data['data_storage_requirements'] = input("Provide the Data Storage Requirements (e.g., location, encryption, data retention policies): ")
|
||||||
|
data['integration_existing_systems'] = input("Describe the Integration with Existing Systems (if any): ")
|
||||||
|
data['api_information'] = input("Provide API Information (if applicable): ")
|
||||||
|
|
||||||
|
# 8. Security Requirements
|
||||||
|
data['data_protection'] = input("Data Security - How will data be protected in transit and at rest?: ")
|
||||||
|
data['data_privacy_compliance'] = input("Data Security - Does the software/vendor comply with relevant data privacy regulations (e.g., GDPR, CCPA)?: ")
|
||||||
|
data['data_encryption_methods'] = input("Data Security - What data encryption methods are used?: ")
|
||||||
|
data['access_control_management'] = input("Access Control - How will access to the software/vendor be granted and managed?: ")
|
||||||
|
data['multi_factor_authentication'] = input("Access Control - Does the software/vendor support multi-factor authentication?: ")
|
||||||
|
data['user_roles_permissions'] = input("Access Control - What user roles and permissions are available?: ")
|
||||||
|
data['vulnerability_disclosure'] = input("Vulnerability Management - Does the software/vendor have a vulnerability disclosure program?: ")
|
||||||
|
data['security_patch_management'] = input("Vulnerability Management - How are security patches and updates managed?: ")
|
||||||
|
data['security_assessments'] = input("Vulnerability Management - Are regular security assessments and penetration testing conducted?: ")
|
||||||
|
data['industry_security_standards'] = input("Compliance - Does the software/vendor comply with relevant industry security standards (e.g., ISO 27001, SOC 2)?: ")
|
||||||
|
data['third_party_certifications'] = input("Compliance - Are there any third-party security certifications or audits available?: ")
|
||||||
|
data['business_continuity_plans'] = input("Business Continuity and Disaster Recovery - What are the software/vendor's business continuity and disaster recovery plans?: ")
|
||||||
|
data['rto_rpo'] = input("Business Continuity and Disaster Recovery - What are the recovery time objectives (RTOs) and recovery point objectives (RPOs)?: ")
|
||||||
|
|
||||||
|
# 9. Privacy Requirements
|
||||||
|
data['personal_data_collected'] = input("Data Collection - What personal data will be collected by the software/vendor?: ")
|
||||||
|
data['data_collection_purpose'] = input("Data Collection - What is the purpose of collecting this data?: ")
|
||||||
|
data['data_sharing'] = input("Data Collection - Will data be shared with third parties? If so, with whom and for what purpose?: ")
|
||||||
|
data['data_subject_rights'] = input("Data Subject Rights - How does the software/vendor ensure data subject rights (e.g., access, rectification, erasure)?: ")
|
||||||
|
data['privacy_policy'] = input("Data Subject Rights - Does the software/vendor provide a privacy policy that is readily accessible to users?: ")
|
||||||
|
|
||||||
|
# 10. Legal and Contractual Requirements
|
||||||
|
data['license_agreement'] = input("Provide the Software License Agreement: ")
|
||||||
|
data['sla'] = input("Provide the Service Level Agreement (SLA): ")
|
||||||
|
data['dpa'] = input("Provide the Data Processing Agreement (DPA): ")
|
||||||
|
data['vendor_security_policies'] = input("Provide the Vendor Security Policies: ")
|
||||||
|
|
||||||
|
# 11. Risk Assessment
|
||||||
|
data['potential_risks'] = input("Identify any potential security or privacy risks associated with the use of the software/vendor: ")
|
||||||
|
data['risk_mitigation'] = input("Describe the risk mitigation measures that will be implemented: ")
|
||||||
|
|
||||||
|
# Generate a unique timestamp
|
||||||
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
|
||||||
|
# Sanitize vendor/software name for filename
|
||||||
|
vendor_name = data['vendor_name'].replace(" ", "_").replace("/", "-")
|
||||||
|
software_name = data['software_name'].replace(" ", "_").replace("/", "-")
|
||||||
|
|
||||||
|
# Create and save the Word document with unique filename
|
||||||
|
doc_filename = f"{vendor_name}_{software_name}_assessment_{timestamp}.docx"
|
||||||
|
doc = create_assessment_doc(data)
|
||||||
|
doc.save(doc_filename)
|
||||||
|
print(f"Word document saved as {doc_filename}")
|
||||||
|
|
||||||
|
# Convert to PDF with unique filename
|
||||||
|
pdf_filename = f"{vendor_name}_{software_name}_assessment_{timestamp}.pdf"
|
||||||
|
convert(doc_filename, pdf_filename)
|
||||||
|
print(f"PDF document saved as {pdf_filename}")
|
||||||
|
|
||||||
|
# Create Markdown file with unique filename
|
||||||
|
md_filename = f"{vendor_name}_{software_name}_assessment_{timestamp}.md"
|
||||||
|
create_markdown_file(data, md_filename) # Pass filename to function
|
||||||
|
print(f"Markdown file saved as {md_filename}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import os
|
||||||
|
import csv
|
||||||
|
import shutil
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
def find_duplicate_basenames(root_directory, valid_extensions):
|
||||||
|
# Dictionary to store basenames and their corresponding file paths
|
||||||
|
basenames_to_paths = defaultdict(list)
|
||||||
|
|
||||||
|
# Traverse the directory tree
|
||||||
|
for dirpath, dirnames, filenames in os.walk(root_directory):
|
||||||
|
for filename in filenames:
|
||||||
|
full_path = os.path.join(dirpath, filename)
|
||||||
|
basename, extension = os.path.splitext(filename)
|
||||||
|
if extension.lower() in valid_extensions:
|
||||||
|
basenames_to_paths[basename].append(full_path)
|
||||||
|
|
||||||
|
# Filter basenames with more than one file
|
||||||
|
duplicate_basenames = {basename: paths for basename, paths in basenames_to_paths.items() if len(paths) > 1}
|
||||||
|
|
||||||
|
return duplicate_basenames
|
||||||
|
|
||||||
|
def get_file_size(path):
|
||||||
|
try:
|
||||||
|
return os.path.getsize(path)
|
||||||
|
except OSError:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def display_parent_levels(path, levels=2):
|
||||||
|
# Display 'levels' parent directories above the file path
|
||||||
|
components = path.split(os.sep)
|
||||||
|
return os.sep.join(components[-levels:])
|
||||||
|
|
||||||
|
def prompt_user_for_selection(paths):
|
||||||
|
print("Choose which file to keep:")
|
||||||
|
for i, path in enumerate(paths, start=1):
|
||||||
|
size_mb = get_file_size(path) / (1024 * 1024)
|
||||||
|
print(f"{i}. {display_parent_levels(path)} ({size_mb:.2f} MB)")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
choice = int(input("Enter the number of the file to keep (or 0 to skip): "))
|
||||||
|
if 0 <= choice <= len(paths):
|
||||||
|
return choice
|
||||||
|
else:
|
||||||
|
print("Invalid choice. Please enter a valid number.")
|
||||||
|
except ValueError:
|
||||||
|
print("Invalid input. Please enter a number.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
server_directory = r"\\truenas\isolation\videos" # Replace with your server directory
|
||||||
|
video_extensions = {".mp4", ".mkv", ".avi", ".mov"} # Add more video extensions if needed
|
||||||
|
duplicate_files = find_duplicate_basenames(server_directory, video_extensions)
|
||||||
|
|
||||||
|
if duplicate_files:
|
||||||
|
for basename, paths in duplicate_files.items():
|
||||||
|
print(f"\nBasename: {basename}")
|
||||||
|
choice = prompt_user_for_selection(paths)
|
||||||
|
if choice == 0:
|
||||||
|
print("Skipping this duplicate set.")
|
||||||
|
else:
|
||||||
|
# Keep the selected file and delete the others
|
||||||
|
keep_path = paths[choice - 1]
|
||||||
|
for path in paths:
|
||||||
|
if path != keep_path:
|
||||||
|
print(f"Deleting: {display_parent_levels(path)}")
|
||||||
|
os.remove(path)
|
||||||
|
print("\nDuplicate files processed.")
|
||||||
|
else:
|
||||||
|
print("No duplicate video files found.")
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import os
|
||||||
|
import csv
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
def find_duplicate_basenames(root_directory, valid_extensions):
|
||||||
|
# Dictionary to store basenames and their corresponding file paths
|
||||||
|
basenames_to_paths = defaultdict(list)
|
||||||
|
|
||||||
|
# Traverse the directory tree
|
||||||
|
for dirpath, dirnames, filenames in os.walk(root_directory):
|
||||||
|
for filename in filenames:
|
||||||
|
full_path = os.path.join(dirpath, filename)
|
||||||
|
basename, extension = os.path.splitext(filename)
|
||||||
|
if extension.lower() in valid_extensions:
|
||||||
|
basenames_to_paths[basename].append(full_path)
|
||||||
|
|
||||||
|
# Filter basenames with more than one file
|
||||||
|
duplicate_basenames = {basename: paths for basename, paths in basenames_to_paths.items() if len(paths) > 1}
|
||||||
|
|
||||||
|
return duplicate_basenames
|
||||||
|
|
||||||
|
def export_to_csv(duplicate_files, output_csv):
|
||||||
|
with open(output_csv, "w", newline="") as csvfile:
|
||||||
|
fieldnames = ["Basename", "File Paths"]
|
||||||
|
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
||||||
|
writer.writeheader()
|
||||||
|
|
||||||
|
for basename, paths in duplicate_files.items():
|
||||||
|
writer.writerow({"Basename": basename, "File Paths": ", ".join(paths)})
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
server_directory = r"\\truenas\isolation\videos" # Replace with your server directory
|
||||||
|
video_extensions = {".mp4", ".mkv", ".avi", ".mov"} # Add more video extensions if needed
|
||||||
|
duplicate_files = find_duplicate_basenames(server_directory, video_extensions)
|
||||||
|
|
||||||
|
if duplicate_files:
|
||||||
|
output_csv_file = "duplicate_video_files.csv"
|
||||||
|
export_to_csv(duplicate_files, output_csv_file)
|
||||||
|
print(f"Duplicate video files exported to {output_csv_file}")
|
||||||
|
else:
|
||||||
|
print("No duplicate video files found.")
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import os
|
||||||
|
import csv
|
||||||
|
import shutil
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
def find_duplicate_basenames(root_directory, valid_extensions):
|
||||||
|
# Dictionary to store basenames and their corresponding file paths
|
||||||
|
basenames_to_paths = defaultdict(list)
|
||||||
|
|
||||||
|
# Traverse the directory tree
|
||||||
|
for dirpath, dirnames, filenames in os.walk(root_directory):
|
||||||
|
for filename in filenames:
|
||||||
|
full_path = os.path.join(dirpath, filename)
|
||||||
|
basename, extension = os.path.splitext(filename)
|
||||||
|
if extension.lower() in valid_extensions:
|
||||||
|
basenames_to_paths[basename].append(full_path)
|
||||||
|
|
||||||
|
# Filter basenames with more than one file
|
||||||
|
duplicate_basenames = {basename: paths for basename, paths in basenames_to_paths.items() if len(paths) > 1}
|
||||||
|
|
||||||
|
return duplicate_basenames
|
||||||
|
|
||||||
|
def get_file_size(path):
|
||||||
|
try:
|
||||||
|
return os.path.getsize(path)
|
||||||
|
except OSError:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def display_parent_levels(path, levels=2):
|
||||||
|
# Display 'levels' parent directories above the file path
|
||||||
|
components = path.split(os.sep)
|
||||||
|
return os.sep.join(components[-levels:])
|
||||||
|
|
||||||
|
def prompt_user_for_selection(paths):
|
||||||
|
print("Choose which file to keep:")
|
||||||
|
for i, path in enumerate(paths, start=1):
|
||||||
|
size_mb = get_file_size(path) / (1024 * 1024)
|
||||||
|
print(f"{i}. {display_parent_levels(path)} ({size_mb:.2f} MB)")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
choice = int(input("Enter the number of the file to keep (or 0 to skip): "))
|
||||||
|
if 0 <= choice <= len(paths):
|
||||||
|
return choice
|
||||||
|
else:
|
||||||
|
print("Invalid choice. Please enter a valid number.")
|
||||||
|
except ValueError:
|
||||||
|
print("Invalid input. Please enter a number.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
server_directory = r"\\truenas\isolation\videos" # Replace with your server directory
|
||||||
|
video_extensions = {".mp4", ".mkv", ".avi", ".mov"} # Add more video extensions if needed
|
||||||
|
duplicate_files = find_duplicate_basenames(server_directory, video_extensions)
|
||||||
|
|
||||||
|
if duplicate_files:
|
||||||
|
for basename, paths in duplicate_files.items():
|
||||||
|
print(f"\nBasename: {basename}")
|
||||||
|
choice = prompt_user_for_selection(paths)
|
||||||
|
if choice == 0:
|
||||||
|
print("Skipping this duplicate set.")
|
||||||
|
else:
|
||||||
|
# Keep the selected file and delete the others
|
||||||
|
keep_path = paths[choice - 1]
|
||||||
|
for path in paths:
|
||||||
|
if path != keep_path:
|
||||||
|
print(f"Deleting: {display_parent_levels(path)}")
|
||||||
|
os.remove(path)
|
||||||
|
print("\nDuplicate files processed.")
|
||||||
|
else:
|
||||||
|
print("No duplicate video files found.")
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import os
|
||||||
|
import csv
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
def find_duplicate_basenames(root_directory, valid_extensions):
|
||||||
|
# Dictionary to store basenames and their corresponding file paths
|
||||||
|
basenames_to_paths = defaultdict(list)
|
||||||
|
|
||||||
|
# Traverse the directory tree
|
||||||
|
for dirpath, dirnames, filenames in os.walk(root_directory):
|
||||||
|
for filename in filenames:
|
||||||
|
full_path = os.path.join(dirpath, filename)
|
||||||
|
basename, extension = os.path.splitext(filename)
|
||||||
|
if extension.lower() in valid_extensions:
|
||||||
|
basenames_to_paths[basename].append(full_path)
|
||||||
|
|
||||||
|
# Filter basenames with more than one file
|
||||||
|
duplicate_basenames = {basename: paths for basename, paths in basenames_to_paths.items() if len(paths) > 1}
|
||||||
|
|
||||||
|
return duplicate_basenames
|
||||||
|
|
||||||
|
def export_to_csv(duplicate_files, output_csv):
|
||||||
|
with open(output_csv, "w", newline="") as csvfile:
|
||||||
|
fieldnames = ["Basename", "File Paths"]
|
||||||
|
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
||||||
|
writer.writeheader()
|
||||||
|
|
||||||
|
for basename, paths in duplicate_files.items():
|
||||||
|
writer.writerow({"Basename": basename, "File Paths": ", ".join(paths)})
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
server_directory = r"\\truenas\isolation\videos" # Replace with your server directory
|
||||||
|
video_extensions = {".mp4", ".mkv", ".avi", ".mov"} # Add more video extensions if needed
|
||||||
|
duplicate_files = find_duplicate_basenames(server_directory, video_extensions)
|
||||||
|
|
||||||
|
if duplicate_files:
|
||||||
|
output_csv_file = "duplicate_video_files.csv"
|
||||||
|
export_to_csv(duplicate_files, output_csv_file)
|
||||||
|
print(f"Duplicate video files exported to {output_csv_file}")
|
||||||
|
else:
|
||||||
|
print("No duplicate video files found.")
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
from pymediainfo import MediaInfo # You might need to install this: pip install pymediainfo
|
||||||
|
|
||||||
|
# Define the network locations - KEEP THESE UNCHANGED as per user's original request
|
||||||
|
LOCATION1 = r"\\truenas\isolation\videos\redmilf_refresh"
|
||||||
|
LOCATION2 = r"\\truenas\isolation\videos\redmilf"
|
||||||
|
FINAL_LOCATION = r"\\truenas\isolation\videos\redmilf_final" # This is the final destination for ALL copied files
|
||||||
|
|
||||||
|
# Ensure the final destination directory exists
|
||||||
|
os.makedirs(FINAL_LOCATION, exist_ok=True)
|
||||||
|
|
||||||
|
def bytes_to_mb(bytes_value):
|
||||||
|
"""Converts bytes to megabytes, rounded to two decimal places."""
|
||||||
|
return round(bytes_value / (1024 * 1024), 2)
|
||||||
|
|
||||||
|
def extract_did_number(filename):
|
||||||
|
"""
|
||||||
|
Extracts the DIDXXX or DIDXXXX number from a filename.
|
||||||
|
Returns the number as a string if found, otherwise None.
|
||||||
|
"""
|
||||||
|
# Regex to match DID followed by 3 or 4 digits
|
||||||
|
match = re.search(r"DID(\d{3,4})", filename, re.IGNORECASE)
|
||||||
|
if match:
|
||||||
|
return match.group(1) # Return the matched number string (e.g., "001" or "1234")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_file_info(filepath):
|
||||||
|
"""
|
||||||
|
Gets file size and attempts to get codec/bitrate using pymediainfo.
|
||||||
|
Returns a dictionary with 'size_bytes', 'size_mb', 'codec', 'bitrate'.
|
||||||
|
If mediainfo fails, 'codec' and 'bitrate' will be None.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
size_bytes = os.path.getsize(filepath)
|
||||||
|
size_mb = bytes_to_mb(size_bytes)
|
||||||
|
media_info = MediaInfo.parse(filepath)
|
||||||
|
codec = None
|
||||||
|
bitrate = 0 # Use 0 as a default for comparison
|
||||||
|
|
||||||
|
for track in media_info.tracks:
|
||||||
|
if track.track_type == 'Video':
|
||||||
|
if track.format:
|
||||||
|
codec = track.format
|
||||||
|
if track.bit_rate:
|
||||||
|
bitrate = track.bit_rate
|
||||||
|
break # Assuming one video track is sufficient
|
||||||
|
|
||||||
|
return {"size_bytes": size_bytes, "size_mb": size_mb, "codec": codec, "bitrate": bitrate}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Could not get media info for {filepath}: {e}")
|
||||||
|
# Fallback if mediainfo fails
|
||||||
|
size_bytes = os.path.getsize(filepath)
|
||||||
|
size_mb = bytes_to_mb(size_bytes)
|
||||||
|
return {"size_bytes": size_bytes, "size_mb": size_mb, "codec": None, "bitrate": 0}
|
||||||
|
|
||||||
|
def main():
|
||||||
|
files_in_loc1 = {}
|
||||||
|
files_in_loc2 = {}
|
||||||
|
|
||||||
|
print("Welcome to the DID Video Comparison and Management Script!")
|
||||||
|
print("\nDo you want to enable 'Auto-Run' mode?")
|
||||||
|
print("In 'Auto-Run' mode, the script will automatically:")
|
||||||
|
print(" 1. Keep the file with the larger size.")
|
||||||
|
print(f" 2. Use the filename from '{os.path.basename(LOCATION2)}' for the final copy.")
|
||||||
|
print("If you decline, you will be prompted for each file manually.")
|
||||||
|
|
||||||
|
auto_run_choice = input("Enter 'yes' to enable Auto-Run, or 'no' to run manually (yes/no): ").strip().lower()
|
||||||
|
auto_run = (auto_run_choice == 'yes')
|
||||||
|
|
||||||
|
if auto_run:
|
||||||
|
print("\nAuto-Run mode enabled. Processing files automatically.")
|
||||||
|
else:
|
||||||
|
print("\nManual mode selected. You will be prompted for each file.")
|
||||||
|
|
||||||
|
# Objective 1: Identify files with the same naming convention
|
||||||
|
# Scan Location 1
|
||||||
|
for root, _, files in os.walk(LOCATION1):
|
||||||
|
for file in files:
|
||||||
|
did_num = extract_did_number(file) # Changed function call
|
||||||
|
if did_num:
|
||||||
|
if did_num not in files_in_loc1:
|
||||||
|
files_in_loc1[did_num] = []
|
||||||
|
files_in_loc1[did_num].append(os.path.join(root, file))
|
||||||
|
|
||||||
|
# Scan Location 2
|
||||||
|
for root, _, files in os.walk(LOCATION2):
|
||||||
|
for file in files:
|
||||||
|
did_num = extract_did_number(file) # Changed function call
|
||||||
|
if did_num:
|
||||||
|
if did_num not in files_in_loc2:
|
||||||
|
files_in_loc2[did_num] = []
|
||||||
|
files_in_loc2[did_num].append(os.path.join(root, file))
|
||||||
|
|
||||||
|
# Process matched files
|
||||||
|
matched_did_numbers = set(files_in_loc1.keys()).intersection(set(files_in_loc2.keys()))
|
||||||
|
|
||||||
|
if not matched_did_numbers:
|
||||||
|
print(f"No matching DID files found in both '{LOCATION1}' and '{LOCATION2}'.")
|
||||||
|
return
|
||||||
|
|
||||||
|
for did_num in sorted(list(matched_did_numbers)):
|
||||||
|
# Assuming only one file per DIDXXX/XXXX number per location for simplicity.
|
||||||
|
path1 = files_in_loc1[did_num][0]
|
||||||
|
path2 = files_in_loc2[did_num][0]
|
||||||
|
|
||||||
|
file1_info = get_file_info(path1)
|
||||||
|
file2_info = get_file_info(path2)
|
||||||
|
|
||||||
|
print(f"\n--- Processing DID{did_num} ---")
|
||||||
|
print(f" File 1 (from '{os.path.basename(os.path.dirname(path1))}'): {os.path.basename(path1)} (Size: {file1_info['size_mb']} MB, Codec: {file1_info['codec']}, Bitrate: {file1_info['bitrate']} bps)")
|
||||||
|
print(f" File 2 (from '{os.path.basename(os.path.dirname(path2))}'): {os.path.basename(path2)} (Size: {file2_info['size_mb']} MB, Codec: {file2_info['codec']}, Bitrate: {file2_info['bitrate']} bps)")
|
||||||
|
|
||||||
|
|
||||||
|
file_to_keep_path = None
|
||||||
|
final_filename = None
|
||||||
|
|
||||||
|
if auto_run:
|
||||||
|
# Auto-run logic: keep larger size, use filename from Location 2
|
||||||
|
if file1_info['size_bytes'] >= file2_info['size_bytes']:
|
||||||
|
file_to_keep_path = path1
|
||||||
|
else:
|
||||||
|
file_to_keep_path = path2
|
||||||
|
final_filename = os.path.basename(path2) # Always use filename from Location 2
|
||||||
|
print(f" [Auto] Keeping: {os.path.basename(file_to_keep_path)}")
|
||||||
|
print(f" [Auto] Using filename: {final_filename}")
|
||||||
|
|
||||||
|
else: # Manual mode
|
||||||
|
# Original objective 2: Compare size, quality, and codec and list the better file
|
||||||
|
recommended_better_path = None
|
||||||
|
# Prioritize bitrate for recommendation
|
||||||
|
if file1_info['bitrate'] > file2_info['bitrate']:
|
||||||
|
recommended_better_path = path1
|
||||||
|
elif file2_info['bitrate'] > file1_info['bitrate']:
|
||||||
|
recommended_better_path = path2
|
||||||
|
else:
|
||||||
|
# If bitrates are similar or unavailable, use size as a proxy for recommendation
|
||||||
|
if file1_info['size_bytes'] >= file2_info['size_bytes']:
|
||||||
|
recommended_better_path = path1
|
||||||
|
else:
|
||||||
|
recommended_better_path = path2
|
||||||
|
print(f" Recommended to keep: {os.path.basename(recommended_better_path)}")
|
||||||
|
|
||||||
|
|
||||||
|
# Original objective 3: Prompt the user which one they would like to keep
|
||||||
|
while True:
|
||||||
|
choice = input("Which file would you like to keep? (1/2): ").strip()
|
||||||
|
if choice == '1':
|
||||||
|
file_to_keep_path = path1
|
||||||
|
break
|
||||||
|
elif choice == '2':
|
||||||
|
file_to_keep_path = path2
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("Invalid choice. Please enter '1' or '2'.")
|
||||||
|
|
||||||
|
# Original objective 4: Prompt the user for each file, which one's file name will be used for the final copy
|
||||||
|
while True:
|
||||||
|
name_choice = input(f"Which file's name would you like to use for the final copy of DID{did_num}? (1/2): ").strip() # Changed prompt
|
||||||
|
if name_choice == '1':
|
||||||
|
final_filename = os.path.basename(path1)
|
||||||
|
break
|
||||||
|
elif name_choice == '2':
|
||||||
|
final_filename = os.path.basename(path2)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("Invalid choice. Please enter '1' or '2'.")
|
||||||
|
|
||||||
|
# Objective 5: Copy the selected file to the new location with the chosen name
|
||||||
|
destination_path = os.path.join(FINAL_LOCATION, final_filename)
|
||||||
|
try:
|
||||||
|
print(f"Copying '{os.path.basename(file_to_keep_path)}' to '{destination_path}'...")
|
||||||
|
shutil.copy2(file_to_keep_path, destination_path) # copy2 preserves metadata
|
||||||
|
print("Copy successful!")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error copying file {file_to_keep_path} to {destination_path}: {e}")
|
||||||
|
|
||||||
|
print("\nComparison and copying process complete.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
from pymediainfo import MediaInfo # You might need to install this: pip install python-mediainfo
|
||||||
|
|
||||||
|
# Define the network locations
|
||||||
|
LOCATION1 = r"\\truenas\isolation\videos\redmilf_refresh"
|
||||||
|
LOCATION2 = r"\\truenas\isolation\videos\redmilf"
|
||||||
|
FINAL_LOCATION = r"\\truenas\isolation\videos\redmilf_final"
|
||||||
|
|
||||||
|
# Ensure the final destination directory exists
|
||||||
|
os.makedirs(FINAL_LOCATION, exist_ok=True)
|
||||||
|
|
||||||
|
def extract_milf_number(filename):
|
||||||
|
"""
|
||||||
|
Extracts the MILFXXX number from a filename.
|
||||||
|
Returns the number as a string if found, otherwise None.
|
||||||
|
"""
|
||||||
|
match = re.search(r"MILF(\d{3})", filename, re.IGNORECASE)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_file_info(filepath):
|
||||||
|
"""
|
||||||
|
Gets file size and attempts to get codec/bitrate using pymediainfo.
|
||||||
|
Returns a dictionary with 'size', 'codec', 'bitrate'.
|
||||||
|
If mediainfo fails, 'codec' and 'bitrate' will be None.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
size = os.path.getsize(filepath)
|
||||||
|
media_info = MediaInfo.parse(filepath)
|
||||||
|
codec = None
|
||||||
|
bitrate = 0 # Use 0 as a default for comparison
|
||||||
|
|
||||||
|
for track in media_info.tracks:
|
||||||
|
if track.track_type == 'Video':
|
||||||
|
if track.format:
|
||||||
|
codec = track.format
|
||||||
|
if track.bit_rate:
|
||||||
|
bitrate = track.bit_rate
|
||||||
|
break # Assuming one video track is sufficient
|
||||||
|
|
||||||
|
return {"size": size, "codec": codec, "bitrate": bitrate}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Could not get media info for {filepath}: {e}")
|
||||||
|
return {"size": os.path.getsize(filepath), "codec": None, "bitrate": 0}
|
||||||
|
|
||||||
|
def determine_better_file(file1_info, file2_info, path1, path2):
|
||||||
|
"""
|
||||||
|
Compares two file infos to determine which one is 'better'.
|
||||||
|
Prioritizes bitrate (quality), then size if bitrate is similar or unavailable.
|
||||||
|
"""
|
||||||
|
print(f"\nComparing:")
|
||||||
|
print(f" File 1: {os.path.basename(path1)} (Size: {file1_info['size']} bytes, Codec: {file1_info['codec']}, Bitrate: {file1_info['bitrate']} bps)")
|
||||||
|
print(f" File 2: {os.path.basename(path2)} (Size: {file2_info['size']} bytes, Codec: {file2_info['codec']}, Bitrate: {file2_info['bitrate']} bps)")
|
||||||
|
|
||||||
|
# Prioritize bitrate for quality
|
||||||
|
if file1_info['bitrate'] > file2_info['bitrate']:
|
||||||
|
return path1
|
||||||
|
elif file2_info['bitrate'] > file1_info['bitrate']:
|
||||||
|
return path2
|
||||||
|
else:
|
||||||
|
# If bitrates are similar or unavailable, use size as a proxy
|
||||||
|
if file1_info['size'] >= file2_info['size']: # Greater or equal size usually means better quality
|
||||||
|
return path1
|
||||||
|
else:
|
||||||
|
return path2
|
||||||
|
|
||||||
|
def main():
|
||||||
|
files_in_loc1 = {}
|
||||||
|
files_in_loc2 = {}
|
||||||
|
|
||||||
|
# Objective 1: Identify files with the same naming convention
|
||||||
|
# Scan Location 1
|
||||||
|
for root, _, files in os.walk(LOCATION1):
|
||||||
|
for file in files:
|
||||||
|
milf_num = extract_milf_number(file)
|
||||||
|
if milf_num:
|
||||||
|
if milf_num not in files_in_loc1:
|
||||||
|
files_in_loc1[milf_num] = []
|
||||||
|
files_in_loc1[milf_num].append(os.path.join(root, file))
|
||||||
|
|
||||||
|
# Scan Location 2
|
||||||
|
for root, _, files in os.walk(LOCATION2):
|
||||||
|
for file in files:
|
||||||
|
milf_num = extract_milf_number(file)
|
||||||
|
if milf_num:
|
||||||
|
if milf_num not in files_in_loc2:
|
||||||
|
files_in_loc2[milf_num] = []
|
||||||
|
files_in_loc2[milf_num].append(os.path.join(root, file))
|
||||||
|
|
||||||
|
# Process matched files
|
||||||
|
matched_milf_numbers = set(files_in_loc1.keys()).intersection(set(files_in_loc2.keys()))
|
||||||
|
|
||||||
|
if not matched_milf_numbers:
|
||||||
|
print("No matching MILF files found in both locations.")
|
||||||
|
return
|
||||||
|
|
||||||
|
for milf_num in sorted(list(matched_milf_numbers)):
|
||||||
|
# Assuming only one file per MILFXXX number per location for simplicity.
|
||||||
|
# If multiple files with the same MILFXXX number exist in one location,
|
||||||
|
# this script will compare the first one it finds.
|
||||||
|
# For a more robust solution, you might need to handle this by comparing
|
||||||
|
# all permutations or selecting the "best" within each location first.
|
||||||
|
path1 = files_in_loc1[milf_num][0]
|
||||||
|
path2 = files_in_loc2[milf_num][0]
|
||||||
|
|
||||||
|
# Objective 2: Compare size, quality, and codec and list the better file
|
||||||
|
file1_info = get_file_info(path1)
|
||||||
|
file2_info = get_file_info(path2)
|
||||||
|
|
||||||
|
recommended_better_path = determine_better_file(file1_info, file2_info, path1, path2)
|
||||||
|
|
||||||
|
# Objective 3: Prompt the user which one they would like to keep
|
||||||
|
print(f"\nFor MILF{milf_num}:")
|
||||||
|
print(f" Option 1 (from '{os.path.basename(os.path.dirname(path1))}'): {os.path.basename(path1)}")
|
||||||
|
print(f" Option 2 (from '{os.path.basename(os.path.dirname(path2))}'): {os.path.basename(path2)}")
|
||||||
|
print(f" Recommended: {os.path.basename(recommended_better_path)}")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
choice = input("Which file would you like to keep? (1/2): ").strip()
|
||||||
|
if choice == '1':
|
||||||
|
file_to_keep_path = path1
|
||||||
|
break
|
||||||
|
elif choice == '2':
|
||||||
|
file_to_keep_path = path2
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("Invalid choice. Please enter '1' or '2'.")
|
||||||
|
|
||||||
|
# Objective 4: Prompt the user for each file, which one's file name will be used for the final copy
|
||||||
|
while True:
|
||||||
|
name_choice = input(f"Which file's name would you like to use for the final copy of MILF{milf_num}? (1/2): ").strip()
|
||||||
|
if name_choice == '1':
|
||||||
|
final_filename = os.path.basename(path1)
|
||||||
|
break
|
||||||
|
elif name_choice == '2':
|
||||||
|
final_filename = os.path.basename(path2)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("Invalid choice. Please enter '1' or '2'.")
|
||||||
|
|
||||||
|
# Objective 5: Copy the selected file to the new location with the chosen name
|
||||||
|
destination_path = os.path.join(FINAL_LOCATION, final_filename)
|
||||||
|
try:
|
||||||
|
print(f"Copying '{os.path.basename(file_to_keep_path)}' to '{destination_path}'...")
|
||||||
|
shutil.copy2(file_to_keep_path, destination_path) # copy2 preserves metadata
|
||||||
|
print("Copy successful!")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error copying file {file_to_keep_path} to {destination_path}: {e}")
|
||||||
|
|
||||||
|
print("\nComparison and copying process complete.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
from pymediainfo import MediaInfo # You might need to install this: pip install pymediainfo
|
||||||
|
|
||||||
|
# Define the network locations
|
||||||
|
LOCATION1 = r"\\truenas\isolation\videos\redmilf_refresh"
|
||||||
|
LOCATION2 = r"\\truenas\isolation\videos\redmilf"
|
||||||
|
FINAL_LOCATION = r"\\truenas\isolation\videos\redmilf_final"
|
||||||
|
|
||||||
|
# Ensure the final destination directory exists
|
||||||
|
os.makedirs(FINAL_LOCATION, exist_ok=True)
|
||||||
|
|
||||||
|
def bytes_to_mb(bytes_value):
|
||||||
|
"""Converts bytes to megabytes, rounded to two decimal places."""
|
||||||
|
return round(bytes_value / (1024 * 1024), 2)
|
||||||
|
|
||||||
|
def extract_milf_number(filename):
|
||||||
|
"""
|
||||||
|
Extracts the MILFXXX or MILFXXXX number from a filename.
|
||||||
|
Returns the number as a string if found, otherwise None.
|
||||||
|
"""
|
||||||
|
# Updated regex to match MILF followed by 3 or 4 digits
|
||||||
|
match = re.search(r"MILF(\d{3,4})", filename, re.IGNORECASE)
|
||||||
|
if match:
|
||||||
|
return match.group(1) # Return the matched number string (e.g., "001" or "1234")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_file_info(filepath):
|
||||||
|
"""
|
||||||
|
Gets file size and attempts to get codec/bitrate using pymediainfo.
|
||||||
|
Returns a dictionary with 'size_bytes', 'size_mb', 'codec', 'bitrate'.
|
||||||
|
If mediainfo fails, 'codec' and 'bitrate' will be None.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
size_bytes = os.path.getsize(filepath)
|
||||||
|
size_mb = bytes_to_mb(size_bytes)
|
||||||
|
media_info = MediaInfo.parse(filepath)
|
||||||
|
codec = None
|
||||||
|
bitrate = 0 # Use 0 as a default for comparison
|
||||||
|
|
||||||
|
for track in media_info.tracks:
|
||||||
|
if track.track_type == 'Video':
|
||||||
|
if track.format:
|
||||||
|
codec = track.format
|
||||||
|
if track.bit_rate:
|
||||||
|
bitrate = track.bit_rate
|
||||||
|
break # Assuming one video track is sufficient
|
||||||
|
|
||||||
|
return {"size_bytes": size_bytes, "size_mb": size_mb, "codec": codec, "bitrate": bitrate}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Could not get media info for {filepath}: {e}")
|
||||||
|
# Fallback if mediainfo fails
|
||||||
|
size_bytes = os.path.getsize(filepath)
|
||||||
|
size_mb = bytes_to_mb(size_bytes)
|
||||||
|
return {"size_bytes": size_bytes, "size_mb": size_mb, "codec": None, "bitrate": 0}
|
||||||
|
|
||||||
|
def main():
|
||||||
|
files_in_loc1 = {}
|
||||||
|
files_in_loc2 = {}
|
||||||
|
|
||||||
|
print("Welcome to the MILF Video Comparison and Management Script!")
|
||||||
|
print("\nDo you want to enable 'Auto-Run' mode?")
|
||||||
|
print("In 'Auto-Run' mode, the script will automatically:")
|
||||||
|
print(" 1. Keep the file with the larger size.")
|
||||||
|
print(f" 2. Use the filename from '{os.path.basename(LOCATION2)}' for the final copy.")
|
||||||
|
print("If you decline, you will be prompted for each file manually.")
|
||||||
|
|
||||||
|
auto_run_choice = input("Enter 'yes' to enable Auto-Run, or 'no' to run manually (yes/no): ").strip().lower()
|
||||||
|
auto_run = (auto_run_choice == 'yes')
|
||||||
|
|
||||||
|
if auto_run:
|
||||||
|
print("\nAuto-Run mode enabled. Processing files automatically.")
|
||||||
|
else:
|
||||||
|
print("\nManual mode selected. You will be prompted for each file.")
|
||||||
|
|
||||||
|
# Objective 1: Identify files with the same naming convention
|
||||||
|
# Scan Location 1
|
||||||
|
for root, _, files in os.walk(LOCATION1):
|
||||||
|
for file in files:
|
||||||
|
milf_num = extract_milf_number(file)
|
||||||
|
if milf_num:
|
||||||
|
if milf_num not in files_in_loc1:
|
||||||
|
files_in_loc1[milf_num] = []
|
||||||
|
files_in_loc1[milf_num].append(os.path.join(root, file))
|
||||||
|
|
||||||
|
# Scan Location 2
|
||||||
|
for root, _, files in os.walk(LOCATION2):
|
||||||
|
for file in files:
|
||||||
|
milf_num = extract_milf_number(file)
|
||||||
|
if milf_num:
|
||||||
|
if milf_num not in files_in_loc2:
|
||||||
|
files_in_loc2[milf_num] = []
|
||||||
|
files_in_loc2[milf_num].append(os.path.join(root, file))
|
||||||
|
|
||||||
|
# Process matched files
|
||||||
|
matched_milf_numbers = set(files_in_loc1.keys()).intersection(set(files_in_loc2.keys()))
|
||||||
|
|
||||||
|
if not matched_milf_numbers:
|
||||||
|
print("No matching MILF files found in both locations.")
|
||||||
|
return
|
||||||
|
|
||||||
|
for milf_num in sorted(list(matched_milf_numbers)):
|
||||||
|
# Assuming only one file per MILFXXX/XXXX number per location for simplicity.
|
||||||
|
path1 = files_in_loc1[milf_num][0]
|
||||||
|
path2 = files_in_loc2[milf_num][0]
|
||||||
|
|
||||||
|
file1_info = get_file_info(path1)
|
||||||
|
file2_info = get_file_info(path2)
|
||||||
|
|
||||||
|
print(f"\n--- Processing MILF{milf_num} ---")
|
||||||
|
print(f" File 1 (from '{os.path.basename(os.path.dirname(path1))}'): {os.path.basename(path1)} (Size: {file1_info['size_mb']} MB, Codec: {file1_info['codec']}, Bitrate: {file1_info['bitrate']} bps)")
|
||||||
|
print(f" File 2 (from '{os.path.basename(os.path.dirname(path2))}'): {os.path.basename(path2)} (Size: {file2_info['size_mb']} MB, Codec: {file2_info['codec']}, Bitrate: {file2_info['bitrate']} bps)")
|
||||||
|
|
||||||
|
|
||||||
|
file_to_keep_path = None
|
||||||
|
final_filename = None
|
||||||
|
|
||||||
|
if auto_run:
|
||||||
|
# Auto-run logic: keep larger size, use filename from Location 2
|
||||||
|
if file1_info['size_bytes'] >= file2_info['size_bytes']:
|
||||||
|
file_to_keep_path = path1
|
||||||
|
else:
|
||||||
|
file_to_keep_path = path2
|
||||||
|
final_filename = os.path.basename(path2) # Always use filename from Location 2
|
||||||
|
print(f" [Auto] Keeping: {os.path.basename(file_to_keep_path)}")
|
||||||
|
print(f" [Auto] Using filename: {final_filename}")
|
||||||
|
|
||||||
|
else: # Manual mode
|
||||||
|
# Original objective 2: Compare size, quality, and codec and list the better file
|
||||||
|
recommended_better_path = None
|
||||||
|
# Prioritize bitrate for recommendation
|
||||||
|
if file1_info['bitrate'] > file2_info['bitrate']:
|
||||||
|
recommended_better_path = path1
|
||||||
|
elif file2_info['bitrate'] > file1_info['bitrate']:
|
||||||
|
recommended_better_path = path2
|
||||||
|
else:
|
||||||
|
# If bitrates are similar or unavailable, use size as a proxy for recommendation
|
||||||
|
if file1_info['size_bytes'] >= file2_info['size_bytes']:
|
||||||
|
recommended_better_path = path1
|
||||||
|
else:
|
||||||
|
recommended_better_path = path2
|
||||||
|
print(f" Recommended to keep: {os.path.basename(recommended_better_path)}")
|
||||||
|
|
||||||
|
|
||||||
|
# Original objective 3: Prompt the user which one they would like to keep
|
||||||
|
while True:
|
||||||
|
choice = input("Which file would you like to keep? (1/2): ").strip()
|
||||||
|
if choice == '1':
|
||||||
|
file_to_keep_path = path1
|
||||||
|
break
|
||||||
|
elif choice == '2':
|
||||||
|
file_to_keep_path = path2
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("Invalid choice. Please enter '1' or '2'.")
|
||||||
|
|
||||||
|
# Original objective 4: Prompt the user for each file, which one's file name will be used for the final copy
|
||||||
|
while True:
|
||||||
|
name_choice = input(f"Which file's name would you like to use for the final copy of MILF{milf_num}? (1/2): ").strip()
|
||||||
|
if name_choice == '1':
|
||||||
|
final_filename = os.path.basename(path1)
|
||||||
|
break
|
||||||
|
elif name_choice == '2':
|
||||||
|
final_filename = os.path.basename(path2)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("Invalid choice. Please enter '1' or '2'.")
|
||||||
|
|
||||||
|
# Objective 5: Copy the selected file to the new location with the chosen name
|
||||||
|
destination_path = os.path.join(FINAL_LOCATION, final_filename)
|
||||||
|
try:
|
||||||
|
print(f"Copying '{os.path.basename(file_to_keep_path)}' to '{destination_path}'...")
|
||||||
|
shutil.copy2(file_to_keep_path, destination_path) # copy2 preserves metadata
|
||||||
|
print("Copy successful!")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error copying file {file_to_keep_path} to {destination_path}: {e}")
|
||||||
|
|
||||||
|
print("\nComparison and copying process complete.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import sqlite3
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Function to convert Linux path to Windows path and replace specific substring
|
||||||
|
def convert_path(path):
|
||||||
|
return path.replace("/", "\\").replace("\\data\\", "\\\\truenas\\isolation\\videos\\")
|
||||||
|
|
||||||
|
# Fileshare path
|
||||||
|
fileshare_path = r"\\truenas\isolation\stashapp\generated\transcodes"
|
||||||
|
|
||||||
|
# SQLite database path
|
||||||
|
db_path = r"\\servervm\main\appdata\stashapp\config\stash-go.sqlite"
|
||||||
|
|
||||||
|
# Create a folder for the attempt with current date and time
|
||||||
|
folder_name = "Attempt_" + datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
os.makedirs(folder_name)
|
||||||
|
|
||||||
|
# Connect to SQLite database
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Error handling for gathering basenames
|
||||||
|
try:
|
||||||
|
# List all file basenames (without extensions) from fileshare path
|
||||||
|
basenames = [os.path.splitext(name)[0] for name in os.listdir(fileshare_path)]
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Error: Fileshare path '{fileshare_path}' not found.")
|
||||||
|
exit()
|
||||||
|
except PermissionError:
|
||||||
|
print(f"Error: Permission denied accessing fileshare path '{fileshare_path}'.")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
# Check if there are no basenames
|
||||||
|
if not basenames:
|
||||||
|
print("Error: No basenames found in the fileshare path.")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
# Construct the IN clause for the query
|
||||||
|
placeholders = ",".join(['"{}"'.format(b) for b in basenames])
|
||||||
|
|
||||||
|
# Construct the query
|
||||||
|
query_template = """
|
||||||
|
SELECT f.*, ff.*, fo.path
|
||||||
|
FROM files AS f
|
||||||
|
INNER JOIN files_fingerprints AS ff ON f.id = ff.file_id
|
||||||
|
INNER JOIN folders AS fo ON f.parent_folder_id = fo.id
|
||||||
|
WHERE ff.fingerprint IN ({});
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Execute query
|
||||||
|
cursor.execute(query_template.format(placeholders))
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
|
# Check if there are any results
|
||||||
|
if len(rows) == 0:
|
||||||
|
print("No results found.")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
# Export result to CSV
|
||||||
|
csv_path = os.path.join(folder_name, "result.csv")
|
||||||
|
with open(csv_path, "w", newline="", encoding="utf-8") as csv_file:
|
||||||
|
csv_writer = csv.writer(csv_file)
|
||||||
|
csv_writer.writerow([i[0] for i in cursor.description]) # Write header
|
||||||
|
csv_writer.writerows(rows)
|
||||||
|
|
||||||
|
# Create new CSV with additional columns
|
||||||
|
new_rows = []
|
||||||
|
for row in rows:
|
||||||
|
converted_path = convert_path(row[-1])
|
||||||
|
full_file_path_video = os.path.join(converted_path, row[1]) # Construct full file path for videos
|
||||||
|
|
||||||
|
# Original filename from transcodes directory
|
||||||
|
original_filename = os.path.join(fileshare_path, str(row[10])) # Assuming the fingerprint ID is in the 11th column (index 10)
|
||||||
|
|
||||||
|
new_rows.append((full_file_path_video, original_filename + ".mp4")) # Append ".mp4" to the second column
|
||||||
|
|
||||||
|
# Write new CSV
|
||||||
|
new_csv_path = os.path.join(folder_name, "new_result.csv")
|
||||||
|
with open(new_csv_path, "w", newline="", encoding="utf-8") as csv_file:
|
||||||
|
csv_writer = csv.writer(csv_file)
|
||||||
|
csv_writer.writerow(["Full File Path (Videos)", "Original File Path (Transcodes)"])
|
||||||
|
csv_writer.writerows(new_rows)
|
||||||
|
|
||||||
|
# Confirm with the user
|
||||||
|
print("CSV files created successfully in folder:", folder_name)
|
||||||
|
print("Do you want to proceed to replace the files in column 1 with the files in column 2?")
|
||||||
|
user_input = input("Enter 'yes' to proceed, or any other key to cancel: ")
|
||||||
|
|
||||||
|
if user_input.lower() == 'yes':
|
||||||
|
print("Proceeding to replace the files...")
|
||||||
|
|
||||||
|
# Prepare to replace files
|
||||||
|
for original_file, new_file in new_rows:
|
||||||
|
original_basename = os.path.basename(original_file)
|
||||||
|
original_dirname = os.path.dirname(original_file)
|
||||||
|
new_basename = os.path.basename(new_file)
|
||||||
|
new_dirname = os.path.dirname(new_file)
|
||||||
|
|
||||||
|
# Remove existing file if exists
|
||||||
|
if os.path.exists(original_file):
|
||||||
|
os.remove(original_file)
|
||||||
|
|
||||||
|
# Move the new file to replace the original file
|
||||||
|
try:
|
||||||
|
os.rename(new_file, os.path.join(original_dirname, original_basename))
|
||||||
|
print(f"Replaced '{original_basename}' with '{new_basename}'.")
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Error: '{new_basename}' not found. Skipped replacement.")
|
||||||
|
|
||||||
|
print("Replacement completed.")
|
||||||
|
else:
|
||||||
|
print("Replacement canceled.")
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name deluge.datdamdavid.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://truenas:8112;
|
||||||
|
proxy_redirect off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443;
|
||||||
|
server_name deluge.datdamdavid.com;
|
||||||
|
ssl_certificate /etc/nginx/ssl/deluge.datdamdavid.com.crt;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/deluge.datdamdavid.com.key;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://truenas:8112;
|
||||||
|
proxy_redirect off;
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name git.datdamdavid.com; # Replace with your actual domain
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://192.168.1.254:30008; # Corrected IP address
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
# Additional headers if needed (e.g., for websockets)
|
||||||
|
# proxy_set_header Upgrade $upgrade;
|
||||||
|
# proxy_set_header Connection "upgrade";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name jellyseer.datdamdavid.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://truenas:30042;
|
||||||
|
proxy_redirect off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443;
|
||||||
|
server_name jellyseer.datdamdavid.com;
|
||||||
|
ssl_certificate /etc/nginx/ssl/jellyseer.datdamdavid.com.crt;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/jellyseer.datdamdavid.com.key;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://truenas:30042;
|
||||||
|
proxy_redirect off;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user