Passed
Push — master ( d83fa0...73c582 )
by manny
02:10
created

racetime_obs   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 376
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 55
eloc 300
dl 0
loc 376
rs 6
c 0
b 0
f 0

28 Functions

Rating   Name   Duplication   Size   Complexity  
B fill_source_list() 0 10 6
A fill_race_list() 0 11 3
A script_qualifier_settings() 0 17 1
A update_sources() 0 16 5
A filter_races_by_category() 0 4 5
A fill_coop_entrant_lists() 0 7 1
A script_defaults() 0 3 1
A fill_entrant_list() 0 7 3
A new_race_selected() 0 17 2
A fill_category_list() 0 7 3
A podium_toggled() 0 5 1
A script_save() 0 3 1
A script_description() 0 5 1
A script_update_coop_settings() 0 10 1
A script_coop_settings() 0 26 1
A script_update_setup_settings() 0 12 1
A script_properties() 0 8 1
A script_update_timer_settings() 0 23 3
A new_category_selected() 0 5 1
A script_load() 0 7 1
A qualifier_toggled() 0 5 1
A script_update_qualifier_settings() 0 11 1
A script_timer_settings() 0 16 1
A coop_toggled() 0 5 1
A script_setup() 0 34 2
A set_source_text() 0 14 4
A refresh_pressed() 0 13 2
A script_update() 0 5 1

How to fix   Complexity   

Complexity

Complex classes like racetime_obs often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
from threading import Thread
2
from typing import List
3
4
import obspython as obs
5
import racetime_client
6
from helpers.obs_context_manager import data_ar, source_ar, source_list_ar
7
from models.category import Category
8
from models.race import Race
9
from rtgg_obs import RacetimeObs
10
11
rtgg_obs = RacetimeObs()
12
13
14
def script_description():
15
    return "<center><p>Select a text source to use as your timer and enter your full " + \
16
        "username on racetime.gg  (including discriminator). This only needs " + \
17
        "to be done once.\n\nThen select the race room each race you join and " + \
18
        "stop worrying about whether you started your timer or not.<hr/></p>"
19
20
21
def script_load(settings):
22
    rtgg_obs.timer.use_podium_colors = obs.obs_data_get_bool(
23
        settings, "use_podium")
24
25
    race_update_t = Thread(target=rtgg_obs.race_update_thread)
26
    race_update_t.daemon = True
27
    race_update_t.start()
28
29
30
def script_save(settings):
31
    obs.obs_data_set_bool(settings, "use_podium",
32
                          rtgg_obs.timer.use_podium_colors)
33
34
35
def script_update(settings):
36
    script_update_setup_settings(settings)
37
    script_update_timer_settings(settings)
38
    script_update_coop_settings(settings)
39
    script_update_qualifier_settings(settings)
40
41
42
def script_update_qualifier_settings(settings):
43
    rtgg_obs.qualifier.enabled = obs.obs_data_get_bool(
44
        settings, "use_qualifier")
45
    rtgg_obs.qualifier.qualifier_cutoff = obs.obs_data_get_int(
46
        settings, "qualifier_cutoff")
47
    rtgg_obs.logger.debug(
48
        f"qualifier_cutoff is {rtgg_obs.qualifier.qualifier_cutoff}")
49
    rtgg_obs.qualifier.par_source = obs.obs_data_get_string(
50
        settings, "qualifier_par_source")
51
    rtgg_obs.qualifier.score_source = obs.obs_data_get_string(
52
        settings, "qualifier_score_source")
53
54
55
def script_update_coop_settings(settings):
56
    rtgg_obs.coop.enabled = obs.obs_data_get_bool(settings, "use_coop")
57
    rtgg_obs.coop.partner = obs.obs_data_get_string(settings, "coop_partner")
58
    rtgg_obs.coop.opponent1 = obs.obs_data_get_string(
59
        settings, "coop_opponent1")
60
    rtgg_obs.coop.opponent2 = obs.obs_data_get_string(
61
        settings, "coop_opponent2")
