redstonehero commited on
Commit
5ae930d
·
verified ·
1 Parent(s): fdd0d52

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +178 -0
app.py ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import random
4
+ import time
5
+ import html
6
+ import os
7
+
8
+ # --- CONFIGURATION ---
9
+ # Pulls credentials secretly from Hugging Face Space Settings
10
+ API_KEY = os.environ.get("GELBOORU_API_KEY")
11
+ USER_ID = os.environ.get("GELBOORU_USER_ID")
12
+
13
+ HEADERS = {"User-Agent": "GelbooruTagFetcher/8.0 (Python/HuggingFace)"}
14
+ ENDPOINT = "https://gelbooru.com/index.php"
15
+
16
+ # --- DATA LISTS (RAW VALUES) ---
17
+ FRANCHISES_RAW =[
18
+ "kancolle", "fire_emblem", "azur_lane", "honkai", "umamusume",
19
+ "arknights", "girls_und_panzer", "girls'_frontline", "zenless_zone_zero",
20
+ "granblue_fantasy", "kemono_friends", "nikke", "project_moon",
21
+ "wuthering_waves", "league_of_legends", "princess_connect!",
22
+ "chainsaw_man", "guilty_gear", "reverse:1999", "mega_man", "dq", "tales"
23
+ ]
24
+
25
+ HAIR_COLORS_RAW =[
26
+ "Any",
27
+ "white_hair",, "red_hair", "purple_hair", "pink_hair",
28
+ "orange_hair", "grey_hair", "green_hair", "brown_hair", "blue_hair",
29
+ "blonde_hair", "black_hair", "aqua_hair"
30
+ ]
31
+
32
+ # --- UI FORMATTERS ---
33
+ FRANCHISE_CHOICES =[(f.replace("_", " ").title(), f) for f in FRANCHISES_RAW]
34
+ HAIR_CHOICES =[(h.replace("_", " ").title(), h) for h in HAIR_COLORS_RAW]
35
+
36
+ # --- BACKEND FUNCTIONS ---
37
+
38
+ def get_tag_count(tag_name):
39
+ params = {
40
+ "page": "dapi", "s": "tag", "q": "index",
41
+ "json": 1, "name": tag_name
42
+ }
43
+ if API_KEY and USER_ID:
44
+ params["api_key"] = API_KEY
45
+ params["user_id"] = USER_ID
46
+
47
+ try:
48
+ response = requests.get(ENDPOINT, params=params, headers=HEADERS)
49
+ response.raise_for_status()
50
+ data = response.json()
51
+ tags = data.get("tag",[]) if isinstance(data, dict) else data
52
+ return int(tags[0].get("count", 0)) if tags else 0
53
+ except Exception:
54
+ return 0
55
+
56
+ def find_character_tag(selected_franchises, selected_hair):
57
+ if not selected_franchises:
58
+ return "⚠️ Error: Select a franchise", "Please select at least one franchise checkbox."
59
+
60
+ chosen_franchise = random.choice(selected_franchises)
61
+ tag_modifier = f"({chosen_franchise}"
62
+
63
+ search_tags = f"score:>10 1girl solo -animated -*cosplay* *{tag_modifier}* sort:random"
64
+ if selected_hair != "Any":
65
+ search_tags += f" {selected_hair}"
66
+
67
+ log_buffer = f"🎲 Randomly selected franchise: {chosen_franchise}\n"
68
+ log_buffer += f"🔎 Searching: {search_tags}\n"
69
+
70
+ params = {
71
+ "page": "dapi", "s": "post", "q": "index",
72
+ "json": 1, "limit": 50, "tags": search_tags
73
+ }
74
+ if API_KEY and USER_ID:
75
+ params["api_key"] = API_KEY
76
+ params["user_id"] = USER_ID
77
+
78
+ try:
79
+ response = requests.get(ENDPOINT, params=params, headers=HEADERS)
80
+ response.raise_for_status()
81
+
82
+ data = response.json()
83
+ posts = data.get("post",[]) if isinstance(data, dict) else data
84
+
85
+ if not posts:
86
+ log_buffer += "❌ No results found. Try selecting 'Any' hair color."
87
+ return "No Match Found", log_buffer
88
+
89
+ for post in posts:
90
+ raw_tags = html.unescape(post.get("tags", ""))
91
+ tag_list = raw_tags.split()
92
+
93
+ franchise_tags = [t for t in tag_list if tag_modifier in t]
94
+
95
+ if len(franchise_tags) == 1:
96
+ candidate = franchise_tags[0]
97
+
98
+ count = get_tag_count(candidate)
99
+ time.sleep(0.2)
100
+
101
+ if count >= 10:
102
+ log_buffer += f"✅ Match found on Post ID {post.get('id')}\n"
103
+ log_buffer += f"🏷️ Tag '{candidate}' has {count} posts."
104
+ return candidate.replace('_', ' '), log_buffer
105
+ else:
106
+ log_buffer += f"⚠️ Candidate '{candidate}' only has {count} posts. Skipping.\n"
107
+
108
+ log_buffer += "❌ Checked 50 posts but none matched criteria."
109
+ return "No Match Found", log_buffer
110
+
111
+ except Exception as e:
112
+ return "API Error", f"An error occurred: {str(e)}"
113
+
114
+ # --- UI HELPER FUNCTIONS ---
115
+ def select_all(): return FRANCHISES_RAW
116
+ def select_none(): return[]
117
+ def invert_selection(current_selection): return[item for item in FRANCHISES_RAW if item not in current_selection]
118
+
119
+ def copy_notification(x):
120
+ if x and "Error" not in x and "No Match" not in x:
121
+ gr.Info(f"Copied '{x}' to clipboard!")
122
+ return x
123
+
124
+ # --- GRADIO LAYOUT ---
125
+ JS_COPY_LOGIC = """
126
+ (x) => {
127
+ if (x && x !== "Result will appear here..." && !x.includes("Error")) {
128
+ navigator.clipboard.writeText(x);
129
+ }
130
+ return x;
131
+ }
132
+ """
133
+
134
+ with gr.Blocks(theme=gr.themes.Soft()) as app:
135
+ gr.Markdown("## 🏷️ Gelbooru Character Tag Finder")
136
+ gr.Markdown("Pick your franchises and a hair color. The script will return **one specific character tag**.")
137
+
138
+ with gr.Row():
139
+ with gr.Column(scale=1):
140
+ franchise_input = gr.CheckboxGroup(
141
+ choices=FRANCHISE_CHOICES, value=FRANCHISES_RAW, label="1. Select Franchises"
142
+ )
143
+
144
+ with gr.Row():
145
+ btn_all = gr.Button("Select All", size="sm")
146
+ btn_none = gr.Button("Clear All", size="sm")
147
+ btn_invert = gr.Button("Invert Selection", size="sm")
148
+
149
+ hair_input = gr.Radio(
150
+ choices=HAIR_CHOICES, value="Any", label="2. Select Hair Color"
151
+ )
152
+ submit_btn = gr.Button("Find Character Tag", variant="primary")
153
+
154
+ with gr.Column(scale=1):
155
+ gr.Markdown("### 👇 Click the button below to copy the tag")
156
+
157
+ result_btn = gr.Button(
158
+ value="Result will appear here...", variant="secondary", size="lg", interactive=True
159
+ )
160
+
161
+ log_output = gr.TextArea(
162
+ label="Process Logs", lines=10, max_lines=15, interactive=False
163
+ )
164
+
165
+ btn_all.click(fn=select_all, outputs=franchise_input)
166
+ btn_none.click(fn=select_none, outputs=franchise_input)
167
+ btn_invert.click(fn=invert_selection, inputs=franchise_input, outputs=franchise_input)
168
+
169
+ submit_btn.click(
170
+ fn=find_character_tag, inputs=[franchise_input, hair_input], outputs=[result_btn, log_output]
171
+ )
172
+
173
+ result_btn.click(
174
+ fn=copy_notification, inputs=result_btn, outputs=result_btn, js=JS_COPY_LOGIC
175
+ )
176
+
177
+ if __name__ == "__main__":
178
+ app.launch()