diff --git a/Database.php b/Database.php
new file mode 100644
index 0000000..93b54c0
--- /dev/null
+++ b/Database.php
@@ -0,0 +1,69 @@
+time = time();
+ $this->file = $file;
+ }
+ function sort() : bool {
+ return usort($this->data, array($this, 'cmp'));
+ }
+ function load() : bool {
+ if ($json = file_get_contents($this->file)) {
+ if ($arr = json_decode($json, true)) {
+ foreach ($arr as $obj) {
+ array_push($this->data, new PatchObject($obj));
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+ function save() : bool {
+ $arr = array();
+ foreach ($this->data as $obj) {
+ array_push($arr, $obj->toArray());
+ }
+ if ($json = json_encode($arr, JSON_PRETTY_PRINT)) {
+ if (file_put_contents($this->file, $json)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ function time() : int {
+ return $this->time;
+ }
+ function count() : int {
+ return count($this->data);
+ }
+ function get(int $n) : PatchObject {
+ return $this->data[$n];
+ }
+ function find(string $id) : PatchObject {
+ foreach ($this->data as $obj) {
+ if (strcmp($obj->GetId(), $id) == 0) {
+ return $obj;
+ }
+ }
+ return new PatchObject();
+ }
+ function addOrUpdate(PatchObject $patch) {
+ $patch->SetTimestamp($this->time);
+ foreach ($this->data as &$obj) {
+ if (strcmp($obj->GetId(), $patch->GetId()) == 0) {
+ $obj = $patch;
+ return;
+ }
+ }
+ array_push($this->data, $patch);
+ }
+ private function cmp(PatchObject $a, PatchObject $b) : int {
+ return strcasecmp($a->getProduct(), $b->getProduct());
+ }
+}
+
+?>
diff --git a/PatchBase.php b/PatchBase.php
new file mode 100644
index 0000000..3cc0cae
--- /dev/null
+++ b/PatchBase.php
@@ -0,0 +1,78 @@
+patch = new PatchObject(array('id' => $this->id(), 'vendor' => $vendor, 'product' => $product, 'url' => $url));
+ }
+ function __toString() : string {
+ return (string)$this->patch;
+ }
+ function id() : string {
+ return get_class($this);
+ }
+ function getPatch() : PatchObject {
+ return $this->patch;
+ }
+ abstract function check() : bool;
+ protected function fetch(string $url, bool $json = false) : bool {
+ $opt = array('http' => array('user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:1.0) Gecko/20100101 Patchbot/1.0'));
+ $ctx = stream_context_create($opt);
+ if ($str = file_get_contents($url, false, $ctx)) {
+ $this->data = $str;
+ if ($json) {
+ if (!($this->data = json_decode($str, true)))
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+ protected function parse(string $re) : bool {
+ if ($str = $this->regex_str($re)) {
+ $this->patch->SetVersion($str);
+ return true;
+ }
+ return false;
+ }
+ protected function parse_json(string $key) : bool {
+ $flat = iterator_to_array(new \RecursiveIteratorIterator(new \RecursiveArrayIterator($this->data)));
+ if (!empty($flat[$key])) {
+ $this->patch->SetVersion($flat[$key]);
+ return true;
+ }
+ return false;
+ }
+ protected function array_extract(string $search) {
+ foreach ($this->data as $val) {
+ if (array_search($search, array_values($val)))
+ $this->data = $val;
+ }
+ }
+ protected function str_crop(string $head = '', string $tail = '') {
+ if (!empty($head)) {
+ if ($str = strstr($this->data, $head))
+ $this->data = $str;
+ }
+ if (!empty($tail)) {
+ if ($str = strstr($this->data, $tail, true))
+ $this->data = $str;
+ }
+ }
+ private function regex(string $pattern) {
+ if (!preg_match($pattern, $this->data, $m))
+ return false;
+ // suppress full pattern match
+ unset($m[0]);
+ return $m;
+ }
+ private function regex_str(string $pattern) {
+ $m = $this->regex($pattern);
+ if ($m)
+ return $m[1];
+ return false;
+ }
+}
+
+?>
diff --git a/PatchCollector.php b/PatchCollector.php
new file mode 100644
index 0000000..ace729a
--- /dev/null
+++ b/PatchCollector.php
@@ -0,0 +1,39 @@
+isFile() && $file->getExtension() == 'php') {
+ if ((include __DIR__ . '/modules/' . $file->getFilename()) == TRUE) {
+ $name = $file->getBasename('.php');
+ array_push($list, new $name());
+ }
+ }
+}
+
+$db = new Database(__DIR__ . '/db.json');
+echo 'Time: ' . date('c', $db->time()) . PHP_EOL;
+if ($db->load()) {
+ foreach ($list as $patch) {
+ $oldVer = $db->find($patch->id())->getVersion();
+ if ($patch->check()) {
+ $newVer = $patch->getPatch()->getVersion();
+ echo $patch->id() . ': Version \'' . $newVer . '\' ';
+ if (!empty($newVer) && $newVer != $oldVer) {
+ echo 'is newer than \'' . $oldVer . '\'!';
+ $db->addOrUpdate($patch->getPatch());
+ }
+ else
+ echo 'is up to date.';
+ echo PHP_EOL;
+ }
+ else
+ fwrite(STDERR, $patch->id() . ': CHECK FAILED!' . PHP_EOL);
+ }
+ $db->save();
+}
+
+?>
diff --git a/PatchCollector_test.php b/PatchCollector_test.php
new file mode 100644
index 0000000..647a4b0
--- /dev/null
+++ b/PatchCollector_test.php
@@ -0,0 +1,39 @@
+isFile() && $file->getExtension() == 'php') {
+ if ((include __DIR__ . '/modules_test/' . $file->getFilename()) == TRUE) {
+ $name = $file->getBasename('.php');
+ array_push($list, new $name());
+ }
+ }
+}
+
+$db = new Database(__DIR__ . '/db.json');
+echo 'Time: ' . date('c', $db->time()) . PHP_EOL;
+if ($db->load()) {
+ foreach ($list as $patch) {
+ $oldVer = $db->find($patch->id())->getVersion();
+ if ($patch->check()) {
+ $newVer = $patch->getPatch()->getVersion();
+ echo $patch->id() . ': Version \'' . $newVer . '\' ';
+ if (!empty($newVer) && $newVer != $oldVer) {
+ echo 'is newer than \'' . $oldVer . '\'!';
+ $db->addOrUpdate($patch->getPatch());
+ }
+ else
+ echo 'is up to date.';
+ echo PHP_EOL;
+ }
+ else
+ fwrite(STDERR, $patch->id() . ': CHECK FAILED!' . PHP_EOL);
+ }
+ $db->save();
+}
+
+?>
diff --git a/PatchObject.php b/PatchObject.php
new file mode 100644
index 0000000..0e9bb3b
--- /dev/null
+++ b/PatchObject.php
@@ -0,0 +1,48 @@
+ $val) {
+ if (property_exists(__CLASS__, $key))
+ $this->{$key} = $val;
+ }
+ }
+ function __toString() : string {
+ return $this->vendor . ' ' . $this->product . ' ' . $this->version;
+ }
+ function toArray() : array {
+ return array('id' => $this->id, 'vendor' => $this->vendor, 'product' => $this->product, 'version' => $this->version, 'url' => $this->url, 'timestamp' => $this->timestamp);
+ }
+ function getId() : string {
+ return $this->id;
+ }
+ function getVendor() : string {
+ return $this->vendor;
+ }
+ function getProduct() : string {
+ return $this->product;
+ }
+ function getVersion() : string {
+ return $this->version;
+ }
+ function setVersion(string $version) {
+ $this->version = $version;
+ }
+ function getURL() : string {
+ return $this->url;
+ }
+ function getTimestamp() : int {
+ return $this->timestamp;
+ }
+ function setTimestamp(int $timestamp) {
+ $this->timestamp = $timestamp;
+ }
+}
+
+?>
diff --git a/PatchViewer.php b/PatchViewer.php
new file mode 100644
index 0000000..33dbc6b
--- /dev/null
+++ b/PatchViewer.php
@@ -0,0 +1,56 @@
+load()) {
+ exit(1);
+}
+$db->sort();
+
+?>
+
+
+
+
+
+
+ Patchbot
+
+
+
+
Patch Notification Robot
+