62
    rtgg_obs.coop.source = obs.obs_data_get_string(settings, "coop_source")
63
    rtgg_obs.coop.label_source = obs.obs_data_get_string(
64
        settings, "coop_label")
65
66
67
def script_update_timer_settings(settings):
68
    obs.timer_remove(update_sources)
69
70
    rtgg_obs.timer.use_podium_colors = obs.obs_data_get_bool(
71
        settings, "use_podium")
72
    rtgg_obs.timer.pre_color = obs.obs_data_get_int(settings, "pre_color")
73
    rtgg_obs.timer.first_color = obs.obs_data_get_int(settings, "first_color")
74
    rtgg_obs.timer.second_color = obs.obs_data_get_int(
75
        settings, "second_color")
76
    rtgg_obs.timer.third_color = obs.obs_data_get_int(settings, "third_color")
77
    rtgg_obs.timer.racing_color = obs.obs_data_get_int(
78
        settings, "racing_color")
79
    rtgg_obs.timer.finished_color = obs.obs_data_get_int(
80
        settings, "finished_color")
81
82
    if rtgg_obs.timer.source_name != "" and rtgg_obs.selected_race != "":
83
        obs.timer_add(update_sources, 100)
84
        rtgg_obs.timer.enabled = True
85
    else:
86
        rtgg_obs.timer.enabled = False
87
    rtgg_obs.logger.debug(f"timer.enabled is {rtgg_obs.timer.enabled}")
88
    rtgg_obs.logger.debug(f"timer.source_name is {rtgg_obs.timer.source_name}")
89
    rtgg_obs.logger.debug(f"selected_race is {rtgg_obs.selected_race}")
90
91
92
def script_update_setup_settings(settings):
93
    rtgg_obs.update_logger(obs.obs_data_get_bool(settings, "enable_log"),
94
                           obs.obs_data_get_bool(settings, "log_to_file"),
95
                           obs.obs_data_get_string(settings, "log_file"),
96
                           obs.obs_data_get_string(settings, "log_level"))
97
98
    rtgg_obs.full_name = obs.obs_data_get_string(settings, "username")
99
100
    rtgg_obs.timer.source_name = obs.obs_data_get_string(settings, "source")
101
102
    rtgg_obs.selected_race = obs.obs_data_get_string(settings, "race")
103
    rtgg_obs.category = obs.obs_data_get_string(settings, "category_filter")
104
105
106
def script_defaults(settings):
107
    obs.obs_data_set_default_string(settings, "race_info", "Race info")
108
    obs.obs_data_set_default_string(settings, "race", "")
109
110
111
def script_properties():
112
    props = obs.obs_properties_create()
113
    script_setup(props)
114
    script_timer_settings(props)
115
    script_coop_settings(props)
116
    script_qualifier_settings(props)
117
118
    return props
119
120
121
def script_qualifier_settings(props):
122
    p = obs.obs_properties_add_bool(
123
        props, "use_qualifier", "Display race results as tournament qualifier?")
124
    obs.obs_property_set_modified_callback(p, qualifier_toggled)
125
    qualifier_group = obs.obs_properties_create()
126
    obs.obs_properties_add_group(
127
        props, "qualifier_group", "Qualifier Mode", obs.OBS_GROUP_NORMAL, qualifier_group)
128
    obs.obs_property_set_visible(
129
        obs.obs_properties_get(props, "qualifier_group"), rtgg_obs.qualifier.enabled)
130
    p = obs.obs_properties_add_int_slider(
131
        qualifier_group, "qualifier_cutoff", "Use Top X as par time, where X=", 3, 10, 1)
132
    p = obs.obs_properties_add_list(qualifier_group, "qualifier_par_source",
133
                                    "Qualifier Par Time Source", obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING)
134
    fill_source_list(p)
135
    p = obs.obs_properties_add_list(qualifier_group, "qualifier_score_source",
136
                                    "Qualifier Score Source", obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING)
