Last active 1705888099

cyberes's Avatar cyberes revised this gist 1705888099. Go to revision

1 file changed, 5 insertions, 3 deletions

server.py

@@ -77,14 +77,14 @@ def get_nodes():
77 77
78 78
79 79 @cache.memoize(timeout=900)
80 - def get_items_by_topics(topics: list):
80 + def get_items_by_topics(included_topics, excluded_topics):
81 81 count_raw = redis_conn.get('node_count')
82 82 if not count_raw or not int(count_raw):
83 83 return []
84 84 nodes = get_nodes()['data']['nodes']
85 85 result = []
86 86 for node in nodes:
87 - if set(topics).issubset(set(node['topics'])):
87 + if set(included_topics).issubset(set(node['topics'])) and not set(excluded_topics).intersection(set(node['topics'])):
88 88 result.append(node)
89 89 return result
90 90
@@ -125,7 +125,9 @@ def random_character():
125 125 response_data['timestamp'] = c['timestamp']
126 126 else:
127 127 tags = tags_arg.split(',')
128 - matches = get_items_by_topics(tags)
128 + included_tags = [x for x in tags if not x.startswith('-')]
129 + excluded_tags = [x.lstrip('-') for x in tags if x.startswith('-')]
130 + matches = get_items_by_topics(included_tags, excluded_tags)
129 131 if len(matches):
130 132 response_data = random.choice(matches)
131 133 response_data['timestamp'] = c['timestamp']

cyberes's Avatar cyberes revised this gist 1705885011. Go to revision

1 file changed, 141 insertions

server.py(file created)

@@ -0,0 +1,141 @@
1 + import json
2 + import random
3 + import threading
4 + import time
5 + import traceback
6 +
7 + import redis
8 + import requests
9 + from flask import Flask, jsonify, request
10 + from flask_caching import Cache
11 +
12 + redis_conn = redis.Redis(host='localhost', port=6379, db=2)
13 + redis_conn.set('last_updated', 0)
14 + redis_conn.set('fetching_new_data', 0)
15 + if not redis_conn.hget('nodes', 'nodes'):
16 + redis_conn.hset('nodes', 'nodes', json.dumps({'data': {'nodes': []}}))
17 +
18 + headers = {
19 + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/114.0',
20 + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
21 + 'Accept-Language': 'en-US,en;q=0.5',
22 + 'Accept-Encoding': 'gzip;q=0,deflate;q=0',
23 + 'DNT': '1',
24 + 'Connection': 'keep-alive',
25 + 'Upgrade-Insecure-Requests': '1',
26 + 'Sec-Fetch-Dest': 'document',
27 + 'Sec-Fetch-Mode': 'navigate',
28 + 'Sec-Fetch-Site': 'cross-site',
29 + 'Pragma': 'no-cache',
30 + 'Cache-Control': 'no-cache',
31 + 'TE': 'trailers',
32 + }
33 +
34 +
35 + def update_node_cache():
36 + # Semaphore
37 + if bool(int(redis_conn.get('fetching_new_data').decode())):
38 + return
39 + redis_conn.set('fetching_new_data', 1)
40 + print('Fetching new data...')
41 + start = time.time()
42 + try:
43 + r = requests.get('https://api.chub.ai/search?search=&first=5000000&min_tokens=50&nsfw=true', headers=headers, timeout=120, proxies={'http': 'http://172.0.4.7:9000', 'https': 'http://172.0.4.7:9000'})
44 + j = r.json()
45 + j['timestamp'] = int(time.time())
46 + redis_conn.set('node_count', len(j['data']['nodes']))
47 + redis_conn.hset('nodes', 'nodes', json.dumps(j))
48 + except:
49 + traceback.print_exc()
50 + finally:
51 + redis_conn.set('fetching_new_data', 0)
52 + print('Finished fetching new data in', time.time() - start)
53 +
54 +
55 + app = Flask(__name__)
56 + cache = Cache(app, config={'CACHE_TYPE': 'redis', 'CACHE_REDIS_URL': 'redis://localhost:6379/1', 'CACHE_KEY_PREFIX': 'chub_random__'})
57 + cache.clear()
58 +
59 +
60 + @cache.memoize(timeout=900)
61 + def cached_node_json():
62 + return json.loads(redis_conn.hget('nodes', 'nodes').decode())
63 +
64 +
65 + def get_nodes():
66 + last_updated = int(redis_conn.get('last_updated'))
67 + if time.time() > last_updated:
68 + threading.Thread(target=update_node_cache).start()
69 + count_raw = redis_conn.get('node_count')
70 + if not count_raw or not int(count_raw):
71 + # Return dummy data if there isn't anything loaded yet.
72 + # This avoids caching the empty data.
73 + return {'data': {'nodes': []}}
74 + else:
75 + # Cache and return data.
76 + return cached_node_json()
77 +
78 +
79 + @cache.memoize(timeout=900)
80 + def get_items_by_topics(topics: list):
81 + count_raw = redis_conn.get('node_count')
82 + if not count_raw or not int(count_raw):
83 + return []
84 + nodes = get_nodes()['data']['nodes']
85 + result = []
86 + for node in nodes:
87 + if set(topics).issubset(set(node['topics'])):
88 + result.append(node)
89 + return result
90 +
91 +
92 + n = get_nodes()
93 + redis_conn.set('node_count', len(n['data']['nodes']))
94 +
95 +
96 + @cache.cached(timeout=900)
97 + @app.route('/api/characters', methods=['GET'])
98 + def all_characters():
99 + c = get_nodes()
100 + if not c:
101 + r = jsonify({'error': 'Something went wrong fetching response'})
102 + r.headers['Access-Control-Allow-Origin'] = '*'
103 + return r, 500
104 + else:
105 + r = jsonify(c)
106 + r.headers['Cache-Control'] = 'public, max-age=900'
107 + r.headers['Access-Control-Allow-Origin'] = '*'
108 + return r
109 +
110 +
111 + @app.route('/api/characters/random', methods=['GET'])
112 + def random_character():
113 + c = get_nodes()
114 + tags_arg = request.args.get('tags')
115 + response_data = {}
116 + if not tags_arg:
117 + if not c:
118 + r = jsonify({'error': 'Something went wrong'})
119 + r.headers['Access-Control-Allow-Origin'] = '*'
120 + return r, 500
121 + elif len(c['data']['nodes']):
122 + count = int(redis_conn.get('node_count').decode())
123 + i = random.randint(0, count)
124 + response_data = c['data']['nodes'][i]
125 + response_data['timestamp'] = c['timestamp']
126 + else:
127 + tags = tags_arg.split(',')
128 + matches = get_items_by_topics(tags)
129 + if len(matches):
130 + response_data = random.choice(matches)
131 + response_data['timestamp'] = c['timestamp']
132 + else:
133 + response_data = []
134 + r = jsonify(response_data)
135 + r.headers['Access-Control-Allow-Origin'] = '*'
136 + r.headers['Cache-Control'] = 'no-store'
137 + return r
138 +
139 +
140 + if __name__ == '__main__':
141 + app.run(debug=True, host='0.0.0.0')

