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.
Files changed (181) hide show
  1. {aa_bb-3.2.0 → aa_bb-3.2.2}/PKG-INFO +4 -4
  2. {aa_bb-3.2.0 → aa_bb-3.2.2}/README.md +3 -3
  3. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/__init__.py +1 -1
  4. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/auth_hooks.py +6 -4
  5. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/awox.py +126 -68
  6. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/clone_state.py +82 -22
  7. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/sus_contacts.py +0 -10
  8. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/tasks.py +10 -8
  9. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/views_paps.py +10 -5
  10. {aa_bb-3.2.0 → aa_bb-3.2.2}/.gitignore +0 -0
  11. {aa_bb-3.2.0 → aa_bb-3.2.2}/LICENSE +0 -0
  12. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/admin.py +0 -0
  13. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/app_settings.py +0 -0
  14. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/apps.py +0 -0
  15. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/celery.py +0 -0
  16. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/__init__.py +0 -0
  17. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/alliance_blacklist.py +0 -0
  18. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/alpha_skills.json +0 -0
  19. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/coalition_blacklist.py +0 -0
  20. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/corp_blacklist.py +0 -0
  21. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/corp_changes.py +0 -0
  22. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/cyno.py +0 -0
  23. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/hostile_assets.py +0 -0
  24. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/hostile_clones.py +0 -0
  25. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/roles_and_tokens.py +0 -0
  26. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/skills.json +0 -0
  27. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/skills.py +0 -0
  28. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/sus_contracts.py +0 -0
  29. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/sus_mails.py +0 -0
  30. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks/sus_trans.py +0 -0
  31. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks_cb/__init__.py +0 -0
  32. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks_cb/hostile_assets.py +0 -0
  33. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks_cb/sus_contracts.py +0 -0
  34. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/checks_cb/sus_trans.py +0 -0
  35. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/esi_cache.py +0 -0
  36. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/esi_client.py +0 -0
  37. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/forms.py +0 -0
  38. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/locale/.gitkeep +0 -0
  39. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/management/__init__.py +0 -0
  40. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/management/commands/__init__.py +0 -0
  41. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/management/commands/manual_notif_test.py +0 -0
  42. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0001_initial.py +0 -0
  43. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0002_bigbrotherconfig_userstatus.py +0 -0
  44. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0003_alter_bigbrotherconfig_pingroleid.py +0 -0
  45. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0004_alter_bigbrotherconfig_is_active_and_more.py +0 -0
  46. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0005_alter_bigbrotherconfig_hostile_alliances.py +0 -0
  47. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0006_alter_bigbrotherconfig_pingroleid.py +0 -0
  48. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0007_alter_general_options.py +0 -0
  49. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0008_alliance_names_corporation_names.py +0 -0
  50. {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
  51. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0010_alter_userstatus_awox_kill_links_and_more.py +0 -0
  52. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0011_character_names.py +0 -0
  53. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0012_id_types.py +0 -0
  54. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0013_bigbrotherconfig_mail_keywords.py +0 -0
  55. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0014_processedcontract_processedmail_and_more.py +0 -0
  56. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0015_processedtransaction_sustransactionnote.py +0 -0
  57. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0016_warmprogress.py +0 -0
  58. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0017_entityinfocache.py +0 -0
  59. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0018_userstatus_cyno_userstatus_has_skills_and_more.py +0 -0
  60. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0019_bigbrotherconfig_whitelist_alliances_and_more.py +0 -0
  61. {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
  62. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0021_alter_messages_id_alter_messages_text.py +0 -0
  63. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0022_messages_sent_in_cycle.py +0 -0
  64. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0023_optmessages1_optmessages2_optmessages3_optmessages4_and_more.py +0 -0
  65. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0024_bigbrotherconfig_dailyschedule_and_more.py +0 -0
  66. {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
  67. {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
  68. {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
  69. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0028_alter_bigbrotherconfig_is_loa_active.py +0 -0
  70. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0029_leaverequest_main_character.py +0 -0
  71. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0030_alter_general_options.py +0 -0
  72. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0031_bigbrotherconfig_loa_max_logoff_days_and_more.py +0 -0
  73. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0032_alter_leaverequest_status.py +0 -0
  74. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0033_messagetype_bigbrotherconfig_pingroleid2_and_more.py +0 -0
  75. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0034_rename_last_updated_userstatus_updated.py +0 -0
  76. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0035_alter_userstatus_options.py +0 -0
  77. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0036_alter_general_options.py +0 -0
  78. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0037_corpstatus.py +0 -0
  79. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0038_bigbrotherconfig_ignored_corporations.py +0 -0
  80. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0039_alter_bigbrotherconfig_ignored_corporations.py +0 -0
  81. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0040_sovereigntymapcache_corporationinfocache_and_more.py +0 -0
  82. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0041_bigbrotherconfig_is_warmer_active.py +0 -0
  83. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0042_alter_general_options_and_more.py +0 -0
  84. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0043_bigbrotherconfig_bb_member_states_and_more.py +0 -0
  85. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0044_bigbrotherconfig_character_scopes_and_more.py +0 -0
  86. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0045_userstatus_sp_age_ratio_result.py +0 -0
  87. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0046_bigbrotherconfig_member_corporations.py +0 -0
  88. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0047_bigbrotherconfig_member_alliances.py +0 -0
  89. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0048_characteraccountstate.py +0 -0
  90. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0049_userstatus_clone_status.py +0 -0
  91. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0050_alter_general_options_and_more.py +0 -0
  92. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0051_monthlypapstats.py +0 -0
  93. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0052_papsconfig_delete_monthlypapstats.py +0 -0
  94. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0053_alter_papsconfig_corp_modifier_and_more.py +0 -0
  95. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0054_alter_general_options.py +0 -0
  96. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0055_papsconfig_group_paps_papsconfig_group_paps_modifier.py +0 -0
  97. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0056_alter_papsconfig_group_paps.py +0 -0
  98. {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
  99. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0058_papsconfig_excluded_groups_get_paps_and_more.py +0 -0
  100. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0059_alter_papsconfig_excluded_groups_get_paps.py +0 -0
  101. {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
  102. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0061_remove_papsconfig_cap_group_and_more.py +0 -0
  103. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0062_complianceticket.py +0 -0
  104. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0063_alter_complianceticket_user.py +0 -0
  105. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0064_alter_complianceticket_reason.py +0 -0
  106. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0065_alter_complianceticket_reason_and_more.py +0 -0
  107. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0066_tickettoolconfig_papcompliance.py +0 -0
  108. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0067_remove_tickettoolconfig_category_id_and_more.py +0 -0
  109. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0068_tickettoolconfig_category_id_and_more.py +0 -0
  110. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0069_tickettoolconfig_ticket_counter.py +0 -0
  111. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0070_remove_tickettoolconfig_role_id_and_more.py +0 -0
  112. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0071_tickettoolconfig_role_id.py +0 -0
  113. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0072_alter_complianceticket_reason.py +0 -0
  114. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0073_tickettoolconfig_starting_pap_compliance.py +0 -0
  115. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0074_alter_bigbrotherconfig_hostile_alliances.py +0 -0
  116. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0075_bbupdatestate_tickettoolconfig_afk_check_frequency_and_more.py +0 -0
  117. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0076_id_types_last_accessed_frequentcorpchangescache_and_more.py +0 -0
  118. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0077_complianceticket_ticket_id_and_more.py +0 -0
  119. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0078_tickettoolconfig_awox_monitor_enabled.py +0 -0
  120. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0079_helptext_guidance.py +0 -0
  121. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0080_bigbrotherconfig_dlc_flags.py +0 -0
  122. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0081_remove_papsconfig_imp_modifier_and_more.py +0 -0
  123. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0082_remove_bigbrotherconfig_token_and_more.py +0 -0
  124. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0083_alter_bigbrotherredditmessage_options_and_more.py +0 -0
  125. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0084_bigbrotherconfig_consider_all_structures_hostile_and_more.py +0 -0
  126. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0085_bigbrotherconfig_awox_notify_and_more.py +0 -0
  127. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0086_bigbrotherconfig_are_recurring_stats_active_and_more.py +0 -0
  128. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0087_bigbrotherconfig_dlc_are_recurring_stats_active.py +0 -0
  129. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0088_bigbrotherconfig_cyno_notify.py +0 -0
  130. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0089_bigbrotherconfig_asset_notify_and_more.py +0 -0
  131. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0090_alter_papsconfig_options_and_more.py +0 -0
  132. {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
  133. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/0092_userstatus_baseline_initialized.py +0 -0
  134. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/migrations/__init__.py +0 -0
  135. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/models.py +0 -0
  136. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/reddit.py +0 -0
  137. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/signals.py +0 -0
  138. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/static/aa_bb/.gitkeep +0 -0
  139. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/tasks_bot.py +0 -0
  140. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/tasks_cb.py +0 -0
  141. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/tasks_ct.py +0 -0
  142. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/tasks_other.py +0 -0
  143. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/tasks_reddit.py +0 -0
  144. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/tasks_tickets.py +0 -0
  145. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/aa_bb/base.html +0 -0
  146. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/aa_bb/disabled.html +0 -0
  147. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/aa_bb/index.html +0 -0
  148. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/aa_cb/base.html +0 -0
  149. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/aa_cb/disabled.html +0 -0
  150. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/aa_cb/index.html +0 -0
  151. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/faq/base.html +0 -0
  152. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/faq/cards.html +0 -0
  153. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/faq/faq.html +0 -0
  154. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/faq/menu.html +0 -0
  155. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/faq/modules.html +0 -0
  156. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/faq/settings_bigbrother.html +0 -0
  157. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/faq/settings_nav.html +0 -0
  158. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/faq/settings_paps.html +0 -0
  159. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/faq/settings_tickets.html +0 -0
  160. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/loa/_loa_subtabs.html +0 -0
  161. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/loa/admin.html +0 -0
  162. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/loa/base.html +0 -0
  163. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/loa/disabled.html +0 -0
  164. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/loa/index.html +0 -0
  165. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/loa/menu.html +0 -0
  166. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/loa/request.html +0 -0
  167. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/paps/base.html +0 -0
  168. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/paps/disabled.html +0 -0
  169. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/paps/history.html +0 -0
  170. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/paps/index.html +0 -0
  171. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/templates/paps/menu.html +0 -0
  172. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/tests/__init__.py +0 -0
  173. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/tests/test_example.py +0 -0
  174. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/urls.py +0 -0
  175. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/urls_cb.py +0 -0
  176. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/urls_loa.py +0 -0
  177. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/urls_paps.py +0 -0
  178. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/views.py +0 -0
  179. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/views_cb.py +0 -0
  180. {aa_bb-3.2.0 → aa_bb-3.2.2}/aa_bb/views_faq.py +0 -0
  181. {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.0
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.0b2
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.0b2
61
+ pip install aa-bb==3.2.1
62
62
  ```
63
63
  ```bash
64
64
  vi myauth/settings/local.py
@@ -1,4 +1,4 @@
1
1
  """Initialize the app"""
2
2
 
3
- __version__ = "3.2.0"
3
+ __version__ = "3.2.2"
4
4
  __title__ = "BigBrother"
@@ -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
- @hooks.register("url_hook")
178
- def register_paps_urls():
179
- """Wire the PAP URLconf into AllianceAuth."""
180
- return UrlHook(urls_paps, "paps", r"^paps/")
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 logging
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
- from ..models import BigBrotherConfig,AwoxKillsCache
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 ..esi_client import esi, call_result
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
- send_message,
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
- logger = logging.getLogger(__name__)
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
- An in-memory timestamp prevents duplicate warnings within the last
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
- # 2 hours = 7200 seconds
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
- # Indefinite DB cache: return cached kills if present
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
- cache.last_accessed = timezone.now()
94
- cache.save(update_fields=["last_accessed"])
119
+ last_accessed = cache.last_accessed
95
120
  except Exception:
96
- cache.save()
97
- return cache.data or []
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
- pass
100
- characters = CharacterOwnership.objects.filter(user__id=user_id)
101
- char_ids = [c.character.character_id for c in characters]
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
- session = requests.Session()
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
- except requests.exceptions.RequestException as e:
119
- logger.warning(f"Error fetching awox for {char_id}: {e}")
120
- continue
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
- ): # Bail out when zKill returns HTML (Cloudflare/error) instead of JSON payloads.
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_: # Ignore malformed entries that lack identifiers.
203
+ if not kill_id or not hash_:
159
204
  continue
160
- if kill_id in kills_by_id: # Skip duplicates pulled from multiple characters.
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
- full_kill, _ = call_result(operation, use_etag=False)
217
+ continue
172
218
  except Exception as e:
173
- logger.warning("Failed to fetch ESI killmail %s: %s", kill_id, e)
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
- attacker_names = set()
227
+ attackers = full_kill.get("attackers", []) or []
181
228
  attacker_affiliations = []
182
-
183
- for attacker in attackers:
184
- a_id = attacker.get("character_id")
185
- if a_id in char_ids and a_id != victim_id: # Friendly fire only counts when attacker differs from victim.
186
- attacker_names.add(char_id_map.get(a_id))
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": attacker.get("corporation_id"),
189
- "alliance_id": attacker.get("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: # No awox behaviour detected for this killmail.
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["corp_id"])
198
- att_alli = resolve_alliance_name(att_info["alliance_id"]) if att_info["alliance_id"] else None
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 = resolve_alliance_name(vic_alli_id) if vic_alli_id else None
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": timezone.now()},
272
+ defaults={"data": data_list, "last_accessed": now},
221
273
  )
222
274
  except Exception:
223
- pass
224
- return data_list if data_list else None
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; callers expect empty list.
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 alpha skill caps
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
- # If we have a previous SP and it's unchanged, reuse cached state
82
- if db_record and total_sp is not None and db_record.last_total_sp is not None:
83
- if int(db_record.last_total_sp) == int(total_sp) and db_record.state in ("alpha", "omega", "unknown"):
84
- result[char_id] = {
85
- "state": db_record.state,
86
- "skill_used": db_record.skill_used,
87
- "last_state": db_record.state,
88
- }
89
- continue
90
-
91
- chars_to_check.append(char_id)
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
- now = timezone.now()
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
- "sp_last_checked": now,
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)