Autsadin commited on
Commit
a82dbc4
·
verified ·
1 Parent(s): 901af3a

Upload 4 files

Browse files
Files changed (4) hide show
  1. adviceprinter.py +211 -0
  2. logo.ico +0 -0
  3. readme +159 -0
  4. requirements.txt +24 -0
adviceprinter.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # adviceprinter2.py
2
+ import sys
3
+ import os
4
+ import platform
5
+ import subprocess
6
+ import shutil
7
+ from pathlib import Path
8
+ import socket
9
+ from fastapi import FastAPI,Body
10
+ import uvicorn
11
+ from smartcard.System import readers
12
+
13
+ # ---------------------------
14
+ # Redirect stdout/stderr (for PyInstaller windowed)
15
+ # ---------------------------
16
+ if sys.stdout is None:
17
+ sys.stdout = open(os.devnull, 'w')
18
+ if sys.stderr is None:
19
+ sys.stderr = open(os.devnull, 'w')
20
+
21
+ # ---------------------------
22
+ # Utility for macOS login items
23
+ # ---------------------------
24
+ def add_to_login_items(app_path):
25
+ """Add macOS app to Login Items using AppleScript."""
26
+ absolute_app_path = os.path.abspath(app_path)
27
+ if not os.path.exists(absolute_app_path):
28
+ print(f"Error: Application not found at '{absolute_app_path}'")
29
+ return
30
+
31
+ script = f'''
32
+ tell application "System Events"
33
+ make new login item at end with properties {{path:"{absolute_app_path}", hidden:false}}
34
+ end tell
35
+ '''
36
+ try:
37
+ subprocess.run(
38
+ ['osascript', '-e', script],
39
+ check=True,
40
+ capture_output=True,
41
+ text=True
42
+ )
43
+ print("✅ Successfully added to Login Items.")
44
+ except subprocess.CalledProcessError as e:
45
+ print(f"Error adding to Login Items: {e.stderr}")
46
+ except FileNotFoundError:
47
+ print("Error: 'osascript' not found. Must be run on macOS.")
48
+
49
+ # ---------------------------
50
+ # Thai Smart Card Utilities
51
+ # ---------------------------
52
+ def thai2unicode(data):
53
+ resp = ''
54
+ if isinstance(data, list):
55
+ resp = bytes(data).decode('tis-620').replace('#', ' ')
56
+ return resp.strip()
57
+ else:
58
+ return data
59
+
60
+ def getData(connection, cmd, req=[0x00, 0xc0, 0x00, 0x00]):
61
+ data, sw1, sw2 = connection.transmit(cmd)
62
+ data, sw1, sw2 = connection.transmit(req + [cmd[-1]])
63
+ return [data, sw1, sw2]
64
+
65
+ # Command sets
66
+ SELECT = [0x00, 0xA4, 0x04, 0x00, 0x08]
67
+ THAI_CARD = [0xA0, 0x00, 0x00, 0x00, 0x54, 0x48, 0x00, 0x01]
68
+ CMD_CID = [0x80, 0xb0, 0x00, 0x04, 0x02, 0x00, 0x0d]
69
+ CMD_THFULLNAME = [0x80, 0xb0, 0x00, 0x11, 0x02, 0x00, 0x64]
70
+ CMD_ENFULLNAME = [0x80, 0xb0, 0x00, 0x75, 0x02, 0x00, 0x64]
71
+ CMD_BIRTH = [0x80, 0xb0, 0x00, 0xD9, 0x02, 0x00, 0x08]
72
+ CMD_GENDER = [0x80, 0xb0, 0x00, 0xE1, 0x02, 0x00, 0x01]
73
+ CMD_ISSUER = [0x80, 0xb0, 0x00, 0xF6, 0x02, 0x00, 0x64]
74
+ CMD_ISSUE = [0x80, 0xb0, 0x01, 0x67, 0x02, 0x00, 0x08]
75
+ CMD_EXPIRE = [0x80, 0xb0, 0x01, 0x6F, 0x02, 0x00, 0x08]
76
+ CMD_ADDRESS = [0x80, 0xb0, 0x15, 0x79, 0x02, 0x00, 0x64]
77
+
78
+ # ---------------------------
79
+ # Read Thai ID card data
80
+ # ---------------------------
81
+ def read_card_value():
82
+ try:
83
+ readerList = readers()
84
+ reader = readerList[0]
85
+ connection = reader.createConnection()
86
+ connection.connect()
87
+ except Exception as e:
88
+ print(e)
89
+ return False
90
+
91
+ atr = connection.getATR()
92
+ req = [0x00, 0xc0, 0x00, 0x01] if (atr[0] == 0x3B & atr[1] == 0x67) else [0x00, 0xc0, 0x00, 0x00]
93
+ connection.transmit(SELECT + THAI_CARD)
94
+
95
+ def read_field(cmd):
96
+ try:
97
+ return thai2unicode(getData(connection, cmd, req)[0])
98
+ except:
99
+ return ''
100
+
101
+ cid = read_field(CMD_CID)
102
+ th_fullname = read_field(CMD_THFULLNAME)
103
+ en_fullname = read_field(CMD_ENFULLNAME)
104
+ date_birth = read_field(CMD_BIRTH)
105
+ gender = read_field(CMD_GENDER)
106
+ card_issuer = read_field(CMD_ISSUER)
107
+ issue_date = read_field(CMD_ISSUE)
108
+ expire_date = read_field(CMD_EXPIRE)
109
+ address = read_field(CMD_ADDRESS)
110
+
111
+ address_list = address.split(' ')
112
+ if not address_list or address_list[0] == '':
113
+ return False
114
+
115
+ val1 = val2 = val3 = val4 = ''
116
+ for e_addr in address_list:
117
+ if 'ตำบล' in e_addr:
118
+ val1 = e_addr.replace('ตำบล', '')
119
+ elif 'อำเภอ' in e_addr:
120
+ val2 = e_addr.replace('อำเภอ', '')
121
+ elif 'จังหวัด' in e_addr:
122
+ val3 = e_addr.replace('จังหวัด', '')
123
+ val4 = ' '.join(address_list[:-3]).strip()
124
+
125
+ if cid == '':
126
+ return False
127
+
128
+ return {
129
+ 'id13': cid,
130
+ 'th_fullname': th_fullname,
131
+ 'en_fullname': en_fullname,
132
+ 'date_birth': date_birth,
133
+ 'gender': gender,
134
+ 'card_issuer': card_issuer,
135
+ 'issue_date': issue_date,
136
+ 'expire_date': expire_date,
137
+ 'address': val4,
138
+ 'subdistrict': val1,
139
+ 'district': val2,
140
+ 'province': val3
141
+ }
142
+
143
+ # ---------------------------
144
+ # Setup for Windows & macOS
145
+ # ---------------------------
146
+ def run_initial():
147
+ system = platform.system()
148
+ if system == "Windows":
149
+ new_folder = Path("C:/Adviceprinter")
150
+ new_folder.mkdir(parents=True, exist_ok=True)
151
+
152
+ exe_file = Path.home() / "Downloads" / "Adviceprinter2.exe"
153
+ start_path = Path.home() / "AppData" / "Roaming" / "Microsoft" / "Windows" / "Start Menu" / "Programs" / "Startup"
154
+ if exe_file.exists():
155
+ shutil.copy(exe_file, new_folder)
156
+ shutil.copy(exe_file, start_path)
157
+ print(f"✅ Copied {exe_file.name} to {new_folder}")
158
+ else:
159
+ print(f"⚠️ {exe_file} not found in Downloads.")
160
+ elif system == "Darwin": # macOS
161
+ app_path = "/Applications/Adviceprinter2.app"
162
+ add_to_login_items(app_path)
163
+ else:
164
+ print("Unsupported OS detected. Exiting.")
165
+ exit()
166
+
167
+ # ---------------------------
168
+ # FastAPI Application
169
+ # ---------------------------
170
+ app = FastAPI(title="Adviceprinter2")
171
+
172
+ @app.get("/")
173
+ def service_active():
174
+ return {"status": "running", "message": "Adviceprinter2 is running."}
175
+
176
+ @app.get("/check_card")
177
+ def check_card():
178
+ try:
179
+ out = read_card_value()
180
+ return {"status": "success", "value": out} if out else {"status": "warning", "message": "No smart card inserted."}
181
+ except Exception as e:
182
+ return {"status": "error", "message": f"Unexpected error: {str(e)}"}
183
+
184
+ @app.get("/get_computername")
185
+ def device_hostname():
186
+ try:
187
+ hostname = socket.gethostname()
188
+ return {"status": "success", "computername": hostname}
189
+ except Exception as e:
190
+ return {"status": "error", "message": f"Unexpected error: {str(e)}"}
191
+
192
+ # ---------------------------
193
+ # Main Entry
194
+ # ---------------------------
195
+ if __name__ == "__main__":
196
+ system = platform.system()
197
+ if system == "Windows":
198
+ config_folder = Path("C:/Adviceprinter")
199
+ elif system == "Darwin":
200
+ config_folder = Path.home() / "Library/Application Support/Adviceprinter"
201
+ else:
202
+ print("Unsupported OS.")
203
+ sys.exit(0)
204
+
205
+ if not config_folder.exists():
206
+ run_initial()
207
+ else:
208
+ print("Configuration folder already exists. Skipping setup.")
209
+
210
+ print("🚀 Starting FastAPI server on port 5555...")
211
+ uvicorn.run(app, port=5555)
logo.ico ADDED
readme ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### `README.md`
2
+
3
+ ```markdown
4
+ # Advice Printer for macOS
5
+
6
+ A simple desktop application built with Python that displays helpful advice. This guide provides instructions for setting up the development environment and building a distributable `.dmg` package on macOS.
7
+
8
+ ## 1. Prerequisites
9
+
10
+ Before you begin, ensure you have the following software installed on your macOS system:
11
+
12
+ * **Git:** To clone the repository.
13
+ * **Conda:** For environment management. You can install it via [Anaconda](https://www.anaconda.com/products/distribution) or the more lightweight [Miniconda](https://docs.conda.io/en/latest/miniconda.html).
14
+
15
+ You can verify your Conda installation by running:
16
+ ```bash
17
+ conda --version
18
+ ```
19
+
20
+ ## 2. Developer Setup
21
+
22
+ Follow these steps to set up the project for local development and running from the source code.
23
+
24
+ ### Step 1: Clone the Repository
25
+ Open your terminal and clone this repository to your local machine.
26
+ ```bash
27
+ git clone <your-repository-url>
28
+ cd <repository-directory>
29
+ ```
30
+
31
+ ### Step 2: Create and Activate the Conda Environment
32
+ We'll create an isolated Python environment to manage the project's dependencies. This ensures that the project's packages do not conflict with other Python projects on your system.
33
+
34
+ ```bash
35
+ # Create a new conda environment named 'advice-printer' with Python 3.9
36
+ conda create -n advice-printer python=3.9
37
+
38
+ # Activate the newly created environment
39
+ conda activate advice-printer
40
+ ```
41
+ Your terminal prompt should now be prefixed with `(advice-printer)`.
42
+
43
+ ### Step 3: Install Dependencies
44
+ Install all the required Python packages using the `requirements.txt` file.
45
+
46
+ ```bash
47
+ pip install -r requirements.txt
48
+ ```
49
+
50
+ ### Step 4: Run the Application from Source
51
+ You can now run the application directly to test it.
52
+
53
+ ```bash
54
+ python adviceprinter.py
55
+ ```
56
+
57
+ ## 3. Building the macOS Application (`.dmg`)
58
+
59
+ These instructions will guide you through packaging the Python script into a standalone macOS application (`.app`) and then creating a user-friendly disk image (`.dmg`) for distribution.
60
+
61
+ ### Step 1: Build the `.app` Bundle
62
+ We will use `PyInstaller` to bundle our script and all its dependencies into a single executable application.
63
+
64
+ ```bash
65
+ pyinstaller --onefile --windowed --icon=assets/logo.icns adviceprinter.py
66
+ ```
67
+ **Command Breakdown:**
68
+ * `--onefile`: Bundles everything into a single executable file inside the `.app` package.
69
+ * `--windowed`: Prevents the terminal console window from appearing when the GUI application is launched.
70
+ * `--icon=assets/logo.icns`: Sets the application icon. **Note:** For macOS, it is highly recommended to use a `.icns` file instead of `.ico` for proper display.
71
+
72
+ This command will create `build` and `dist` directories. Your final application, `adviceprinter.app`, will be inside the `dist` directory.
73
+
74
+ ### Step 2: Prepare the Disk Image (`.dmg`) Contents
75
+ A `.dmg` file provides a familiar drag-and-drop installation experience for macOS users. We'll create a temporary folder to stage the contents of our disk image.
76
+
77
+ ```bash
78
+ # Create a staging directory for the DMG contents
79
+ mkdir dmg_content
80
+
81
+ # Copy the generated .app bundle into the staging directory
82
+ cp -R "dist/adviceprinter.app" dmg_content/
83
+
84
+ # Create a symbolic link to the user's Applications folder inside the staging directory
85
+ # This allows the user to drag the app to install it.
86
+ ln -s /Applications dmg_content/Applications
87
+ ```
88
+
89
+ ### Step 3: Create the Final `.dmg` File
90
+ Now, use macOS's built-in `hdiutil` to create the compressed disk image from our staging folder.
91
+
92
+ ```bash
93
+ hdiutil create -volname "Advice Printer" -srcfolder dmg_content -ov -format UDZO "adviceprinter.dmg"
94
+ ```
95
+
96
+ **Command Breakdown:**
97
+ * `-volname "Advice Printer"`: Sets the name of the mounted volume when the user opens the `.dmg`.
98
+ * `-srcfolder dmg_content`: Specifies the source directory we just prepared.
99
+ * `-ov`: Overwrites the DMG file if it already exists.
100
+ * `-format UDZO`: Creates a compressed, read-only disk image, which is ideal for distribution.
101
+
102
+ After this step, you will have a distributable **`adviceprinter.dmg`** file in your project's root directory.
103
+
104
+ ## 4. Cleaning Up
105
+
106
+ After a successful build, you can remove the temporary directories and files created by PyInstaller and the DMG staging process.
107
+
108
+ ```bash
109
+ # WARNING: This command permanently deletes files and directories.
110
+ rm -rf build/ dist/ dmg_content/ adviceprinter.spec
111
+ ```
112
+
113
+ ---
114
+
115
+ ## 5. Installation and First Run (for End-Users)
116
+
117
+ If you have downloaded the `adviceprinter.dmg` file and want to install and run the application, follow these instructions.
118
+
119
+ ### Step 1: Mount the Disk Image
120
+
121
+ 1. Navigate to your `Downloads` folder (or wherever you saved the file).
122
+ 2. Double-click the **`adviceprinter.dmg`** file.
123
+ 3. A new window will appear on your desktop, showing the contents of the disk image. It will contain `adviceprinter.app` and a shortcut to your `Applications` folder.
124
+
125
+
126
+ *(Image for illustrative purposes)*
127
+
128
+ ### Step 2: Install the Application
129
+
130
+ 1. In the new window that appeared, **click and drag the `adviceprinter.app` icon** over to the **`Applications` folder icon**.
131
+ 2. This will copy the application to your main Applications folder, installing it on your system.
132
+ 3. You can now close the disk image window and eject the "Advice Printer" volume from your desktop or Finder sidebar (right-click -> Eject).
133
+
134
+ ### Step 3: Handling the First Launch Security Warning
135
+
136
+ macOS has a security feature called Gatekeeper that blocks apps from unidentified developers by default. Because this app is not yet signed with an Apple Developer ID, you will need to grant it permission to run *one time*.
137
+
138
+ 1. Open your **Applications** folder and double-click **`adviceprinter.app`** to launch it.
139
+ 2. You will likely see a warning dialog that says: *"`adviceprinter.app` cannot be opened because the developer cannot be verified."*
140
+ 3. Click **Cancel** on this dialog.
141
+ 4. Open **System Settings** (the gear icon in your Dock).
142
+ 5. Go to **Privacy & Security** in the sidebar.
143
+ 6. Scroll down to the **Security** section. You will see a message near the bottom that reads:
144
+ > "`adviceprinter.app` was blocked from use because it is not from an identified developer."
145
+ 7. Click the **Open Anyway** button next to this message.
146
+
147
+
148
+ *(Image for illustrative purposes)*
149
+
150
+ 8. You will be prompted to enter your Mac password to confirm.
151
+ 9. A final warning dialog will appear. Click **Open**.
152
+
153
+ The application will now launch.
154
+
155
+ ### Step 4: Future Launches
156
+
157
+ Congratulations! You only need to perform the security steps once. From now on, you can open **Advice Printer** directly from your **Applications** folder or Launchpad just like any other app.
158
+
159
+
requirements.txt ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ altgraph==0.17.4
2
+ annotated-doc==0.0.3
3
+ annotated-types==0.7.0
4
+ anyio==4.11.0
5
+ click==8.3.0
6
+ fastapi==0.120.1
7
+ h11==0.16.0
8
+ idna==3.11
9
+ macholib==1.16.3
10
+ packaging==25.0
11
+ pillow==12.0.0
12
+ pip==25.2
13
+ pydantic==2.12.3
14
+ pydantic_core==2.41.4
15
+ pyinstaller==6.16.0
16
+ pyinstaller-hooks-contrib==2025.9
17
+ pyscard==2.3.0
18
+ setuptools==80.9.0
19
+ sniffio==1.3.1
20
+ starlette==0.49.1
21
+ typing_extensions==4.15.0
22
+ typing-inspection==0.4.2
23
+ uvicorn==0.38.0
24
+ wheel==0.45.1