137
    fill_source_list(p)
138
139
140
def script_coop_settings(props):
141
    p = obs.obs_properties_add_bool(
142
        props, "use_coop", "Display coop information?")
143
    obs.obs_property_set_modified_callback(p, coop_toggled)
144
    coop_group = obs.obs_properties_create()
145
    obs.obs_properties_add_group(
146
        props, "coop_group", "Co-op Mode", obs.OBS_GROUP_NORMAL, coop_group)
147
    obs.obs_property_set_visible(
148
        obs.obs_properties_get(props, "coop_group"), rtgg_obs.coop.enabled)
149
    p = obs.obs_properties_add_list(
150
        coop_group, "coop_partner", "Co-op Partner", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING)
151
    p = obs.obs_properties_add_list(
152
        coop_group, "coop_opponent1", "Co-op Opponent 1", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING)
153
    p = obs.obs_properties_add_list(
154
        coop_group, "coop_opponent2", "Co-op Opponent 2", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING)
155
    fill_coop_entrant_lists(props)
156
    p = obs.obs_properties_add_list(coop_group, "coop_source", "Coop Text Source",
157
                                    obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING)
158
    obs.obs_property_set_long_description(
159
        p, "This text source will display the time that the last racer needs to finish for their team to win")
160
    fill_source_list(p)
161
    p = obs.obs_properties_add_list(coop_group, "coop_label", "Coop Label Text Source",
162
                                    obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING)
163
    obs.obs_property_set_long_description(
164
        p, "This text source will be use to display a label such as \'<PartnerName> needs to finish before\' based on who the last racer is")
165
    fill_source_list(p)
166
167
168
def script_timer_settings(props):
169
    p = obs.obs_properties_add_bool(
170
        props, "use_podium", "Use custom color for podium finishes?")
171
    obs.obs_property_set_modified_callback(p, podium_toggled)
172
    podium_group = obs.obs_properties_create()
173
    obs.obs_properties_add_group(
174
        props, "podium_group", "Podium Colors", obs.OBS_GROUP_NORMAL, podium_group)
175
    obs.obs_property_set_visible(obs.obs_properties_get(
176
        props, "podium_group"), rtgg_obs.timer.use_podium_colors)
177
    obs.obs_properties_add_color(podium_group, "pre_color", "Pre-race:")
178
    obs.obs_properties_add_color(podium_group, "racing_color", "Still racing:")
179
    obs.obs_properties_add_color(podium_group, "first_color", "1st place:")
180
    obs.obs_properties_add_color(podium_group, "second_color", "2nd place:")
181
    obs.obs_properties_add_color(podium_group, "third_color", "3rd place:")
182
    obs.obs_properties_add_color(
183
        podium_group, "finished_color", "After podium:")
184
185
186
def script_setup(props):
187
    setup_group = obs.obs_properties_create()
188
    obs.obs_properties_add_group(
189
        props, "initial_setup", "Initial setup - Check to make changes", obs.OBS_GROUP_CHECKABLE, setup_group)
190
    p = obs.obs_properties_add_list(
191
        setup_group, "source", "Text Source", obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING)
192
    fill_source_list(p)
193
    obs.obs_properties_add_text(
194
        setup_group, "username", "Username", obs.OBS_TEXT_DEFAULT)
195
    logging = obs.obs_properties_add_bool(
196
        setup_group, "enable_log", "Enable logging")
197
    log_levels = obs.obs_properties_add_list(
198
        setup_group, "log_level", "Log lever", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING)
199
    obs.obs_property_list_add_string(log_levels, "Error", "Error")
200
    obs.obs_property_list_add_string(log_levels, "Debug", "Debug")
201
    obs.obs_property_list_add_string(log_levels, "Info", "Info")
202
    obs.obs_property_set_long_description(
203
        logging, "Generally, only log errors unless you are developing or are trying to find a specific problem.")
