Krixter commited on
Commit
d5118b1
ยท
verified ยท
1 Parent(s): b8d2b02

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +90 -164
app.py CHANGED
@@ -1,166 +1,92 @@
1
- import gradio as gr
2
- import os
3
- import pandas as pd
4
- from huggingface_hub import HfFileSystem
5
- import tempfile
6
-
7
- # --- CONFIGURATION ---
8
- HF_BUCKET = "hf://buckets/Krixter/storage"
9
-
10
- def get_fs():
11
- """Initialize the Hugging Face File System. Uses a token if available, otherwise runs anonymously."""
12
- token = os.environ.get("HF_TOKEN")
13
- return HfFileSystem(token=token)
14
-
15
- def format_size(size_bytes):
16
- """Convert bytes to a human-readable format."""
17
- for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
18
- if size_bytes < 1024.0:
19
- return f"{size_bytes:.2f} {unit}"
20
- size_bytes /= 1024.0
21
- return f"{size_bytes:.2f} PB"
22
-
23
- def list_files(sort_by="Date (Newest)"):
24
- """Fetch files from the bucket, extract accurate metadata, and sort."""
25
- try:
26
- fs = get_fs()
27
- files = fs.ls(HF_BUCKET, detail=True)
28
-
29
- data = []
30
- for f in files:
31
- if f.get("type") == "directory":
32
- continue
33
-
34
- name = os.path.basename(f["name"])
35
- raw_size = f.get("size", 0)
36
- size_str = format_size(raw_size)
37
-
38
- # --- AGGRESSIVE DATE EXTRACTION ---
39
- raw_date = None
40
-
41
- # Prioritizing CREATED date keys as requested, falling back to modified keys
42
- date_keys = [
43
- "created", "created_at", "CreatedAt", "CreationDate", "ctime", "date_created",
44
- "last_modified", "LastModified", "updated", "updatedAt", "client_modified"
45
- ]
46
-
47
- # 1. Check the primary file metadata dictionary
48
- for k in date_keys:
49
- if k in f and f[k] is not None:
50
- raw_date = f[k]
51
- break
52
-
53
- # 2. If still missing, ping info() directly for deeper hidden keys
54
- if raw_date is None:
55
- try:
56
- info = fs.info(f["name"])
57
- for k in date_keys:
58
- if k in info and info[k] is not None:
59
- raw_date = info[k]
60
- break
61
- except Exception:
62
- pass
63
-
64
- data.append({
65
- "File Name": name,
66
- "Size": size_str,
67
- "_raw_date": raw_date,
68
- "_raw_size": raw_size
69
- })
70
-
71
- df = pd.DataFrame(data)
72
- if not df.empty:
73
- # Safely parse dates. utc=True prevents timezone conflicts from turning valid dates into "Unknown" (NaT)
74
- dt_col = pd.to_datetime(df["_raw_date"], errors='coerce', utc=True).dt.tz_localize(None)
75
- df["_raw_date"] = dt_col
76
-
77
- # Format display column, filling NaT back to "Unknown"
78
- df["Date Created"] = dt_col.dt.strftime('%Y-%m-%d %H:%M:%S').fillna("Unknown")
79
-
80
- # --- EXPLICIT SORTING ---
81
- if sort_by == "Name (A-Z)":
82
- df = df.sort_values(by="File Name", ascending=True)
83
- elif sort_by == "Name (Z-A)":
84
- df = df.sort_values(by="File Name", ascending=False)
85
- elif sort_by == "Date (Newest)":
86
- df = df.sort_values(by="_raw_date", ascending=False, na_position='last')
87
- elif sort_by == "Date (Oldest)":
88
- df = df.sort_values(by="_raw_date", ascending=True, na_position='last')
89
- elif sort_by == "Size (Largest)":
90
- df = df.sort_values(by="_raw_size", ascending=False)
91
- elif sort_by == "Size (Smallest)":
92
- df = df.sort_values(by="_raw_size", ascending=True)
93
-
94
- # Clean up raw columns and enforce column order
95
- df = df.drop(columns=["_raw_date", "_raw_size"])
96
- df = df[["File Name", "Date Created", "Size"]]
97
-
98
- file_names = df["File Name"].tolist() if not df.empty else []
99
- return df, gr.update(choices=file_names)
100
-
101
- except Exception as e:
102
- print(f"Error fetching files: {e}")
103
- return pd.DataFrame(columns=["File Name", "Date Created", "Size"]), gr.update(choices=[])
104
-
105
- def download_file(filename):
106
- """Download the selected file from the bucket to a temporary local file."""
107
- if not filename:
108
- return None
109
-
110
- try:
111
- fs = get_fs()
112
- src_path = f"{HF_BUCKET}/{filename}"
113
-
114
- temp_dir = tempfile.mkdtemp()
115
- dest_path = os.path.join(temp_dir, filename)
116
-
117
- print(f"๐Ÿ“ฅ Downloading {filename}...")
118
- fs.get(src_path, dest_path)
119
- print("โœ… Download complete!")
120
-
121
- return dest_path
122
- except Exception as e:
123
- print(f"โš ๏ธ Error downloading {filename}: {e}")
124
- return None
125
-
126
- # --- UI LAYOUT ---
127
- with gr.Blocks() as app:
128
- gr.Markdown("# ๐Ÿ—„๏ธ AudioNovel Bucket Explorer")
129
- gr.Markdown("View, sort, and download your processed audio files. Use the dropdown to sort your files perfectly.")
130
-
131
- with gr.Row():
132
- with gr.Column(scale=2):
133
- refresh_btn = gr.Button("๐Ÿ”„ Refresh Bucket contents", variant="secondary")
134
- with gr.Column(scale=1):
135
- sort_dropdown = gr.Dropdown(
136
- label="Sort Files By",
137
- choices=["Name (A-Z)", "Name (Z-A)", "Date (Newest)", "Date (Oldest)", "Size (Largest)", "Size (Smallest)"],
138
- value="Date (Newest)",
139
- interactive=True
140
- )
141
-
142
- # Interactive table (Headers updated)
143
- file_table = gr.Dataframe(
144
- headers=["File Name", "Date Created", "Size"],
145
- interactive=False,
146
- label="Bucket Contents"
147
- )
148
-
149
- gr.Markdown("---")
150
- gr.Markdown("### ๐Ÿ“ฅ Download a File")
151
-
152
- with gr.Row():
153
- with gr.Column(scale=2):
154
- file_dropdown = gr.Dropdown(label="Select File to Download", choices=[])
155
- with gr.Column(scale=1):
156
- download_btn = gr.Button("๐Ÿš€ Prepare Download", variant="primary")
157
-
158
- file_output = gr.File(label="Ready for Download")
159
-
160
- # --- EVENT TRIGGERS ---
161
- refresh_btn.click(fn=list_files, inputs=[sort_dropdown], outputs=[file_table, file_dropdown])
162
- sort_dropdown.change(fn=list_files, inputs=[sort_dropdown], outputs=[file_table, file_dropdown])
163
- app.load(fn=list_files, inputs=[sort_dropdown], outputs=[file_table, file_dropdown])
164
 
