I happened to find this repo on GitHub and immediately had so many ideas on how to improve it. The original creator wrote
I bumped into FlyTech's Video and at the end of the video he challenged the viewers to make a program to generate the keys, so I tried, and this is the result.
This project consists of a Windows 95-styled GUI.
On the backend, an API handles all the requests. You can also call this API in your browser or reference it in your code.
- Install all the necessary dependencies:
npm i
. - Then compile the web server:
npm run build
. - Start the web server with
npm run start
.
To enter dev mode, run npm run dev
.
10-digit: curl
https://win95.thevalleyy.tk/api/generate/10
11-digit: curl
https://win95.thevalleyy.tk/api/generate/11
OEM key: curl
https://win95.thevalleyy.tk/api/generate/oem
To generate more than one key at a time, add amount
as a query parameter:
curl
https://win95.thevalleyy.tk/api/generate/10?amount=2
Validate:
curl X POST https://win95.thevalleyy.tk/api/validate
-H "Content-Type: application/json"
-d "{\"key\": \"KEY\"}"
or
Validate: curl
https://win95.thevalleyy.tk/api/validate?key=KEY
Endpoint | Description | Affected by ratelimit |
---|---|---|
/api/generate/10 |
Generates a 10-digit key | ✅ |
/api/generate/11 |
Generates an 11-digit key | ✅ |
/api/generate/oem |
Generates an OEM key | ✅ |
/api/validate |
Validates the above keys | ✅ |
Example parameter | Description | Endpoints | Default limits |
---|---|---|---|
?amount=3 |
Generates the specified number of keys. Must be a non-negative integer. | /api/generate/10 , /api/generate/11 , /api/generate/oem |
Max. number of keys: 5 keys at a time |
?key=123-456789 |
Validates the given key. Can be a 10-digit, 11-digit, or OEM key. | /api/validate |
- |
?whitelistKey=password |
Whitelists the request (no rate limit). ⚠ If the IP address is blacklisted, the request will be blocked with the highest priority! |
Can be used on any endpoint | No whitelisted strings set |
Generally speaking, if something goes wrong on the server side, the API will return a 4xx code. If the validation fails, this will result in a 200 OK
code, as everything went successfully, even though the key may be faulty.
Call | Response | Status | Explanation if needed |
---|---|---|---|
curl https://win95.thevalleyy.tk/api/generate/11?amount=2 |
{"keys":["6490-9991563","3080-0374734"],"amount":2} |
200 OK | - |
curl https://win95.thevalleyy.tk/api/generate/11?amount=69 |
{"error":"You can create a maximum of 5 11-digit keys at once","amount":69} |
400 Bad Request | Max. amount exceeded |
curl https://win95.thevalleyy.tk/api/generate/11 |
"blocked" |
403 Forbidden | The requesting IP address has been blacklisted |
curl https://win95.thevalleyy.tk/api/generate/11 |
"Too Many Requests. Timeout will be: 300 seconds" |
429 Too Many Requests | The requesting IP address has been rate-limited |
curl https://win95.thevalleyy.tk/api/validate?key=000-0000007 |
{"message":"Valid 10-Digit Key"}} |
200 OK | - |
curl https://win95.thevalleyy.tk/api/validate?key=000-0000000 |
{"message":{"check":"Invalid 10-digit key","details":"Second segment last digit control failed"}} |
200 OK | Bad key. details gives the reason. ⚠ Although the key is bad, the response code will be 200 OK |
curl https://win95.thevalleyy.tk/api/validate?key=hjfdsjk+ |
{"message":"Unknown Key"}} |
200 OK | The type of the given key is unknown |
curl https://win95.thevalleyy.tk/api/validate |
{"error":"Please specify a key via the \"key\" query parameter or POST body."} |
400 Bad Request | No key was given as a query parameter or as a POST json body |
curl https://win95.thevalleyy.tk/api/notARealEndpoint |
{"error":"/api/notARealEndpoint is not a valid request. Valid endpoints are listed below. Detailed instructions can be found under https://github.com/thevalleyy/windows95keygen#api-documentation.","methods":{"api":{"generate":{"10":"Generates a 10-digit Key","11":"Generates a 11-digit Key","oem":"Generates an OEM Key"},"validate":"Validates a Key"}}} |
404 Not Found | - |
The default configuration blocks requests from a specific IP for 300 seconds (5 mins), if this IP sends more than 50 requests in 120 seconds (2 mins).
All the endpoints are also accessible via the GUI. The rate limit also applies.
Path (json) | Description | Default | Data type |
---|---|---|---|
.config.port |
Port of the web server | 3000 |
Number |
.config.ip_blocklist |
Whether to enable or disable the IP blacklist | true |
Boolean |
.config.rate_limit_requests |
Whether to enable or disable the rate limit | true |
Boolean |
.config.skip_local_ips |
Whether to ignore requests from the same machine (!) as the web server or API | true |
Boolean |
.config.whitelist_keys |
Array of strings that are used to whitelist a request (= no rate limit) | [] |
Array > Strings |
.log.enabled |
Whether to log requests and rate limits | false |
Boolean |
.log.ips |
Whether to log IP addresses | false |
Boolean |
.log.requests |
Whether to log requests (actually, this just logs the URL of the request. but, you know, I am way too lazy to correct the name) | true |
Boolean |
.log.log_path |
The file path to store the log files in | "./logs/" |
String |
.log.log_extension |
The file extension of the log files | ".log" |
String |
.limits.keys.10 |
Max. number of 10-digit keys that can be requested at a time | 5 |
Number |
.limits.keys.11 |
Max. number of 11-digit keys that can be requested at a time | 5 |
Number |
.limits.keys.oem |
Max. number of OEM keys that can be requested at a time | 5 |
Number |
.limits.requests.requests |
Max. number of requests in a specified time (one line below) | 50 |
Number |
.limits.requests.specified_time |
Time in seconds to allow a specified (one line above) number of requests in | 120 |
Number |
.limits.requests.block_duration |
Time in seconds to block an IP address that has exceeded the limit for | 300 |
Number |
.blocked-ips |
Array of strings representing blocked IP addresses. IPs in thit list will be blocked 100% of the time | [] |
Array > Strings |
.html-meta-data.title |
HTML meta title | "Windows NT 4.0 and Windows 95 Key Generator" |
String |
.html-meta-data.description |
HTML meta description | "This Windows 95 styled website allows you to create Windows NT 4.0, Windows 95, Windows 98 and Office 7.0 keys. It supports different forms such as 10-digit, 11-digit, or OEM format. There's also a public API available!" |
String |
.html-meta-data.url |
HTML meta url, must contain the protocol. .html-meta-data.title must be set to use a URL |
"https://win95.thevalleyy.tk/" |
String |
.html-meta-data.image |
HTML meta image, must contain the protocol | "https://i.imgur.com/skt7k4g.png" |
String |
.html-meta-data.large_image |
Should the image be large or small? | true |
Boolean |
.html-meta-data.color |
HTML meta color, has to be a hex value | "#000181" |
String |
.oEmbed-meta-data.author_name |
oEmbed author name | "Free and open source on GitHub" |
String |
.oEmbed-meta-data.author_url |
oEmbed author URL, must contain the protocol. .oEmbed-meta-data.author_name must be set to use an URL |
"https://github.com/thevalleyy/windows95keygen" |
String |
.oEmbed-meta-data.provider_name |
oEmbed provider name | "windows95keygen by thevalleyy" |
String |
.oEmbed-meta-data.provider_url |
oEmbed provider URL, must contain the protocol. .oEmbed-meta-data.provider_name must be set to use an URL |
"https://github.com/thevalleyy" |
String |
.url |
URL of your web page. Must be
window.location.origin in the console. ⚠ oEmbed meta data will only work if the value in .url is correct. If you leave it at default, the oEmbed meta data won't change |
"https://win95.thevalleyy.tk/" |
String |
.generate["robots.txt"] |
Generate a robots.txt file? |
true |
Boolean |
.generate["sitemap.xml"] |
Generate a sitemap.xml file? |
true |
Boolean |
.pun-arr |
Funny jokes | See below | Array > Strings |
[
"Bill Gates",
"You",
"Me",
"joe",
"Everybody.",
"Your Mom",
"Chuck Norris",
"The monster under your bed",
"Tasmanian devils and koalas",
"Rick Astley",
"Caffeine refusers",
"Technophobes who want to make an exception",
"Axel Voss",
"People who code in English",
"People who believe that \"Linux\" is a lifestyle",
"People who call \"Inkscape\" a tattoo",
"People who like to work Excel-lent",
"People who drink a cup of java in the morning",
"People who program pythons at the zoo",
"People who are obsessed with JavaScript",
"People who code in HTML, just like me!",
"My chemistry teacher ♥"
]
.html-meta-data
:
Difference between .html-meta-data.large_image = true
and .html-meta-data.large_image = false
:
oEmbed-meta-data
:
If you are familiar with the EmbedBuilder
constructor in discord.js
, here are the corresponding functions:
HTML or oEmbed | discord.js |
---|---|
.html-meta-data.title |
.setTitle() |
.html-meta-data.description |
.setDescription() |
.html-meta-data.url |
.setURL() |
.html-meta-data.image && .html-meta-data.large_image = true |
.setImage() |
.html-meta-data.image && .html-meta-data.large_image = false |
.setThumbnail() |
.html-meta-data.color |
.setColor() |
.oEmbed-meta-data.author_name |
.setAuthor({ name: "" }) |
.oEmbed-meta-data.author_url |
.setAuthor({ name: "", url: "" }) |
.oEmbed-meta-data.provider_name |
n/a |
.oEmbed-meta-data.provider_url |
n/a |
The log directory and file extension can be configured in config.json
(.log.log_path
and .log.log_extension
). Each log file contains 24 hours of information and is named with the corresponding date in ISO format. E.g. 2023-06-06.log
Each log entry has the same structure:
Key | Value(s) | Description | Example |
---|---|---|---|
type |
"blocked" or "allowed" |
Whether the request was blocked or not | "blocked" |
reason |
|
Reason for blocking or allowing the request:
|
"rl" |
time |
A unix timestamp | Exact time of the request | 1685996280444 |
ip |
IPv4 address or IPv6 address | IP address of the request, if not disabled in config.json |
"85.9.18.42" (don't even try, it's not real) |
url |
URL | URL of the request, if not disabled in config.json |
"/api/generate/oem" |
To summarise, the log line explained above would look like this:
{ "type": "blocked", "reason": "rl", "time": 1685996280444, "ip": "85.9.18.42", "url": "/api/generate/oem" }
⚠ These json objects are stored directly below each other without any trailing commas. This means that to convert the log file to json, you need to add commas after each line.
- Original repository by andreazllin - 🌱 Root of the project. Thank you for creating it, and making it open source!
- Blog post by gurney - 📝 Detailed explanation of the algorithm and more.
- @dgurney's article - 📰 Article on how it works.
- FlyTech's Video - 🎥 Video about this topic.