204
    obs.obs_properties_add_bool(setup_group, "log_to_file", "Log to file?")
205
    category_list = obs.obs_properties_add_list(
206
        props, "category_filter", "Filter by Category", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING)
207
    race_list = obs.obs_properties_add_list(
208
        props, "race", "Race", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING)
209
    obs.obs_property_set_modified_callback(race_list, new_race_selected)
210
    obs.obs_property_set_modified_callback(
211
        category_list, new_category_selected)
212
213
    p = obs.obs_properties_add_text(
214
        props, "race_info", "Race Desc", obs.OBS_TEXT_MULTILINE)
215
    obs.obs_property_set_enabled(p, False)
216
217
    refresh = obs.obs_properties_add_button(
218
        props, "button", "Refresh", lambda *props: None)
219
    obs.obs_property_set_modified_callback(refresh, refresh_pressed)
220
221
222
def refresh_pressed(props, prop, *args, **kwargs):
223
    fill_source_list(obs.obs_properties_get(props, "source"))
224
    fill_source_list(obs.obs_properties_get(props, "coop_label"))
225
    fill_source_list(obs.obs_properties_get(props, "coop_text"))
226
    fill_source_list(obs.obs_properties_get(props, "qualifier_par_source"))
227
    fill_source_list(obs.obs_properties_get(props, "qualifier_score_source"))
228
    fill_race_list(obs.obs_properties_get(props, "race"),
229
                   obs.obs_properties_get(props, "category_filter"))
230
    if rtgg_obs.race is not None:
231
        rtgg_obs.coop.update_coop_text(rtgg_obs.race, rtgg_obs.full_name)
232
        rtgg_obs.qualifier.update_qualifier_text(
233
            rtgg_obs.race, rtgg_obs.full_name)
234
    return True
235
236
237
def new_race_selected(props, prop, settings):
238
    rtgg_obs.selected_race = obs.obs_data_get_string(settings, "race")
239
    r = racetime_client.get_race(rtgg_obs.selected_race)
240
    if r is not None:
241
        rtgg_obs.race = r
242
        rtgg_obs.coop.update_coop_text(rtgg_obs.race, rtgg_obs.full_name)
243
        rtgg_obs.qualifier.update_qualifier_text(
244
            rtgg_obs.race, rtgg_obs.full_name)
245
        rtgg_obs.logger.info(f"new race selected: {rtgg_obs.race}")
246
        obs.obs_data_set_default_string(settings, "race_info", r.info)
247
        fill_coop_entrant_lists(props)
248
    else:
249
        obs.obs_data_set_default_string(
250
            settings, "race_info", "Race not found")
251
252
    rtgg_obs.race_changed = True
253
    return True
254
255
256
def new_category_selected(props, prop, settings):
257
    rtgg_obs.category = obs.obs_data_get_string(settings, "category_filter")
258
    rtgg_obs.logger.info(f"new category selected: {rtgg_obs.category}")
259
    fill_race_list(obs.obs_properties_get(props, "race"), prop)
260
    return True
261
262
263
def podium_toggled(props, prop, settings):
264
    vis = obs.obs_data_get_bool(settings, "use_podium")
265
    obs.obs_property_set_visible(
266
        obs.obs_properties_get(props, "podium_group"), vis)
267
    return True
268
269
270
def coop_toggled(props, prop, settings):
271
    vis = obs.obs_data_get_bool(settings, "use_coop")
272
    obs.obs_property_set_visible(
273
        obs.obs_properties_get(props, "coop_group"), vis)
274
    return True
275
276
277
def qualifier_toggled(props, prop, settings):
278
    vis = obs.obs_data_get_bool(settings, "use_qualifier")
279
    obs.obs_property_set_visible(
280
        obs.obs_properties_get(props, "qualifier_group"), vis)
281
    return True
282
283
284
def update_sources():
285
    if rtgg_obs.race is not None:
286
        if rtgg_obs.timer.enabled:
