Size
11.8 KB
Version
1.1.1
Created
Nov 8, 2025
Updated
about 1 month ago
1// ==UserScript==
2// @name Lichess Hourly Bullet Tournament Creator
3// @description A new extension
4// @version 1.1.1
5// @match https://*.lichess.org/*
6// @icon https://lichess1.org/assets/logo/lichess-favicon-32.png
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('Lichess Hourly Bullet Tournament Creator loaded');
12
13 // Configuration
14 const TOURNAMENT_CONFIG = {
15 clockTime: 60, // 1 minute in seconds
16 clockIncrement: 0, // 0 seconds increment
17 minutes: 57, // Tournament duration
18 name: 'Hourly Bullet 1+0',
19 waitMinutes: 3, // Wait 3 minutes before starting
20 variant: 'standard',
21 rated: true,
22 berserk: true,
23 scheduledHour: 23 // 11pm in 24-hour format
24 };
25
26 // Check if we're on a team page
27 function isTeamPage() {
28 return window.location.pathname.startsWith('/team/');
29 }
30
31 // Get team ID from URL
32 function getTeamId() {
33 const match = window.location.pathname.match(/\/team\/([^\/]+)/);
34 return match ? match[1] : null;
35 }
36
37 // Create tournament button
38 function createTournamentButton() {
39 const button = document.createElement('button');
40 button.textContent = '⚡ Create Hourly Bullet Tournament';
41 button.style.cssText = `
42 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
43 color: white;
44 border: none;
45 padding: 12px 24px;
46 font-size: 16px;
47 font-weight: bold;
48 border-radius: 8px;
49 cursor: pointer;
50 margin: 16px 0;
51 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
52 transition: all 0.3s ease;
53 display: flex;
54 align-items: center;
55 gap: 8px;
56 `;
57
58 button.addEventListener('mouseenter', () => {
59 button.style.transform = 'translateY(-2px)';
60 button.style.boxShadow = '0 6px 12px rgba(0, 0, 0, 0.15)';
61 });
62
63 button.addEventListener('mouseleave', () => {
64 button.style.transform = 'translateY(0)';
65 button.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1)';
66 });
67
68 button.addEventListener('click', async () => {
69 await createTournament(button);
70 });
71
72 return button;
73 }
74
75 // Create tournament via API
76 async function createTournament(button) {
77 const teamId = getTeamId();
78
79 button.disabled = true;
80 button.textContent = '⏳ Creating tournament...';
81 button.style.opacity = '0.7';
82
83 try {
84 // Build form data
85 const formData = new URLSearchParams();
86 formData.append('name', TOURNAMENT_CONFIG.name);
87 formData.append('clockTime', TOURNAMENT_CONFIG.clockTime);
88 formData.append('clockIncrement', TOURNAMENT_CONFIG.clockIncrement);
89 formData.append('minutes', TOURNAMENT_CONFIG.minutes);
90 formData.append('waitMinutes', TOURNAMENT_CONFIG.waitMinutes);
91 formData.append('variant', TOURNAMENT_CONFIG.variant);
92 formData.append('rated', TOURNAMENT_CONFIG.rated);
93 formData.append('berserk', TOURNAMENT_CONFIG.berserk);
94
95 if (teamId) {
96 formData.append('teamBattleByTeam', teamId);
97 }
98
99 console.log('Creating tournament with config:', Object.fromEntries(formData));
100
101 // Make API request
102 const response = await GM.xmlhttpRequest({
103 method: 'POST',
104 url: 'https://lichess.org/api/tournament',
105 headers: {
106 'Content-Type': 'application/x-www-form-urlencoded',
107 },
108 data: formData.toString(),
109 responseType: 'json'
110 });
111
112 console.log('Tournament creation response:', response);
113
114 if (response.status === 200) {
115 const data = typeof response.response === 'string'
116 ? JSON.parse(response.response)
117 : response.response;
118
119 console.log('Tournament created successfully:', data);
120
121 button.textContent = '✅ Tournament Created!';
122 button.style.background = 'linear-gradient(135deg, #11998e 0%, #38ef7d 100%)';
123
124 // Show success message with tournament link
125 showNotification('Tournament created successfully!', data.id);
126
127 // Reset button after 3 seconds
128 setTimeout(() => {
129 button.disabled = false;
130 button.textContent = '⚡ Create Hourly Bullet Tournament';
131 button.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
132 button.style.opacity = '1';
133 }, 3000);
134 } else {
135 throw new Error(`Failed to create tournament: ${response.status} ${response.statusText}`);
136 }
137 } catch (error) {
138 console.error('Error creating tournament:', error);
139 button.textContent = '❌ Failed to create';
140 button.style.background = 'linear-gradient(135deg, #eb3349 0%, #f45c43 100%)';
141
142 showNotification('Failed to create tournament. Check console for details.', null, true);
143
144 setTimeout(() => {
145 button.disabled = false;
146 button.textContent = '⚡ Create Hourly Bullet Tournament';
147 button.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
148 button.style.opacity = '1';
149 }, 3000);
150 }
151 }
152
153 // Check if tournament should be created now
154 async function checkAndCreateScheduledTournament() {
155 const now = new Date();
156 const currentHour = now.getHours();
157 const currentMinute = now.getMinutes();
158
159 // Check if it's 11pm (23:00) and within the first minute
160 if (currentHour === TOURNAMENT_CONFIG.scheduledHour && currentMinute === 0) {
161 // Check if we already created a tournament today
162 const lastCreated = await GM.getValue('lastTournamentCreated', '');
163 const today = now.toDateString();
164
165 if (lastCreated !== today) {
166 console.log('Creating scheduled tournament at 11pm');
167 await createScheduledTournament();
168 await GM.setValue('lastTournamentCreated', today);
169 }
170 }
171 }
172
173 // Create scheduled tournament (without button UI)
174 async function createScheduledTournament() {
175 const teamId = getTeamId();
176
177 try {
178 const formData = new URLSearchParams();
179 formData.append('name', TOURNAMENT_CONFIG.name);
180 formData.append('clockTime', TOURNAMENT_CONFIG.clockTime);
181 formData.append('clockIncrement', TOURNAMENT_CONFIG.clockIncrement);
182 formData.append('minutes', TOURNAMENT_CONFIG.minutes);
183 formData.append('waitMinutes', TOURNAMENT_CONFIG.waitMinutes);
184 formData.append('variant', TOURNAMENT_CONFIG.variant);
185 formData.append('rated', TOURNAMENT_CONFIG.rated);
186 formData.append('berserk', TOURNAMENT_CONFIG.berserk);
187
188 if (teamId) {
189 formData.append('teamBattleByTeam', teamId);
190 }
191
192 console.log('Creating scheduled tournament with config:', Object.fromEntries(formData));
193
194 const response = await GM.xmlhttpRequest({
195 method: 'POST',
196 url: 'https://lichess.org/api/tournament',
197 headers: {
198 'Content-Type': 'application/x-www-form-urlencoded',
199 },
200 data: formData.toString(),
201 responseType: 'json'
202 });
203
204 console.log('Scheduled tournament creation response:', response);
205
206 if (response.status === 200) {
207 const data = typeof response.response === 'string'
208 ? JSON.parse(response.response)
209 : response.response;
210
211 console.log('Scheduled tournament created successfully:', data);
212 showNotification('Scheduled tournament created at 11pm!', data.id);
213 } else {
214 throw new Error(`Failed to create scheduled tournament: ${response.status}`);
215 }
216 } catch (error) {
217 console.error('Error creating scheduled tournament:', error);
218 showNotification('Failed to create scheduled tournament. Check console for details.', null, true);
219 }
220 }
221
222 // Show notification
223 function showNotification(message, tournamentId = null, isError = false) {
224 const notification = document.createElement('div');
225 notification.style.cssText = `
226 position: fixed;
227 top: 20px;
228 right: 20px;
229 background: ${isError ? '#f45c43' : '#38ef7d'};
230 color: white;
231 padding: 16px 24px;
232 border-radius: 8px;
233 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
234 z-index: 10000;
235 font-weight: bold;
236 max-width: 400px;
237 `;
238
239 notification.textContent = message;
240
241 if (tournamentId) {
242 const link = document.createElement('a');
243 link.href = `https://lichess.org/tournament/${tournamentId}`;
244 link.textContent = 'View Tournament';
245 link.style.cssText = `
246 display: block;
247 margin-top: 8px;
248 color: white;
249 text-decoration: underline;
250 `;
251 notification.appendChild(link);
252 }
253
254 document.body.appendChild(notification);
255
256 setTimeout(() => {
257 notification.remove();
258 }, 5000);
259 }
260
261 // Add button to page
262 function addButtonToPage() {
263 // Wait for page to load
264 const checkInterval = setInterval(() => {
265 const targetContainer = document.querySelector('.team__info') ||
266 document.querySelector('.page-menu__content') ||
267 document.querySelector('main.page-small');
268
269 if (targetContainer && !document.getElementById('tournament-creator-button')) {
270 const button = createTournamentButton();
271 button.id = 'tournament-creator-button';
272
273 // Insert button at the top of the container
274 targetContainer.insertBefore(button, targetContainer.firstChild);
275
276 console.log('Tournament creator button added to page');
277 clearInterval(checkInterval);
278 }
279 }, 1000);
280
281 // Stop checking after 10 seconds
282 setTimeout(() => clearInterval(checkInterval), 10000);
283 }
284
285 // Initialize
286 function init() {
287 console.log('Initializing Lichess Hourly Bullet Tournament Creator');
288
289 // Add button to current page
290 addButtonToPage();
291
292 // Start checking for scheduled tournament creation every minute
293 setInterval(checkAndCreateScheduledTournament, 60000); // Check every minute
294 checkAndCreateScheduledTournament(); // Check immediately on load
295
296 // Watch for navigation changes (Lichess uses client-side routing)
297 const observer = new MutationObserver(() => {
298 if (!document.getElementById('tournament-creator-button')) {
299 addButtonToPage();
300 }
301 });
302
303 observer.observe(document.body, {
304 childList: true,
305 subtree: true
306 });
307 }
308
309 // Start when DOM is ready
310 if (document.readyState === 'loading') {
311 document.addEventListener('DOMContentLoaded', init);
312 } else {
313 init();
314 }
315})();