cyberes's Avatar cyberes revised this gist 1705883773. Go to revision

1 file changed, 2 insertions, 11 deletions

index.html

@@ -26,10 +26,6 @@
26 26 </div>
27 27 </div>
28 28
29 - <div id="content">
30 -
31 - </div>
32 -
33 29 <script>
34 30 function eraseItem() {
35 31 document.getElementById("avatar").innerText = '';
@@ -47,10 +43,9 @@
47 43 document.getElementById('newItemButton').disabled = true;
48 44 eraseItem()
49 45 const tagsText = document.getElementById('topicsInput').value.replace(" ", "");
50 - const tagsList = tagsText.split(',');
51 -
52 46 let apiURL = 'https://chub-archive.evulid.cc/api/chub/characters/random'
53 - if (tagsList.length > 0) {
47 + if (tagsText.length > 0) {
48 + const tagsList = tagsText.split(',');
54 49 apiURL = apiURL + '?tags=' + tagsList.join(',')
55 50 }
56 51
@@ -160,19 +155,15 @@
160 155 text-align: center;
161 156 }
162 157
163 - /* or, for legacy browsers */
164 -
165 158 ::-webkit-input-placeholder {
166 159 text-align: center;
167 160 }
168 161
169 162 :-moz-placeholder {
170 - /* Firefox 18- */
171 163 text-align: center;
172 164 }
173 165
174 166 ::-moz-placeholder {
175 - /* Firefox 19+ */
176 167 text-align: center;
177 168 }
178 169

cyberes's Avatar cyberes revised this gist 1705883039. Go to revision

1 file changed, 188 insertions

index.html(file created)

@@ -0,0 +1,188 @@
1 + <!DOCTYPE html>
2 + <html lang="en">
3 +
4 + <head>
5 + <title>Random Chub Characters</title>
6 + <meta name="viewport" content="width=device-width, initial-scale=1" />
7 + </head>
8 +
9 + <body>
10 + <button id="newItemButton" style="width:100%;margin-bottom:10px;">Get New Item</button>
11 + <input id="topicsInput" type="text" placeholder="tags" style="width:45%;margin:auto;display:block;">
12 + <br><br>
13 + <div class="flex-container">
14 + <div style="min-width: 25%;">
15 + <div id="avatar"></div>
16 + </div>
17 + <div class="card-desc" style="width: 100%;">
18 + <div id="name"></div>
19 + <div id="tagline"></div>
20 + <div id="desc">Loading...</div>
21 + <div id="tags"></div>
22 + <div id="tokens"></div>
23 + <div id="author"></div>
24 + <div id="date"></div>
25 + <div id="downloads"></div>
26 + </div>
27 + </div>
28 +
29 + <div id="content">
30 +
31 + </div>
32 +
33 + <script>
34 + function eraseItem() {
35 + document.getElementById("avatar").innerText = '';
36 + document.getElementById("name").innerText = '';
37 + document.getElementById("tagline").innerText = '';
38 + document.getElementById("desc").innerText = 'Loading...';
39 + document.getElementById("tags").innerText = '';
40 + document.getElementById("tokens").innerText = '';
41 + document.getElementById("author").innerText = '';
42 + document.getElementById("date").innerText = '';
43 + document.getElementById("downloads").innerText = '';
44 + }
45 +
46 + function displayRandomItem() {
47 + document.getElementById('newItemButton').disabled = true;
48 + eraseItem()
49 + const tagsText = document.getElementById('topicsInput').value.replace(" ", "");
50 + const tagsList = tagsText.split(',');
51 +
52 + let apiURL = 'https://chub-archive.evulid.cc/api/chub/characters/random'
53 + if (tagsList.length > 0) {
54 + apiURL = apiURL + '?tags=' + tagsList.join(',')
55 + }
56 +
57 + fetch(apiURL)
58 + .then(response => response.json())
59 + .then(data => {
60 + if (data.name === undefined) {
61 + document.getElementById("name").innerHTML = "No cards found";
62 + document.getElementById("desc").innerText = '';
63 + } else {
64 + document.getElementById("avatar").innerHTML = `<img src="https://avatars.charhub.io/avatars/${data.fullPath}/avatar.webp?size=0.07711525216207438" alt="">`;
65 + document.getElementById("name").innerHTML = `<a href="https://www.chub.ai/characters/${data.fullPath}" target="_blank">${data.name}</a>`;
66 + document.getElementById("tagline").innerText = data.tagline;
67 + document.getElementById("desc").innerText = data.description;
68 + document.getElementById("tags").innerText = data.topics.join(', ');
69 + document.getElementById("tokens").innerText = `Approximately ${data.nTokens} tokens`;
70 + document.getElementById("author").innerHTML = `<a href="https://www.chub.ai/users/${data.fullPath.split('/')[0]}" target="_blank">${data.fullPath.split('/')[0]}</a>`;
71 + document.getElementById("date").innerText = data.lastActivityAt;
72 + document.getElementById("downloads").innerText = `${data.starCount} Downloads`;
73 + }
74 + document.getElementById('newItemButton').disabled = false;
75 + })
76 + .catch(error => console.error(error));
77 + }
78 +
79 + window.onload = displayRandomItem;
80 + document.getElementById('topicsInput').addEventListener("keydown", (event) => {
81 + if (event.key === "Enter") {
82 + displayRandomItem();
83 + }
84 + });
85 + document.getElementById('newItemButton').addEventListener("click", displayRandomItem);
86 + </script>
87 + </body>
88 +
89 + <style>
90 + .flex-container {
91 + display: flex;
92 + flex-wrap: nowrap;
93 + background-color: #7C0818;
94 + }
95 +
96 + .flex-container>div {
97 + background-color: #f1f1f1;
98 + margin: 10px;
99 + padding: 10px;
100 + }
101 +
102 +
103 + .card-desc {
104 + font-size: 12pt;
105 + word-wrap: normal;
106 + }
107 +
108 + #newItemButton {
109 + background-color: #7C0818;
110 + border-radius: 10px;
111 + color: white;
112 + font-weight: bold;
113 + cursor: pointer;
114 + font-size: 20pt;
115 + }
116 +
117 + #newItemButton:disabled {
118 + cursor: not-allowed;
119 + }
120 +
121 + #avatar>img {
122 + max-width: 100%;
123 + display: block;
124 + margin: auto;
125 + }
126 +
127 + #name {
128 + font-weight: bold;
129 + font-size: 20pt;
130 + margin-bottom: 10px;
131 + }
132 +
133 + /*#name > a, #name > a:visited {*/
134 + /* color: black;*/
135 + /*}*/
136 +
137 + #tagline {
138 + font-style: italic;
139 + margin-bottom: 20px;
140 + }
141 +
142 + #desc {
143 + padding: 5px;
144 + border: 1px solid black;
145 + margin-bottom: 20px;
146 + }
147 +
148 + #tags {
149 + font-style: italic;
150 + margin-bottom: 10px;
151 + }
152 +
153 + #tokens,
154 + #author,
155 + #date {
156 + margin-bottom: 5px;
157 + }
158 +
159 + ::placeholder {
160 + text-align: center;
161 + }
162 +
163 + /* or, for legacy browsers */
164 +
165 + ::-webkit-input-placeholder {
166 + text-align: center;
167 + }
168 +
169 + :-moz-placeholder {
170 + /* Firefox 18- */
171 + text-align: center;
172 + }
173 +
174 + ::-moz-placeholder {
175 + /* Firefox 19+ */
176 + text-align: center;
177 + }
178 +
179 + :-ms-input-placeholder {
180 + text-align: center;
181 + }
182 +
183 + input {
184 + text-align: center;
185 + }
186 + </style>
187 +
188 + </html>
Newer Older