+
Providing you the latest update notifications:
+
+
+
+ | Vendor |
+ Product |
+ Version |
+ Release Date* |
+
+
+
+count(); $i++) {
+ $patch = $db->get($i);
+ echo '';
+ echo '| ' . $patch->getVendor() . ' | ';
+ echo '' . $patch->getProduct() . ' | ';
+ echo '' . $patch->getVersion() . ' | ';
+ echo '' . date('Y-m-d', $patch->getTimestamp()) . ' | ';
+ echo '
' . "\r\n";
+ }
+
+?>
+
+
+
+
+
+
diff --git a/db.json b/db.json
new file mode 100644
index 0000000..12f4cb1
--- /dev/null
+++ b/db.json
@@ -0,0 +1,202 @@
+[
+ {
+ "id": "GoogleChrome",
+ "vendor": "Google",
+ "product": "Chrome",
+ "version": "75.0.3770.100",
+ "url": "https:\/\/www.google.de\/chrome\/",
+ "timestamp": 1561739765
+ },
+ {
+ "id": "AdobeReader",
+ "vendor": "Adobe",
+ "product": "Acrobat Reader DC",
+ "version": "2019.012.20034",
+ "url": "https:\/\/get.adobe.com\/de\/reader\/",
+ "timestamp": 1558278109
+ },
+ {
+ "id": "AdoptOpenJDK11",
+ "vendor": "AdoptOpenJDK",
+ "product": "OpenJDK JRE 11",
+ "version": "11.0.3+7",
+ "url": "https:\/\/adoptopenjdk.net\/",
+ "timestamp": 1558278109
+ },
+ {
+ "id": "AdoptOpenJDK12",
+ "vendor": "AdoptOpenJDK",
+ "product": "OpenJDK JRE 12",
+ "version": "12.0.1+12",
+ "url": "https:\/\/adoptopenjdk.net\/",
+ "timestamp": 1558278109
+ },
+ {
+ "id": "AdoptOpenJDK8",
+ "vendor": "AdoptOpenJDK",
+ "product": "OpenJDK JRE 8",
+ "version": "8u212-b04",
+ "url": "https:\/\/adoptopenjdk.net\/",
+ "timestamp": 1559992285
+ },
+ {
+ "id": "Arduino",
+ "vendor": "Arduino",
+ "product": "Arduino IDE",
+ "version": "1.8.9",
+ "url": "https:\/\/www.arduino.cc\/",
+ "timestamp": 1561541195
+ },
+ {
+ "id": "DirectoryLister",
+ "vendor": "Chris Kankiewicz",
+ "product": "Directory Lister",
+ "version": "2.7.1",
+ "url": "https:\/\/www.directorylister.com\/",
+ "timestamp": 1561318803
+ },
+ {
+ "id": "GIMP",
+ "vendor": "GIMP Team",
+ "product": "GNU Image Manipulation Program",
+ "version": "2.10.12",
+ "url": "https:\/\/www.gimp.org\/",
+ "timestamp": 1563107345
+ },
+ {
+ "id": "GpgWin",
+ "vendor": "Gpg4win Initiative",
+ "product": "GNU Privacy Guard for Windows",
+ "version": "3.1.10",
+ "url": "https:\/\/www.gpg4win.de\/",
+ "timestamp": 1563107271
+ },
+ {
+ "id": "MSOffice2019",
+ "vendor": "Microsoft",
+ "product": "Office 2019",
+ "version": "1906 (Build 11727.20244)",
+ "url": "https:\/\/docs.microsoft.com\/en-us\/OfficeUpdates\/update-history-office-2019#retail-versions-of-office-2016-c2r-and-office-2019",
+ "timestamp": 1562745139
+ },
+ {
+ "id": "MSOffice2019VL",
+ "vendor": "Microsoft",
+ "product": "Office 2019 VL",
+ "version": "1808 (Build 10348.20020)",
+ "url": "https:\/\/docs.microsoft.com\/en-us\/OfficeUpdates\/update-history-office-2019#volume-licensed-versions-of-office-2019",
+ "timestamp": 1562745139
+ },
+ {
+ "id": "MikroTikRouterOS",
+ "vendor": "MikroTik",
+ "product": "RouterOS",
+ "version": "6.45.1",
+ "url": "https:\/\/mikrotik.com\/download",
+ "timestamp": 1562431335
+ },
+ {
+ "id": "MozillaFirefox",
+ "vendor": "Mozilla",
+ "product": "Firefox",
+ "version": "68.0",
+ "url": "https:\/\/www.mozilla.org\/de\/firefox\/all\/",
+ "timestamp": 1562745139
+ },
+ {
+ "id": "MozillaFirefoxESR",
+ "vendor": "Mozilla",
+ "product": "Firefox ESR",
+ "version": "60.8.0",
+ "url": "https:\/\/www.mozilla.org\/de\/firefox\/organizations\/all\/",
+ "timestamp": 1562745139
+ },
+ {
+ "id": "MozillaThunderbird",
+ "vendor": "Mozilla",
+ "product": "Thunderbird",
+ "version": "60.8.0",
+ "url": "https:\/\/www.thunderbird.net\/de\/thunderbird\/all\/",
+ "timestamp": 1563107107
+ },
+ {
+ "id": "PuTTY",
+ "vendor": "Simon Tatham",
+ "product": "PuTTY",
+ "version": "0.71",
+ "url": "https:\/\/www.chiark.greenend.org.uk\/~sgtatham\/putty\/",
+ "timestamp": 1561380235
+ },
+ {
+ "id": "SevenZip",
+ "vendor": "Igor Pavlov",
+ "product": "7-Zip",
+ "version": "19.00",
+ "url": "https:\/\/www.7-zip.org\/",
+ "timestamp": 1561234479
+ },
+ {
+ "id": "TotalCommander",
+ "vendor": "Ghisler",
+ "product": "Total Commander",
+ "version": "9.22a",
+ "url": "https:\/\/www.ghisler.com\/",
+ "timestamp": 1561405210
+ },
+ {
+ "id": "VLC",
+ "vendor": "VideoLAN",
+ "product": "VLC media player",
+ "version": "3.0.7.1",
+ "url": "https:\/\/www.videolan.org\/vlc\/",
+ "timestamp": 1560345804
+ },
+ {
+ "id": "Wireshark",
+ "vendor": "Gerald Combs",
+ "product": "Wireshark",
+ "version": "3.0.2",
+ "url": "https:\/\/www.wireshark.org\/",
+ "timestamp": 1561489221
+ },
+ {
+ "id": "AdobeFlashPlayer",
+ "vendor": "Adobe",
+ "product": "Flash Player",
+ "version": "32.0.0.223",
+ "url": "https:\/\/get.adobe.com\/de\/flashplayer\/",
+ "timestamp": 1562745139
+ },
+ {
+ "id": "PHP",
+ "vendor": "PHP Group",
+ "product": "PHP",
+ "version": "7.3.7",
+ "url": "https:\/\/www.php.net\/",
+ "timestamp": 1562431335
+ },
+ {
+ "id": "Caddy",
+ "vendor": "Matthew Holt",
+ "product": "Caddy",
+ "version": "v1.0.1",
+ "url": "https:\/\/caddyserver.com\/",
+ "timestamp": 1563107271
+ },
+ {
+ "id": "Gitea",
+ "vendor": "Gitea Authors",
+ "product": "Gitea",
+ "version": "v1.9.0-rc2",
+ "url": "https:\/\/gitea.io\/",
+ "timestamp": 1563208203
+ },
+ {
+ "id": "cTop",
+ "vendor": "Bradley Cicenas",
+ "product": "ctop",
+ "version": "v0.7.2",
+ "url": "https:\/\/ctop.sh\/",
+ "timestamp": 1563107271
+ }
+]
\ No newline at end of file
diff --git a/empty.json b/empty.json
new file mode 100644
index 0000000..93d5140
--- /dev/null
+++ b/empty.json
@@ -0,0 +1 @@
+[{}]
diff --git a/modules/AdobeFlashPlayer.php b/modules/AdobeFlashPlayer.php
new file mode 100644
index 0000000..025b183
--- /dev/null
+++ b/modules/AdobeFlashPlayer.php
@@ -0,0 +1,16 @@
+fetch('https://get.adobe.com/flashplayer/webservices/json/?platform_type=Windows&platform_dist=XP&platform_arch=x86-32&platform_misc=&exclude_version=10&browser_arch=&browser_type=&browser_vers=&browser_dist=&eventname=flashplayerotherversions', true)) {
+ $this->array_extract('flashplayerax');
+ return $this->parse_json('Version');
+ }
+ return false;
+ }
+}
+
+?>
diff --git a/modules/AdobeReader.php b/modules/AdobeReader.php
new file mode 100644
index 0000000..10c83ba
--- /dev/null
+++ b/modules/AdobeReader.php
@@ -0,0 +1,14 @@
+fetch('https://get.adobe.com/de/reader/'))
+ return $this->parse('_Version ([\d\.]+)_');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/AdoptOpenJDK11.php b/modules/AdoptOpenJDK11.php
new file mode 100644
index 0000000..3ad65aa
--- /dev/null
+++ b/modules/AdoptOpenJDK11.php
@@ -0,0 +1,14 @@
+fetch('https://api.adoptopenjdk.net/v2/info/releases/openjdk11?release=latest&openjdk_impl=hotspot&type=jre&os=windows&arch=x64', true))
+ return $this->parse_json('openjdk_version');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/AdoptOpenJDK12.php b/modules/AdoptOpenJDK12.php
new file mode 100644
index 0000000..1551a17
--- /dev/null
+++ b/modules/AdoptOpenJDK12.php
@@ -0,0 +1,14 @@
+fetch('https://api.adoptopenjdk.net/v2/info/releases/openjdk12?release=latest&openjdk_impl=hotspot&type=jre&os=windows&arch=x64', true))
+ return $this->parse_json('openjdk_version');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/AdoptOpenJDK8.php b/modules/AdoptOpenJDK8.php
new file mode 100644
index 0000000..54b027c
--- /dev/null
+++ b/modules/AdoptOpenJDK8.php
@@ -0,0 +1,14 @@
+fetch('https://api.adoptopenjdk.net/v2/info/releases/openjdk8?release=latest&openjdk_impl=hotspot&type=jre&os=windows&arch=x64', true))
+ return $this->parse_json('openjdk_version');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/Arduino.php b/modules/Arduino.php
new file mode 100644
index 0000000..cb0262b
--- /dev/null
+++ b/modules/Arduino.php
@@ -0,0 +1,14 @@
+fetch('https://www.arduino.cc/en/Main/Software'))
+ return $this->parse('_[\s]*ARDUINO ([\d\.]+)[\s]*
_');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/Caddy.php b/modules/Caddy.php
new file mode 100644
index 0000000..4c8219c
--- /dev/null
+++ b/modules/Caddy.php
@@ -0,0 +1,14 @@
+fetch('https://api.github.com/repos/caddyserver/caddy/releases/latest', true))
+ return $this->parse_json('tag_name');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/DirectoryLister.php b/modules/DirectoryLister.php
new file mode 100644
index 0000000..c849ed8
--- /dev/null
+++ b/modules/DirectoryLister.php
@@ -0,0 +1,14 @@
+fetch('https://api.github.com/repos/DirectoryLister/DirectoryLister/releases/latest', true))
+ return $this->parse_json('tag_name');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/GIMP.php b/modules/GIMP.php
new file mode 100644
index 0000000..9b72e3c
--- /dev/null
+++ b/modules/GIMP.php
@@ -0,0 +1,14 @@
+fetch('https://www.gimp.org/downloads/'))
+ return $this->parse('_//download\.gimp\.org/mirror/pub/gimp/v[\d\.]+/windows/gimp-([\d\.]+)-setup[\-\d]*\.exe_');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/Gitea.php b/modules/Gitea.php
new file mode 100644
index 0000000..a5e8abe
--- /dev/null
+++ b/modules/Gitea.php
@@ -0,0 +1,14 @@
+fetch('https://api.github.com/repos/go-gitea/gitea/releases/latest', true))
+ return $this->parse_json('tag_name');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/GoogleChrome.php b/modules/GoogleChrome.php
new file mode 100644
index 0000000..b41e806
--- /dev/null
+++ b/modules/GoogleChrome.php
@@ -0,0 +1,14 @@
+fetch('https://omahaproxy.appspot.com/all'))
+ return $this->parse('/win64,stable,([\d\.]+),/');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/GpgWin.php b/modules/GpgWin.php
new file mode 100644
index 0000000..58afee8
--- /dev/null
+++ b/modules/GpgWin.php
@@ -0,0 +1,14 @@
+fetch('https://www.gpg4win.de/get-gpg4win-de.html'))
+ return $this->parse('/Download Gpg4win ([\d\.]+) \(/');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/MSOffice2019.php b/modules/MSOffice2019.php
new file mode 100644
index 0000000..22199c9
--- /dev/null
+++ b/modules/MSOffice2019.php
@@ -0,0 +1,16 @@
+fetch('https://docs.microsoft.com/en-us/OfficeUpdates/update-history-office-2019')) {
+ $this->str_crop('retail-versions-of-office-2016-c2r-and-office-2019', '');
+ return $this->parse('/Version ([\d]+ \(Build [\d\.]+\))/');
+ }
+ return false;
+ }
+}
+
+?>
diff --git a/modules/MSOffice2019VL.php b/modules/MSOffice2019VL.php
new file mode 100644
index 0000000..e527c6a
--- /dev/null
+++ b/modules/MSOffice2019VL.php
@@ -0,0 +1,16 @@
+fetch('https://docs.microsoft.com/en-us/OfficeUpdates/update-history-office-2019')) {
+ $this->str_crop('volume-licensed-versions-of-office-2019', '');
+ return $this->parse('/Version ([\d]+ \(Build [\d\.]+\))/');
+ }
+ return false;
+ }
+}
+
+?>
diff --git a/modules/MikroTikRouterOS.php b/modules/MikroTikRouterOS.php
new file mode 100644
index 0000000..a2f2a05
--- /dev/null
+++ b/modules/MikroTikRouterOS.php
@@ -0,0 +1,16 @@
+fetch('https://mikrotik.com/download')) {
+ $this->str_crop('id="routeros"', '');
+ return $this->parse('/| ([\d\.]+) \(Stable\)/');
+ }
+ return false;
+ }
+}
+
+?>
diff --git a/modules/MozillaFirefox.php b/modules/MozillaFirefox.php
new file mode 100644
index 0000000..55c5005
--- /dev/null
+++ b/modules/MozillaFirefox.php
@@ -0,0 +1,14 @@
+fetch('https://www.mozilla.org/de/firefox/all/'))
+ return $this->parse('/data-latest-firefox="([\d\.]+)"/');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/MozillaFirefoxESR.php b/modules/MozillaFirefoxESR.php
new file mode 100644
index 0000000..c29ed0a
--- /dev/null
+++ b/modules/MozillaFirefoxESR.php
@@ -0,0 +1,14 @@
+fetch('https://www.mozilla.org/de/firefox/organizations/all/'))
+ return $this->parse('/data-esr-versions="([\d\.]+)[ "]/');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/MozillaThunderbird.php b/modules/MozillaThunderbird.php
new file mode 100644
index 0000000..cba0a12
--- /dev/null
+++ b/modules/MozillaThunderbird.php
@@ -0,0 +1,14 @@
+fetch('https://www.thunderbird.net/de/thunderbird/all/'))
+ return $this->parse('_"https://download\.mozilla\.org/\?product=thunderbird-([\d\.]+)(-SSL)?&(amp;)?os=win&(amp;)?lang=en-US"_');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/PHP.php b/modules/PHP.php
new file mode 100644
index 0000000..a830d2b
--- /dev/null
+++ b/modules/PHP.php
@@ -0,0 +1,14 @@
+fetch('https://www.php.net/downloads.php'))
+ return $this->parse('_Current Stable[\s]*PHP ([\d\.]+)_');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/PuTTY.php b/modules/PuTTY.php
new file mode 100644
index 0000000..99de541
--- /dev/null
+++ b/modules/PuTTY.php
@@ -0,0 +1,14 @@
+fetch('https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html'))
+ return $this->parse('/latest release \(([\d\.]+)\)/');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/SevenZip.php b/modules/SevenZip.php
new file mode 100644
index 0000000..0d13e53
--- /dev/null
+++ b/modules/SevenZip.php
@@ -0,0 +1,14 @@
+fetch('https://www.7-zip.org/download.html'))
+ return $this->parse('/Download 7-Zip ([\d\.]+) \(/');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/TotalCommander.php b/modules/TotalCommander.php
new file mode 100644
index 0000000..2189821
--- /dev/null
+++ b/modules/TotalCommander.php
@@ -0,0 +1,14 @@
+fetch('https://www.ghisler.com/download.htm'))
+ return $this->parse('/version ([\d\w\.]+) of/');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/VLC.php b/modules/VLC.php
new file mode 100644
index 0000000..db846eb
--- /dev/null
+++ b/modules/VLC.php
@@ -0,0 +1,14 @@
+fetch('https://www.videolan.org/vlc/download-windows.html'))
+ return $this->parse('_//get\.videolan\.org/vlc/([\d\.]+)/win32/vlc-[\d\.]+-win32\.exe_');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/Wireshark.php b/modules/Wireshark.php
new file mode 100644
index 0000000..e18c06b
--- /dev/null
+++ b/modules/Wireshark.php
@@ -0,0 +1,14 @@
+fetch('https://www.wireshark.org/download.html'))
+ return $this->parse('/>Stable Release \(([\d\.]+)\)/');
+ return false;
+ }
+}
+
+?>
diff --git a/modules/cTop.php b/modules/cTop.php
new file mode 100644
index 0000000..2045c8c
--- /dev/null
+++ b/modules/cTop.php
@@ -0,0 +1,14 @@
+fetch('https://api.github.com/repos/bcicen/ctop/releases/latest', true))
+ return $this->parse_json('tag_name');
+ return false;
+ }
+}
+
+?>
diff --git a/modules_test/.gitkeep b/modules_test/.gitkeep
new file mode 100644
index 0000000..e69de29
|