From a80418710a4cd46983c2869fb64670fd87de01ba Mon Sep 17 00:00:00 2001 From: xploreinfinity <36274394+XploreInfinity@users.noreply.github.com> Date: Sun, 3 Jul 2022 13:09:34 +0530 Subject: [PATCH] Release 0.3-beta --- .gitignore | 5 +- CHANGELOG.md | 10 + MANIFEST.in | 2 +- README.md | 60 +++-- setup.py | 28 ++- swift_block/Parser.py | 119 ++++++++-- swift_block/Parser.py.bak | 393 ------------------------------- swift_block/RuleManager.py | 19 +- swift_block/__init__.py | 16 +- swift_block/assets/uninstall.png | Bin 0 -> 958 bytes swift_block/elevate/README.rst | 2 +- swift_block/elevate/__init__.py | 4 +- swift_block/elevate/posix.py | 9 +- swift_block/elevate/windows.py | 4 +- swift_block/main.py | 72 ++++-- swift_block/main.py.bak | 263 --------------------- swift_block/ui/adblock.ui | 41 +++- swift_block/ui/filters.ui | 12 + 18 files changed, 310 insertions(+), 749 deletions(-) delete mode 100644 swift_block/Parser.py.bak create mode 100644 swift_block/assets/uninstall.png delete mode 100644 swift_block/main.py.bak diff --git a/.gitignore b/.gitignore index 31958c3..260ced4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,7 @@ __pycache__ elevate/__pycache__ swift_block/__pycache__ .vscode -testing \ No newline at end of file +testing +build +dist +swift_block.egg-info \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index f69c36f..b9553d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## CHANGELOG + +**Version 0.3-beta:** + +* Created a Pypi package +* Several improvements and bug fixes in `elevate` +* Adjustments in code to adapt swift_block to run as a package +* Swift-Block will now create a launcher/start menu shortcut on first run +* Added an uninstaller + **Version 0.2-beta:** * Windows testing completed. diff --git a/MANIFEST.in b/MANIFEST.in index 153e72a..19dc90b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,2 @@ recursive-include ./swift_block/assets/ * -recursive-include ./swift_block/ui/ * \ No newline at end of file +recursive-include ./swift_block/ui/ * diff --git a/README.md b/README.md index 1aecd6f..ef28e87 100644 --- a/README.md +++ b/README.md @@ -4,58 +4,72 @@ Version:0.2-beta