287
            color, time = rtgg_obs.timer.get_timer_text(
288
                rtgg_obs.race, rtgg_obs.full_name)
289
            set_source_text(rtgg_obs.timer.source_name, time, color)
290
        if rtgg_obs.coop.enabled:
291
            set_source_text(rtgg_obs.coop.source, rtgg_obs.coop.text, None)
292
            set_source_text(rtgg_obs.coop.label_source,
293
                            rtgg_obs.coop.label_text, None)
294
        if rtgg_obs.qualifier.enabled:
295
            set_source_text(rtgg_obs.qualifier.par_source,
296
                            rtgg_obs.qualifier.par_text, None)
297
            set_source_text(rtgg_obs.qualifier.score_source,
298
                            rtgg_obs.qualifier.entrant_score, None)
299
        pass
300
301
302
def fill_source_list(p):
303
    obs.obs_property_list_clear(p)
304
    obs.obs_property_list_add_string(p, "", "")
305
    with source_list_ar() as sources:
306
        if sources is not None:
307
            for source in sources:
308
                source_id = obs.obs_source_get_unversioned_id(source)
309
                if source_id == "text_gdiplus" or source_id == "text_ft2_source":
310
                    name = obs.obs_source_get_name(source)
311
                    obs.obs_property_list_add_string(p, name, name)
312
313
314
def fill_race_list(race_list, category_list):
315
    obs.obs_property_list_clear(race_list)
316
    obs.obs_property_list_clear(category_list)
317
    obs.obs_property_list_add_string(category_list, "All", "All")
318
319
    obs.obs_property_list_add_string(race_list, "", "")
320
    races = racetime_client.get_races()
321
    if races is not None:
322
        fill_category_list(category_list, races)
323
        for race in filter_races_by_category(races, rtgg_obs.category):
324
            obs.obs_property_list_add_string(race_list, race.name, race.name)
325
326
327
def fill_category_list(category_list, races: List[Race]):
328
    categories = []
329
    for race in races:
330
        if not race.category.name in categories:
331
            categories.append(race.category.name)
332
            obs.obs_property_list_add_string(
333
                category_list, race.category.name, race.category.name)
334
335
336
def filter_races_by_category(races: List[Race], category: Category) -> Race:
337
    for race in races:
338
        if rtgg_obs.category == "" or rtgg_obs.category == "All" or race.category.name == rtgg_obs.category:
339
            yield race
340
341
342
def fill_coop_entrant_lists(props):
343
    fill_entrant_list(
344
        rtgg_obs.race, obs.obs_properties_get(props, "coop_partner"))
345
    fill_entrant_list(rtgg_obs.race, obs.obs_properties_get(
346
        props, "coop_opponent1"))
347
    fill_entrant_list(rtgg_obs.race, obs.obs_properties_get(
348
        props, "coop_opponent2"))
349
350
351
def fill_entrant_list(race, entrant_list):
352
    obs.obs_property_list_clear(entrant_list)
353
    obs.obs_property_list_add_string(entrant_list, "", "")
354
    if race is not None:
355
        for entrant in race.entrants:
356
            obs.obs_property_list_add_string(
357
                entrant_list, entrant.user.full_name, entrant.user.full_name)
358
359
# copied and modified from scripted-text.py by UpgradeQ
360
361
362
def set_source_text(source_name: str, text: str, color: int):
363
    with source_ar(source_name) as source, data_ar() as settings:
364
        obs.obs_data_set_string(settings, "text", text)
365
        source_id = obs.obs_source_get_unversioned_id(source)
366
        if color is not None:
367
            if source_id == "text_gdiplus":
368
                obs.obs_data_set_int(settings, "color", color)  # colored text
369
370
            else:  # freetype2,if taken from user input it should be reversed for getting correct color
371
                number = "".join(hex(color)[2:])
372
                color = int("0xff" f"{number}", base=16)
373
                obs.obs_data_set_int(settings, "color1", color)
374
375
        obs.obs_source_update(source, settings)
376