165
  if __name__ == "__main__":
166
- app.launch(theme=gr.themes.Soft(primary_hue="indigo"))
 
1
+ """
2
+ Webnovel.com NTR keyword scraper
3
+ Searches Webnovel's public search API for titles/descriptions
4
+ containing "netorase", "netorare", or "ntr" and saves results to CSV.
5
+
6
+ Run with: python webnovel_ntr_scraper.py
7
+ Requires: pip install requests
8
+ """
9
+
10
+ import requests
11
+ import csv
12
+ import time
13
+ import json
14
+
15
+ KEYWORDS = ["netorase", "netorare", "ntr"]
16
+ OUTPUT_FILE = "webnovel_ntr_results.csv"
17
+
18
+ # Webnovel's internal search API (subject to change)
19
+ SEARCH_URL = "https://www.webnovel.com/go/pcm/search/result"
20
+
21
+ HEADERS = {
22
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
23
+ "(KHTML, like Gecko) Chrome/124.0 Safari/537.36",
24
+ "Accept": "application/json, text/plain, */*",
25
+ }
26
+
27
+ def search_keyword(keyword, page_size=20, max_pages=20, delay=1.5):
28
+ """Query Webnovel's search endpoint for a keyword, paginating through results."""
29
+ results = []
30
+ for page in range(1, max_pages + 1):
31
+ params = {
32
+ "keywords": keyword,
33
+ "pageIndex": page,
34
+ "pageSize": page_size,
35
+ }
36
+ try:
37
+ resp = requests.get(SEARCH_URL, params=params, headers=HEADERS, timeout=10)
38
+ resp.raise_for_status()
39
+ data = resp.json()
40
+ except (requests.RequestException, json.JSONDecodeError) as e:
41
+ print(f" [!] Error on page {page} for '{keyword}': {e}")
42
+ break
43
+
44
+ # Adjust this path based on actual API response structure
45
+ items = (
46
+ data.get("data", {}).get("items")
47
+ or data.get("data", {}).get("bookItems")
48
+ or []
49
+ )
50
+
51
+ if not items:
52
+ break
53
+
54
+ for item in items:
55
+ title = item.get("bookName") or item.get("title", "")
56
+ desc = item.get("description") or item.get("bookDesc", "")
57
+ book_id = item.get("bookId", "")
58
+ url = f"https://www.webnovel.com/book/{book_id}" if book_id else ""
59
+ results.append({"title": title, "description": desc, "url": url})
60
+
61
+ print(f" Keyword '{keyword}' page {page}: {len(items)} items")
62
+ time.sleep(delay)
63
+
64
+ return results
65
+
66
+ def matches_ntr(title, description, keywords):
67
+ text = f"{title} {description}".lower()
68
+ return any(k.lower() in text for k in keywords)
69
+
70
+ def main():
71
+ all_matches = {}
72
+
73
+ for kw in KEYWORDS:
74
+ print(f"Searching for keyword: {kw}")
75
+ items = search_keyword(kw)
76
+ for item in items:
77
+ if matches_ntr(item["title"], item["description"], KEYWORDS):
78
+ key = item["url"] or item["title"]
79
+ all_matches[key] = item
80
+
81
+ print(f"\nFound {len(all_matches)} unique matching novels.")
82
+
83
+ with open(OUTPUT_FILE, "w", newline="", encoding="utf-8") as f:
84
+ writer = csv.DictWriter(f, fieldnames=["title", "description", "url"])
85
+ writer.writeheader()
86
+ for item in all_matches.values():
87
+ writer.writerow(item)
88
+
89
+ print(f"Saved results to {OUTPUT_FILE}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
  if __name__ == "__main__":
92
+ main()