aa-bb 3.2.0__tar.gz → 3.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aa_bb-3.2.0 → aa_bb-3.2.2}/PKG-INFO +4 -4
- {aa_bb-3.2.0 → aa_bb-3.2.2}/README.md +3 -3
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/__init__.py +1 -1
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/auth_hooks.py +6 -4
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/awox.py +126 -68
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/clone_state.py +82 -22
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/sus_contacts.py +0 -10
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/tasks.py +10 -8
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/views_paps.py +10 -5
- {aa_bb-3.2.0 → aa_bb-3.2.2}/.gitignore +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/LICENSE +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/admin.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/app_settings.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/apps.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/celery.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/__init__.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/alliance_blacklist.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/alpha_skills.json +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/coalition_blacklist.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/corp_blacklist.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/corp_changes.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/cyno.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/hostile_assets.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/hostile_clones.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/roles_and_tokens.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/skills.json +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/skills.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/sus_contracts.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/sus_mails.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/sus_trans.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks_cb/__init__.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks_cb/hostile_assets.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks_cb/sus_contracts.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks_cb/sus_trans.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/esi_cache.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/esi_client.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/forms.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/locale/.gitkeep +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/management/__init__.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/management/commands/__init__.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/management/commands/manual_notif_test.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0001_initial.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0002_bigbrotherconfig_userstatus.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0003_alter_bigbrotherconfig_pingroleid.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0004_alter_bigbrotherconfig_is_active_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0005_alter_bigbrotherconfig_hostile_alliances.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0006_alter_bigbrotherconfig_pingroleid.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0007_alter_general_options.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0008_alliance_names_corporation_names.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0009_userstatus_sus_contacts_userstatus_sus_contracts_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0010_alter_userstatus_awox_kill_links_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0011_character_names.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0012_id_types.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0013_bigbrotherconfig_mail_keywords.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0014_processedcontract_processedmail_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0015_processedtransaction_sustransactionnote.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0016_warmprogress.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0017_entityinfocache.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0018_userstatus_cyno_userstatus_has_skills_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0019_bigbrotherconfig_whitelist_alliances_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0020_messages_bigbrotherconfig_are_daily_messages_active_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0021_alter_messages_id_alter_messages_text.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0022_messages_sent_in_cycle.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0023_optmessages1_optmessages2_optmessages3_optmessages4_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0024_bigbrotherconfig_dailyschedule_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0025_alter_messages_options_alter_optmessages1_options_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0026_alter_general_options_alter_bigbrotherconfig_id_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0027_alter_general_options_bigbrotherconfig_is_loa_active_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0028_alter_bigbrotherconfig_is_loa_active.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0029_leaverequest_main_character.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0030_alter_general_options.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0031_bigbrotherconfig_loa_max_logoff_days_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0032_alter_leaverequest_status.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0033_messagetype_bigbrotherconfig_pingroleid2_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0034_rename_last_updated_userstatus_updated.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0035_alter_userstatus_options.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0036_alter_general_options.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0037_corpstatus.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0038_bigbrotherconfig_ignored_corporations.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0039_alter_bigbrotherconfig_ignored_corporations.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0040_sovereigntymapcache_corporationinfocache_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0041_bigbrotherconfig_is_warmer_active.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0042_alter_general_options_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0043_bigbrotherconfig_bb_member_states_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0044_bigbrotherconfig_character_scopes_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0045_userstatus_sp_age_ratio_result.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0046_bigbrotherconfig_member_corporations.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0047_bigbrotherconfig_member_alliances.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0048_characteraccountstate.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0049_userstatus_clone_status.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0050_alter_general_options_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0051_monthlypapstats.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0052_papsconfig_delete_monthlypapstats.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0053_alter_papsconfig_corp_modifier_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0054_alter_general_options.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0055_papsconfig_group_paps_papsconfig_group_paps_modifier.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0056_alter_papsconfig_group_paps.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0057_papsconfig_excluded_groups_papsconfig_excluded_users_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0058_papsconfig_excluded_groups_get_paps_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0059_alter_papsconfig_excluded_groups_get_paps.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0060_papsconfig_cap_group_papsconfig_cap_group_paps_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0061_remove_papsconfig_cap_group_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0062_complianceticket.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0063_alter_complianceticket_user.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0064_alter_complianceticket_reason.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0065_alter_complianceticket_reason_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0066_tickettoolconfig_papcompliance.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0067_remove_tickettoolconfig_category_id_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0068_tickettoolconfig_category_id_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0069_tickettoolconfig_ticket_counter.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0070_remove_tickettoolconfig_role_id_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0071_tickettoolconfig_role_id.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0072_alter_complianceticket_reason.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0073_tickettoolconfig_starting_pap_compliance.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0074_alter_bigbrotherconfig_hostile_alliances.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0075_bbupdatestate_tickettoolconfig_afk_check_frequency_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0076_id_types_last_accessed_frequentcorpchangescache_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0077_complianceticket_ticket_id_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0078_tickettoolconfig_awox_monitor_enabled.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0079_helptext_guidance.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0080_bigbrotherconfig_dlc_flags.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0081_remove_papsconfig_imp_modifier_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0082_remove_bigbrotherconfig_token_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0083_alter_bigbrotherredditmessage_options_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0084_bigbrotherconfig_consider_all_structures_hostile_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0085_bigbrotherconfig_awox_notify_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0086_bigbrotherconfig_are_recurring_stats_active_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0087_bigbrotherconfig_dlc_are_recurring_stats_active.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0088_bigbrotherconfig_cyno_notify.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0089_bigbrotherconfig_asset_notify_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0090_alter_papsconfig_options_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0091_remove_bigbrotherconfig_dlc_are_recurring_stats_active_and_more.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0092_userstatus_baseline_initialized.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/__init__.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/models.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/reddit.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/signals.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/static/aa_bb/.gitkeep +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/tasks_bot.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/tasks_cb.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/tasks_ct.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/tasks_other.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/tasks_reddit.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/tasks_tickets.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/aa_bb/base.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/aa_bb/disabled.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/aa_bb/index.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/aa_cb/base.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/aa_cb/disabled.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/aa_cb/index.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/faq/base.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/faq/cards.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/faq/faq.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/faq/menu.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/faq/modules.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/faq/settings_bigbrother.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/faq/settings_nav.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/faq/settings_paps.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/faq/settings_tickets.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/loa/_loa_subtabs.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/loa/admin.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/loa/base.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/loa/disabled.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/loa/index.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/loa/menu.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/loa/request.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/paps/base.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/paps/disabled.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/paps/history.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/paps/index.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/paps/menu.html +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/tests/__init__.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/tests/test_example.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/urls.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/urls_cb.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/urls_loa.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/urls_paps.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/views.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/views_cb.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/views_faq.py +0 -0
- {aa_bb-3.2.0 → aa_bb-3.2.2}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aa_bb
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.2
|
|
4
4
|
Summary: BigBrother
|
|
5
5
|
Project-URL: Changelog, https://github.com/BroodLK/aa_bb/blob/main/CHANGELOG.md
|
|
6
6
|
Project-URL: Issue / Bug Reports, https://github.com/BroodLK/aa_bb/issues
|
|
@@ -76,8 +76,6 @@ All while invisible to the general membership unless you chose to expose it to t
|
|
|
76
76
|
### The following AllianceAuth plugins are **_required_**:
|
|
77
77
|
|
|
78
78
|
```md
|
|
79
|
-
allianceauth-afat >= 4.1.1
|
|
80
|
-
allianceauth-blacklist >= 0.1.1
|
|
81
79
|
allianceauth-corptools >= 2.12.0 (this app will adopt 3.0.0 as soon as version 3 is out of beta)
|
|
82
80
|
allianceauth-discordbot >= 4.1.0
|
|
83
81
|
aa-charlink >= 1.11.1
|
|
@@ -88,6 +86,8 @@ You do not have to use afat, but it does need to be installed.
|
|
|
88
86
|
|
|
89
87
|
### Optional plugins
|
|
90
88
|
```md
|
|
89
|
+
allianceauth-afat >= 4.1.1
|
|
90
|
+
allianceauth-blacklist >= 0.1.1
|
|
91
91
|
aa-contacts >= 0.10.2
|
|
92
92
|
```
|
|
93
93
|
As stated, this plugin is optional but is obviously required for using it to sync your hostiles.
|
|
@@ -98,7 +98,7 @@ After making sure to add the above prerequisite applications.
|
|
|
98
98
|
source /home/allianceserver/venv/auth/bin/activate && cd /home/allianceserver/myauth/
|
|
99
99
|
```
|
|
100
100
|
```bash
|
|
101
|
-
pip install aa-bb==3.1
|
|
101
|
+
pip install aa-bb==3.2.1
|
|
102
102
|
```
|
|
103
103
|
```bash
|
|
104
104
|
vi myauth/settings/local.py
|
|
@@ -36,8 +36,6 @@ All while invisible to the general membership unless you chose to expose it to t
|
|
|
36
36
|
### The following AllianceAuth plugins are **_required_**:
|
|
37
37
|
|
|
38
38
|
```md
|
|
39
|
-
allianceauth-afat >= 4.1.1
|
|
40
|
-
allianceauth-blacklist >= 0.1.1
|
|
41
39
|
allianceauth-corptools >= 2.12.0 (this app will adopt 3.0.0 as soon as version 3 is out of beta)
|
|
42
40
|
allianceauth-discordbot >= 4.1.0
|
|
43
41
|
aa-charlink >= 1.11.1
|
|
@@ -48,6 +46,8 @@ You do not have to use afat, but it does need to be installed.
|
|
|
48
46
|
|
|
49
47
|
### Optional plugins
|
|
50
48
|
```md
|
|
49
|
+
allianceauth-afat >= 4.1.1
|
|
50
|
+
allianceauth-blacklist >= 0.1.1
|
|
51
51
|
aa-contacts >= 0.10.2
|
|
52
52
|
```
|
|
53
53
|
As stated, this plugin is optional but is obviously required for using it to sync your hostiles.
|
|
@@ -58,7 +58,7 @@ After making sure to add the above prerequisite applications.
|
|
|
58
58
|
source /home/allianceserver/venv/auth/bin/activate && cd /home/allianceserver/myauth/
|
|
59
59
|
```
|
|
60
60
|
```bash
|
|
61
|
-
pip install aa-bb==3.1
|
|
61
|
+
pip install aa-bb==3.2.1
|
|
62
62
|
```
|
|
63
63
|
```bash
|
|
64
64
|
vi myauth/settings/local.py
|
|
@@ -11,6 +11,7 @@ from allianceauth.services.hooks import MenuItemHook, UrlHook
|
|
|
11
11
|
from aa_bb import urls, urls_loa, urls_cb, urls_paps
|
|
12
12
|
from .models import BigBrotherConfig, LeaveRequest
|
|
13
13
|
|
|
14
|
+
from .app_settings import afat_active
|
|
14
15
|
|
|
15
16
|
class CorpBrotherMenuItem(MenuItemHook):
|
|
16
17
|
"""This class ensures only authorized users will see the menu entry"""
|
|
@@ -174,10 +175,11 @@ def register_paps_menu():
|
|
|
174
175
|
"""Register the PAP stats sidebar entry."""
|
|
175
176
|
return PapsMenuItem()
|
|
176
177
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
if afat_active():
|
|
179
|
+
@hooks.register("url_hook")
|
|
180
|
+
def register_paps_urls():
|
|
181
|
+
"""Wire the PAP URLconf into AllianceAuth."""
|
|
182
|
+
return UrlHook(urls_paps, "paps", r"^paps/")
|
|
181
183
|
|
|
182
184
|
|
|
183
185
|
@hooks.register('discord_cogs_hook')
|
|
@@ -6,30 +6,33 @@ cache management, and rendering helpers so the calling views do not have to
|
|
|
6
6
|
care about throttling or HTML generation.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
import requests
|
|
10
9
|
import time
|
|
11
|
-
import
|
|
12
|
-
from django.utils.html import format_html
|
|
13
|
-
from django.utils.safestring import mark_safe
|
|
14
|
-
from allianceauth.authentication.models import CharacterOwnership
|
|
10
|
+
from functools import lru_cache
|
|
15
11
|
|
|
16
|
-
|
|
12
|
+
import requests
|
|
13
|
+
from allianceauth.authentication.models import CharacterOwnership
|
|
17
14
|
from django.utils import timezone
|
|
15
|
+
from django.utils.html import format_html
|
|
16
|
+
from django.utils.safestring import mark_safe
|
|
18
17
|
from esi.exceptions import HTTPNotModified
|
|
19
|
-
from
|
|
18
|
+
from requests.adapters import HTTPAdapter
|
|
19
|
+
from urllib3.util.retry import Retry
|
|
20
|
+
|
|
20
21
|
from ..app_settings import (
|
|
21
22
|
DATASOURCE,
|
|
22
23
|
esi_tenant_kwargs,
|
|
23
|
-
get_site_url,
|
|
24
24
|
get_contact_email,
|
|
25
25
|
get_owner_name,
|
|
26
|
-
|
|
26
|
+
get_site_url,
|
|
27
27
|
resolve_alliance_name,
|
|
28
|
+
send_message,
|
|
28
29
|
)
|
|
29
|
-
from requests.adapters import HTTPAdapter
|
|
30
|
-
from urllib3.util.retry import Retry
|
|
31
30
|
|
|
32
|
-
|
|
31
|
+
from ..esi_client import call_result, esi
|
|
32
|
+
from ..models import AwoxKillsCache, BigBrotherConfig
|
|
33
|
+
|
|
34
|
+
from allianceauth.services.hooks import get_extension_logger
|
|
35
|
+
logger = get_extension_logger(__name__)
|
|
33
36
|
|
|
34
37
|
USER_AGENT = f"{get_site_url()} Maintainer: {get_owner_name()} {get_contact_email()}"
|
|
35
38
|
HEADERS = {
|
|
@@ -38,6 +41,17 @@ HEADERS = {
|
|
|
38
41
|
"Accept": "application/json",
|
|
39
42
|
}
|
|
40
43
|
|
|
44
|
+
# How long we consider the cached awox data "fresh"
|
|
45
|
+
AWOX_CACHE_TTL_SECONDS = 60 * 60 # 60 minutes
|
|
46
|
+
|
|
47
|
+
# How many recent zKill entries we will hydrate per character (prevents runaway ESI calls)
|
|
48
|
+
MAX_KILLS_PER_CHARACTER = 25
|
|
49
|
+
|
|
50
|
+
# Limit zKill "down" notifications to once every 2 hours
|
|
51
|
+
_last_zkill_down_notice_monotonic = 0.0
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@lru_cache(maxsize=512)
|
|
41
55
|
def _get_corp_name(corp_id):
|
|
42
56
|
if not corp_id:
|
|
43
57
|
return "None"
|
|
@@ -48,21 +62,21 @@ def _get_corp_name(corp_id):
|
|
|
48
62
|
except Exception:
|
|
49
63
|
return f"Unknown ({corp_id})"
|
|
50
64
|
|
|
51
|
-
# Limit zKill "down" notifications to once every 2 hours
|
|
52
|
-
_last_zkill_down_notice_monotonic = 0.0
|
|
53
65
|
|
|
66
|
+
@lru_cache(maxsize=512)
|
|
67
|
+
def _get_alliance_name(alliance_id):
|
|
68
|
+
if not alliance_id:
|
|
69
|
+
return None
|
|
70
|
+
try:
|
|
71
|
+
return resolve_alliance_name(alliance_id)
|
|
72
|
+
except Exception:
|
|
73
|
+
return None
|
|
54
74
|
|
|
55
|
-
def _notify_zkill_down_once(preview: str, status: int | None, content_type: str | None):
|
|
56
|
-
"""
|
|
57
|
-
Fire a single Discord notification when zKill returns junk.
|
|
58
75
|
|
|
59
|
-
|
|
60
|
-
~hour so alerts do not spam when zKill is flaking.
|
|
61
|
-
"""
|
|
76
|
+
def _notify_zkill_down_once(preview: str, status: int | None, content_type: str | None):
|
|
62
77
|
global _last_zkill_down_notice_monotonic
|
|
63
78
|
now = time.monotonic()
|
|
64
|
-
|
|
65
|
-
if now - _last_zkill_down_notice_monotonic < 3500: # Skip notification if a warning was sent recently.
|
|
79
|
+
if now - _last_zkill_down_notice_monotonic < 2 * 60 * 60:
|
|
66
80
|
return
|
|
67
81
|
_last_zkill_down_notice_monotonic = now
|
|
68
82
|
msg = (
|
|
@@ -71,13 +85,23 @@ def _notify_zkill_down_once(preview: str, status: int | None, content_type: str
|
|
|
71
85
|
f"body preview: ```{preview}```"
|
|
72
86
|
)
|
|
73
87
|
try:
|
|
74
|
-
awox_notify = BigBrotherConfig.awox_notify
|
|
88
|
+
awox_notify = BigBrotherConfig.get_solo().awox_notify
|
|
75
89
|
if awox_notify:
|
|
76
90
|
send_message(msg)
|
|
77
91
|
except Exception as e:
|
|
78
92
|
logger.warning(f"Failed to send zKill down notification: {e}")
|
|
79
93
|
|
|
80
94
|
|
|
95
|
+
def _get_requests_session() -> requests.Session:
|
|
96
|
+
session = requests.Session()
|
|
97
|
+
session.headers.update(HEADERS)
|
|
98
|
+
retries = Retry(total=3, backoff_factor=0.2, status_forcelist=[500, 502, 503, 504])
|
|
99
|
+
adapter = HTTPAdapter(max_retries=retries)
|
|
100
|
+
session.mount("https://", adapter)
|
|
101
|
+
session.mount("http://", adapter)
|
|
102
|
+
return session
|
|
103
|
+
|
|
104
|
+
|
|
81
105
|
def fetch_awox_kills(user_id, delay=0.2):
|
|
82
106
|
"""
|
|
83
107
|
Return a deduplicated list of awox kill summaries for the given user.
|
|
@@ -86,48 +110,64 @@ def fetch_awox_kills(user_id, delay=0.2):
|
|
|
86
110
|
recent awox activity is pulled from zKill, the full mail is hydrated
|
|
87
111
|
via ESI, and the resulting summary is cached for future calls.
|
|
88
112
|
"""
|
|
89
|
-
|
|
113
|
+
now = timezone.now()
|
|
114
|
+
|
|
115
|
+
# DB cache with TTL: return cached kills if present & fresh
|
|
90
116
|
try:
|
|
91
117
|
cache = AwoxKillsCache.objects.get(pk=user_id)
|
|
92
118
|
try:
|
|
93
|
-
|
|
94
|
-
cache.save(update_fields=["last_accessed"])
|
|
119
|
+
last_accessed = cache.last_accessed
|
|
95
120
|
except Exception:
|
|
96
|
-
|
|
97
|
-
|
|
121
|
+
last_accessed = None
|
|
122
|
+
|
|
123
|
+
if last_accessed and (now - last_accessed).total_seconds() < AWOX_CACHE_TTL_SECONDS:
|
|
124
|
+
try:
|
|
125
|
+
cache.last_accessed = now
|
|
126
|
+
cache.save(update_fields=["last_accessed"])
|
|
127
|
+
except Exception:
|
|
128
|
+
try:
|
|
129
|
+
cache.save()
|
|
130
|
+
except Exception:
|
|
131
|
+
pass
|
|
132
|
+
return cache.data or None
|
|
98
133
|
except AwoxKillsCache.DoesNotExist:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
char_id_map = {c.character.character_id: c.character.character_name for c in characters}
|
|
134
|
+
cache = None
|
|
135
|
+
except Exception:
|
|
136
|
+
cache = None
|
|
103
137
|
|
|
138
|
+
characters = CharacterOwnership.objects.filter(user__id=user_id).select_related("character")
|
|
139
|
+
char_ids_list = [c.character.character_id for c in characters if getattr(c, "character", None)]
|
|
140
|
+
char_ids = set(char_ids_list)
|
|
141
|
+
if not char_ids_list:
|
|
142
|
+
return None
|
|
104
143
|
|
|
105
144
|
kills_by_id = {}
|
|
145
|
+
session = _get_requests_session()
|
|
106
146
|
|
|
107
|
-
|
|
108
|
-
session.headers.update(HEADERS)
|
|
109
|
-
session.headers.update({"Connection": "close"})
|
|
110
|
-
retries = Retry(total=3, backoff_factor=0.2, status_forcelist=[500, 502, 503, 504])
|
|
111
|
-
session.mount("https://", HTTPAdapter(max_retries=retries))
|
|
112
|
-
|
|
113
|
-
for char_id in char_ids:
|
|
147
|
+
for char_id in char_ids_list:
|
|
114
148
|
zkill_url = f"https://zkillboard.com/api/characterID/{char_id}/awox/1/"
|
|
149
|
+
start_ts = time.monotonic()
|
|
115
150
|
try:
|
|
116
151
|
response = session.get(zkill_url, timeout=(3, 10))
|
|
117
152
|
response.raise_for_status()
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
153
|
+
finally:
|
|
154
|
+
elapsed = time.monotonic() - start_ts
|
|
155
|
+
logger.info(
|
|
156
|
+
"[AWOX][zKill] char_id=%s elapsed=%.3fs status=%s",
|
|
157
|
+
char_id,
|
|
158
|
+
elapsed,
|
|
159
|
+
getattr(response, "status_code", "ERR"),
|
|
160
|
+
)
|
|
121
161
|
|
|
122
|
-
# zKill may return HTML/Cloudflare challenge or a custom error page
|
|
123
162
|
content_type = response.headers.get("Content-Type", "")
|
|
124
|
-
text_preview = (response.text or "").strip()[:200]
|
|
125
163
|
text_lower = (response.text or "").lower()
|
|
164
|
+
text_preview = (response.text or "").strip()[:200]
|
|
165
|
+
|
|
126
166
|
if (
|
|
127
167
|
not content_type.startswith("application/json")
|
|
128
168
|
or "so a big oops happened" in text_lower
|
|
129
169
|
or "cdn-cgi/challenge-platform" in text_lower
|
|
130
|
-
):
|
|
170
|
+
):
|
|
131
171
|
logger.warning(
|
|
132
172
|
"Non-JSON response from zKillboard for %s: status=%s content_type=%s body='%s'",
|
|
133
173
|
char_id,
|
|
@@ -150,14 +190,19 @@ def fetch_awox_kills(user_id, delay=0.2):
|
|
|
150
190
|
_notify_zkill_down_once(text_preview, response.status_code, content_type)
|
|
151
191
|
continue
|
|
152
192
|
|
|
193
|
+
if isinstance(killmails, list):
|
|
194
|
+
killmails = killmails[:MAX_KILLS_PER_CHARACTER]
|
|
195
|
+
else:
|
|
196
|
+
continue
|
|
197
|
+
|
|
153
198
|
for kill in killmails:
|
|
154
199
|
kill_id = kill.get("killmail_id")
|
|
155
200
|
hash_ = kill.get("zkb", {}).get("hash")
|
|
156
201
|
value = kill.get("zkb", {}).get("totalValue", 0)
|
|
157
202
|
|
|
158
|
-
if not kill_id or not hash_:
|
|
203
|
+
if not kill_id or not hash_:
|
|
159
204
|
continue
|
|
160
|
-
if kill_id in kills_by_id:
|
|
205
|
+
if kill_id in kills_by_id:
|
|
161
206
|
continue
|
|
162
207
|
|
|
163
208
|
operation = esi.client.Killmails.GetKillmailsKillmailIdKillmailHash(
|
|
@@ -165,45 +210,50 @@ def fetch_awox_kills(user_id, delay=0.2):
|
|
|
165
210
|
killmail_hash=hash_,
|
|
166
211
|
**esi_tenant_kwargs(DATASOURCE),
|
|
167
212
|
)
|
|
213
|
+
|
|
168
214
|
try:
|
|
169
215
|
full_kill, _ = call_result(operation)
|
|
170
216
|
except HTTPNotModified:
|
|
171
|
-
|
|
217
|
+
continue
|
|
172
218
|
except Exception as e:
|
|
173
|
-
logger.warning("
|
|
219
|
+
logger.warning(f"Error fetching killmail from ESI for kill_id={kill_id}: {e}")
|
|
174
220
|
continue
|
|
175
221
|
|
|
176
|
-
attackers = full_kill.get("attackers", [])
|
|
177
222
|
victim = full_kill.get("victim", {})
|
|
178
223
|
victim_id = victim.get("character_id")
|
|
224
|
+
if not victim_id or victim_id not in char_ids:
|
|
225
|
+
continue
|
|
179
226
|
|
|
180
|
-
|
|
227
|
+
attackers = full_kill.get("attackers", []) or []
|
|
181
228
|
attacker_affiliations = []
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
229
|
+
attacker_names = []
|
|
230
|
+
|
|
231
|
+
for a in attackers:
|
|
232
|
+
a_id = a.get("character_id")
|
|
233
|
+
if not a_id:
|
|
234
|
+
continue
|
|
235
|
+
if a_id in char_ids:
|
|
236
|
+
attacker_names.append(a_id)
|
|
187
237
|
attacker_affiliations.append({
|
|
188
|
-
"corp_id":
|
|
189
|
-
"alliance_id":
|
|
238
|
+
"corp_id": a.get("corporation_id"),
|
|
239
|
+
"alliance_id": a.get("alliance_id"),
|
|
190
240
|
})
|
|
191
241
|
|
|
192
|
-
if not attacker_names:
|
|
242
|
+
if not attacker_names:
|
|
193
243
|
continue
|
|
194
244
|
|
|
195
245
|
# Resolve names
|
|
196
|
-
att_info = attacker_affiliations[0]
|
|
197
|
-
att_corp = _get_corp_name(att_info
|
|
198
|
-
att_alli =
|
|
246
|
+
att_info = attacker_affiliations[0] if attacker_affiliations else {}
|
|
247
|
+
att_corp = _get_corp_name(att_info.get("corp_id"))
|
|
248
|
+
att_alli = _get_alliance_name(att_info.get("alliance_id"))
|
|
199
249
|
|
|
200
250
|
vic_corp_id = victim.get("corporation_id")
|
|
201
251
|
vic_alli_id = victim.get("alliance_id")
|
|
202
252
|
vic_corp = _get_corp_name(vic_corp_id)
|
|
203
|
-
vic_alli =
|
|
253
|
+
vic_alli = _get_alliance_name(vic_alli_id)
|
|
204
254
|
|
|
205
255
|
kills_by_id[kill_id] = {
|
|
206
|
-
"value": int(value),
|
|
256
|
+
"value": int(value) if value is not None else 0,
|
|
207
257
|
"link": f"https://zkillboard.com/kill/{kill_id}/",
|
|
208
258
|
"chars": attacker_names,
|
|
209
259
|
"att_corp": att_corp,
|
|
@@ -213,16 +263,24 @@ def fetch_awox_kills(user_id, delay=0.2):
|
|
|
213
263
|
"date": full_kill.get("killmail_time"),
|
|
214
264
|
}
|
|
215
265
|
|
|
266
|
+
time.sleep(delay)
|
|
267
|
+
|
|
216
268
|
data_list = list(kills_by_id.values()) if kills_by_id else []
|
|
217
269
|
try:
|
|
218
270
|
AwoxKillsCache.objects.update_or_create(
|
|
219
271
|
user_id=user_id,
|
|
220
|
-
defaults={"data": data_list, "last_accessed":
|
|
272
|
+
defaults={"data": data_list, "last_accessed": now},
|
|
221
273
|
)
|
|
222
274
|
except Exception:
|
|
223
|
-
|
|
224
|
-
|
|
275
|
+
try:
|
|
276
|
+
if cache:
|
|
277
|
+
cache.data = data_list
|
|
278
|
+
cache.last_accessed = now
|
|
279
|
+
cache.save()
|
|
280
|
+
except Exception:
|
|
281
|
+
pass
|
|
225
282
|
|
|
283
|
+
return data_list if data_list else None
|
|
226
284
|
|
|
227
285
|
def render_awox_kills_html(userID):
|
|
228
286
|
"""
|
|
@@ -269,7 +327,7 @@ def get_awox_kill_links(user_id):
|
|
|
269
327
|
without having to duplicate the fetch/cache logic.
|
|
270
328
|
"""
|
|
271
329
|
kills = fetch_awox_kills(user_id)
|
|
272
|
-
if not kills: # No cached kills yet
|
|
330
|
+
if not kills: # No cached kills yet
|
|
273
331
|
return []
|
|
274
332
|
|
|
275
333
|
results = []
|
|
@@ -14,6 +14,7 @@ from django.utils import timezone
|
|
|
14
14
|
import json
|
|
15
15
|
import os
|
|
16
16
|
import logging
|
|
17
|
+
from datetime import timedelta, time
|
|
17
18
|
|
|
18
19
|
try:
|
|
19
20
|
from corptools.models import CharacterAudit, Skill
|
|
@@ -23,6 +24,41 @@ except Exception:
|
|
|
23
24
|
|
|
24
25
|
logger = logging.getLogger(__name__)
|
|
25
26
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
27
|
+
MAX_CACHE_AGE = timedelta(hours=24)
|
|
28
|
+
UTC_WINDOW_START = time(6, 0) # 06:00 UTC
|
|
29
|
+
UTC_WINDOW_END = time(10, 0) # 10:00 UTC
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _load_fallback_skill_ids():
|
|
33
|
+
"""
|
|
34
|
+
Load skills.json and return a sorted list of all skill IDs contained.
|
|
35
|
+
skills.json is a mapping of category name -> list[dict], where the 2nd dict
|
|
36
|
+
contains skill_id(str) -> skill_name.
|
|
37
|
+
"""
|
|
38
|
+
skills_json_file = os.path.join(BASE_DIR, "skills.json")
|
|
39
|
+
try:
|
|
40
|
+
with open(skills_json_file, "r") as f:
|
|
41
|
+
skills_by_cat = json.load(f)
|
|
42
|
+
except Exception:
|
|
43
|
+
logger.exception("Failed to load skills.json fallback list.")
|
|
44
|
+
return []
|
|
45
|
+
|
|
46
|
+
ids = set()
|
|
47
|
+
for _cat, blocks in skills_by_cat.items():
|
|
48
|
+
for block in blocks:
|
|
49
|
+
for k in block.keys():
|
|
50
|
+
if k == "Category ID":
|
|
51
|
+
continue
|
|
52
|
+
try:
|
|
53
|
+
ids.add(int(k))
|
|
54
|
+
except Exception:
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
return sorted(ids)
|
|
58
|
+
|
|
59
|
+
def in_utc_update_window(now):
|
|
60
|
+
utc_time = now.time()
|
|
61
|
+
return UTC_WINDOW_START <= utc_time < UTC_WINDOW_END
|
|
26
62
|
|
|
27
63
|
|
|
28
64
|
def determine_character_state(user_id, save: bool = False):
|
|
@@ -31,12 +67,16 @@ def determine_character_state(user_id, save: bool = False):
|
|
|
31
67
|
"""
|
|
32
68
|
alpha_skills_file = os.path.join(BASE_DIR, "alpha_skills.json")
|
|
33
69
|
|
|
34
|
-
# Load
|
|
70
|
+
# Load skill
|
|
35
71
|
with open(alpha_skills_file, "r") as f:
|
|
36
72
|
alpha_skills = json.load(f)
|
|
37
73
|
alpha_caps = {skill["id"]: skill["cap"] for skill in alpha_skills}
|
|
38
74
|
alpha_skill_ids = [skill["id"] for skill in alpha_skills]
|
|
39
75
|
|
|
76
|
+
skills_json_file = os.path.join(BASE_DIR, "skills.json")
|
|
77
|
+
with open(skills_json_file, "r") as f:
|
|
78
|
+
skills = json.load(f)
|
|
79
|
+
|
|
40
80
|
char_db_records = {
|
|
41
81
|
rec.char_id: rec for rec in CharacterAccountState.objects.all()
|
|
42
82
|
}
|
|
@@ -57,7 +97,6 @@ def determine_character_state(user_id, save: bool = False):
|
|
|
57
97
|
|
|
58
98
|
char_ids = list(all_char_ids)
|
|
59
99
|
|
|
60
|
-
# Build total_sp map (FAST: select_related only, no skill prefetch)
|
|
61
100
|
audits = (
|
|
62
101
|
CharacterAudit.objects
|
|
63
102
|
.filter(character__character_id__in=char_ids)
|
|
@@ -72,31 +111,27 @@ def determine_character_state(user_id, save: bool = False):
|
|
|
72
111
|
except Exception:
|
|
73
112
|
total_sp_by_char[cid] = None
|
|
74
113
|
|
|
75
|
-
# Decide which characters need checking (SP gate)
|
|
76
114
|
chars_to_check = []
|
|
77
115
|
for char_id in char_ids:
|
|
78
116
|
db_record = char_db_records.get(char_id)
|
|
79
|
-
total_sp = total_sp_by_char.get(char_id)
|
|
80
117
|
|
|
81
|
-
#
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
118
|
+
# Reuse cached state unless it's been 24 hours since last
|
|
119
|
+
now = timezone.now()
|
|
120
|
+
if db_record and db_record.last_checked_at and (now - db_record.last_checked_at) < MAX_CACHE_AGE and in_utc_update_window(now):
|
|
121
|
+
result[char_id] = {
|
|
122
|
+
"state": db_record.state if db_record.state else "unknown",
|
|
123
|
+
"skill_used": db_record.skill_used,
|
|
124
|
+
"last_state": db_record.state,
|
|
125
|
+
"last_checked_at": db_record.last_checked_at,
|
|
126
|
+
}
|
|
127
|
+
continue
|
|
128
|
+
else:
|
|
129
|
+
chars_to_check.append(char_id)
|
|
92
130
|
|
|
93
131
|
# Nothing to do
|
|
94
132
|
if not chars_to_check:
|
|
95
133
|
return result
|
|
96
134
|
|
|
97
|
-
# Build the minimal set of skill IDs to fetch:
|
|
98
|
-
# - all alpha-locked skills
|
|
99
|
-
# - plus any cached skill_used for chars needing check (so we keep the fast shortcut idea)
|
|
100
135
|
extra_skill_ids = set()
|
|
101
136
|
for char_id in chars_to_check:
|
|
102
137
|
db_record = char_db_records.get(char_id)
|
|
@@ -105,7 +140,6 @@ def determine_character_state(user_id, save: bool = False):
|
|
|
105
140
|
|
|
106
141
|
skill_ids_to_fetch = sorted(set(alpha_skill_ids) | extra_skill_ids)
|
|
107
142
|
|
|
108
|
-
# Single query: pull only the relevant Skill rows for just these characters
|
|
109
143
|
skill_rows = (
|
|
110
144
|
Skill.objects
|
|
111
145
|
.filter(character__character__character_id__in=chars_to_check, skill_id__in=skill_ids_to_fetch)
|
|
@@ -126,8 +160,7 @@ def determine_character_state(user_id, save: bool = False):
|
|
|
126
160
|
"active": int(row["active_skill_level"]),
|
|
127
161
|
}
|
|
128
162
|
|
|
129
|
-
|
|
130
|
-
|
|
163
|
+
fallback_skill_ids = _load_fallback_skill_ids()
|
|
131
164
|
for char_id in chars_to_check:
|
|
132
165
|
db_record = char_db_records.get(char_id)
|
|
133
166
|
total_sp = total_sp_by_char.get(char_id)
|
|
@@ -167,6 +200,32 @@ def determine_character_state(user_id, save: bool = False):
|
|
|
167
200
|
skill_used = sid
|
|
168
201
|
break
|
|
169
202
|
|
|
203
|
+
# 3) Fallback: if still unknown, check skills.json list
|
|
204
|
+
if state is None and fallback_skill_ids:
|
|
205
|
+
fb_rows = (
|
|
206
|
+
Skill.objects
|
|
207
|
+
.filter(
|
|
208
|
+
character__character__character_id=char_id,
|
|
209
|
+
skill_id__in=fallback_skill_ids
|
|
210
|
+
)
|
|
211
|
+
.values("skill_id", "trained_skill_level", "active_skill_level")
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
for row in fb_rows:
|
|
215
|
+
sid = int(row["skill_id"])
|
|
216
|
+
trained = int(row["trained_skill_level"])
|
|
217
|
+
active = int(row["active_skill_level"])
|
|
218
|
+
cap = alpha_caps.get(sid, 5)
|
|
219
|
+
|
|
220
|
+
if active > cap:
|
|
221
|
+
state = "omega"
|
|
222
|
+
skill_used = sid
|
|
223
|
+
break
|
|
224
|
+
elif trained > active:
|
|
225
|
+
state = "alpha"
|
|
226
|
+
skill_used = sid
|
|
227
|
+
break
|
|
228
|
+
|
|
170
229
|
if state is None:
|
|
171
230
|
state = "unknown"
|
|
172
231
|
skill_used = None
|
|
@@ -179,6 +238,7 @@ def determine_character_state(user_id, save: bool = False):
|
|
|
179
238
|
}
|
|
180
239
|
|
|
181
240
|
if save:
|
|
241
|
+
now = timezone.now()
|
|
182
242
|
with transaction.atomic():
|
|
183
243
|
CharacterAccountState.objects.update_or_create(
|
|
184
244
|
char_id=char_id,
|
|
@@ -186,7 +246,7 @@ def determine_character_state(user_id, save: bool = False):
|
|
|
186
246
|
"state": state,
|
|
187
247
|
"skill_used": skill_used,
|
|
188
248
|
"last_total_sp": total_sp,
|
|
189
|
-
"
|
|
249
|
+
"last_checked_at": now,
|
|
190
250
|
},
|
|
191
251
|
)
|
|
192
252
|
|
|
@@ -38,11 +38,9 @@ def get_user_contacts(user_id: int) -> dict[int, dict]:
|
|
|
38
38
|
Fetch and filter contacts for a user, excluding NPCs and self-contacts,
|
|
39
39
|
and annotate each with standing, grouping support.
|
|
40
40
|
"""
|
|
41
|
-
# 1. Build a dict of the user's characters { character_id: character_name }
|
|
42
41
|
user_chars = get_user_characters(user_id)
|
|
43
42
|
user_char_ids = set(user_chars.keys())
|
|
44
43
|
|
|
45
|
-
# 2. Pull in all CharacterContact rows for those character IDs
|
|
46
44
|
qs = CharacterContact.objects.filter(
|
|
47
45
|
character__character__character_id__in=user_char_ids
|
|
48
46
|
).select_related('contact_name', 'character__character')
|
|
@@ -131,7 +129,6 @@ def get_cell_style_for_row(cid: int, column: str, row: dict) -> str:
|
|
|
131
129
|
Determine inline CSS used when rendering the contact tables so that
|
|
132
130
|
hostiles/blacklist hits pop out immediately.
|
|
133
131
|
"""
|
|
134
|
-
# standing color (not shown in the 3-col table but kept for compatibility)
|
|
135
132
|
if column == 'standing': # Legacy standing column retains rainbow colors.
|
|
136
133
|
s = row.get('standing', 0)
|
|
137
134
|
if s >= 6: # High positive standings.
|
|
@@ -204,7 +201,6 @@ def render_contacts(user_id: int) -> str:
|
|
|
204
201
|
html_parts.append('<p>No contacts in this category.</p>')
|
|
205
202
|
continue
|
|
206
203
|
|
|
207
|
-
# Fixed 3-column table per requirements
|
|
208
204
|
headers = ['character', 'corporation', 'alliance']
|
|
209
205
|
html_parts.append('<table class="table table-striped table-hover stats">')
|
|
210
206
|
html_parts.append(' <thead>')
|
|
@@ -240,7 +236,6 @@ def get_user_hostile_notifications(user_id: int) -> dict[int, str]:
|
|
|
240
236
|
contacts = get_user_contacts(user_id)
|
|
241
237
|
notifications: dict[int, str] = {}
|
|
242
238
|
|
|
243
|
-
# Shortcut to the current hostile lists
|
|
244
239
|
cfg = BigBrotherConfig.get_solo()
|
|
245
240
|
hostile_corps = cfg.hostile_corporations
|
|
246
241
|
hostile_allis = cfg.hostile_alliances
|
|
@@ -248,7 +243,6 @@ def get_user_hostile_notifications(user_id: int) -> dict[int, str]:
|
|
|
248
243
|
|
|
249
244
|
for cid, info in contacts.items():
|
|
250
245
|
ctype = info['contact_type'] # 'character' | 'corporation' | 'alliance'
|
|
251
|
-
# derive a display name based on the type
|
|
252
246
|
if ctype == 'character': # Display actual character name for hostile characters.
|
|
253
247
|
cname = info.get('character') or ''
|
|
254
248
|
elif ctype == 'corporation': # Corporations show corp display name.
|
|
@@ -267,23 +261,19 @@ def get_user_hostile_notifications(user_id: int) -> dict[int, str]:
|
|
|
267
261
|
alerts: list[str] = []
|
|
268
262
|
logger.info(f"{cname},{aid},{alli_name}")
|
|
269
263
|
|
|
270
|
-
# 1) Character-specific blacklist check
|
|
271
264
|
if aablacklist_active():
|
|
272
265
|
if ctype == 'character' and check_char_corp_bl(cid): # Character is on blacklist.
|
|
273
266
|
alerts.append(f"**{cname}** is on blacklist")
|
|
274
267
|
|
|
275
|
-
# 2) Hostile corporation check (characters & corporations)
|
|
276
268
|
if ctype in ('character', 'corporation') and coid != 0: # Evaluate corp affiliation when present.
|
|
277
269
|
if str(coid) in hostile_corps: # Corp matches hostile list.
|
|
278
270
|
alerts.append(f"corporation **{corp_name}** is on hostile list")
|
|
279
271
|
|
|
280
|
-
# 3) Hostile alliance check (characters, corporations & alliances)
|
|
281
272
|
if aid != 0 and str(aid) in hostile_allis: # Alliance belongs to hostile list.
|
|
282
273
|
alerts.append(f"alliance **{alli_name}** is on hostile list")
|
|
283
274
|
|
|
284
275
|
if alerts: # Build notification only when this contact triggered alerts.
|
|
285
276
|
char_list = ', '.join(sorted(chars)) if chars else 'no characters'
|
|
286
|
-
# Build a single notification string
|
|
287
277
|
message = (
|
|
288
278
|
f"- A {s} **{ctype}** type contact **{cname}** found on **{char_list}**, flags: "
|
|
289
279
|
+ "; ".join(alerts)
|