-**About:** +## About: Swiftblock is a free and open-source hosts file based ad,malware and tracker blocker written in Python's Pyqt6 framework. -**Features:** +## Features: * Free & Open Source(SwiftBlock is licensed under GPLv3) * Custom sources(You can easily add custom sources of hosts files) -* Custom Rules(You can manually redirect,allow or block specific hostnames!) +* Custom Rules(You can manually redirect,allow or block specific hostnames) +* Easy access(Swift-Block will create a launcher/start menu shortcut on first run) -**Supported Platforms:** +## Supported Platforms: -Most linux distributions, Windows, FreeBSD and MacOS[Testing on FreeBSD and MacOS is pending. Please note though, that I currently have no plans to test it on MacOS] +Most linux distributions, Windows, FreeBSD and MacOS[Not tested on FreeBSD and MacOS] -**Run Instructions:** +## Installation: -To run swift block,execute the following commands as root/Administrator in your terminal/command prompt: +* Open your terminal/command prompt and run the following command as superuser/administrator : + `python -m pip install swift_block` +* Now launch swift-block from your terminal/command-line by running this command(ensure you have internet first): + `swift-block` +* Swift-Block will pull hosts sources from the internet and create a launcher/start menu shortcut during the first run +* Swift-Block can be accessed from your DE's menu/start menu like any normal application from this point onwards. -* First get all the dependencies of swift_block by running the following: -* `python -m pip install pyqt6 requests` on Linux/FreeBSD/MacOS or `py -m pip install pyqt6 requests` on Windows -* Next,open the terminal/command prompt in the root folder of the project -* `cd swift_block` -* For Linux/FreeBSD/MacOS: `python __init__.py` -* For Windows `py __init__.py` -* Swift-Block should now be running +## Uninstallation: -**SPECIAL INSTRUCTIONS FOR WINDOWS USERS[VERY IMPORTANT]:** +* Open Swift-Block +* Go to the `About` tab and click on the `Uninstall` button +* It is not recommended to directly use pip to uninstall swift-block unless the above method fails +* To uninstall Swift-Block directly using pip, run this as superuser/administrator from your terminal/cmd: + `python -m pip uninstall swift_block` -Windows has issues with larger hosts files.The DNS Client service needs to be disabled to mitigate this. Recent changes in security within Windows 10 denies access to changing services via other tools except registry hacks. Use the [this cmd file](https://github.com/StevenBlack/hosts/blob/master/disable-dnscache-service-win.cmd)(Obviously,run this file as an Administrator) to make proper changes to the Windows registry. You will need to reboot your device once that's done. +## Building from source(for contributors): -**Why the weird way of distribution?** +* Clone this repo and open a terminal/cmd in the main project folder +* Run the following command as superuser/administrator: + `python -m pip install .` +* Swift-Block will be built & installed -I'm experiencing several issues with packaging and publishing swift-block on pypi,until I resolve those issues, I'm afraid this is the only way swift-block will be distributed. +## (IMPORTANT) SPECIAL INSTRUCTIONS FOR WINDOWS USERS: -**Why are my changes not applied?** +Windows has issues with larger hosts files.The DNS Client service needs to be disabled to mitigate this. Recent changes in security within Windows 10 denies access to changing services via other tools except registry hacks. Use [this bat file](https://github.com/StevenBlack/hosts/blob/master/disable-dnscache-service-win.bat))(Obviously,run this file as an Administrator) to make proper changes to the Windows registry. You will need to reboot your device once that's done. + +## Why are my changes not applied? Sometimes, to refresh the hosts file, a reboot is required. If you think your changes haven't been applied, either reboot or follow [this guide](https://github.com/StevenBlack/hosts#reloading-hosts-file). -**Inspiration:** +## Inspiration: Swiftblock is inspired from [Adaway](https://adaway.org) and uses some UX concepts from it[No code from the project has been taken,however]. -**For Contributors:** +## For Contributors: * I've used qt-designer to create all the GUI interfaces,kindly use the same/another compatible designer for making any modifications in GUI. All the ui files are in `swift_block/ui` * `swift_block/__init__.py` is the entry point/script executed to initialise everything -* `swift_block/main.py` is the home page of swift-block - it offers users options to manage their hosts sources,update source files, enable/disable swift-block and other misc. stuff. -* `swift_block/Parser.py` is the heart of swift-block, with low level functions for performing operations on hosts files and sources,first-start,restoring/replacing corrupt files,validation tasks, etc.[It is a non-GUI module] +* `swift_block/main.py` is the home page of swift-block - it offers users options to manage their hosts sources,update source files, enable/disable or uninstall swift-block, etc. +* `swift_block/Parser.py` is the heart of swift-block, with low level functions for performing operations on hosts files and sources,first-start,restoring/replacing corrupt files,validation tasks,uninstall scripts, etc.[It is a non-GUI module] * `swift_block/RuleManager.py` is the GUI rule editor. It offers users options to block/redirect/allow custom/specific hostnames and also allow or redirect the hostnames being blocked by the source files * Images and icons used within the GUI are stored in the `swift_block/assets` directory. * `swift_block/elevate` is a sub-package that provides privilege escalation functionality(Required to read/write to the system hosts file). It is my modification of the original and currently broken [elevate](https://github.com/barneygale/elevate). +### Attribution: + +* [Uninstall icons created by Us and Up - Flaticon](https://www.flaticon.com/free-icons/uninstall) + +
Made with ❤️ by Xploreinfinity P.S:Yes,I am aware swift-block's logo is that of [Swift lang](https://www.swift.org/), but there really aren't any better icons out there,sadly[None that suit the overall feel of swift-block,anyway]. diff --git a/setup.py b/setup.py index 9d6e9b2..c91d0e9 100644 --- a/setup.py +++ b/setup.py @@ -1,22 +1,33 @@ -import setuptools +''' +Copyright (C) 2021 xploreinfinity + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + +import setuptools with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read() - setuptools.setup( name="swift_block", - version="3.7", + version="0.3", author="Xploreinfinity", - author_email="author@example.com", + license="GPLv3", description="Swiftblock is a free and open-source hosts file based ad,malware and tracker blocker", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/XploreInfinity/swift-block", entry_points={ - 'console_scripts': [ + 'gui_scripts': [ 'swift-block =swift_block.__init__:main', ], + 'console_scripts': [ + 'swift-block-win =swift_block.__init__:main', + ], }, project_urls={ "Bug Tracker": "https://github.com/XploreInfinity/swift-block/issues", @@ -32,6 +43,11 @@ package_dir={"": ".",}, packages=setuptools.find_packages(where="."), include_package_data=True, - install_requires=['pyqt6>=6.2.2'], + install_requires=[ + 'pyqt6>=6.2.2', + 'requests', + 'winshell; sys_platform == "win32"', + 'pywin32; sys_platform == "win32"' + ], python_requires=">=3.6", ) diff --git a/swift_block/Parser.py b/swift_block/Parser.py index 0ffe79d..1b1627f 100644 --- a/swift_block/Parser.py +++ b/swift_block/Parser.py @@ -1,4 +1,14 @@ -import os,requests,re,sqlite3,ipaddress,sys +''' +Copyright (C) 2021 xploreinfinity + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + +import os,requests,re,sqlite3,ipaddress,sys,inspect,subprocess class Parser: def __init__(self): @@ -8,6 +18,55 @@ def __init__(self): r'^127\.0\.0\.1( +)local$', r'^255\.255\.255\.255( +)broadcasthost$', r'^0.0.0.0( +)0.0.0.0$'] + + #*Perform a few pre-run checks: + self.prerun_checks() + + #*Checks whether the menu launcher and swiftblock user directory are created. If not, it regenerates them: + def prerun_checks(self): + #*TODO: ADD SUPPORT FOR MACOS LAUNCHER + #*Ensure a launcher shortcut is present,if not,regenerate: + try: + scriptPath=os.path.abspath(os.path.dirname(inspect.getsourcefile(lambda:0))).replace('\\','/') + if sys.platform.startswith('linux') or sys.platform.startswith('freebsd'): + install_path='' + #*Generally,.desktop files are stored in either of these two locations, so we will place our .desktop file in any one of these locations. + #*If any of the locations don't exist, don't create a .desktop file + if os.path.exists('/usr/share/applications'): + install_path='/usr/share/applications' + elif os.path.exists('/usr/local/share/applications'): + install_path='/usr/local/share/applications' + if install_path and not os.path.exists(install_path+'/SwiftBlock.desktop'): + os.chdir(install_path) + desktopFile = open('SwiftBlock.desktop','w') + desktopFile.write( + """ + [Desktop Entry] + Name=Swift Block + Comment= Block ads, trackers and malware swiftly + Exec=swift-block + Icon=%s/assets/app_icon.svg + Terminal=false + Type=Application + Categories=Qt;System; + """%scriptPath + ) + desktopFile.close() + elif sys.platform.startswith('win32'): + import winshell + programData = os.getenv('ProgramData') + #*If programData exists and the swift block start shortcut doesn't, create it: + if programData and not os.path.exists(programData+'\\Microsoft\\Windows\\Start Menu\\Programs\\Swift-Block.lnk'): + os.chdir(programData+'\\Microsoft\\Windows\\Start Menu\\Programs\\') + with winshell.shortcut(programData+'\\Microsoft\\Windows\\Start Menu\\Programs\\'+'Swift-Block.lnk') as lnk: + lnk.path = 'swift-block' + lnk.description = 'Block ads, trackers and malware swiftly' + else: + print('Cant create a menu launcher(Platform not supported). Skipping... :(') + #*If an error occurs, warn the user, but proceed anyway since failing to create a launcher doesn't deter swift-block's functionality: + except Exception as err: + print('Oops! An error occurred while creating a menu launcher. Proceeding anyway.Error Details:\n',str(err)) + #*Ensure the swiftblock user directory and its components are present,if not, regenerate: try: os.chdir(os.path.expanduser("~/.swiftblock")) @@ -38,13 +97,11 @@ def __init__(self): #*initialise the database: self.init_db() - + print('Generating the first hosts file...') self.regen_hosts() print('All done :)') - - #*Initialises the DB and regenerates it,if DB file is found missing or corrupt: def init_db(self): try: @@ -75,7 +132,7 @@ def fetch_sources(self): query="SELECT * FROM sources;" result=self.cursor.execute(query) return result.fetchall() - + #*This generates a basic hosts file consisting of rules compiled from various sources present in the DB def generateSourceRules(self,updateSources=False): #*Clear the sourceslist file to remove old rules: @@ -97,7 +154,7 @@ def generateSourceRules(self,updateSources=False): source_file=open(name+".txt",'r') #*We're using readlines() because its generally recommended and we wont have blank '' that are returned by read() when it reaches EOF file_lst=set([i.strip() for i in source_file.readlines()]) - main_sources=open('sourceslist','r') + main_sources=open('sourceslist','r') main_lst=set([i.strip() for i in main_sources.readlines()]) main_sources.close() #*Find rules present in the current source file that arent in the main_sources file and then add those @@ -111,7 +168,7 @@ def generateSourceRules(self,updateSources=False): main_sources.write(i+'\n') main_sources.close() - #*This method will regenerate the hosts file with the various hosts sources + #*This method will regenerate the hosts file with the various hosts sources def regen_hosts(self): #*Clean the hosts file to remove old rules main_hosts=open('hosts','w') @@ -121,7 +178,7 @@ def regen_hosts(self): main_sources=open('sourceslist','r') main_hosts.write(main_sources.read()) main_hosts.close() - + #*Next, add the user defined rules to the hosts file: user_hosts=open('userlist','r') user_list=[i.strip() for i in user_hosts.readlines()] @@ -140,7 +197,7 @@ def regen_hosts(self): if i.split()[1]==j.split()[1]: compiled_list.append(j) replaced=True - user_rules.append(j) + user_rules.append(j) if not replaced: compiled_list.append(i) #*Now add other user defined rules which didn't replace any host defined rules: @@ -177,7 +234,7 @@ def regen_hosts(self): #*Write these changes to the system hosts file[only if swiftblock is active]: if blockerStatus: self.write_changes() - + #*Downloads hosts files from remote sources(can also get files from filesystem-but this feature is currently unused): def download_source(self,name,url,offline=False): #*get the url corresponding to the source name: @@ -204,7 +261,7 @@ def download_source(self,name,url,offline=False): clean=False break if clean: - + #*Replace 0.0.0.0(or any other source specified ip) with 127.0.0.1 for safety reasons. #!Do this only for remote sources.User's custom list isn't affected. if not offline: @@ -299,7 +356,7 @@ def is_valid_hostname(self,hostname): hostname = hostname[:-1] #*strip exactly one dot from the right, if present allowed = re.compile(r"^(?!-)[A-Z\d-]{1,63}(? 255: - return False - if hostname[-1] == ".": - hostname = hostname[:-1] #*strip exactly one dot from the right, if present - allowed = re.compile(r"^(?!-)[A-Z\d-]{1,63}(?. +''' + from PyQt6 import QtWidgets,QtGui,QtCore,uic import os,sys -import Parser +from swift_block import Parser class RuleManager(QtWidgets.QWidget): def __init__(self,scriptPath): super().__init__() @@ -29,6 +39,7 @@ def __init__(self,scriptPath): self.reconf_ui() def reconf_ui(self): + self.setWindowIcon(QtGui.QIcon(self.scriptPath+"/assets/app_icon.svg")) #*Apply icons to the home button on each tab: #*For sourceDefine_tab: self.sourceBlockedHome_btn.setIcon(QtGui.QIcon(self.scriptPath+"/assets/home.svg")) @@ -338,7 +349,7 @@ def openHome(self): os.chdir(self.scriptPath) self.hide() self.parser.close_db() - import main + from swift_block import main self.home=main.Ui() @@ -586,7 +597,7 @@ def addRedirectedHostClicked(self): #*Removes user selected rule from the redirected list: def deleteRedirectedHostClicked(self): #*Display a prompt asking the user for confirmation: - question=QtWidgets.QMessageBox(QtWidgets.QMessageBox.Icon.Question,"Please Confirm","Are you sure remove this hostname from the allowed list?",(QtWidgets.QMessageBox.StandardButton.Yes|QtWidgets.QMessageBox.StandardButton.No)) + question=QtWidgets.QMessageBox(QtWidgets.QMessageBox.Icon.Question,"Please Confirm","Are you sure remove this hostname from the redirected list?",(QtWidgets.QMessageBox.StandardButton.Yes|QtWidgets.QMessageBox.StandardButton.No)) confirm=question.exec() if confirm==QtWidgets.QMessageBox.StandardButton.Yes: #*Get the hostname and IP from redirectedTable: @@ -706,4 +717,4 @@ def cancelAddAllowedHostClicked(self): #*Infact,we will have to disable it again,in case the user selected a hostname from the allowed list BEFORE clicking on the addAddAllowedHost_btn: self.deleteAllowedHost_btn.setDisabled(True) #*Hide the status label(since if user makes an error,but then clicks on cancel button the status label needs to be hidden): - self.mainAllowedStatus_lbl.hide() \ No newline at end of file + self.mainAllowedStatus_lbl.hide() diff --git a/swift_block/__init__.py b/swift_block/__init__.py index cf464ef..480a9b1 100644 --- a/swift_block/__init__.py +++ b/swift_block/__init__.py @@ -1,8 +1,20 @@ +''' +Copyright (C) 2021 xploreinfinity + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + import sys from PyQt6 import QtWidgets -import main +from swift_block import main,elevate def start(): + #*Privilege escalation: + elevate.elevate(__file__) app=QtWidgets.QApplication(sys.argv) ui=main.Ui() sys.exit(app.exec()) -start() \ No newline at end of file +start() diff --git a/swift_block/assets/uninstall.png b/swift_block/assets/uninstall.png new file mode 100644 index 0000000000000000000000000000000000000000..71c78a1646949e900f4fa38c3b85567f819cc308 GIT binary patch literal 958 zcmV;v13~zgJab45D76AQD-2j3iUG#COxP*>249t`MBgv5hqb18PkY0E`VVG6U~E`19Vvr-uutL909hy)*o5of1u^t={vnYpwJ*JY z;Ma{}ly1U+eV)S}Qw0ZNia5bgP(hsH6bi3B1E+|9m_xOvn}TZr_iYrzl>s19K0cn5 zX1P3oS?4+cl=`Te;Wh)v^#G790NB<6tis3U08CDMVG3xC&~B+QCBj@^0MWlE2N3#= z#b&zdSJlG)J_w^_=J(fXY1NFGK4z220c1FR_c+ld7ozjQHNY+iw2NA|8G>3$VIJ5$ z;N-7~0nCj$hBj^L7gV}ly4oy)H9~%~Y2qwq!v&`z1ErNvjLxVsB1YYE|MmxGW}XB% z{M5If7*-!SiHc)b3Sj0tkVXw%tZDbi^M{x30EnVT5T=29hkW?*Vpg#P45d$?jZcr} zVW;^d^M%<`^ttQi`iQ`VLH)3mLKM=F@g?V6BSbZ4#aVin%(O3R3nDvwH`SWJPkk2Z zCO-wmj=?G>%XH<|ZmqUxaCOZE>a|cTdKYg4&R=U$RftOW!0kyofA;m*Z1OrQ#?T#S zSY5=mLHqjs&V2G?_`=fe42Weq6%toY9xSyV=f1x(d3>w19S2}}asc^e%K+kdiw%lF zg(O2yY#HFSh3Rt$&w~81iQt#bZ1%_Z9{Z|FaqOqr>8H(q*J gc5(3K^uLvV0VxHXG$JbHiU0rr07*qoM6N<$f~QWqy#N3J literal 0 HcmV?d00001 diff --git a/swift_block/elevate/README.rst b/swift_block/elevate/README.rst index e007562..40fed2e 100644 --- a/swift_block/elevate/README.rst +++ b/swift_block/elevate/README.rst @@ -7,4 +7,4 @@ root/admin privileges using one of the following mechanisms: - UAC (Windows) - AppleScript (macOS) - ``pkexec``, ``gksudo`` or ``kdesudo`` (Linux) -- ``sudo`` (Linux, macOS) +- ``sudo``, ``doas`` (Linux, macOS) diff --git a/swift_block/elevate/__init__.py b/swift_block/elevate/__init__.py index ccf42cf..55a9a34 100644 --- a/swift_block/elevate/__init__.py +++ b/swift_block/elevate/__init__.py @@ -1,7 +1,7 @@ import sys -def elevate(show_console=True, graphical=True): +def elevate(file_path,show_console=True, graphical=True): """ Re-launch the current process with root/admin privileges @@ -19,5 +19,5 @@ def elevate(show_console=True, graphical=True): from .windows import elevate else: from .posix import elevate - elevate(show_console, graphical) + elevate(file_path,show_console, graphical) diff --git a/swift_block/elevate/posix.py b/swift_block/elevate/posix.py index 0d61535..d4d3b34 100644 --- a/swift_block/elevate/posix.py +++ b/swift_block/elevate/posix.py @@ -22,11 +22,11 @@ def quote_applescript(string): return '"%s"' % "".join(charmap.get(char, char) for char in string) -def elevate(show_console=True, graphical=True): +def elevate(file_path,show_console=True, graphical=True): if os.getuid() == 0: return - args = [sys.executable, os.path.realpath(sys.argv[0]), *sys.argv[1:]] + args = [sys.executable, os.path.realpath(file_path)] commands = [] if graphical: @@ -39,14 +39,13 @@ def elevate(show_console=True, graphical=True): "without altering line endings" % quote_applescript(quote_shell(args))]) - if sys.platform.startswith("linux"): - print("pkexec env DISPLAY={0} XAUTHORITY={1}".format(os.environ.get("DISPLAY"),os.environ.get("XAUTHORITY"))) - print("Who am i?",os.getuid()) + if sys.platform.startswith("linux") or sys.platform.startswith("freebsd"): commands.append(["pkexec"]+["env"]+["DISPLAY="+os.environ.get("DISPLAY")]+["XAUTHORITY="+os.environ.get("XAUTHORITY")]+args) commands.append(["gksudo"] + args) commands.append(["kdesudo"] + args) commands.append(["sudo"] + args) + commands.append(["doas"] + args) for args in commands: try: diff --git a/swift_block/elevate/windows.py b/swift_block/elevate/windows.py index 3098de3..33292bf 100644 --- a/swift_block/elevate/windows.py +++ b/swift_block/elevate/windows.py @@ -63,7 +63,7 @@ def __init__(self, **kw): # At last, the actual implementation! -def elevate(show_console=True, graphical=True): +def elevate(file_path,show_console=True, graphical=True): if windll.shell32.IsUserAnAdmin(): return @@ -73,7 +73,7 @@ def elevate(show_console=True, graphical=True): lpVerb=b'runas', lpFile=sys.executable.encode('cp1252'), lpParameters=subprocess.list2cmdline( - [realpath(sys.argv[0]), *sys.argv[1:]] + [realpath(file_path), *sys.argv[1:]] ).encode('cp1252'), nShow=int(show_console)) diff --git a/swift_block/main.py b/swift_block/main.py index 263a7e8..11005af 100644 --- a/swift_block/main.py +++ b/swift_block/main.py @@ -1,12 +1,20 @@ +''' +Copyright (C) 2021 xploreinfinity + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + from PyQt6 import QtWidgets,QtCore,QtGui,uic import sys,os,inspect -import Parser -import elevate -import RuleManager +from swift_block import Parser +#from swift_block import elevate +from swift_block import RuleManager class Ui(QtWidgets.QWidget): def __init__(self): - #*Privilege escalation - elevate.elevate() super().__init__() #*load the ui file self.scriptPath=os.path.abspath(os.path.dirname(inspect.getsourcefile(lambda:0))).replace('\\','/') @@ -20,7 +28,7 @@ def __init__(self): self.editMode=True #*controls whether the source editing form is set to edit source mode or add source mode self.selectedSource=''#*Will store QListWidgetItem that is currently selected by the user self.sourceDct={}#*Stores source names and corresponding source URLs for display in the source edit form when user clicks on a source - + self.SignalSlotConfig() self.show() self.reconf_ui() @@ -45,7 +53,9 @@ def reconf_ui(self): self.gitRepo_btn.setIcon(QtGui.QIcon(self.scriptPath+'/assets/github.svg')) self.gitRepo_btn.setIconSize(QtCore.QSize(35,50)) self.license_btn.setIcon(QtGui.QIcon(self.scriptPath+'/assets/license.png')) - self.license_btn.setIconSize(QtCore.QSize(30,30)) + self.license_btn.setIconSize(QtCore.QSize(30,30)) + self.uninstall_btn.setIcon(QtGui.QIcon(self.scriptPath+'/assets/uninstall.png')) + self.uninstall_btn.setIconSize(QtCore.QSize(30,30)) self.appIcon_lbl.setStyleSheet("border-image:url("+self.scriptPath+"/assets/app_icon.svg);") #*Several utility functions that prevent code repetition: @@ -87,7 +97,7 @@ def loadStatus(self): self.background_lbl.setStyleSheet("background-image:url('"+self.scriptPath+"/assets/autumn.png');") self.statusShield_lbl.setStyleSheet("border-image:url('"+self.scriptPath+"/assets/inactive_shield.svg');") - + #*fetches and shows user's host sources on the sourcesList: def loadSrcData(self): self.sourcesList.clear() @@ -100,9 +110,9 @@ def loadSrcData(self): #*Reset the form and disable said form and the delete btn: self.sourceName_tf.setText('') self.sourceURL_tf.setText('') - self.sourcesForm_widget.setDisabled(True) + self.sourcesForm_widget.setDisabled(True) self.sourceDelete_btn.setDisabled(True) - + #*A vital function that assigns all widgets handlers(slots) for specific events(signals): def SignalSlotConfig(self): #*for events occurring in status tab: @@ -117,7 +127,8 @@ def SignalSlotConfig(self): #*for events occurring in the about tab: self.gitRepo_btn.clicked.connect(self.gitRepo_btnClicked) self.license_btn.clicked.connect(self.license_btnClicked) - + self.uninstall_btn.clicked.connect(self.uninstall_btnClicked) + #*SLOTS FOR EACH SIGNAL BELOW: #*slots for status tab: #*Enables or disables swiftblock: @@ -135,11 +146,11 @@ def toggleStatusClicked(self): #*Reload the status to reflect the change in the GUI: self.loadStatus() - #*Opens the rule manager window: + #*Opens the rule manager window: def openRuleManager(self): self.rm=RuleManager.RuleManager(self.scriptPath) self.close() - + #*Updates the sources(fetches them fro their origin) and then regenerates hosts file: def updateSourcesClicked(self): @@ -187,7 +198,7 @@ def addBtnClicked(self): self.formStatus_lbl.hide() def deleteBtnClicked(self): - #*ensure that a source from the list is selected,warn the user otherwise: + #*ensure that a source from the list is selected,warn the user otherwise: if not self.selectedSource: self.showStatus_lbl("Select a source from the list first!",self.formStatus_lbl) else: @@ -204,7 +215,7 @@ def deleteBtnClicked(self): self.err_msg(err) self.showStatus_lbl("Oops! An error occurred",self.formStatus_lbl) self.sourceDelete_btn.setDisabled(True) - + def sourceSaveBtnClicked(self): srcName=self.sourceName_tf.text() srcURL=self.sourceURL_tf.text() @@ -212,7 +223,7 @@ def sourceSaveBtnClicked(self): self.formStatus_lbl.setStyleSheet("color:white;background-color:crimson;font-weight:bold") self.formStatus_lbl.setText("Fields can't be empty!") self.formStatus_lbl.show() - #*ensure that a source from the list is selected,warn the user otherwise: + #*ensure that a source from the list is selected,warn the user otherwise: elif not self.selectedSource and self.editMode: self.showStatus_lbl("Select a source from the list first!",self.formStatus_lbl) else: @@ -227,10 +238,10 @@ def sourceSaveBtnClicked(self): except Exception as err: self.err_msg(err) self.showStatus_lbl("Oops! An error occurred",self.formStatus_lbl) - #*calling this here incase the editing of the source succeeded but something else failed:(which would effectively make existing sourceList entries old and obsolete) + #*calling this here incase the editing of the source succeeded but something else failed:(which would effectively make existing sourceList entries old and obsolete) self.loadSrcData() - - + + else: try: self.parser.add_source(srcName,srcURL) @@ -239,12 +250,12 @@ def sourceSaveBtnClicked(self): except Exception as err: self.err_msg(err) self.showStatus_lbl("Oops! An error occurred",self.formStatus_lbl) - #*calling this here incase the adding of the source succeeded but something else failed:(which would effectively make existing sourceList entries old and obsolete) + #*calling this here incase the adding of the source succeeded but something else failed:(which would effectively make existing sourceList entries old and obsolete) self.loadSrcData() - + #*re-enable the save btn self.sourceSave_btn.setDisabled(False) - + #*Slots for the about tab: def gitRepo_btnClicked(self): import webbrowser @@ -259,5 +270,20 @@ def license_btnClicked(self): webbrowser.open("https://github.com/XploreInfinity/swift-block/blob/main/LICENSE") else: self.err_msg("To see the license,visit 'https://github.com/XploreInfinity/swift-block/blob/main/LICENSE' in your web-browser.\n\nSince swiftblock runs as root/administrator user,we can't(safely)open it for you") - + def uninstall_btnClicked(self): + #*Ask the user if they really want to uninstall swift-block: + question=QtWidgets.QMessageBox(QtWidgets.QMessageBox.Icon.Question,"Please Confirm","Swift Block will be removed along with its menu launcher. Are you sure you want to continue?",(QtWidgets.QMessageBox.StandardButton.Yes|QtWidgets.QMessageBox.StandardButton.No)) + confirm=question.exec() + if confirm==QtWidgets.QMessageBox.StandardButton.Yes: + try: + #*Call the uninstaller: + self.parser.uninstall() + except Exception as err: + #*Unfortunately, windows doesnt allow running programs to be deleted. So uninstalling swiftblock from within swiftblock will certainly fail + #*Pip will uninstall the package,but some .exe files will persist in the %TEMP% directory. This is a trivial faliure and not worth asking + #*the user to manually uninstall swiftblock from cmd. Exit swiftblock and call it a day: + if sys.platform.startswith('win32'): + print(err) + exit() + self.err_msg(str(err)+'\nTry uninstalling swift-block using pip from your terminal/cmd') diff --git a/swift_block/main.py.bak b/swift_block/main.py.bak deleted file mode 100644 index 0a6d2f7..0000000 --- a/swift_block/main.py.bak +++ /dev/null @@ -1,263 +0,0 @@ -from PyQt6 import QtWidgets,QtCore,QtGui,uic -import sys,os,inspect -import Parser -import elevate -import RuleManager -class Ui(QtWidgets.QWidget): - def __init__(self): - #*Privilege escalation - elevate.elevate() - super().__init__() - #*load the ui file - self.scriptPath=os.path.abspath(os.path.dirname(inspect.getsourcefile(lambda:0))).replace('\\','/') - uic.loadUi(self.scriptPath+"/ui/adblock.ui",self) - #*init the library for interacting with host sources,etc... - #!WARNING:This changes the current directory - self.parser=Parser.Parser() - - #*GLOBAL VARIABLES DECLARATION: - self.status=False #*Flag which indicates whether swiftblock is enabled or disabled - self.editMode=True #*controls whether the source editing form is set to edit source mode or add source mode - self.selectedSource=''#*Will store QListWidgetItem that is currently selected by the user - self.sourceDct={}#*Stores source names and corresponding source URLs for display in the source edit form when user clicks on a source - - self.SignalSlotConfig() - self.show() - self.reconf_ui() - - def reconf_ui(self): - self.setWindowIcon(QtGui.QIcon(self.scriptPath+"/assets/app_icon.svg")) - #*Reconfig for the status tab: - self.loadStatus() - self.manageRules_btn.setIcon(QtGui.QIcon(self.scriptPath+"/assets/ruleMan.svg")) - self.manageRules_btn.setIconSize(QtCore.QSize(30,30)) - self.updateSources_btn.setIcon(QtGui.QIcon(self.scriptPath+"/assets/sources_update.png")) - self.updateSources_btn.setIconSize(QtCore.QSize(35,35)) - #*Reconfig for the sources tab: - self.loadSrcData() - self.sourcesForm_widget.setDisabled(True) - self.sourceDelete_btn.setDisabled(True) - self.editMode_lbl.hide() - self.formStatus_lbl.hide() - self.sourceName_tf.setPlaceholderText("Unqiue nickname for the source") - self.sourceURL_tf.setPlaceholderText("Unique URL of the source") - #*Reconfig for the about tab: - self.gitRepo_btn.setIcon(QtGui.QIcon(self.scriptPath+'/assets/github.svg')) - self.gitRepo_btn.setIconSize(QtCore.QSize(35,50)) - self.license_btn.setIcon(QtGui.QIcon(self.scriptPath+'/assets/license.png')) - self.license_btn.setIconSize(QtCore.QSize(30,30)) - self.appIcon_lbl.setStyleSheet("border-image:url("+self.scriptPath+"/assets/app_icon.svg);") - - #*Several utility functions that prevent code repetition: - #*Shows an error MessageBox which informs the user of the error(and provides additional info): - def err_msg(self,err): - msg=QtWidgets.QMessageBox() - msg.setWindowTitle("Error") - msg.setIcon(QtWidgets.QMessageBox.Icon.Critical) - msg.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Ok) - msg.setText("Oops! An error occurred. Additional info is provided below") - msg.setDetailedText(str(err)) - msg.exec() - - #*Displays a success/error message on the label passed as an argument: - def showStatus_lbl(self,message,lbl,success=False): - if success: - lbl.setStyleSheet("color:black;background-color:limegreen;font-weight:bold") - else: - lbl.setStyleSheet("color:white;background-color:crimson;font-weight:bold") - lbl.setText(message) - lbl.show() - - #*load the status of the adblocker(whether active,no. of hosts blocked/redirected/allowed): - def loadStatus(self): - blocked,redirected,allowed,self.status=self.parser.getStatus() - if blocked !=None and redirected!=None and allowed !=None: - self.blockedCount_lbl.setText(str(blocked)) - self.redirectedCount_lbl.setText(str(redirected)) - self.allowedCount_lbl.setText(str(allowed)) - #*Based on swiftblock's status,change the gui accordingly: - if self.status: - self.status_lbl.setText("SwiftBlock is enabled") - self.toggleStatus_btn.setText("Disable") - self.background_lbl.setStyleSheet("background-image:url('"+self.scriptPath+"/assets/martini.png');") - self.statusShield_lbl.setStyleSheet("border-image:url('"+self.scriptPath+"/assets/active_shield.svg');") - else: - self.status_lbl.setText("SwiftBlock is disabled") - self.toggleStatus_btn.setText("Enable") - self.background_lbl.setStyleSheet("background-image:url('"+self.scriptPath+"/assets/autumn.png');") - self.statusShield_lbl.setStyleSheet("border-image:url('"+self.scriptPath+"/assets/inactive_shield.svg');") - - - #*fetches and shows user's host sources on the sourcesList: - def loadSrcData(self): - self.sourcesList.clear() - self.selectedSource=None - sources=self.parser.fetch_sources() - self.sourceDct={} - for source in sources: - self.sourcesList.addItem(source[0]) - self.sourceDct[source[0]]=source[1] - #*Reset the form and disable said form and the delete btn: - self.sourceName_tf.setText('') - self.sourceURL_tf.setText('') - self.sourcesForm_widget.setDisabled(True) - self.sourceDelete_btn.setDisabled(True) - - #*A vital function that assigns all widgets handlers(slots) for specific events(signals): - def SignalSlotConfig(self): - #*for events occurring in status tab: - self.toggleStatus_btn.clicked.connect(self.toggleStatusClicked) - self.manageRules_btn.clicked.connect(self.openRuleManager) - self.updateSources_btn.clicked.connect(self.updateSourcesClicked) - #*for events occurring in sources tab: - self.sourcesList.selectionModel().currentChanged.connect(self.sourceSelected) - self.sourceAdd_btn.clicked.connect(self.addBtnClicked) - self.sourceDelete_btn.clicked.connect(self.deleteBtnClicked) - self.sourceSave_btn.clicked.connect(self.sourceSaveBtnClicked) - #*for events occurring in the about tab: - self.gitRepo_btn.clicked.connect(self.gitRepo_btnClicked) - self.license_btn.clicked.connect(self.license_btnClicked) - - #*SLOTS FOR EACH SIGNAL BELOW: - #*slots for status tab: - #*Enables or disables swiftblock: - def toggleStatusClicked(self): - #*If swiftblock is enabled, disable it: - if self.status: - self.status=False - self.parser.write_changes(purge=True) - #*Reload the status to reflect the change in the GUI: - self.loadStatus() - #*If swiftblock is disabled, enable it: - else: - self.status=False - self.parser.write_changes() - #*Reload the status to reflect the change in the GUI: - self.loadStatus() - - #*Opens the rule manager window: - def openRuleManager(self): - self.rm=RuleManager.RuleManager(self.scriptPath) - self.hide() - - - #*Updates the sources(fetches them fro their origin) and then regenerates hosts file: - def updateSourcesClicked(self): - try: - self.parser.generateSourceRules(updateSources=True) - self.parser.regen_hosts() - self.loadStatus()#*Refresh the blocked/redirected/allowed counts after the update - #*Inform the user that the update succeeded: - msg=QtWidgets.QMessageBox() - msg.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Ok) - msg.setWindowTitle("Success") - msg.setText("Sources updated successfully and changes applied!") - msg.setIcon(QtWidgets.QMessageBox.Icon.Information) - msg.exec() - except Exception as err: - self.err_msg(err) - - #*slots for the sources tab: - def sourceSelected(self,current): - #*Make sure the list isnt empty(which makes selected item None type) - if self.sourcesList.currentItem(): - item=self.sourcesList.currentItem().text() - #*update the value of the selectedSource global var: - self.selectedSource=item - self.sourceName_tf.setText(item) - self.sourceURL_tf.setText(self.sourceDct[item]) - #*Activate the form and show the user that they're now editing a source: - self.sourcesForm_widget.setDisabled(False) - self.editMode=True - self.editMode_lbl.setText("Editing an existing source:") - self.editMode_lbl.show() - #*also hide previous status messages and enable the delete btn: - self.formStatus_lbl.hide() - self.sourceDelete_btn.setDisabled(False) - - def addBtnClicked(self): - #*Clear(and enable if disabled) the form and change the editMode to add mode: - self.sourcesForm_widget.setDisabled(False) - self.editMode_lbl.show() - self.sourceName_tf.setText('') - self.sourceURL_tf.setText('') - self.editMode=False - self.editMode_lbl.setText("Adding a new source:") - #*Also hide any previous messages: - self.formStatus_lbl.hide() - - def deleteBtnClicked(self): - #*ensure that a source from the list is selected,warn the user otherwise: - if not self.selectedSource: - self.showStatus_lbl("Select a source from the list first!",self.formStatus_lbl) - else: - #*Ask the user if they really want to delete the source - question=QtWidgets.QMessageBox(QtWidgets.QMessageBox.Icon.Question,"Please Confirm","Are you sure you want to delete this source?",(QtWidgets.QMessageBox.StandardButton.Yes|QtWidgets.QMessageBox.StandardButton.No)) - confirm=question.exec() - if confirm==QtWidgets.QMessageBox.StandardButton.Yes: - self.sourceDelete_btn.setDisabled(True) - try: - self.parser.del_source(self.selectedSource) - self.loadSrcData() - self.showStatus_lbl("Deleted source successfully!",self.formStatus_lbl,True) - except Exception as err: - self.err_msg(err) - self.showStatus_lbl("Oops! An error occurred",self.formStatus_lbl) - self.sourceDelete_btn.setDisabled(True) - - def sourceSaveBtnClicked(self): - srcName=self.sourceName_tf.text() - srcURL=self.sourceURL_tf.text() - if(srcName=="" or srcURL==""): - self.formStatus_lbl.setStyleSheet("color:white;background-color:crimson;font-weight:bold") - self.formStatus_lbl.setText("Fields can't be empty!") - self.formStatus_lbl.show() - #*ensure that a source from the list is selected,warn the user otherwise: - elif not self.selectedSource and self.editMode: - self.showStatus_lbl("Select a source from the list first!",self.formStatus_lbl) - else: - #*Save button disabled to prevent multiple save attempts at once - self.sourceSave_btn.setDisabled(True) - #*Check the mode[editMode when True is to edit an existing source and to add a new source when False] - if self.editMode: - try: - self.parser.edit_source(self.selectedSource,srcName,self.sourceDct[self.selectedSource],srcURL) - self.loadSrcData() - self.showStatus_lbl("Edited source successfully!",self.formStatus_lbl,True) - except Exception as err: - self.err_msg(err) - self.showStatus_lbl("Oops! An error occurred",self.formStatus_lbl) - #*calling this here incase the editing of the source succeeded but something else failed:(which would effectively make existing sourceList entries old and obsolete) - self.loadSrcData() - - - else: - try: - self.parser.add_source(srcName,srcURL) - self.loadSrcData() - self.showStatus_lbl("Added source successfully!",self.formStatus_lbl,True) - except Exception as err: - self.err_msg(err) - self.showStatus_lbl("Oops! An error occurred",self.formStatus_lbl) - #*calling this here incase the adding of the source succeeded but something else failed:(which would effectively make existing sourceList entries old and obsolete) - self.loadSrcData() - - #*re-enable the save btn - self.sourceSave_btn.setDisabled(False) - - #*Slots for the about tab: - def gitRepo_btnClicked(self): - import webbrowser - if sys.platform.startswith('win32'): - webbrowser.open("https://github.com/XploreInfinity/swift-block") - else: - self.err_msg("To see the Git Repo,visit 'https://github.com/XploreInfinity/swift-block' in your web-browser.\n\nSince swiftblock runs as root/administrator user,we can't(safely)open it for you") - - def license_btnClicked(self): - import webbrowser - if sys.platform.startswith('win32'): - webbrowser.open("https://github.com/XploreInfinity/swift-block/blob/main/LICENSE") - else: - self.err_msg("To see the license,visit 'https://github.com/XploreInfinity/swift-block/blob/main/LICENSE' in your web-browser.\n\nSince swiftblock runs as root/administrator user,we can't(safely)open it for you") - - diff --git a/swift_block/ui/adblock.ui b/swift_block/ui/adblock.ui index e32ab36..55bf212 100644 --- a/swift_block/ui/adblock.ui +++ b/swift_block/ui/adblock.ui @@ -1,4 +1,16 @@ + + + Form @@ -180,14 +192,14 @@ - 470 + 490 0 71 20 - v0.2-Beta + v0.3-Beta Qt::AlignCenter @@ -555,7 +567,7 @@ to edit or delete. - v0.2-Beta + v0.3-Beta Qt::AlignCenter @@ -583,7 +595,7 @@ to edit or delete. - 160 + 90 300 101 41 @@ -634,7 +646,7 @@ to edit or delete. - 300 + 230 300 101 41 @@ -650,6 +662,25 @@ to edit or delete. + + + + 370 + 300 + 101 + 41 + + + + Uninstall + + + + 0 + 0 + + + diff --git a/swift_block/ui/filters.ui b/swift_block/ui/filters.ui index 220476b..1f12a5e 100644 --- a/swift_block/ui/filters.ui +++ b/swift_block/ui/filters.ui @@ -1,4 +1,16 @@ + + + Form