rhul-attendance-bot 0.1.48__tar.gz → 0.1.49__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {rhul_attendance_bot-0.1.48/rhul_attendance_bot.egg-info → rhul_attendance_bot-0.1.49}/PKG-INFO +29 -14
  2. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/README.md +28 -13
  3. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/README_CN.md +28 -13
  4. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/RHUL_attendance_bot.py +51 -3
  5. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/app_paths.py +48 -0
  6. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/auto_login.py +24 -6
  7. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/display_manager.py +1 -0
  8. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/fetch_ics.py +75 -2
  9. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/pyproject.toml +1 -1
  10. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49/rhul_attendance_bot.egg-info}/PKG-INFO +29 -14
  11. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/LICENSE +0 -0
  12. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/MANIFEST.in +0 -0
  13. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/assets/discord_bot_screenshot.png +0 -0
  14. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/assets/ui_screenshot.png +0 -0
  15. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/discord_broadcast.py +0 -0
  16. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/local_2fa.py +0 -0
  17. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/rhul_attendance_bot.egg-info/SOURCES.txt +0 -0
  18. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/rhul_attendance_bot.egg-info/dependency_links.txt +0 -0
  19. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/rhul_attendance_bot.egg-info/entry_points.txt +0 -0
  20. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/rhul_attendance_bot.egg-info/requires.txt +0 -0
  21. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/rhul_attendance_bot.egg-info/top_level.txt +0 -0
  22. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/setup.cfg +0 -0
  23. {rhul_attendance_bot-0.1.48 → rhul_attendance_bot-0.1.49}/update.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rhul-attendance-bot
3
- Version: 0.1.48
3
+ Version: 0.1.49
4
4
  Summary: RHUL attendance automation bot
5
5
  Author: PandaQuQ
6
6
  License:
@@ -77,13 +77,26 @@ The RHUL Attendance Bot automates attendance marking for Royal Holloway students
77
77
 
78
78
  Install Google Chrome if you don't have it yet: [download page](https://www.google.com/chrome/) (macOS users can also `brew install --cask google-chrome`).
79
79
 
80
+ Choose one of the two modes below:
81
+
82
+ 1) **Recommended: Install via pip**
83
+ 2) **Run from source**
84
+
85
+ ### Mode 1: Install via pip (recommended)
86
+
87
+ ```bash
88
+ pip install rhul-attendance-bot
89
+ ```
90
+
91
+ ### Mode 2: Run from source
92
+
80
93
  ### Step 1: Clone the Repository
81
94
 
82
95
  ```bash
83
96
  git clone https://github.com/PandaQuQ/RHUL_attendance_bot.git
84
97
  ```
85
98
 
86
- ### Step 2: Navigate to the Project Directory
99
+ ### Step 2: Navigate to the Project Directory (source install only)
87
100
 
88
101
  ```bash
89
102
  cd RHUL_attendance_bot
@@ -103,15 +116,7 @@ python3 -m venv venv
103
116
  source venv/bin/activate
104
117
  ```
105
118
 
106
- ### Step 4: Install Dependencies
107
-
108
- #### Option A: Install from PyPI (recommended)
109
-
110
- ```bash
111
- pip install rhul-attendance-bot
112
- ```
113
-
114
- #### Option B: Install from source
119
+ ### Step 4: Install dependencies
115
120
 
116
121
  ```bash
117
122
  pip install -r requirements.txt
@@ -167,11 +172,20 @@ The bot now auto-downloads your timetable on first run and stores it in `ics/`.
167
172
  ```bash
168
173
  rhul-attendance-bot -user Alice
