Dateien nach "/" hochladen
This commit is contained in:
commit
f700326944
29
README.md
Normal file
29
README.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# APK Updater
|
||||||
|
|
||||||
|
This python script downloads all new APKs for your local f-droid repo from apkcombo.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Use Ubuntu 22.04 server (headless) with at least 1 GB RAM
|
||||||
|
Install python 3.10 and PIP
|
||||||
|
`apt install python3 python-pip3`
|
||||||
|
Install selenium
|
||||||
|
`pip install selenium`
|
||||||
|
Install MySQL Connector
|
||||||
|
`pip install mysql-connector-python`
|
||||||
|
Download latest firefox binary (do not use apt install!) and extraxt into /opt
|
||||||
|
Download latest geckodriver from https://github.com/mozilla/geckodriver/releases and extract into /opt
|
||||||
|
ln -s the firefox and geckodriver binaries to /usr/bin/
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Download python-script and edit values for paths (regaring your f-droid config) and mysql connection details.
|
||||||
|
Execute db.sql file in your database to set up the working database.
|
||||||
|
Execute the python-file for the first test run and watch console outputs.
|
||||||
|
`python3 updater.py`
|
||||||
|
If everything went well, configure crontab to run the script hourly (this is the refresh interval of the apkcombo RSS feed).
|
||||||
|
`crontab -e`
|
||||||
|
`30 * * * * /usr/bin/python3 /mnt/data/updater/update.py > /dev/null 2>&1`
|
||||||
|
|
||||||
|
## Remarks
|
||||||
|
I am not affiliated with any APK developer, nor f-droid or apkcombo. All rights belong to their owner.
|
62
db.sql
Normal file
62
db.sql
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
-- phpMyAdmin SQL Dump
|
||||||
|
-- version 4.9.5deb2
|
||||||
|
-- https://www.phpmyadmin.net/
|
||||||
|
--
|
||||||
|
-- Host: localhost:3306
|
||||||
|
-- Erstellungszeit: 07. Jan 2024 um 19:15
|
||||||
|
-- Server-Version: 8.0.35-0ubuntu0.20.04.1
|
||||||
|
-- PHP-Version: 7.4.3-4ubuntu2.19
|
||||||
|
|
||||||
|
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||||
|
SET AUTOCOMMIT = 0;
|
||||||
|
START TRANSACTION;
|
||||||
|
SET time_zone = "+00:00";
|
||||||
|
|
||||||
|
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||||
|
/*!40101 SET NAMES utf8mb4 */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Datenbank: `appstore`
|
||||||
|
--
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Tabellenstruktur für Tabelle `appstore`
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE `appstore` (
|
||||||
|
`id` int NOT NULL,
|
||||||
|
`url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
|
||||||
|
`app_name` varchar(500) NOT NULL,
|
||||||
|
`updated` datetime NOT NULL,
|
||||||
|
`marked_for_update` tinyint(1) NOT NULL DEFAULT '0'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indizes der exportierten Tabellen
|
||||||
|
--
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indizes für die Tabelle `appstore`
|
||||||
|
--
|
||||||
|
ALTER TABLE `appstore`
|
||||||
|
ADD PRIMARY KEY (`id`);
|
||||||
|
|
||||||
|
--
|
||||||
|
-- AUTO_INCREMENT für exportierte Tabellen
|
||||||
|
--
|
||||||
|
|
||||||
|
--
|
||||||
|
-- AUTO_INCREMENT für Tabelle `appstore`
|
||||||
|
--
|
||||||
|
ALTER TABLE `appstore`
|
||||||
|
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=101;
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
125
updater.py
Normal file
125
updater.py
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
#APK Updater v1.0
|
||||||
|
#(C) 2023 by Manuel Kamper (kmpr.at)
|
||||||
|
#Do not remove copyright when distributing!
|
||||||
|
import feedparser, mysql.connector, os, glob, time, shutil
|
||||||
|
from datetime import datetime
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.firefox.options import Options
|
||||||
|
from selenium.webdriver.firefox.firefox_profile import FirefoxProfile
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
|
||||||
|
metadata_path = "/mnt/data/metadata"
|
||||||
|
repo_path = "/mnt/data/repo"
|
||||||
|
temp_path = "/tmp/apkdown"
|
||||||
|
|
||||||
|
mydb = mysql.connector.connect(
|
||||||
|
host="localhost",
|
||||||
|
user="appstore",
|
||||||
|
password="<your-pwd>",
|
||||||
|
database="appstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
### NO EDITS BELOW NECESSARY ###
|
||||||
|
|
||||||
|
url = "https://apkcombo.com/latest-updates/feed"
|
||||||
|
|
||||||
|
def get_last_filename_and_rename(new_filename):
|
||||||
|
global temp_path, repo_path
|
||||||
|
files = glob.glob(temp_path + '/*')
|
||||||
|
max_file = max(files, key=os.path.getctime)
|
||||||
|
filename_stripped = max_file.replace("_apkcombo.com","")
|
||||||
|
filename = filename_stripped.split("/")[-1].split("_")[-2]
|
||||||
|
new_filename2 = max_file.replace(filename, new_filename)
|
||||||
|
new_filename2 = new_filename2.replace("_apkcombo.com","")
|
||||||
|
print(new_filename2)
|
||||||
|
new_path = new_filename2.replace(temp_path, repo_path)
|
||||||
|
shutil.move(max_file, new_path)
|
||||||
|
return new_path
|
||||||
|
|
||||||
|
mycursor = mydb.cursor()
|
||||||
|
|
||||||
|
print("APK Updater started.")
|
||||||
|
print("Clearing APK Database from previous updates")
|
||||||
|
sql = "TRUNCATE appstore"
|
||||||
|
mycursor.execute(sql)
|
||||||
|
mydb.commit()
|
||||||
|
|
||||||
|
#fetch updated apps from rss feed
|
||||||
|
print("Fetching updated APKs from RSS...")
|
||||||
|
feed = feedparser.parse(url)
|
||||||
|
updates = 0
|
||||||
|
for entry in feed.entries:
|
||||||
|
updates += 1
|
||||||
|
appname = str(entry.link).split("/")[-2]
|
||||||
|
sql = "INSERT INTO appstore (url, app_name, updated) VALUES (%s, %s, %s)"
|
||||||
|
date_str = datetime.strptime(entry.published, "%a, %d %b %Y %H:%M:%S %z")
|
||||||
|
val = (entry.link+"download/apk", appname, date_str)
|
||||||
|
mycursor.execute(sql, val)
|
||||||
|
mydb.commit()
|
||||||
|
print("Imported " + str(updates) + " updated APK infos from RSS into Database.")
|
||||||
|
|
||||||
|
#find local available apps from metadata directory (filename without .yml)
|
||||||
|
print("Searching for Apps in local Appstore...")
|
||||||
|
list_apps = ['.'.join(x.split('.')[:-1]) for x in os.listdir(metadata_path) if os.path.isfile(os.path.join(metadata_path, x))]
|
||||||
|
print("Found " + str(len(list_apps)) + " Apps in local Appstore.")
|
||||||
|
|
||||||
|
#iterate throug local apps and query db to find matches. if match found, mark app for update
|
||||||
|
real_updates = 0
|
||||||
|
for app in list_apps:
|
||||||
|
sql = "SELECT * FROM appstore WHERE app_name='" + app + "'"
|
||||||
|
mycursor.execute(sql)
|
||||||
|
row = mycursor.fetchone()
|
||||||
|
if row is not None:
|
||||||
|
print("Local App " + app + " marked for update!")
|
||||||
|
sql2 = "UPDATE appstore SET marked_for_update=1 WHERE id=" + str(row[0])
|
||||||
|
mycursor.execute(sql2)
|
||||||
|
real_updates +=1
|
||||||
|
mydb.commit()
|
||||||
|
print("Found " + str(real_updates) + " new updates for Apps in local Appstore")
|
||||||
|
|
||||||
|
if real_updates > 0:
|
||||||
|
#select all marked_for_update entries from db and download apk from apk_url into repo_path
|
||||||
|
#with filename: app_name_version.apk/.xapk
|
||||||
|
#make dir temp_path if it does not exist
|
||||||
|
if not os.path.exists(temp_path):
|
||||||
|
os.makedirs(temp_path)
|
||||||
|
|
||||||
|
#load firefox selenium and download apk
|
||||||
|
options=Options()
|
||||||
|
options.set_preference("browser.download.folderList", 2)
|
||||||
|
options.set_preference("browser.download.manager.showWhenStarting", False)
|
||||||
|
options.set_preference("browser.download.dir", temp_path)
|
||||||
|
options.set_preference("browser.helperApps.neverAsk.saveToDisk", "application/octet-stream")
|
||||||
|
options.add_argument("--headless")
|
||||||
|
driver = webdriver.Firefox(options=options)
|
||||||
|
driver.set_window_size(1200, 700)
|
||||||
|
|
||||||
|
#get download-url for every file marked for update
|
||||||
|
sql = "SELECT * FROM appstore WHERE marked_for_update=1"
|
||||||
|
mycursor.execute(sql)
|
||||||
|
rows = mycursor.fetchall()
|
||||||
|
for row in rows:
|
||||||
|
#make sure temp_path is empty
|
||||||
|
for file in os.scandir(temp_path):
|
||||||
|
if file.name.endswith(".apk") or file.name.endswith(".xapk"):
|
||||||
|
os.unlink(file.path)
|
||||||
|
driver.get(row[1])
|
||||||
|
time.sleep(5)
|
||||||
|
cookiebutton = driver.find_element(By.CSS_SELECTOR, 'svg.icon.ic-download-right')
|
||||||
|
cookiebutton.click()
|
||||||
|
print("APK " + row[2] + " downloaded.")
|
||||||
|
time.sleep(5)
|
||||||
|
#rename and move file to repo
|
||||||
|
print("Moved APK to: " + get_last_filename_and_rename(row[2]))
|
||||||
|
driver.close()
|
||||||
|
driver.quit()
|
||||||
|
|
||||||
|
#delete temp_path directory and all files in it
|
||||||
|
shutil.rmtree(temp_path)
|
||||||
|
|
||||||
|
#kill all running firefox processes to free RAM
|
||||||
|
os.system("pkill -f firefox")
|
||||||
|
|
||||||
|
#todo run updating f-droid appstore command
|
||||||
|
os.system("cd /mnt/data && fdroid update -c && cp /mnt/data/icons/icon.png /mnt/data/repo/icons/icon.png")
|
||||||
|
print("Finished APK update.")
|
Loading…
Reference in New Issue
Block a user