169
174
  ```
170
- - If `-user` is not provided, the app will list existing profiles and ask you to choose (or create a new one).
175
+ - If `-user` is not provided and profiles exist, the app will list them and ask you to choose.
176
+ - If no profiles exist, it starts onboarding automatically and then renames the profile folder to your Profile Nickname.
177
+
178
+ 5. **Cleanup (delete local data)**:
179
+
180
+ - Use `-clean` to remove all local app data under `~/.rhul_attendance_bot`:
181
+ ```bash
182
+ rhul-attendance-bot -clean
183
+ ```
171
184
 
172
- 5. **Keyboard Shortcuts**:
185
+ 6. **Keyboard Shortcuts**:
173
186
 
174
187
  - **Manually Trigger the Next Event**: Press `[`, then `]`
188
+ - **Refresh Calendar (re-fetch ICS)**: Press `[`, then `c`
175
189
  - **Exit the Script**: Press `[`, then `q`
176
190
 
177
191
  ## Important Notes
@@ -220,10 +234,11 @@ Current focus / future ideas:
220
234
  - ✅ **Automatic Login**: Stored credentials + TOTP drive a fully automatic login flow.
221
235
  - ✅ **2FA Code Reading**: OTP is generated locally from your saved secret.
222
236
  - ✅ **Discord Webhook Bot**: Discord webhook notifications for attendance status, login, and lifecycle (optional; disable by leaving webhook URL empty)
237
+ - ✅ **PyPI Release**: Package published and installable via `pip install rhul-attendance-bot`
223
238
 
224
239
  ## License
225
240
 
226
- This project is licensed under the MIT License with an additional clause. See the [LICENSE](LICENSE) file for details.
241
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
227
242
 
228
243
  ## Acknowledgments
229
244
 
@@ -32,13 +32,26 @@ The RHUL Attendance Bot automates attendance marking for Royal Holloway students
32
32
 
33
33
  Install Google Chrome if you don't have it yet: [download page](https://www.google.com/chrome/) (macOS users can also `brew install --cask google-chrome`).
34
34
 
35
+ Choose one of the two modes below:
36
+
37
+ 1) **Recommended: Install via pip**
38
+ 2) **Run from source**
39
+
40
+ ### Mode 1: Install via pip (recommended)
41
+
42
+ ```bash
43
+ pip install rhul-attendance-bot
44
+ ```
45
+
46
+ ### Mode 2: Run from source
47
+
35
48
  ### Step 1: Clone the Repository
36
49
 
37
50
  ```bash
38
51
  git clone https://github.com/PandaQuQ/RHUL_attendance_bot.git
39
52
  ```
40
53
 
41
- ### Step 2: Navigate to the Project Directory
54
+ ### Step 2: Navigate to the Project Directory (source install only)
42
55
 
43
56
  ```bash
44
57
  cd RHUL_attendance_bot
@@ -58,15 +71,7 @@ python3 -m venv venv
58
71
  source venv/bin/activate
59
72
  ```
60
73
 
61
- ### Step 4: Install Dependencies
62
-
63
- #### Option A: Install from PyPI (recommended)
64
-
65
- ```bash
66
- pip install rhul-attendance-bot
67
- ```
68
-
69
- #### Option B: Install from source
74
+ ### Step 4: Install dependencies
70
75
 
71
76
  ```bash
72
77
  pip install -r requirements.txt
@@ -122,11 +127,20 @@ The bot now auto-downloads your timetable on first run and stores it in `ics/`.
122
127
  ```bash
123
128
  rhul-attendance-bot -user Alice
124
129
  ```
125
- - If `-user` is not provided, the app will list existing profiles and ask you to choose (or create a new one).
130
+ - If `-user` is not provided and profiles exist, the app will list them and ask you to choose.
131
+ - If no profiles exist, it starts onboarding automatically and then renames the profile folder to your Profile Nickname.
132
+
133
+ 5. **Cleanup (delete local data)**:
134
+
135
+ - Use `-clean` to remove all local app data under `~/.rhul_attendance_bot`:
136
+ ```bash
137
+ rhul-attendance-bot -clean
138
+ ```
126
139
 
127
- 5. **Keyboard Shortcuts**:
140
+ 6. **Keyboard Shortcuts**:
128
141
 
129
142
  - **Manually Trigger the Next Event**: Press `[`, then `]`
143
+ - **Refresh Calendar (re-fetch ICS)**: Press `[`, then `c`
130
144
  - **Exit the Script**: Press `[`, then `q`
131
145
 
132
146
  ## Important Notes
@@ -175,10 +189,11 @@ Current focus / future ideas:
175
189
  - ✅ **Automatic Login**: Stored credentials + TOTP drive a fully automatic login flow.
176
190
  - ✅ **2FA Code Reading**: OTP is generated locally from your saved secret.
177
191
  - ✅ **Discord Webhook Bot**: Discord webhook notifications for attendance status, login, and lifecycle (optional; disable by leaving webhook URL empty)
192
+ - ✅ **PyPI Release**: Package published and installable via `pip install rhul-attendance-bot`
178
193
 
179
194
  ## License
180
195
 
181
- This project is licensed under the MIT License with an additional clause. See the [LICENSE](LICENSE) file for details.
196
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
182
197
 
183
198
  ## Acknowledgments
184
199
 
@@ -32,13 +32,26 @@ RHUL 自动签到脚本通过使用网页自动化来为 Royal Holloway 学生
32
32
 
33
33
  在安装前请先确保已安装 Google Chrome:可前往 [下载页面](https://www.google.com/chrome/)(macOS 也可使用 `brew install --cask google-chrome`)。
34
34
 
35
+ 请从下面两种模式中选择:
36
+
37
+ 1)**推荐:使用 pip 安装**
38
+ 2)**源码运行**
39
+
40
+ ### 模式 1:使用 pip 安装(推荐)
41
+
42
+ ```bash
43
+ pip install rhul-attendance-bot
44
+ ```
45
+
46
+ ### 模式 2:源码运行
47
+
35
48
  ### 步骤 1:克隆代码仓库
36
49
 
37
50
  ```bash
38
51
  git clone https://github.com/PandaQuQ/RHUL_attendance_bot.git
39
52
  ```
40
53
 
41
- ### 步骤 2:进入项目目录
54
+ ### 步骤 2:进入项目目录(仅源码安装需要)
42
55
 
43
56
  ```bash
44
57
  cd RHUL_attendance_bot
@@ -58,15 +71,7 @@ python3 -m venv venv
58
71
  source venv/bin/activate
59
72
  ```
60
73
 
61
- ### 步骤 4:安装依赖项
62
-
63
- #### 方式 A:通过 PyPI 安装(推荐)
64
-
65
- ```bash
66
- pip install rhul-attendance-bot
67
- ```
68
-
69
- #### 方式 B:从源码安装
74
+ ### 步骤 4:安装依赖
70
75
 
71
76
  ```bash
72
77
  pip install -r requirements.txt
@@ -122,11 +127,20 @@ pip install -r requirements.txt
122
127
  ```bash
123
128
  rhul-attendance-bot -user 用户1
124
129
  ```
125
- - 不传 `-user` 会列出已有 Profile 并让你选择/创建。
130
+ - 不传 `-user` 且已有 Profile 时,会列出并让你选择。
131
+ - 没有任何 Profile 时,会自动进入首次引导,完成后将 Profile 文件夹重命名为你的 Profile Nickname。
132
+
133
+ 5. **清理本地数据**:
134
+
135
+ - 使用 `-clean` 删除 `~/.rhul_attendance_bot` 下的所有本地数据:
136
+ ```bash
137
+ rhul-attendance-bot -clean
138
+ ```
126
139
 
127
- 5. **快捷键说明**:
140
+ 6. **快捷键说明**:
128
141
 
129
142
  - **手动触发下一个事件**:按下 `[` 然后按 `]`
143
+ - **刷新课表(重新获取 ICS)**:按下 `[` 然后按 `c`
130
144
  - **退出脚本**:按下 `[` 然后按 `q`
131
145
 
132
146
  ## 注意事项
@@ -175,10 +189,11 @@ Profile 数据存放在 `~/.rhul_attendance_bot/profiles/<profile_name>` 下,
175
189
  - ✅ **读取 2FA 验证码**:使用本地秘钥生成 OTP 并自动填写。
176
190
  - ✅ **自动登录**:已实现全自动登录流程。
177
191
  - ✅ **Discord Hook Bot**:已支持 Discord Webhook 通知(可选,Webhook 置空即禁用),推送签到状态、登录和生命周期事件
192
+ - ✅ **PyPI 发布**:已发布,可通过 `pip install rhul-attendance-bot` 安装
178
193
 
179
194
  ## 许可证
180
195
 
181
- 本项目采用 MIT 许可证并附加额外条款。有关详细信息,请参阅 [LICENSE](LICENSE) 文件。
196
+ 本项目采用 MIT 许可证。有关详细信息,请参阅 [LICENSE](LICENSE) 文件。
182
197
 
183
198
  ## 鸣谢
184
199
 
@@ -19,6 +19,9 @@ from app_paths import (
19
19
  get_ics_dir,
20
20
  get_chrome_user_data_dir,
21
21
  prompt_select_profile,
22
+ delete_app_data,
23
+ profile_exists,
24
+ list_profiles,
22
25
  )
23
26
 
24
27
  # Create a logger
@@ -85,9 +88,25 @@ def main():
85
88
  import argparse
86
89
 
87
90
  parser = argparse.ArgumentParser(description="RHUL attendance bot")
88
- parser.add_argument("-user", "--user", dest="profile", default=None, help="Profile name")
91
+ parser.add_argument("-user", "--user", dest="profile", default=None, help="Start with profile name")
92
+ parser.add_argument("-clean", action="store_true", help="Delete all local app data")
89
93
  args = parser.parse_args()
90
- profile_name = args.profile if args.profile else prompt_select_profile()
94
+ if args.clean:
95
+ deleted = delete_app_data()
96
+ if deleted:
97
+ print("Local app data removed: ~/.rhul_attendance_bot")
98
+ else:
99
+ print("No local app data found to remove.")
100
+ return
101
+ profiles_before = list_profiles()
102
+
103
+ if args.profile:
104
+ if not profile_exists(args.profile):
105
+ print("Profile not exist.")
106
+ return
107
+ profile_name = args.profile
108
+ else:
109
+ profile_name = prompt_select_profile()
91
110
 
92
111
  script_dir = os.path.dirname(os.path.abspath(__file__))
93
112
  os.chdir(script_dir)
@@ -123,10 +142,22 @@ def main():
123
142
  if first_run:
124
143
  print('First run detected: running onboarding steps...')
125
144
  # Run auto_login.py for first-time login
126
- subprocess.run([sys.executable, os.path.join(script_dir, 'auto_login.py'), '--user', profile_name])
145
+ if profiles_before:
146
+ subprocess.run([sys.executable, os.path.join(script_dir, 'auto_login.py'), '--user', profile_name])
147
+ else:
148
+ subprocess.run([sys.executable, os.path.join(script_dir, 'auto_login.py')])
149
+ profiles_after = list_profiles()
150
+ if len(profiles_after) == 1:
151
+ profile_name = profiles_after[0]
127
152
  # Run fetch_ics.py to get timetable
128
153
  subprocess.run([sys.executable, os.path.join(script_dir, 'fetch_ics.py'), '--user', profile_name])
129
154
 
155
+ # Recompute profile paths if profile_name changed during onboarding
156
+ profile_dir = get_profile_dir(profile_name)
157
+ credentials_path = get_credentials_path(profile_name)
158
+ ics_folder = get_ics_dir(profile_name)
159
+ ics_file = os.path.join(ics_folder, 'student_timetable.ics')
160
+
130
161
  # Now proceed to import the rest of the modules
131
162
  import threading
132
163
  import time
@@ -148,6 +179,7 @@ def main():
148
179
  import ntplib # Used to check system time synchronization
149
180
  from auto_login import attempt_login
150
181
  from update import check_for_updates
182
+ from fetch_ics import fetch_ics_url, refresh_calendar, renew_calendar
151
183
  from discord_broadcast import DiscordBroadcaster
152
184
  from display_manager import DisplayManager
153
185
  # Reuse helpers but add custom MFA fallback below
@@ -377,6 +409,7 @@ def main():
377
409
  if exit_event.wait(timeout=min(sleep_duration, 60)):
378
410
  break
379
411
 
412
+
380
413
  def listen_for_keypress(upcoming_events, exit_event):
381
414
  ctrl_pressed = [False] # Use a mutable object to share state
382
415
 
@@ -396,6 +429,21 @@ def main():
396
429
  threading.Thread(target=automated_function, args=(next_event_time, next_event_name, upcoming_events), daemon=True).start()
397
430
  else:
398
431
  logger.warning("No upcoming events to process.")
432
+ elif key.char == 'c':
433
+ if ctrl_pressed[0]:
434
+ logger.info("Refreshing calendar (fetching ICS)...", extra={"gray": True})
435
+ threading.Thread(
436
+ target=renew_calendar,
437
+ args=(
438
+ upcoming_events,
439
+ events_lock,
440
+ profile_name,
441
+ load_calendar,
442
+ get_upcoming_events,
443
+ logger,
444
+ ),
445
+ daemon=True,
446
+ ).start()
399
447
  elif key.char == 'q':
400
448
  if ctrl_pressed[0]:
401
449
  logger.info("Exit shortcut pressed. Terminating the script.")
@@ -26,6 +26,32 @@ def get_profile_dir(profile_name=None):
26
26
  return profile_dir
27
27
 
28
28
 
29
+ def profile_exists(profile_name):
30
+ base = os.path.join(get_app_dir(), PROFILES_DIR_NAME)
31
+ name = _normalize_profile(profile_name)
32
+ return os.path.isdir(os.path.join(base, name))
33
+
34
+
35
+ def rename_profile(old_name, new_name):
36
+ old_norm = _normalize_profile(old_name)
37
+ new_norm = _normalize_profile(new_name)
38
+ if old_norm == new_norm:
39
+ return True
40
+ base = os.path.join(get_app_dir(), PROFILES_DIR_NAME)
41
+ old_path = os.path.join(base, old_norm)
42
+ new_path = os.path.join(base, new_norm)
43
+ if not os.path.isdir(old_path):
44
+ return False
45
+ if os.path.exists(new_path):
46
+ return False
47
+ os.makedirs(base, exist_ok=True)
48
+ try:
49
+ os.rename(old_path, new_path)
50
+ return True
51
+ except Exception:
52
+ return False
53
+
54
+
29
55
  def list_profiles():
30
56
  base = os.path.join(get_app_dir(), PROFILES_DIR_NAME)
31
57
  if not os.path.isdir(base):
@@ -72,3 +98,25 @@ def get_chrome_user_data_dir(profile_name=None):
72
98
  user_dir = os.path.join(get_profile_dir(profile_name), "chrome_user_data")
73
99
  os.makedirs(user_dir, exist_ok=True)
74
100
  return user_dir
101
+
102
+
103
+ def delete_app_data():
104
+ app_dir = get_app_dir()
105
+ if not os.path.isdir(app_dir):
106
+ return False
107
+ for root, dirs, files in os.walk(app_dir, topdown=False):
108
+ for name in files:
109
+ try:
110
+ os.remove(os.path.join(root, name))
111
+ except Exception:
112
+ pass
113
+ for name in dirs:
114
+ try:
115
+ os.rmdir(os.path.join(root, name))
116
+ except Exception:
117
+ pass
118
+ try:
119
+ os.rmdir(app_dir)
120
+ except Exception:
121
+ pass
122
+ return True
@@ -12,7 +12,13 @@ from selenium.webdriver.support import expected_conditions as EC
12
12
  from selenium.webdriver.chrome.options import Options
13
13
  from webdriver_manager.chrome import ChromeDriverManager
14
14
  from local_2fa import bind, get_otp
15
- from app_paths import get_2fa_config_path, get_credentials_path, prompt_select_profile
15
+ from app_paths import (
16
+ get_2fa_config_path,
17
+ get_credentials_path,
18
+ prompt_select_profile,
19
+ profile_exists,
20
+ rename_profile,
21
+ )
16
22
  SECURITY_INFO_URL = 'https://mysignins.microsoft.com/security-info'
17
23
  LOGIN_URL = 'https://mysignins.microsoft.com/security-info'
18
24
 
@@ -157,6 +163,11 @@ def first_time_setup(profile_name=None):
157
163
  payload['discord_webhook_url'] = discord_webhook_url
158
164
  payload['enable_discord_webhook'] = bool(discord_webhook_url) if enable_discord_webhook is None else bool(enable_discord_webhook)
159
165
 
166
+ # Align profile folder name to profile nickname if needed
167
+ if profile_name and profile_nickname and profile_name != profile_nickname:
168
+ rename_profile(profile_name, profile_nickname)
169
+ profile_name = profile_nickname
170
+
160
171
  credentials_path = get_credentials_path(profile_name)
161
172
  with open(credentials_path, 'w') as f:
162
173
  json.dump(payload, f)
@@ -654,7 +665,7 @@ def renew_login(driver, expected_url=None, logger=None, profile_name=None):
654
665
  current_url = driver.current_url
655
666
  page_src = driver.page_source
656
667
  if ('login.microsoftonline.com' in current_url) or ('mysignins.microsoft.com' in current_url) or ('loginfmt' in page_src):
657
- _log(logger, logging.INFO, "Detected Microsoft login page; attempting auto login...")
668
+ _log(logger, logging.INFO, "Detected Microsoft login page; attempting auto login...", gray=True)
658
669
  fill_ms_login(driver, username, password)
659
670
  handle_mfa_code(driver)
660
671
  handle_kmsi(driver)
@@ -665,7 +676,7 @@ def renew_login(driver, expected_url=None, logger=None, profile_name=None):
665
676
  if expected_url:
666
677
  try:
667
678
  WebDriverWait(driver, 60).until(EC.url_contains(expected_url))
668
- _log(logger, logging.INFO, "Login detected via URL match.")
679
+ _log(logger, logging.INFO, "Login detected via URL match.", gray=True)
669
680
  return True
670
681
  except Exception:
671
682
  _log(logger, logging.ERROR if logger else logging.INFO, "Login not detected within timeout.")
@@ -686,7 +697,7 @@ def verify_login(driver, expected_url, max_wait_minutes=30, logger=None):
686
697
  current_url = driver.current_url
687
698
 
688
699
  if current_url == expected_url:
689
- _log(logger, logging.INFO, "Already logged in.")
700
+ _log(logger, logging.INFO, "Already logged in.", gray=True)
690
701
  return True
691
702
  else:
692
703
  _log(logger, logging.INFO, "Need to login.")
@@ -713,7 +724,7 @@ def attempt_login(driver, expected_url, broadcaster=None, logger=None, profile_n
713
724
  _log(logger, logging.INFO, "Existing session is valid; skipping renew_login.", gray=True)
714
725
  return True
715
726
  except Exception:
716
- _log(logger, logging.INFO, "Session check did not confirm login; attempting renew_login.")
727
+ _log(logger, logging.INFO, "Session check did not confirm login; attempting renew_login.", gray=True)
717
728
 
718
729
  result = renew_login(driver, expected_url, logger=logger, profile_name=profile_name)
719
730
  if result:
@@ -733,7 +744,14 @@ if __name__ == '__main__':
733
744
  import argparse
734
745
 
735
746
  parser = argparse.ArgumentParser(description="RHUL auto login setup")
747
+ parser.add_argument("-help", action="help", help="Show this help message and exit")
736
748
  parser.add_argument("-user", "--user", dest="profile", default=None, help="Profile name")
737
749
  args = parser.parse_args()
738
- profile_name = args.profile if args.profile else prompt_select_profile()
750
+ if args.profile:
751
+ if not profile_exists(args.profile):
752
+ print("Profile not exist.")
753
+ raise SystemExit(1)
754
+ profile_name = args.profile
755
+ else:
756
+ profile_name = prompt_select_profile()
739
757
  first_time_setup(profile_name=profile_name)
@@ -229,6 +229,7 @@ class DisplayManager:
229
229
 
230
230
  instructions = Text.from_markup(
231
231
  "Press [yellow][[/yellow] then [yellow]][/yellow] to manually trigger the next event\n"
232
+ "Press [yellow][[/yellow] then [yellow]c[/yellow] to refresh the calendar\n"
232
233
  "Press [yellow][[/yellow] then [yellow]q[/yellow] to exit the script",
233
234
  justify="center",
234
235
  )
@@ -8,7 +8,7 @@ from selenium.webdriver.support.ui import WebDriverWait, Select
8
8
  from selenium.webdriver.support import expected_conditions as EC
9
9
  from selenium.webdriver.chrome.options import Options
10
10
  from webdriver_manager.chrome import ChromeDriverManager
11
- from app_paths import get_credentials_path, get_ics_dir, prompt_select_profile
11
+ from app_paths import get_credentials_path, get_ics_dir, prompt_select_profile, profile_exists
12
12
  TIMETABLE_URL = 'https://webtimetables.royalholloway.ac.uk/'
13
13
 
14
14
 
@@ -93,11 +93,84 @@ def fetch_ics_url(profile_name=None):
93
93
  time.sleep(5)
94
94
  driver.quit()
95
95
 
96
+
97
+ def refresh_calendar(profile_name=None, load_calendar_fn=None, get_upcoming_events_fn=None, logger=None):
98
+ def _log(msg, level="info"):
99
+ if logger:
100
+ try:
101
+ getattr(logger, level)(msg)
102
+ return
103
+ except Exception:
104
+ pass
105
+ print(msg)
106
+
107
+ ics_path = os.path.join(get_ics_dir(profile_name), 'student_timetable.ics')
108
+ try:
109
+ if os.path.exists(ics_path):
110
+ os.remove(ics_path)
111
+ fetch_ics_url(profile_name=profile_name)
112
+
113
+ if load_calendar_fn and get_upcoming_events_fn:
114
+ calendar = load_calendar_fn(ics_path)
115
+ if not calendar:
116
+ _log("Failed to reload calendar after refresh.", level="error")
117
+ return None, ics_path
118
+ new_events = get_upcoming_events_fn(calendar)
119
+ return new_events, ics_path
120
+ return None, ics_path
121
+ except Exception as e:
122
+ _log(f"Failed to refresh calendar: {e}", level="error")
123
+ return None, ics_path
124
+
125
+
126
+ def renew_calendar(upcoming_events, events_lock, profile_name=None, load_calendar_fn=None, get_upcoming_events_fn=None, logger=None):
127
+ new_events, _ = refresh_calendar(
128
+ profile_name=profile_name,
129
+ load_calendar_fn=load_calendar_fn,
130
+ get_upcoming_events_fn=get_upcoming_events_fn,
131
+ logger=logger,
132
+ )
133
+ if new_events is None:
134
+ return
135
+ with events_lock:
136
+ upcoming_events.clear()
137
+ upcoming_events.extend(new_events)
138
+ if new_events:
139
+ next_event_start, next_event_name, _, next_event_end = new_events[0]
140
+ local_next_event_start = next_event_start.astimezone()
141
+ duration = next_event_end - next_event_start
142
+ msg = (
143
+ f"Waiting for next event: [bold magenta]{next_event_name}[/bold magenta] at "
144
+ f"[bold cyan]{local_next_event_start.strftime('%Y-%m-%d %H:%M:%S')}[/bold cyan] "
145
+ f"(duration: [bold green]{str(duration).split('.')[0]}[/bold green])"
146
+ )
147
+ if logger:
148
+ logger.info(msg)
149
+ else:
150
+ print(msg)
151
+ else:
152
+ if logger:
153
+ logger.info("No upcoming events after refresh.")
154
+ else:
155
+ print("No upcoming events after refresh.")
156
+
157
+ if logger:
158
+ logger.info("Calendar refreshed successfully.", extra={"gray": True})
159
+ else:
160
+ print("Calendar refreshed successfully.")
161
+
96
162
  if __name__ == '__main__':
97
163
  import argparse
98
164
 
99
165
  parser = argparse.ArgumentParser(description="RHUL timetable ICS fetcher")
166
+ parser.add_argument("-help", action="help", help="Show this help message and exit")
100
167
  parser.add_argument("-user", "--user", dest="profile", default=None, help="Profile name")
101
168
  args = parser.parse_args()
102
- profile_name = args.profile if args.profile else prompt_select_profile()
169
+ if args.profile:
170
+ if not profile_exists(args.profile):
171
+ print("Profile not exist.")
172
+ raise SystemExit(1)
173
+ profile_name = args.profile
174
+ else:
175
+ profile_name = prompt_select_profile()
103
176
  fetch_ics_url(profile_name=profile_name)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rhul-attendance-bot"
7
- version = "0.1.48"
7
+ version = "0.1.49"
8
8
  description = "RHUL attendance automation bot"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rhul-attendance-bot
3
- Version: 0.1.48
3
+ Version: 0.1.49
4
4
  Summary: RHUL attendance automation bot
5
5
  Author: PandaQuQ
6
6
  License:
@@ -77,13 +77,26 @@ The RHUL Attendance Bot automates attendance marking for Royal Holloway students
77
77
 
78
78
  Install Google Chrome if you don't have it yet: [download page](https://www.google.com/chrome/) (macOS users can also `brew install --cask google-chrome`).
79
79
 
80
+ Choose one of the two modes below:
81
+
82
+ 1) **Recommended: Install via pip**
83
+ 2) **Run from source**
84
+
85
+ ### Mode 1: Install via pip (recommended)
86
+
87
+ ```bash
88
+ pip install rhul-attendance-bot
89
+ ```
90
+
91
+ ### Mode 2: Run from source
92
+
80
93
  ### Step 1: Clone the Repository
81
94
 
82
95
  ```bash
83
96
  git clone https://github.com/PandaQuQ/RHUL_attendance_bot.git
84
97
  ```
85
98
 
86
- ### Step 2: Navigate to the Project Directory
99
+ ### Step 2: Navigate to the Project Directory (source install only)
87
100
 
88
101
  ```bash
89
102
  cd RHUL_attendance_bot
@@ -103,15 +116,7 @@ python3 -m venv venv
103
116
  source venv/bin/activate
104
117
  ```
105
118
 
106
- ### Step 4: Install Dependencies
107
-
108
- #### Option A: Install from PyPI (recommended)
109
-
110
- ```bash
111
- pip install rhul-attendance-bot
112
- ```
113
-
114
- #### Option B: Install from source
119
+ ### Step 4: Install dependencies
115
120
 
116
121
  ```bash
117
122
  pip install -r requirements.txt
@@ -167,11 +172,20 @@ The bot now auto-downloads your timetable on first run and stores it in `ics/`.
167
172
  ```bash
168
173
  rhul-attendance-bot -user Alice
169
174
  ```
170
- - If `-user` is not provided, the app will list existing profiles and ask you to choose (or create a new one).
175
+ - If `-user` is not provided and profiles exist, the app will list them and ask you to choose.
176
+ - If no profiles exist, it starts onboarding automatically and then renames the profile folder to your Profile Nickname.
177
+
178
+ 5. **Cleanup (delete local data)**:
179
+
180
+ - Use `-clean` to remove all local app data under `~/.rhul_attendance_bot`:
181
+ ```bash
182
+ rhul-attendance-bot -clean
183
+ ```
171
184
 
172
- 5. **Keyboard Shortcuts**:
185
+ 6. **Keyboard Shortcuts**:
173
186
 
174
187
  - **Manually Trigger the Next Event**: Press `[`, then `]`
188
+ - **Refresh Calendar (re-fetch ICS)**: Press `[`, then `c`
175
189
  - **Exit the Script**: Press `[`, then `q`
176
190
 
177
191
  ## Important Notes
@@ -220,10 +234,11 @@ Current focus / future ideas:
220
234
  - ✅ **Automatic Login**: Stored credentials + TOTP drive a fully automatic login flow.
221
235
  - ✅ **2FA Code Reading**: OTP is generated locally from your saved secret.
222
236
  - ✅ **Discord Webhook Bot**: Discord webhook notifications for attendance status, login, and lifecycle (optional; disable by leaving webhook URL empty)
237
+ - ✅ **PyPI Release**: Package published and installable via `pip install rhul-attendance-bot`
223
238
 
224
239
  ## License
225
240
 
226
- This project is licensed under the MIT License with an additional clause. See the [LICENSE](LICENSE) file for details.
241
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
227
242
 
228
243
  ## Acknowledgments
229
244