Delete visibilitylib directory

This commit is contained in:
dogemanttv 2024-01-10 17:10:23 -06:00 committed by GitHub
parent f9ece9ef6e
commit 5861bd41bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
243 changed files with 0 additions and 32315 deletions

View File

@ -1,29 +0,0 @@
target(
dependencies = [
"visibility/lib/src/main/scala/com/twitter/visibility",
],
)
target(
name = "conversations",
dependencies = [
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/tweets",
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/conversations",
],
)
target(
name = "tweets",
dependencies = [
"visibility/lib/src/main/scala/com/twitter/visibility/generators",
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/tweets",
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/tweets",
],
)
target(
name = "users",
dependencies = [
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/users",
],
)

View File

@ -1,51 +0,0 @@
Overview
========
Visibility Filtering is a centralized rule engine that instructs clients how to alter the display of certain Twitter content on read time. The Visibility Filtering library is responsible for filtering Twitter content to support legal compliance, improve product quality, increase user trust, protect revenue through the use of hard-filtering, visible product treatments, and coarse-grained downranking.
Notice
======
Visibility Filtering library is currently being reviewed and rebuilt, and part of the code has been removed and is not ready to be shared yet. The remaining part of the code needs further review and will be shared once its ready. Also code comments have been sanitized.
SafetyLevel
===========
Represents the product context in which the Viewer is requesting to view the Content (e.g. Timeline, Profile).
Features
========
Include safety labels and other metadata of a Tweet, flags set on a User (including the Viewer), relationships between Users (e.g. block, follow), User settings, relationships between Users and Content (e.g. reported for spam).
Action
======
The way the Visibility Framework instructs the client to respond to the Viewers request for Content, and can include hard filtering (e.g. Drop), soft filtering (e.g. Labels and Interstitials), ranking clues, etc.
Condition
=========
Returns a boolean when given map of Features. Conditions can be combined to determine if a Rule should return an Action or the default (Allow).
Policy
======
Rules are expressed as a sequence in priority order to create a Visibility Policy. The library has one policy
per SafetyLevel.
RuleEngine
===========
Evaluates the Action for a Request.
SafetyLabel
===========
A primary labeling mechanism for Safety. A labeled entity associates with tweet, user, Direct Message, media, space etc. Safety labels power different ways of remediations (e.g. applying a SafetyLabel that results in tweet interstitial or notice).
SafetyLabelType
===============
Describes a particular policy violation for a given noun instance, and usually leads to reduced visibility of the
labeled entity in product surfaces. There are many deprecated, and experimental safety label types. Labels with these safety label types have no effect on VF. Additionally, some safety label types are not used, and not designed for VF.

View File

@ -1,6 +0,0 @@
resources(
sources = [
"com/twitter/visibility/*.csv",
"com/twitter/visibility/*.yml",
],
)

View File

@ -1,909 +0,0 @@
visibility_library_enable_all_subscribed_lists_safety_level:
default_availability: 10000
visibility_library_enable_ads_business_settings_safety_level:
default_availability: 10000
visibility_library_enable_ads_campaign_safety_level:
default_availability: 10000
visibility_library_enable_ads_manager_safety_level:
default_availability: 10000
visibility_library_enable_appeals_safety_level:
default_availability: 10000
visibility_library_enable_article_tweet_timeline_safety_level:
default_availability: 10000
visibility_library_enable_birdwatch_note_author_safety_level:
default_availability: 10000
visibility_library_enable_birdwatch_note_tweets_timeline_safety_level:
default_availability: 10000
visibility_library_enable_birdwatch_needs_your_help_notifications_safety_level:
default_availability: 10000
visibility_library_enable_block_mute_users_timeline_safety_level:
default_availability: 10000
visibility_library_enable_brand_safety_safety_level:
default_availability: 10000
visibility_library_enable_card_poll_voting_safety_level:
default_availability: 10000
visibility_library_enable_cards_service_safety_level:
default_availability: 10000
visibility_library_enable_communities_safety_level:
default_availability: 10000
visibility_library_enable_conversation_focal_prehydration_safety_level:
default_availability: 10000
visibility_library_enable_conversation_focal_tweet_safety_level:
default_availability: 10000
visibility_library_enable_conversation_injected_tweet_safety_level:
default_availability: 10000
visibility_library_enable_conversation_reply_safety_level:
default_availability: 10000
visibility_library_curated_trends_representative_tweet:
default_availability: 10000
visibility_library_curation_policy_violations:
default_availability: 10000
visibility_library_enable_deprecated_safety_level_safety_level:
default_availability: 10000
visibility_library_enable_dev_platform_get_list_tweets_safety_level:
default_availability: 10000
visibility_library_enable_des_following_and_followers_user_list_safety_level:
default_availability: 10000
visibility_library_enable_des_home_timeline_safety_level:
default_availability: 10000
visibility_library_enable_des_quote_tweet_timeline_safety_level:
default_availability: 10000
visibility_library_enable_des_realtime_safety_level:
default_availability: 10000
visibility_library_enable_des_realtime_spam_enrichment_safety_level:
default_availability: 10000
visibility_library_enable_des_realtime_tweet_filter_safety_level:
default_availability: 10000
visibility_library_enable_des_retweeting_users_safety_level:
default_availability: 10000
visibility_library_enable_des_tweet_detail_safety_level:
default_availability: 10000
visibility_library_enable_des_tweet_liking_users_safety_level:
default_availability: 10000
visibility_library_enable_des_user_bookmarks_safety_level:
default_availability: 10000
visibility_library_enable_des_user_liked_tweets_safety_level:
default_availability: 10000
visibility_library_enable_des_user_mentions_safety_level:
default_availability: 10000
visibility_library_enable_des_user_tweets_safety_level:
default_availability: 10000
visibility_library_enable_dev_platform_compliance_stream_safety_level:
default_availability: 10000
visibility_library_enable_direct_messages_safety_level:
default_availability: 10000
visibility_library_enable_direct_messages_conversation_list_safety_level:
default_availability: 10000
visibility_library_enable_direct_messages_conversation_timeline_safety_level:
default_availability: 10000
visibility_library_enable_direct_messages_inbox_safety_level:
default_availability: 10000
visibility_library_enable_direct_messages_muted_users_safety_level:
default_availability: 10000
visibility_library_enable_direct_messages_pinned_safety_level:
default_availability: 10000
visibility_library_enable_elevated_quote_tweet_timeline_safety_level:
default_availability: 10000
visibility_library_enable_direct_messages_search_safety_level:
default_availability: 10000
visibility_library_enable_embedded_tweet_safety_level:
default_availability: 10000
visibility_library_enable_embeds_public_interest_notice_safety_level:
default_availability: 10000
visibility_library_enable_embed_tweet_markup_safety_level:
default_availability: 10000
visibility_library_enable_write_path_limited_actions_enforcement_safety_level:
default_availability: 10000
visibility_library_enable_filter_all_safety_level:
default_availability: 10000
visibility_library_enable_filter_all_placeholder_safety_level:
default_availability: 10000
visibility_library_enable_filter_default_safety_level:
default_availability: 10000
visibility_library_enable_filter_none_safety_level:
default_availability: 10000
visibility_library_enable_followed_topics_timeline_safety_level:
default_availability: 10000
visibility_library_enable_follower_connections_safety_level:
default_availability: 10000
visibility_library_enable_following_and_followers_user_list_safety_level:
default_availability: 10000
visibility_library_enable_for_development_only_safety_level:
default_availability: 10000
visibility_library_enable_friends_following_list_safety_level:
default_availability: 10000
visibility_library_enable_graphql_default_safety_level:
default_availability: 10000
visibility_library_enable_gryphon_decks_and_columns_safety_level:
default_availability: 10000
visibility_library_enable_humanization_nudge_safety_level:
default_availability: 10000
visibility_library_enable_kitchen_sink_development_safety_level:
default_availability: 10000
visibility_library_enable_list_header_safety_level:
default_availability: 10000
visibility_library_enable_list_memberships_safety_level:
default_availability: 10000
visibility_library_enable_list_ownerships_safety_level:
default_availability: 10000
visibility_library_enable_list_recommendations_safety_level:
default_availability: 10000
visibility_library_enable_list_search_safety_level:
default_availability: 10000
visibility_library_enable_list_subscriptions_safety_level:
default_availability: 10000
visibility_library_enable_live_pipeline_engagement_counts_safety_level:
default_availability: 10000
visibility_library_enable_live_video_timeline_safety_level:
default_availability: 10000
visibility_library_enable_magic_recs_safety_level:
default_availability: 10000
visibility_library_enable_magic_recs_aggressive_safety_level:
default_availability: 10000
visibility_library_enable_magic_recs_aggressive_v2_safety_level:
default_availability: 10000
visibility_library_enable_magic_recs_v2_safety_level:
default_availability: 10000
visibility_library_enable_minimal_safety_level:
default_availability: 10000
visibility_library_enable_moderated_tweets_timeline_safety_level:
default_availability: 10000
visibility_library_enable_moments_safety_level:
default_availability: 10000
visibility_library_enable_nearby_timeline_safety_level:
default_availability: 10000
visibility_library_enable_new_user_experience_safety_level:
default_availability: 10000
visibility_library_enable_notifications_ibis_safety_level:
default_availability: 10000
visibility_library_enable_notifications_platform_safety_level:
default_availability: 10000
visibility_library_enable_notifications_platform_push_safety_level:
default_availability: 10000
visibility_library_enable_notifications_read_safety_level:
default_availability: 10000
visibility_library_enable_notifications_timeline_device_follow_safety_level:
default_availability: 10000
visibility_library_enable_notifications_write_safety_level:
default_availability: 10000
visibility_library_enable_notification_writer_v2_safety_level:
default_availability: 10000
visibility_library_enable_notifications_writer_tweet_hydrator_safety_level:
default_availability: 10000
visibility_library_enable_quick_promote_tweet_eligibility_safety_level:
default_availability: 10000
visibility_library_enable_quote_tweet_timeline_safety_level:
default_availability: 10000
visibility_library_enable_quoted_tweet_rules_safety_level:
default_availability: 10000
visibility_library_enable_recommendations_safety_level:
default_availability: 10000
visibility_library_enable_recos_video_safety_level:
default_availability: 10000
visibility_library_enable_recos_write_path_safety_level:
default_availability: 10000
visibility_library_enable_replies_grouping_safety_level:
default_availability: 10000
visibility_library_enable_report_center_safety_level:
default_availability: 10000
visibility_library_enable_returning_user_experience_safety_level:
default_availability: 10000
visibility_library_enable_returning_user_experience_focal_tweet_safety_level:
default_availability: 10000
visibility_library_enable_revenue_safety_level:
default_availability: 10000
visibility_library_enable_rito_actioned_tweet_timeline_safety_level:
default_availability: 10000
visibility_library_enable_safe_search_minimal_safety_level:
default_availability: 10000
visibility_library_enable_safe_search_strict_safety_level:
default_availability: 10000
visibility_library_enable_search_hydration_safety_level:
default_availability: 10000
visibility_library_enable_search_latest_safety_level:
default_availability: 10000
visibility_library_enable_search_mixer_srp_minimal_safety_level:
default_availability: 10000
visibility_library_enable_search_mixer_srp_strict_safety_level:
default_availability: 10000
visibility_library_enable_user_search_srp_safety_level:
default_availability: 10000
visibility_library_enable_user_search_typeahead_safety_level:
default_availability: 10000
visibility_library_enable_search_people_srp_safety_level:
default_availability: 10000
visibility_library_enable_search_people_typeahead_safety_level:
default_availability: 10000
visibility_library_enable_search_photo_safety_level:
default_availability: 10000
visibility_library_enable_search_top_safety_level:
default_availability: 10000
visibility_library_enable_search_trend_takeover_promoted_tweet_safety_level:
default_availability: 10000
visibility_library_enable_search_video_safety_level:
default_availability: 10000
visibility_library_enable_search_latest_user_rules_safety_level:
default_availability: 10000
visibility_library_enable_shopping_manager_spy_mode_safety_level:
default_availability: 10000
visibility_library_enable_signals_reactions_safety_level:
default_availability: 10000
visibility_library_enable_signals_tweet_reacting_users_safety_level:
default_availability: 10000
visibility_library_enable_social_proof_safety_level:
default_availability: 10000
visibility_library_enable_soft_intervention_pivot_safety_level:
default_availability: 10000
visibility_library_enable_space_fleetline_safety_level:
default_availability: 10000
visibility_library_enable_space_home_timeline_upranking_safety_level:
default_availability: 10000
visibility_library_enable_space_join_screen_safety_level:
default_availability: 10000
visibility_library_enable_space_notifications_safety_level:
default_availability: 10000
visibility_library_enable_spaces_safety_level:
default_availability: 10000
visibility_library_enable_spaces_participants_safety_level:
default_availability: 0
visibility_library_enable_spaces_seller_application_status_safety_level:
default_availability: 10000
visibility_library_enable_spaces_sharing_safety_level:
default_availability: 10000
visibility_library_enable_space_tweet_avatar_home_timeline_safety_level:
default_availability: 10000
visibility_library_enable_stickers_timeline_safety_level:
default_availability: 10000
visibility_library_enable_strato_ext_limited_engagements_safety_level:
default_availability: 10000
visibility_library_enable_stream_services_safety_level:
default_availability: 10000
visibility_library_enable_test_safety_level:
default_availability: 10000
visibility_library_enable_timeline_bookmark_safety_level:
default_availability: 10000
visibility_library_enable_timeline_content_controls_safety_level:
default_availability: 10000
visibility_library_enable_timeline_conversations_safety_level:
default_availability: 10000
visibility_library_enable_timeline_conversations_downranking_safety_level:
default_availability: 10000
visibility_library_enable_timeline_conversations_downranking_minimal_safety_level:
default_availability: 10000
visibility_library_enable_timeline_favorites_safety_level:
default_availability: 10000
visibility_library_enable_self_view_timeline_favorites_safety_level:
default_availability: 10000
visibility_library_enable_timeline_focal_tweet_safety_level:
default_availability: 10000
visibility_library_enable_timeline_following_activity_safety_level:
default_availability: 10000
visibility_library_enable_timeline_home_safety_level:
default_availability: 10000
visibility_library_enable_timeline_home_communities_safety_level:
default_availability: 10000
visibility_library_enable_timeline_home_hydration_safety_level:
default_availability: 10000
visibility_library_enable_timeline_home_latest_safety_level:
default_availability: 10000
visibility_library_enable_timeline_home_recommendations_safety_level:
default_availability: 10000
visibility_library_enable_timeline_home_topic_follow_recommendations_safety_level:
default_availability: 10000
visibility_library_enable_timeline_scorer_safety_level:
default_availability: 10000
visibility_library_enable_topics_landing_page_topic_recommendations_safety_level:
default_availability: 10000
visibility_library_enable_explore_recommendations_safety_level:
default_availability: 10000
visibility_library_enable_timeline_moderated_tweets_hydration_safety_level:
default_availability: 10000
visibility_library_enable_timeline_injection_safety_level:
default_availability: 10000
visibility_library_enable_timeline_liked_by_safety_level:
default_availability: 10000
visibility_library_enable_timeline_lists_safety_level:
default_availability: 10000
visibility_library_enable_timeline_media_safety_level:
default_availability: 10000
visibility_library_enable_timeline_mentions_safety_level:
default_availability: 10000
visibility_library_enable_timeline_profile_safety_level:
default_availability: 10000
visibility_library_enable_timeline_profile_all_safety_level:
default_availability: 10000
visibility_library_enable_timeline_profile_spaces_safety_level:
default_availability: 10000
visibility_library_enable_timeline_profile_super_follows_safety_level:
default_availability: 10000
visibility_library_enable_timeline_reactive_blending_safety_level:
default_availability: 10000
visibility_library_enable_timeline_retweeted_by_safety_level:
default_availability: 10000
visibility_library_enable_timeline_super_liked_by_safety_level:
default_availability: 10000
visibility_library_enable_tombstoning_safety_level:
default_availability: 10000
visibility_library_enable_trends_representative_tweet_safety_level:
default_availability: 10000
visibility_library_enable_trusted_friends_user_list_safety_level:
default_availability: 10000
visibility_library_enable_twitter_delegate_user_list_safety_level:
default_availability: 10000
visibility_library_enable_tweet_detail_safety_level:
default_availability: 10000
visibility_library_enable_tweet_detail_non_too_safety_level:
default_availability: 10000
visibility_library_enable_tweet_detail_with_injections_hydration_safety_level:
default_availability: 10000
visibility_library_enable_tweet_engagers_safety_level:
default_availability: 10000
visibility_library_enable_tweet_reply_nudge_safety_level:
default_availability: 10000
visibility_library_enable_tweet_scoped_timeline_safety_level:
default_availability: 10000
visibility_library_enable_tweet_writes_api_safety_level:
default_availability: 10000
visibility_library_enable_twitter_article_compose_safety_level:
default_availability: 10000
visibility_library_enable_twitter_article_profile_tab_safety_level:
default_availability: 10000
visibility_library_enable_twitter_article_read_safety_level:
default_availability: 10000
visibility_library_enable_user_profile_header_safety_level:
default_availability: 10000
visibility_library_enable_user_milestone_recommendation_safety_level:
default_availability: 10000
visibility_library_enable_user_scoped_timeline_safety_level:
default_availability: 10000
visibility_library_enable_user_settings_safety_level:
default_availability: 10000
visibility_library_enable_video_ads_safety_level:
default_availability: 10000
visibility_library_enable_timeline_home_promoted_hydration_safety_level:
default_availability: 10000
visibility_library_enable_super_follower_connnections_safety_level:
default_availability: 10000
visibility_library_enable_super_like_safety_level:
default_availability: 10000
visibility_library_enable_topic_recommendations_safety_level:
default_availability: 10000
visibility_library_enable_ads_reporting_dashboard_safety_level:
default_availability: 10000
visibility_library_enable_search_top_qig_safety_level:
default_availability: 10000
visibility_library_enable_content_control_tool_install_safety_level:
default_availability: 10000
visibility_library_enable_conversation_control_rules:
default_availability: 10000
visibility_library_enable_community_tweets_rules:
default_availability: 10000
visibility_library_enable_drop_community_tweet_with_undefined_community_rule:
default_availability: 10000
visibility_library_enable_p_spammy_tweet_downrank_convos_low_quality:
default_availability: 10000
visibility_library_enable_high_p_spammy_tweet_score_search_tweet_label_drop_rule:
default_availability: 10000
visibility_library_enable_rito_actioned_tweet_downrank_convos_low_quality:
default_availability: 10000
visibility_library_enable_toxic_reply_filter_conversation:
default_availability: 10000
visibility_library_enable_toxic_reply_filter_notifications:
default_availability: 10000
visibility_library_enable_new_sensitive_media_settings_interstitial_rules_home_timeline:
default_availability: 10000
visibility_library_enable_legacy_sensitive_media_rules_home_timeline:
default_availability: 10000
visibility_library_enable_new_sensitive_media_settings_interstitial_rules_conversation:
default_availability: 10000
visibility_library_enable_legacy_sensitive_media_rules_conversation:
default_availability: 10000
visibility_library_enable_new_sensitive_media_settings_interstitials_rules_profile_timeline:
default_availability: 10000
visibility_library_enable_legacy_sensitive_media_rules_profile_timeline:
default_availability: 10000
visibility_library_enable_new_sensitive_media_settings_interstitials_rules_tweet_detail:
default_availability: 10000
visibility_library_enable_legacy_sensitive_media_rules_tweet_detail:
default_availability: 10000
visibility_library_enable_legacy_sensitive_media_rules_direct_messages:
default_availability: 10000
visibility_library_enable_smyte_spam_tweet_rule:
default_availability: 10000
visibility_library_enable_high_spammy_tweet_content_score_search_latest_tweet_label_drop_rule:
default_availability: 10000
visibility_library_enable_high_spammy_tweet_content_score_search_top_tweet_label_drop_rule:
default_availability: 10000
visibility_library_enable_high_spammy_tweet_content_score_convo_downrank_abusive_quality_rule:
default_availability: 10000
visibility_library_enable_high_cryptospam_score_convo_downrank_abusive_quality_rule:
default_availability: 10000
visibility_library_enable_high_spammy_tweet_content_score_trends_top_tweet_label_drop_rule:
default_availability: 10000
visibility_library_enable_high_spammy_tweet_content_score_trends_latest_tweet_label_drop_rule:
default_availability: 10000
visibility_library_enable_gore_and_violence_topic_high_recall_tweet_label_rule:
default_availability: 10000
visibility_library_enable_limit_replies_followers_conversation_rule:
default_availability: 10000
visibility_library_enable_blink_bad_downranking_rule:
default_availability: 10000
visibility_library_enable_blink_worst_downranking_rule:
default_availability: 10000
visibility_library_enable_copypasta_spam_downrank_convos_abusive_quality_rule:
default_availability: 10000
visibility_library_enable_copypasta_spam_search_drop_rule:
default_availability: 10000
visibility_library_enable_spammy_user_model_high_precision_drop_tweet_rule:
default_availability: 10000
visibility_library_enable_avoid_nsfw_rules:
default_availability: 10000
visibility_library_enable_reported_tweet_interstitial_rule:
default_availability: 10000
visibility_library_enable_reported_tweet_interstitial_search_rule:
default_availability: 10000
visibility_library_enable_drop_exclusive_tweet_content_rule:
default_availability: 10000
visibility_library_enable_drop_exclusive_tweet_content_rule_fail_closed:
default_availability: 10000
visibility_library_enable_drop_all_exclusive_tweets_rule:
default_availability: 10000
visibility_library_enable_drop_all_exclusive_tweets_rule_fail_closed:
default_availability: 10000
visibility_library_enable_tombstone_exclusive_quoted_tweet_content_rule:
default_availability: 10000
visibility_library_enable_downrank_spam_reply_sectioning_rule:
default_availability: 10000
visibility_library_enable_nsfw_text_sectioning_rule:
default_availability: 10000
visibility_library_enable_search_ipi_safe_search_without_user_in_query_drop_rule:
default_availability: 10000
visibility_library_enable_timeline_home_promoted_tweet_health_enforcement_rules:
default_availability: 10000
visibility_library_enable_muted_keyword_filtering_space_title_notifications_rule:
default_availability: 10000
visibility_library_enable_drop_tweets_with_georestricted_media_rule:
default_availability: 10000
visibility_library_enable_drop_all_trusted_friends_tweets_rule:
default_availability: 10000
visibility_library_enable_drop_all_trusted_friends_tweet_content_rule:
default_availability: 10000
visibility_library_enable_drop_all_collab_invitation_tweets_rule:
default_availability: 10000
visibility_library_enable_fetch_tweet_reported_perspective:
default_availability: 10000
visibility_library_enable_fetch_tweet_media_metadata:
default_availability: 10000
visibility_library_enable_follow_check_in_mutedkeyword:
default_availability: 10000
visibility_library_enable_media_interstitial_composition:
default_availability: 10000
visibility_library_enable_verdict_scribing_from_tweet_visibility_library:
default_availability: 0
visibility_library_enable_verdict_logger_event_publisher_instantiation_from_tweet_visibility_library:
default_availability: 10000
visibility_library_enable_verdict_scribing_from_timeline_conversations_visibility_library:
default_availability: 0
visibility_library_enable_verdict_logger_event_publisher_instantiation_from_timeline_conversations_visibility_library:
default_availability: 10000
visibility_library_enable_verdict_scribing_from_blender_visibility_library:
default_availability: 0
visibility_library_enable_verdict_logger_event_publisher_instantiation_from_blender_visibility_library:
default_availability: 10000
visibility_library_enable_verdict_scribing_from_search_visibility_library:
default_availability: 0
visibility_library_enable_verdict_logger_event_publisher_instantiation_from_search_visibility_library:
default_availability: 0
visibility_library_enable_localized_tombstones_on_visibility_results:
default_availability: 10000
visibility_library_enable_short_circuiting_from_tweet_visibility_library:
default_availability: 10000
visibility_library_enable_card_visibility_library_card_uri_parsing:
default_availability: 10000
visibility_library_enable_short_circuiting_from_timeline_conversations_visibility_library:
default_availability: 10000
visibility_library_enable_short_circuiting_from_blender_visibility_library:
default_availability: 10000
visibility_library_enable_short_circuiting_from_search_visibility_library:
default_availability: 0
visibility_library_enable_nsfw_text_high_precision_drop_rule:
default_availability: 10000
visibility_library_enable_spammy_tweet_rule_verdict_logging:
default_availability: 0
visibility_library_enable_downlevel_rule_verdict_logging:
default_availability: 0
visibility_library_enable_likely_likely_ivs_user_label_drop_rule:
default_availability: 10000
visibility_library_enable_card_uri_root_domain_deny_list_rule:
default_availability: 10000
visibility_library_enable_community_non_member_poll_card_rule:
default_availability: 10000
visibility_library_enable_community_non_member_poll_card_rule_fail_closed:
default_availability: 10000
visibility_library_enable_experimental_nudge_label_rule:
default_availability: 10000
visibility_library_enable_user_self_view_only_safety_level:
default_availability: 10000
visibility_library_nsfw_high_precision_user_label_avoid_tweet_rule_enabled:
default_availability: 10000
visibility_library_enable_new_ad_avoidance_rules:
default_availability: 10000
visibility_library_enable_nsfa_high_recall_ad_avoidance_rules:
default_availability: 0
visibility_library_enable_nsfa_keywords_high_precision_ad_avoidance_rules:
default_availability: 0
visibility_library_enable_stale_tweet_drop_rule:
default_availability: 10000
visibility_library_enable_stale_tweet_drop_rule_fail_closed:
default_availability: 10000
visibility_library_enable_edit_history_timeline_safety_level:
default_availability: 10000
visibility_library_enable_delete_state_tweet_rules:
default_availability: 10000
visibility_library_enable_spaces_sharing_nsfw_drop_rule:
default_availability: 10000
visibility_library_enable_viewer_is_soft_user_drop_rule:
default_availability: 10000
visibility_library_enable_backend_limited_actions:
default_availability: 10000
visibility_library_enable_base_qig_safety_level:
default_availability: 10000
visibility_library_enable_notifications_qig_safety_level:
default_availability: 10000
visibility_library_enable_access_internal_promoted_content_safety_level:
default_availability: 10000
visibility_library_enable_pdna_quoted_tweet_tombstone_rule:
default_availability: 10000
visibility_library_enable_spam_quoted_tweet_tombstone_rule:
default_availability: 10000
visibility_library_enable_nsfw_hp_quoted_tweet_drop_experiment_rule:
default_availability: 10000
visibility_library_enable_nsfw_hp_quoted_tweet_tombstone_experiment_rule:
default_availability: 10000
visibility_library_enable_inner_quoted_tweet_viewer_blocks_author_interstitial_rule:
default_availability: 10000
visibility_library_enable_inner_quoted_tweet_viewer_mutes_author_interstitial_rule:
default_availability: 10000
visibility_library_enable_experimental_rule_engine:
default_availability: 10000
visibility_library_enable_fosnr_rules:
default_availability: 0
visibility_library_enable_localized_interstitial_generator:
default_availability: 10000
visibility_library_convos_enable_legacy_interstitial:
default_availability: 10000
visibility_library_convos_enable_localized_interstitial:
default_availability: 10000
visibility_library_enable_profile_mixer_media_safety_level:
default_availability: 10000
visibility_library_enable_profile_mixer_favorites_safety_level:
default_availability: 10000
visibility_library_enable_zipbird_consumer_archives_safety_level:
default_availability: 10000
visibility_library_enable_tweet_award_safety_level:
default_availability: 10000
visibility_library_disable_legacy_interstitial_filtered_reason:
default_availability: 10000
visibility_library_enable_search_basic_block_mute_rules:
default_availability: 10000
visibility_library_enable_localized_interstitial_user_state_lib:
default_availability: 10000
visibility_library_enable_abusive_behavior_drop_rule:
default_availability: 10000
visibility_library_enable_abusive_behavior_interstitial_rule:
default_availability: 10000
visibility_library_enable_abusive_behavior_limited_engagements_rule:
default_availability: 10000
visibility_library_enable_not_graduated_downrank_convos_abusive_quality_rule:
default_availability: 0
visibility_library_enable_not_graduated_search_drop_rule:
default_availability: 0
visibility_library_enable_not_graduated_drop_rule:
default_availability: 0
visibility_library_enable_memoize_safety_level_params:
default_availability: 0
visibility_library_enable_author_blocks_viewer_drop_rule:
default_availability: 0

View File

@ -1,42 +0,0 @@
scala_library(
sources = ["*.scala"],
compiler_option_sets = ["fatal_warnings"],
platform = "java8",
strict_deps = True,
tags = ["bazel-compatible"],
dependencies = [
"abdecider/src/main/scala",
"configapi/configapi-core",
"decider/src/main/scala",
"featureswitches/featureswitches-core/src/main/scala",
"servo/decider/src/main/scala",
"servo/util/src/main/scala",
"stitch/stitch-core",
"util/util-logging/src/main/scala",
"util/util-stats/src/main/scala",
"visibility/common/src/main/scala/com/twitter/visibility/common/actions",
"visibility/common/src/main/scala/com/twitter/visibility/common/stitch",
"visibility/lib/src/main/resources/config",
"visibility/lib/src/main/scala/com/twitter/visibility/builder",
"visibility/lib/src/main/scala/com/twitter/visibility/configapi",
"visibility/lib/src/main/scala/com/twitter/visibility/configapi/configs",
"visibility/lib/src/main/scala/com/twitter/visibility/configapi/params",
"visibility/lib/src/main/scala/com/twitter/visibility/engine",
"visibility/lib/src/main/scala/com/twitter/visibility/features",
"visibility/lib/src/main/scala/com/twitter/visibility/generators",
"visibility/lib/src/main/scala/com/twitter/visibility/models",
"visibility/lib/src/main/scala/com/twitter/visibility/rules",
"visibility/lib/src/main/scala/com/twitter/visibility/rules/generators",
"visibility/lib/src/main/scala/com/twitter/visibility/rules/providers",
"visibility/lib/src/main/scala/com/twitter/visibility/util",
],
exports = [
"configapi/configapi-core",
"visibility/common/src/main/scala/com/twitter/visibility/common/actions",
"visibility/lib/src/main/scala/com/twitter/visibility/builder",
"visibility/lib/src/main/scala/com/twitter/visibility/configapi",
"visibility/lib/src/main/scala/com/twitter/visibility/configapi/configs",
"visibility/lib/src/main/scala/com/twitter/visibility/models",
"visibility/lib/src/main/scala/com/twitter/visibility/rules",
],
)

View File

@ -1,387 +0,0 @@
package com.twitter.visibility
import com.twitter.abdecider.LoggingABDecider
import com.twitter.abdecider.NullABDecider
import com.twitter.decider.Decider
import com.twitter.decider.NullDecider
import com.twitter.featureswitches.v2.FeatureSwitches
import com.twitter.featureswitches.v2.NullFeatureSwitches
import com.twitter.finagle.stats.NullStatsReceiver
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.logging.Logger
import com.twitter.logging.NullLogger
import com.twitter.servo.util.Gate
import com.twitter.servo.util.MemoizingStatsReceiver
import com.twitter.stitch.Stitch
import com.twitter.timelines.configapi.Params
import com.twitter.util.Try
import com.twitter.visibility.builder._
import com.twitter.visibility.common.stitch.StitchHelpers
import com.twitter.visibility.configapi.VisibilityParams
import com.twitter.visibility.configapi.configs.VisibilityDeciderGates
import com.twitter.visibility.engine.DeciderableVisibilityRuleEngine
import com.twitter.visibility.engine.VisibilityResultsMetricRecorder
import com.twitter.visibility.engine.VisibilityRuleEngine
import com.twitter.visibility.engine.VisibilityRulePreprocessor
import com.twitter.visibility.features.FeatureMap
import com.twitter.visibility.models.ContentId
import com.twitter.visibility.models.SafetyLevel
import com.twitter.visibility.models.ViewerContext
import com.twitter.visibility.rules.EvaluationContext
import com.twitter.visibility.rules.Rule
import com.twitter.visibility.rules.generators.TweetRuleGenerator
import com.twitter.visibility.rules.providers.InjectedPolicyProvider
import com.twitter.visibility.util.DeciderUtil
import com.twitter.visibility.util.FeatureSwitchUtil
import com.twitter.visibility.util.LoggingUtil
object VisibilityLibrary {
object Builder {
def apply(log: Logger, statsReceiver: StatsReceiver): Builder = new Builder(
log,
new MemoizingStatsReceiver(statsReceiver)
)
}
case class Builder(
log: Logger,
statsReceiver: StatsReceiver,
decider: Option[Decider] = None,
abDecider: Option[LoggingABDecider] = None,
featureSwitches: Option[FeatureSwitches] = None,
enableStitchProfiling: Gate[Unit] = Gate.False,
captureDebugStats: Gate[Unit] = Gate.False,
enableComposableActions: Gate[Unit] = Gate.False,
enableFailClosed: Gate[Unit] = Gate.False,
enableShortCircuiting: Gate[Unit] = Gate.True,
memoizeSafetyLevelParams: Gate[Unit] = Gate.False) {
def withDecider(decider: Decider): Builder = copy(decider = Some(decider))
@deprecated("use .withDecider and pass in a decider that is properly configured per DC")
def withDefaultDecider(isLocal: Boolean, useLocalOverrides: Boolean = false): Builder = {
if (isLocal) {
withLocalDecider
} else {
withDecider(
DeciderUtil.mkDecider(
useLocalDeciderOverrides = useLocalOverrides,
))
}
}
def withLocalDecider(): Builder = withDecider(DeciderUtil.mkLocalDecider)
def withNullDecider(): Builder =
withDecider(new NullDecider(isAvail = true, availabilityDefined = true))
def withABDecider(abDecider: LoggingABDecider, featureSwitches: FeatureSwitches): Builder =
abDecider match {
case abd: NullABDecider =>
copy(abDecider = Some(abd), featureSwitches = Some(NullFeatureSwitches))
case _ =>
copy(
abDecider = Some(abDecider),
featureSwitches = Some(featureSwitches)
)
}
def withABDecider(abDecider: LoggingABDecider): Builder = abDecider match {
case abd: NullABDecider =>
withABDecider(abDecider = abd, featureSwitches = NullFeatureSwitches)
case _ =>
withABDecider(
abDecider = abDecider,
featureSwitches =
FeatureSwitchUtil.mkVisibilityLibraryFeatureSwitches(abDecider, statsReceiver)
)
}
def withClientEventsLogger(clientEventsLogger: Logger): Builder =
withABDecider(DeciderUtil.mkABDecider(Some(clientEventsLogger)))
def withDefaultABDecider(isLocal: Boolean): Builder =
if (isLocal) {
withABDecider(NullABDecider)
} else {
withClientEventsLogger(LoggingUtil.mkDefaultLogger(statsReceiver))
}
def withNullABDecider(): Builder = withABDecider(NullABDecider)
def withEnableStitchProfiling(gate: Gate[Unit]): Builder =
copy(enableStitchProfiling = gate)
def withCaptureDebugStats(gate: Gate[Unit]): Builder =
copy(captureDebugStats = gate)
def withEnableComposableActions(gate: Gate[Unit]): Builder =
copy(enableComposableActions = gate)
def withEnableComposableActions(gateBoolean: Boolean): Builder = {
val gate = Gate.const(gateBoolean)
copy(enableComposableActions = gate)
}
def withEnableFailClosed(gate: Gate[Unit]): Builder =
copy(enableFailClosed = gate)
def withEnableFailClosed(gateBoolean: Boolean): Builder = {
val gate = Gate.const(gateBoolean)
copy(enableFailClosed = gate)
}
def withEnableShortCircuiting(gate: Gate[Unit]): Builder =
copy(enableShortCircuiting = gate)
def withEnableShortCircuiting(gateBoolean: Boolean): Builder = {
val gate = Gate.const(gateBoolean)
copy(enableShortCircuiting = gate)
}
def memoizeSafetyLevelParams(gate: Gate[Unit]): Builder =
copy(memoizeSafetyLevelParams = gate)
def memoizeSafetyLevelParams(gateBoolean: Boolean): Builder = {
val gate = Gate.const(gateBoolean)
copy(memoizeSafetyLevelParams = gate)
}
def build(): VisibilityLibrary = {
(decider, abDecider, featureSwitches) match {
case (None, _, _) =>
throw new IllegalStateException(
"Decider is unset! If intentional, please call .withNullDecider()."
)
case (_, None, _) =>
throw new IllegalStateException(
"ABDecider is unset! If intentional, please call .withNullABDecider()."
)
case (_, _, None) =>
throw new IllegalStateException(
"FeatureSwitches is unset! This is a bug."
)
case (Some(d), Some(abd), Some(fs)) =>
new VisibilityLibrary(
statsReceiver,
d,
abd,
VisibilityParams(log, statsReceiver, d, abd, fs),
enableStitchProfiling = enableStitchProfiling,
captureDebugStats = captureDebugStats,
enableComposableActions = enableComposableActions,
enableFailClosed = enableFailClosed,
enableShortCircuiting = enableShortCircuiting,
memoizeSafetyLevelParams = memoizeSafetyLevelParams)
}
}
}
val nullDecider = new NullDecider(true, true)
lazy val NullLibrary: VisibilityLibrary = new VisibilityLibrary(
NullStatsReceiver,
nullDecider,
NullABDecider,
VisibilityParams(
NullLogger,
NullStatsReceiver,
nullDecider,
NullABDecider,
NullFeatureSwitches),
enableStitchProfiling = Gate.False,
captureDebugStats = Gate.False,
enableComposableActions = Gate.False,
enableFailClosed = Gate.False,
enableShortCircuiting = Gate.True,
memoizeSafetyLevelParams = Gate.False
)
}
class VisibilityLibrary private[VisibilityLibrary] (
baseStatsReceiver: StatsReceiver,
decider: Decider,
abDecider: LoggingABDecider,
visibilityParams: VisibilityParams,
enableStitchProfiling: Gate[Unit],
captureDebugStats: Gate[Unit],
enableComposableActions: Gate[Unit],
enableFailClosed: Gate[Unit],
enableShortCircuiting: Gate[Unit],
memoizeSafetyLevelParams: Gate[Unit]) {
val statsReceiver: StatsReceiver =
new MemoizingStatsReceiver(baseStatsReceiver.scope("visibility_library"))
val metricsRecorder = VisibilityResultsMetricRecorder(statsReceiver, captureDebugStats)
val visParams: VisibilityParams = visibilityParams
val visibilityDeciderGates = VisibilityDeciderGates(decider)
val profileStats: MemoizingStatsReceiver = new MemoizingStatsReceiver(
statsReceiver.scope("profiling"))
val perSafetyLevelProfileStats: StatsReceiver = profileStats.scope("for_safety_level")
val featureMapBuilder: FeatureMapBuilder.Build =
FeatureMapBuilder(statsReceiver, enableStitchProfiling)
private lazy val tweetRuleGenerator = new TweetRuleGenerator()
lazy val policyProvider = new InjectedPolicyProvider(
visibilityDeciderGates = visibilityDeciderGates,
tweetRuleGenerator = tweetRuleGenerator)
val candidateVisibilityRulePreprocessor: VisibilityRulePreprocessor = VisibilityRulePreprocessor(
metricsRecorder,
policyProviderOpt = Some(policyProvider)
)
val fallbackVisibilityRulePreprocessor: VisibilityRulePreprocessor = VisibilityRulePreprocessor(
metricsRecorder)
lazy val candidateVisibilityRuleEngine: VisibilityRuleEngine = VisibilityRuleEngine(
Some(candidateVisibilityRulePreprocessor),
metricsRecorder,
enableComposableActions,
enableFailClosed,
policyProviderOpt = Some(policyProvider)
)
lazy val fallbackVisibilityRuleEngine: VisibilityRuleEngine = VisibilityRuleEngine(
Some(fallbackVisibilityRulePreprocessor),
metricsRecorder,
enableComposableActions,
enableFailClosed)
val ruleEngineVersionStatsReceiver = statsReceiver.scope("rule_engine_version")
def isReleaseCandidateEnabled: Boolean = visibilityDeciderGates.enableExperimentalRuleEngine()
private def visibilityRuleEngine: DeciderableVisibilityRuleEngine = {
if (isReleaseCandidateEnabled) {
ruleEngineVersionStatsReceiver.counter("release_candidate").incr()
candidateVisibilityRuleEngine
} else {
ruleEngineVersionStatsReceiver.counter("fallback").incr()
fallbackVisibilityRuleEngine
}
}
private def profileStitch[A](result: Stitch[A], safetyLevelName: String): Stitch[A] =
if (enableStitchProfiling()) {
StitchHelpers.profileStitch(
result,
Seq(profileStats, perSafetyLevelProfileStats.scope(safetyLevelName))
)
} else {
result
}
def getParams(viewerContext: ViewerContext, safetyLevel: SafetyLevel): Params = {
if (memoizeSafetyLevelParams()) {
visibilityParams.memoized(viewerContext, safetyLevel)
} else {
visibilityParams(viewerContext, safetyLevel)
}
}
def evaluationContextBuilder(viewerContext: ViewerContext): EvaluationContext.Builder =
EvaluationContext
.Builder(statsReceiver, visibilityParams, viewerContext)
.withMemoizedParams(memoizeSafetyLevelParams)
def runRuleEngine(
contentId: ContentId,
featureMap: FeatureMap,
evaluationContextBuilder: EvaluationContext.Builder,
safetyLevel: SafetyLevel
): Stitch[VisibilityResult] =
profileStitch(
visibilityRuleEngine(
evaluationContextBuilder.build(safetyLevel),
safetyLevel,
new VisibilityResultBuilder(contentId, featureMap),
enableShortCircuiting
),
safetyLevel.name
)
def runRuleEngine(
contentId: ContentId,
featureMap: FeatureMap,
viewerContext: ViewerContext,
safetyLevel: SafetyLevel
): Stitch[VisibilityResult] =
profileStitch(
visibilityRuleEngine(
EvaluationContext(safetyLevel, getParams(viewerContext, safetyLevel), statsReceiver),
safetyLevel,
new VisibilityResultBuilder(contentId, featureMap),
enableShortCircuiting
),
safetyLevel.name
)
def runRuleEngine(
viewerContext: ViewerContext,
safetyLevel: SafetyLevel,
preprocessedResultBuilder: VisibilityResultBuilder,
preprocessedRules: Seq[Rule]
): Stitch[VisibilityResult] =
profileStitch(
visibilityRuleEngine(
EvaluationContext(safetyLevel, getParams(viewerContext, safetyLevel), statsReceiver),
safetyLevel,
preprocessedResultBuilder,
enableShortCircuiting,
Some(preprocessedRules)
),
safetyLevel.name
)
def runRuleEngineBatch(
contentIds: Seq[ContentId],
featureMapProvider: (ContentId, SafetyLevel) => FeatureMap,
viewerContext: ViewerContext,
safetyLevel: SafetyLevel,
): Stitch[Seq[Try[VisibilityResult]]] = {
val params = getParams(viewerContext, safetyLevel)
profileStitch(
Stitch.traverse(contentIds) { contentId =>
visibilityRuleEngine(
EvaluationContext(safetyLevel, params, NullStatsReceiver),
safetyLevel,
new VisibilityResultBuilder(contentId, featureMapProvider(contentId, safetyLevel)),
enableShortCircuiting
).liftToTry
},
safetyLevel.name
)
}
def runRuleEngineBatch(
contentIds: Seq[ContentId],
featureMapProvider: (ContentId, SafetyLevel) => FeatureMap,
evaluationContextBuilder: EvaluationContext.Builder,
safetyLevel: SafetyLevel
): Stitch[Seq[Try[VisibilityResult]]] = {
val evaluationContext = evaluationContextBuilder.build(safetyLevel)
profileStitch(
Stitch.traverse(contentIds) { contentId =>
visibilityRuleEngine(
evaluationContext,
safetyLevel,
new VisibilityResultBuilder(contentId, featureMapProvider(contentId, safetyLevel)),
enableShortCircuiting
).liftToTry
},
safetyLevel.name
)
}
}

View File

@ -1,29 +0,0 @@
scala_library(
sources = ["*.scala"],
compiler_option_sets = ["fatal_warnings"],
platform = "java8",
strict_deps = True,
tags = ["bazel-compatible"],
dependencies = [
"3rdparty/jvm/com/twitter/src/java/com/twitter/logpipeline/client:logpipeline-event-publisher-thin",
"configapi/configapi-core",
"decider/src/main/scala",
"servo/util/src/main/scala",
"src/thrift/com/twitter/search/common:constants-scala",
"src/thrift/com/twitter/spam/rtf:safety-level-scala",
"src/thrift/com/twitter/spam/rtf:safety-result-scala",
"src/thrift/com/twitter/spam/rtf:tweet-rtf-event-scala",
"stitch/stitch-core",
"util/util-stats/src/main/scala/com/twitter/finagle/stats",
"visibility/common/src/main/scala/com/twitter/visibility/common/actions",
"visibility/common/src/main/scala/com/twitter/visibility/common/actions/converter/scala",
"visibility/common/src/main/scala/com/twitter/visibility/common/stitch",
"visibility/common/src/main/thrift/com/twitter/visibility:action-scala",
"visibility/lib/src/main/scala/com/twitter/visibility/configapi/configs",
"visibility/lib/src/main/scala/com/twitter/visibility/features",
"visibility/lib/src/main/scala/com/twitter/visibility/models",
"visibility/lib/src/main/scala/com/twitter/visibility/rules",
"visibility/lib/src/main/scala/com/twitter/visibility/util",
"visibility/lib/src/main/thrift/com/twitter/visibility/logging:vf-logging-scala",
],
)

View File

@ -1,64 +0,0 @@
package com.twitter.visibility.builder
import com.twitter.finagle.stats.NullStatsReceiver
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.servo.util.Gate
import com.twitter.stitch.Stitch
import com.twitter.visibility.features._
import com.twitter.visibility.common.stitch.StitchHelpers
import scala.collection.mutable
object FeatureMapBuilder {
type Build = Seq[FeatureMapBuilder => FeatureMapBuilder] => FeatureMap
def apply(
statsReceiver: StatsReceiver = NullStatsReceiver,
enableStitchProfiling: Gate[Unit] = Gate.False
): Build =
fns =>
Function
.chain(fns).apply(
new FeatureMapBuilder(statsReceiver, enableStitchProfiling)
).build
}
class FeatureMapBuilder private[builder] (
statsReceiver: StatsReceiver,
enableStitchProfiling: Gate[Unit] = Gate.False) {
private[this] val hydratedScope =
statsReceiver.scope("visibility_result_builder").scope("hydrated")
val mapBuilder: mutable.Builder[(Feature[_], Stitch[_]), Map[Feature[_], Stitch[_]]] =
Map.newBuilder[Feature[_], Stitch[_]]
val constantMapBuilder: mutable.Builder[(Feature[_], Any), Map[Feature[_], Any]] =
Map.newBuilder[Feature[_], Any]
def build: FeatureMap = new FeatureMap(mapBuilder.result(), constantMapBuilder.result())
def withConstantFeature[T](feature: Feature[T], value: T): FeatureMapBuilder = {
val anyValue: Any = value.asInstanceOf[Any]
constantMapBuilder += (feature -> anyValue)
this
}
def withFeature[T](feature: Feature[T], stitch: Stitch[T]): FeatureMapBuilder = {
val profiledStitch = if (enableStitchProfiling()) {
val featureScope = hydratedScope.scope(feature.name)
StitchHelpers.profileStitch(stitch, Seq(hydratedScope, featureScope))
} else {
stitch
}
val featureStitchRef = Stitch.ref(profiledStitch)
mapBuilder += FeatureMap.rescueFeatureTuple(feature -> featureStitchRef)
this
}
def withConstantFeature[T](feature: Feature[T], option: Option[T]): FeatureMapBuilder = {
option.map(withConstantFeature(feature, _)).getOrElse(this)
}
}

View File

@ -1,187 +0,0 @@
package com.twitter.visibility.builder
import com.twitter.datatools.entityservice.entities.thriftscala.FleetInterstitial
import com.twitter.decider.Decider
import com.twitter.decider.Decider.NullDecider
import com.twitter.finagle.stats.NullStatsReceiver
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.logpipeline.client.common.EventPublisher
import com.twitter.logpipeline.client.EventPublisherManager
import com.twitter.logpipeline.client.serializers.EventLogMsgThriftStructSerializer
import com.twitter.spam.rtf.thriftscala.SafetyLevel
import com.twitter.visibility.builder.VerdictLogger.FailureCounterName
import com.twitter.visibility.builder.VerdictLogger.SuccessCounterName
import com.twitter.visibility.features.Feature
import com.twitter.visibility.logging.thriftscala.ActionSource
import com.twitter.visibility.logging.thriftscala.EntityId
import com.twitter.visibility.logging.thriftscala.EntityIdType
import com.twitter.visibility.logging.thriftscala.EntityIdValue
import com.twitter.visibility.logging.thriftscala.HealthActionType
import com.twitter.visibility.logging.thriftscala.MisinfoPolicyCategory
import com.twitter.visibility.logging.thriftscala.VFLibType
import com.twitter.visibility.logging.thriftscala.VFVerdictLogEntry
import com.twitter.visibility.models.ContentId
import com.twitter.visibility.rules._
object VerdictLogger {
private val BaseStatsNamespace = "vf_verdict_logger"
private val FailureCounterName = "failures"
private val SuccessCounterName = "successes"
val LogCategoryName: String = "visibility_filtering_verdicts"
val Empty: VerdictLogger = new VerdictLogger(NullStatsReceiver, NullDecider, None)
def apply(
statsReceiver: StatsReceiver,
decider: Decider
): VerdictLogger = {
val eventPublisher: EventPublisher[VFVerdictLogEntry] =
EventPublisherManager
.newScribePublisherBuilder(
LogCategoryName,
EventLogMsgThriftStructSerializer.getNewSerializer[VFVerdictLogEntry]()).build()
new VerdictLogger(statsReceiver.scope(BaseStatsNamespace), decider, Some(eventPublisher))
}
}
class VerdictLogger(
statsReceiver: StatsReceiver,
decider: Decider,
publisherOpt: Option[EventPublisher[VFVerdictLogEntry]]) {
def log(
verdictLogEntry: VFVerdictLogEntry,
publisher: EventPublisher[VFVerdictLogEntry]
): Unit = {
publisher
.publish(verdictLogEntry)
.onSuccess(_ => statsReceiver.counter(SuccessCounterName).incr())
.onFailure { e =>
statsReceiver.counter(FailureCounterName).incr()
statsReceiver.scope(FailureCounterName).counter(e.getClass.getName).incr()
}
}
private def toEntityId(contentId: ContentId): Option[EntityId] = {
contentId match {
case ContentId.TweetId(id) => Some(EntityId(EntityIdType.TweetId, EntityIdValue.EntityId(id)))
case ContentId.UserId(id) => Some(EntityId(EntityIdType.UserId, EntityIdValue.EntityId(id)))
case ContentId.QuotedTweetRelationship(outerTweetId, _) =>
Some(EntityId(EntityIdType.TweetId, EntityIdValue.EntityId(outerTweetId)))
case ContentId.NotificationId(Some(id)) =>
Some(EntityId(EntityIdType.NotificationId, EntityIdValue.EntityId(id)))
case ContentId.DmId(id) => Some(EntityId(EntityIdType.DmId, EntityIdValue.EntityId(id)))
case ContentId.BlenderTweetId(id) =>
Some(EntityId(EntityIdType.TweetId, EntityIdValue.EntityId(id)))
case ContentId.SpacePlusUserId(_) =>
}
}
private def getLogEntryData(
actingRule: Option[Rule],
secondaryActingRules: Seq[Rule],
verdict: Action,
secondaryVerdicts: Seq[Action],
resolvedFeatureMap: Map[Feature[_], Any]
): (Seq[String], Seq[ActionSource], Seq[HealthActionType], Option[FleetInterstitial]) = {
actingRule
.filter {
case decideredRule: DoesLogVerdictDecidered =>
decider.isAvailable(decideredRule.verdictLogDeciderKey.toString)
case rule: DoesLogVerdict => true
case _ => false
}
.map { primaryRule =>
val secondaryRulesAndVerdicts = secondaryActingRules zip secondaryVerdicts
var actingRules: Seq[Rule] = Seq(primaryRule)
var actingRuleNames: Seq[String] = Seq(primaryRule.name)
var actionSources: Seq[ActionSource] = Seq()
var healthActionTypes: Seq[HealthActionType] = Seq(verdict.toHealthActionTypeThrift.get)
val misinfoPolicyCategory: Option[FleetInterstitial] = {
verdict match {
case softIntervention: SoftIntervention =>
softIntervention.fleetInterstitial
case tweetInterstitial: TweetInterstitial =>
tweetInterstitial.softIntervention.flatMap(_.fleetInterstitial)
case _ => None
}
}
secondaryRulesAndVerdicts.foreach(ruleAndVerdict => {
if (ruleAndVerdict._1.isInstanceOf[DoesLogVerdict]) {
actingRules = actingRules :+ ruleAndVerdict._1
actingRuleNames = actingRuleNames :+ ruleAndVerdict._1.name
healthActionTypes = healthActionTypes :+ ruleAndVerdict._2.toHealthActionTypeThrift.get
}
})
actingRules.foreach(rule => {
rule.actionSourceBuilder
.flatMap(_.build(resolvedFeatureMap, verdict))
.map(actionSource => {
actionSources = actionSources :+ actionSource
})
})
(actingRuleNames, actionSources, healthActionTypes, misinfoPolicyCategory)
}
.getOrElse((Seq.empty[String], Seq.empty[ActionSource], Seq.empty[HealthActionType], None))
}
def scribeVerdict(
visibilityResult: VisibilityResult,
safetyLevel: SafetyLevel,
vfLibType: VFLibType,
viewerId: Option[Long] = None
): Unit = {
publisherOpt.foreach { publisher =>
toEntityId(visibilityResult.contentId).foreach { entityId =>
visibilityResult.verdict.toHealthActionTypeThrift.foreach { healthActionType =>
val (actioningRules, actionSources, healthActionTypes, misinfoPolicyCategory) =
getLogEntryData(
actingRule = visibilityResult.actingRule,
secondaryActingRules = visibilityResult.secondaryActingRules,
verdict = visibilityResult.verdict,
secondaryVerdicts = visibilityResult.secondaryVerdicts,
resolvedFeatureMap = visibilityResult.resolvedFeatureMap
)
if (actioningRules.nonEmpty) {
log(
VFVerdictLogEntry(
entityId = entityId,
viewerId = viewerId,
timestampMsec = System.currentTimeMillis(),
vfLibType = vfLibType,
healthActionType = healthActionType,
safetyLevel = safetyLevel,
actioningRules = actioningRules,
actionSources = actionSources,
healthActionTypes = healthActionTypes,
misinfoPolicyCategory =
fleetInterstitialToMisinfoPolicyCategory(misinfoPolicyCategory)
),
publisher
)
}
}
}
}
}
def fleetInterstitialToMisinfoPolicyCategory(
fleetInterstitialOption: Option[FleetInterstitial]
): Option[MisinfoPolicyCategory] = {
fleetInterstitialOption.map {
case FleetInterstitial.Generic =>
MisinfoPolicyCategory.Generic
case FleetInterstitial.Samm =>
MisinfoPolicyCategory.Samm
case FleetInterstitial.CivicIntegrity =>
MisinfoPolicyCategory.CivicIntegrity
case _ => MisinfoPolicyCategory.Unknown
}
}
}

View File

@ -1,112 +0,0 @@
package com.twitter.visibility.builder
import com.twitter.spam.rtf.thriftscala.SafetyResult
import com.twitter.visibility.common.actions.converter.scala.DropReasonConverter
import com.twitter.visibility.rules.ComposableActions._
import com.twitter.visibility.features.Feature
import com.twitter.visibility.features.FeatureMap
import com.twitter.visibility.models.ContentId
import com.twitter.visibility.rules._
import com.twitter.visibility.{thriftscala => t}
case class VisibilityResult(
contentId: ContentId,
featureMap: FeatureMap = FeatureMap.empty,
ruleResultMap: Map[Rule, RuleResult] = Map.empty,
verdict: Action = Allow,
finished: Boolean = false,
actingRule: Option[Rule] = None,
secondaryActingRules: Seq[Rule] = Seq(),
secondaryVerdicts: Seq[Action] = Seq(),
resolvedFeatureMap: Map[Feature[_], Any] = Map.empty) {
def getSafetyResult: SafetyResult =
verdict match {
case InterstitialLimitedEngagements(reason: Reason, _, _, _)
if PublicInterest.Reasons
.contains(reason) =>
SafetyResult(
Some(PublicInterest.ReasonToSafetyResultReason(reason)),
verdict.toActionThrift()
)
case ComposableActionsWithInterstitialLimitedEngagements(tweetInterstitial)
if PublicInterest.Reasons.contains(tweetInterstitial.reason) =>
SafetyResult(
Some(PublicInterest.ReasonToSafetyResultReason(tweetInterstitial.reason)),
verdict.toActionThrift()
)
case FreedomOfSpeechNotReachReason(appealableReason) =>
SafetyResult(
Some(FreedomOfSpeechNotReach.reasonToSafetyResultReason(appealableReason)),
verdict.toActionThrift()
)
case _ => SafetyResult(None, verdict.toActionThrift())
}
def getUserVisibilityResult: Option[t.UserVisibilityResult] =
(verdict match {
case Drop(reason, _) =>
Some(
t.UserAction.Drop(t.Drop(Reason.toDropReason(reason).map(DropReasonConverter.toThrift))))
case _ => None
}).map(userAction => t.UserVisibilityResult(Some(userAction)))
}
object VisibilityResult {
class Builder {
var featureMap: FeatureMap = FeatureMap.empty
var ruleResultMap: Map[Rule, RuleResult] = Map.empty
var verdict: Action = Allow
var finished: Boolean = false
var actingRule: Option[Rule] = None
var secondaryActingRules: Seq[Rule] = Seq()
var secondaryVerdicts: Seq[Action] = Seq()
var resolvedFeatureMap: Map[Feature[_], Any] = Map.empty
def withFeatureMap(featureMapBld: FeatureMap) = {
featureMap = featureMapBld
this
}
def withRuleResultMap(ruleResultMapBld: Map[Rule, RuleResult]) = {
ruleResultMap = ruleResultMapBld
this
}
def withVerdict(verdictBld: Action) = {
verdict = verdictBld
this
}
def withFinished(finishedBld: Boolean) = {
finished = finishedBld
this
}
def withActingRule(actingRuleBld: Option[Rule]) = {
actingRule = actingRuleBld
this
}
def withSecondaryActingRules(secondaryActingRulesBld: Seq[Rule]) = {
secondaryActingRules = secondaryActingRulesBld
this
}
def withSecondaryVerdicts(secondaryVerdictsBld: Seq[Action]) = {
secondaryVerdicts = secondaryVerdictsBld
this
}
def build(contentId: ContentId) = VisibilityResult(
contentId,
featureMap,
ruleResultMap,
verdict,
finished,
actingRule,
secondaryActingRules,
secondaryVerdicts,
resolvedFeatureMap)
}
}

View File

@ -1,114 +0,0 @@
package com.twitter.visibility.builder
import com.twitter.visibility.features.Feature
import com.twitter.visibility.features.FeatureMap
import com.twitter.visibility.models.ContentId
import com.twitter.visibility.rules.Action
import com.twitter.visibility.rules.Allow
import com.twitter.visibility.rules.EvaluationContext
import com.twitter.visibility.rules.FailClosedException
import com.twitter.visibility.rules.FeaturesFailedException
import com.twitter.visibility.rules.MissingFeaturesException
import com.twitter.visibility.rules.Rule
import com.twitter.visibility.rules.RuleFailedException
import com.twitter.visibility.rules.RuleResult
import com.twitter.visibility.rules.State.FeatureFailed
import com.twitter.visibility.rules.State.MissingFeature
import com.twitter.visibility.rules.State.RuleFailed
class VisibilityResultBuilder(
val contentId: ContentId,
val featureMap: FeatureMap = FeatureMap.empty,
private var ruleResultMap: Map[Rule, RuleResult] = Map.empty) {
private var mapBuilder = Map.newBuilder[Rule, RuleResult]
mapBuilder ++= ruleResultMap
var verdict: Action = Allow
var finished: Boolean = false
var features: FeatureMap = featureMap
var actingRule: Option[Rule] = None
var secondaryVerdicts: Seq[Action] = Seq()
var secondaryActingRules: Seq[Rule] = Seq()
var resolvedFeatureMap: Map[Feature[_], Any] = Map.empty
def ruleResults: Map[Rule, RuleResult] = mapBuilder.result()
def withFeatureMap(featureMap: FeatureMap): VisibilityResultBuilder = {
this.features = featureMap
this
}
def withRuleResultMap(ruleResultMap: Map[Rule, RuleResult]): VisibilityResultBuilder = {
this.ruleResultMap = ruleResultMap
mapBuilder = Map.newBuilder[Rule, RuleResult]
mapBuilder ++= ruleResultMap
this
}
def withRuleResult(rule: Rule, result: RuleResult): VisibilityResultBuilder = {
mapBuilder += ((rule, result))
this
}
def withVerdict(verdict: Action, ruleOpt: Option[Rule] = None): VisibilityResultBuilder = {
this.verdict = verdict
this.actingRule = ruleOpt
this
}
def withSecondaryVerdict(verdict: Action, rule: Rule): VisibilityResultBuilder = {
this.secondaryVerdicts = this.secondaryVerdicts :+ verdict
this.secondaryActingRules = this.secondaryActingRules :+ rule
this
}
def withFinished(finished: Boolean): VisibilityResultBuilder = {
this.finished = finished
this
}
def withResolvedFeatureMap(
resolvedFeatureMap: Map[Feature[_], Any]
): VisibilityResultBuilder = {
this.resolvedFeatureMap = resolvedFeatureMap
this
}
def isVerdictComposable(): Boolean = this.verdict.isComposable
def failClosedException(evaluationContext: EvaluationContext): Option[FailClosedException] = {
mapBuilder
.result().collect {
case (r: Rule, RuleResult(_, MissingFeature(mf)))
if r.shouldFailClosed(evaluationContext.params) =>
Some(MissingFeaturesException(r.name, mf))
case (r: Rule, RuleResult(_, FeatureFailed(ff)))
if r.shouldFailClosed(evaluationContext.params) =>
Some(FeaturesFailedException(r.name, ff))
case (r: Rule, RuleResult(_, RuleFailed(t)))
if r.shouldFailClosed(evaluationContext.params) =>
Some(RuleFailedException(r.name, t))
}.toList.foldLeft(None: Option[FailClosedException]) { (acc, arg) =>
(acc, arg) match {
case (None, Some(_)) => arg
case (Some(FeaturesFailedException(_, _)), Some(MissingFeaturesException(_, _))) => arg
case (Some(RuleFailedException(_, _)), Some(MissingFeaturesException(_, _))) => arg
case (Some(RuleFailedException(_, _)), Some(FeaturesFailedException(_, _))) => arg
case _ => acc
}
}
}
def build: VisibilityResult = {
VisibilityResult(
contentId = contentId,
featureMap = features,
ruleResultMap = mapBuilder.result(),
verdict = verdict,
finished = finished,
actingRule = actingRule,
secondaryActingRules = secondaryActingRules,
secondaryVerdicts = secondaryVerdicts,
resolvedFeatureMap = resolvedFeatureMap
)
}
}

View File

@ -1,32 +0,0 @@
scala_library(
sources = ["*.scala"],
compiler_option_sets = ["fatal_warnings"],
platform = "java8",
strict_deps = True,
tags = ["bazel-compatible"],
dependencies = [
"3rdparty/jvm/com/google/guava",
"communities/thrift/src/main/thrift/com/twitter/communities:thrift-scala",
"communities/thrift/src/main/thrift/com/twitter/communities/moderation:thrift-scala",
"escherbird/src/thrift/com/twitter/escherbird/softintervention:softintervention_thrift-scala",
"snowflake/src/main/scala/com/twitter/snowflake/id",
"src/thrift/com/twitter/context:twitter-context-scala",
"src/thrift/com/twitter/escherbird/common:common-scala",
"src/thrift/com/twitter/gizmoduck:user-thrift-scala",
"src/thrift/com/twitter/search/common:constants-scala",
"src/thrift/com/twitter/spam/rtf:safety-level-scala",
"src/thrift/com/twitter/spam/rtf:tweet-rtf-event-scala",
"src/thrift/com/twitter/tweetypie:tweet-scala",
"stitch/stitch-core",
"tweetypie/src/scala/com/twitter/tweetypie/additionalfields",
"twitter-context/src/main/scala",
"visibility/common/src/main/scala/com/twitter/visibility/common",
"visibility/common/src/main/scala/com/twitter/visibility/common/stitch",
"visibility/lib/src/main/resources/config",
"visibility/lib/src/main/scala/com/twitter/visibility",
"visibility/lib/src/main/scala/com/twitter/visibility/builder/users",
"visibility/lib/src/main/scala/com/twitter/visibility/features",
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/blender",
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/tweets",
],
)

View File

@ -1,228 +0,0 @@
package com.twitter.visibility.builder.common
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.gizmoduck.thriftscala.MuteOption
import com.twitter.gizmoduck.thriftscala.MuteSurface
import com.twitter.gizmoduck.thriftscala.{MutedKeyword => GdMutedKeyword}
import com.twitter.servo.util.Gate
import com.twitter.stitch.Stitch
import com.twitter.tweetypie.thriftscala.Tweet
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common._
import com.twitter.visibility.features._
import com.twitter.visibility.models.{MutedKeyword => VfMutedKeyword}
import java.util.Locale
class MutedKeywordFeatures(
userSource: UserSource,
userRelationshipSource: UserRelationshipSource,
keywordMatcher: KeywordMatcher.Matcher = KeywordMatcher.TestMatcher,
statsReceiver: StatsReceiver,
enableFollowCheckInMutedKeyword: Gate[Unit] = Gate.False) {
private[this] val scopedStatsReceiver: StatsReceiver =
statsReceiver.scope("muted_keyword_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val viewerMutesKeywordInTweetForHomeTimeline =
scopedStatsReceiver.scope(ViewerMutesKeywordInTweetForHomeTimeline.name).counter("requests")
private[this] val viewerMutesKeywordInTweetForTweetReplies =
scopedStatsReceiver.scope(ViewerMutesKeywordInTweetForTweetReplies.name).counter("requests")
private[this] val viewerMutesKeywordInTweetForNotifications =
scopedStatsReceiver.scope(ViewerMutesKeywordInTweetForNotifications.name).counter("requests")
private[this] val excludeFollowingForMutedKeywordsRequests =
scopedStatsReceiver.scope("exclude_following").counter("requests")
private[this] val viewerMutesKeywordInTweetForAllSurfaces =
scopedStatsReceiver.scope(ViewerMutesKeywordInTweetForAllSurfaces.name).counter("requests")
def forTweet(
tweet: Tweet,
viewerId: Option[Long],
authorId: Long
): FeatureMapBuilder => FeatureMapBuilder = { featureMapBuilder =>
requests.incr()
viewerMutesKeywordInTweetForHomeTimeline.incr()
viewerMutesKeywordInTweetForTweetReplies.incr()
viewerMutesKeywordInTweetForNotifications.incr()
viewerMutesKeywordInTweetForAllSurfaces.incr()
val keywordsBySurface = allMutedKeywords(viewerId)
val keywordsWithoutDefinedSurface = allMutedKeywordsWithoutDefinedSurface(viewerId)
featureMapBuilder
.withFeature(
ViewerMutesKeywordInTweetForHomeTimeline,
tweetContainsMutedKeyword(
tweet,
keywordsBySurface,
MuteSurface.HomeTimeline,
viewerId,
authorId
)
)
.withFeature(
ViewerMutesKeywordInTweetForTweetReplies,
tweetContainsMutedKeyword(
tweet,
keywordsBySurface,
MuteSurface.TweetReplies,
viewerId,
authorId
)
)
.withFeature(
ViewerMutesKeywordInTweetForNotifications,
tweetContainsMutedKeyword(
tweet,
keywordsBySurface,
MuteSurface.Notifications,
viewerId,
authorId
)
)
.withFeature(
ViewerMutesKeywordInTweetForAllSurfaces,
tweetContainsMutedKeywordWithoutDefinedSurface(
tweet,
keywordsWithoutDefinedSurface,
viewerId,
authorId
)
)
}
def allMutedKeywords(viewerId: Option[Long]): Stitch[Map[MuteSurface, Seq[GdMutedKeyword]]] =
viewerId
.map { id => userSource.getAllMutedKeywords(id) }.getOrElse(Stitch.value(Map.empty))
def allMutedKeywordsWithoutDefinedSurface(viewerId: Option[Long]): Stitch[Seq[GdMutedKeyword]] =
viewerId
.map { id => userSource.getAllMutedKeywordsWithoutDefinedSurface(id) }.getOrElse(
Stitch.value(Seq.empty))
private def mutingKeywordsText(
mutedKeywords: Seq[GdMutedKeyword],
muteSurface: MuteSurface,
viewerIdOpt: Option[Long],
authorId: Long
): Stitch[Option[String]] = {
if (muteSurface == MuteSurface.HomeTimeline && mutedKeywords.nonEmpty) {
Stitch.value(Some(mutedKeywords.map(_.keyword).mkString(",")))
} else {
mutedKeywords.partition(kw =>
kw.muteOptions.contains(MuteOption.ExcludeFollowingAccounts)) match {
case (_, mutedKeywordsFromAnyone) if mutedKeywordsFromAnyone.nonEmpty =>
Stitch.value(Some(mutedKeywordsFromAnyone.map(_.keyword).mkString(",")))
case (mutedKeywordsExcludeFollowing, _)
if mutedKeywordsExcludeFollowing.nonEmpty && enableFollowCheckInMutedKeyword() =>
excludeFollowingForMutedKeywordsRequests.incr()
viewerIdOpt match {
case Some(viewerId) =>
userRelationshipSource.follows(viewerId, authorId).map {
case true =>
case false => Some(mutedKeywordsExcludeFollowing.map(_.keyword).mkString(","))
}
case _ => Stitch.None
}
case (_, _) => Stitch.None
}
}
}
private def mutingKeywordsTextWithoutDefinedSurface(
mutedKeywords: Seq[GdMutedKeyword],
viewerIdOpt: Option[Long],
authorId: Long
): Stitch[Option[String]] = {
mutedKeywords.partition(kw =>
kw.muteOptions.contains(MuteOption.ExcludeFollowingAccounts)) match {
case (_, mutedKeywordsFromAnyone) if mutedKeywordsFromAnyone.nonEmpty =>
Stitch.value(Some(mutedKeywordsFromAnyone.map(_.keyword).mkString(",")))
case (mutedKeywordsExcludeFollowing, _)
if mutedKeywordsExcludeFollowing.nonEmpty && enableFollowCheckInMutedKeyword() =>
excludeFollowingForMutedKeywordsRequests.incr()
viewerIdOpt match {
case Some(viewerId) =>
userRelationshipSource.follows(viewerId, authorId).map {
case true =>
case false => Some(mutedKeywordsExcludeFollowing.map(_.keyword).mkString(","))
}
case _ => Stitch.None
}
case (_, _) => Stitch.None
}
}
def tweetContainsMutedKeyword(
tweet: Tweet,
mutedKeywordMap: Stitch[Map[MuteSurface, Seq[GdMutedKeyword]]],
muteSurface: MuteSurface,
viewerIdOpt: Option[Long],
authorId: Long
): Stitch[VfMutedKeyword] = {
mutedKeywordMap.flatMap { keywordMap =>
if (keywordMap.isEmpty) {
Stitch.value(VfMutedKeyword(None))
} else {
val mutedKeywords = keywordMap.getOrElse(muteSurface, Nil)
val matchTweetFn: KeywordMatcher.MatchTweet = keywordMatcher(mutedKeywords)
val locale = tweet.language.map(l => Locale.forLanguageTag(l.language))
val text = tweet.coreData.get.text
matchTweetFn(locale, text).flatMap { results =>
mutingKeywordsText(results, muteSurface, viewerIdOpt, authorId).map(VfMutedKeyword)
}
}
}
}
def tweetContainsMutedKeywordWithoutDefinedSurface(
tweet: Tweet,
mutedKeywordSeq: Stitch[Seq[GdMutedKeyword]],
viewerIdOpt: Option[Long],
authorId: Long
): Stitch[VfMutedKeyword] = {
mutedKeywordSeq.flatMap { mutedKeyword =>
if (mutedKeyword.isEmpty) {
Stitch.value(VfMutedKeyword(None))
} else {
val matchTweetFn: KeywordMatcher.MatchTweet = keywordMatcher(mutedKeyword)
val locale = tweet.language.map(l => Locale.forLanguageTag(l.language))
val text = tweet.coreData.get.text
matchTweetFn(locale, text).flatMap { results =>
mutingKeywordsTextWithoutDefinedSurface(results, viewerIdOpt, authorId).map(
VfMutedKeyword
)
}
}
}
}
def spaceTitleContainsMutedKeyword(
spaceTitle: String,
spaceLanguageOpt: Option[String],
mutedKeywordMap: Stitch[Map[MuteSurface, Seq[GdMutedKeyword]]],
muteSurface: MuteSurface,
): Stitch[VfMutedKeyword] = {
mutedKeywordMap.flatMap { keywordMap =>
if (keywordMap.isEmpty) {
Stitch.value(VfMutedKeyword(None))
} else {
val mutedKeywords = keywordMap.getOrElse(muteSurface, Nil)
val matchTweetFn: KeywordMatcher.MatchTweet = keywordMatcher(mutedKeywords)
val locale = spaceLanguageOpt.map(l => Locale.forLanguageTag(l))
matchTweetFn(locale, spaceTitle).flatMap { results =>
if (results.nonEmpty) {
Stitch.value(Some(results.map(_.keyword).mkString(","))).map(VfMutedKeyword)
} else {
Stitch.None.map(VfMutedKeyword)
}
}
}
}
}
}

View File

@ -1,23 +0,0 @@
scala_library(
sources = ["*.scala"],
compiler_option_sets = ["fatal_warnings"],
platform = "java8",
strict_deps = True,
tags = ["bazel-compatible"],
dependencies = [
"src/thrift/com/twitter/convosvc:convosvc-scala",
"src/thrift/com/twitter/convosvc/internal:internal-scala",
"src/thrift/com/twitter/spam/rtf:safety-level-scala",
"stitch/stitch-core",
"stitch/stitch-core/src/main/scala/com/twitter/stitch",
"visibility/common/src/main/scala/com/twitter/visibility/common",
"visibility/common/src/main/scala/com/twitter/visibility/common/dm_sources",
"visibility/lib/src/main/resources/config",
"visibility/lib/src/main/scala/com/twitter/visibility",
"visibility/lib/src/main/scala/com/twitter/visibility/builder",
"visibility/lib/src/main/scala/com/twitter/visibility/builder/users",
"visibility/lib/src/main/scala/com/twitter/visibility/features",
"visibility/lib/src/main/scala/com/twitter/visibility/util",
"visibility/lib/src/main/thrift/com/twitter/visibility/safety_label_store:safety-label-store-scala",
],
)

View File

@ -1,196 +0,0 @@
package com.twitter.visibility.builder.dms
import com.twitter.convosvc.thriftscala.ConversationQuery
import com.twitter.convosvc.thriftscala.ConversationQueryOptions
import com.twitter.convosvc.thriftscala.ConversationType
import com.twitter.convosvc.thriftscala.TimelineLookupState
import com.twitter.stitch.NotFound
import com.twitter.stitch.Stitch
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.builder.users.AuthorFeatures
import com.twitter.visibility.common.DmConversationId
import com.twitter.visibility.common.UserId
import com.twitter.visibility.common.dm_sources.DmConversationSource
import com.twitter.visibility.features._
case class InvalidDmConversationFeatureException(message: String) extends Exception(message)
class DmConversationFeatures(
dmConversationSource: DmConversationSource,
authorFeatures: AuthorFeatures) {
def forDmConversationId(
dmConversationId: DmConversationId,
viewerIdOpt: Option[UserId]
): FeatureMapBuilder => FeatureMapBuilder =
_.withFeature(
DmConversationIsOneToOneConversation,
dmConversationIsOneToOneConversation(dmConversationId, viewerIdOpt))
.withFeature(
DmConversationHasEmptyTimeline,
dmConversationHasEmptyTimeline(dmConversationId, viewerIdOpt))
.withFeature(
DmConversationHasValidLastReadableEventId,
dmConversationHasValidLastReadableEventId(dmConversationId, viewerIdOpt))
.withFeature(
DmConversationInfoExists,
dmConversationInfoExists(dmConversationId, viewerIdOpt))
.withFeature(
DmConversationTimelineExists,
dmConversationTimelineExists(dmConversationId, viewerIdOpt))
.withFeature(
AuthorIsSuspended,
dmConversationHasSuspendedParticipant(dmConversationId, viewerIdOpt))
.withFeature(
AuthorIsDeactivated,
dmConversationHasDeactivatedParticipant(dmConversationId, viewerIdOpt))
.withFeature(
AuthorIsErased,
dmConversationHasErasedParticipant(dmConversationId, viewerIdOpt))
.withFeature(
ViewerIsDmConversationParticipant,
viewerIsDmConversationParticipant(dmConversationId, viewerIdOpt))
def dmConversationIsOneToOneConversation(
dmConversationId: DmConversationId,
viewerIdOpt: Option[UserId]
): Stitch[Boolean] =
viewerIdOpt match {
case Some(viewerId) =>
dmConversationSource.getConversationType(dmConversationId, viewerId).flatMap {
case Some(ConversationType.OneToOneDm | ConversationType.SecretOneToOneDm) =>
Stitch.True
case None =>
Stitch.exception(InvalidDmConversationFeatureException("Conversation type not found"))
case _ => Stitch.False
}
case _ => Stitch.exception(InvalidDmConversationFeatureException("Viewer id missing"))
}
private[dms] def dmConversationHasEmptyTimeline(
dmConversationId: DmConversationId,
viewerIdOpt: Option[UserId]
): Stitch[Boolean] =
dmConversationSource
.getConversationTimelineEntries(
dmConversationId,
ConversationQuery(
conversationId = Some(dmConversationId),
options = Some(
ConversationQueryOptions(
perspectivalUserId = viewerIdOpt,
hydrateEvents = Some(false),
supportsReactions = Some(true)
)
),
maxCount = 10
)
).map(_.forall(entries => entries.isEmpty))
private[dms] def dmConversationHasValidLastReadableEventId(
dmConversationId: DmConversationId,
viewerIdOpt: Option[UserId]
): Stitch[Boolean] =
viewerIdOpt match {
case Some(viewerId) =>
dmConversationSource
.getConversationLastReadableEventId(dmConversationId, viewerId).map(_.exists(id =>
id > 0L))
case _ => Stitch.exception(InvalidDmConversationFeatureException("Viewer id missing"))
}
private[dms] def dmConversationInfoExists(
dmConversationId: DmConversationId,
viewerIdOpt: Option[UserId]
): Stitch[Boolean] =
viewerIdOpt match {
case Some(viewerId) =>
dmConversationSource
.getDmConversationInfo(dmConversationId, viewerId).map(_.isDefined)
case _ => Stitch.exception(InvalidDmConversationFeatureException("Viewer id missing"))
}
private[dms] def dmConversationTimelineExists(
dmConversationId: DmConversationId,
viewerIdOpt: Option[UserId]
): Stitch[Boolean] =
dmConversationSource
.getConversationTimelineState(
dmConversationId,
ConversationQuery(
conversationId = Some(dmConversationId),
options = Some(
ConversationQueryOptions(
perspectivalUserId = viewerIdOpt,
hydrateEvents = Some(false),
supportsReactions = Some(true)
)
),
maxCount = 1
)
).map {
case Some(TimelineLookupState.NotFound) | None => false
case _ => true
}
private[dms] def anyConversationParticipantMatchesCondition(
condition: UserId => Stitch[Boolean],
dmConversationId: DmConversationId,
viewerIdOpt: Option[UserId]
): Stitch[Boolean] =
viewerIdOpt match {
case Some(viewerId) =>
dmConversationSource
.getConversationParticipantIds(dmConversationId, viewerId).flatMap {
case Some(participants) =>
Stitch
.collect(participants.map(condition)).map(_.contains(true)).rescue {
case NotFound =>
Stitch.exception(InvalidDmConversationFeatureException("User not found"))
}
case _ => Stitch.False
}
case _ => Stitch.exception(InvalidDmConversationFeatureException("Viewer id missing"))
}
def dmConversationHasSuspendedParticipant(
dmConversationId: DmConversationId,
viewerIdOpt: Option[UserId]
): Stitch[Boolean] =
anyConversationParticipantMatchesCondition(
participant => authorFeatures.authorIsSuspended(participant),
dmConversationId,
viewerIdOpt)
def dmConversationHasDeactivatedParticipant(
dmConversationId: DmConversationId,
viewerIdOpt: Option[UserId]
): Stitch[Boolean] =
anyConversationParticipantMatchesCondition(
participant => authorFeatures.authorIsDeactivated(participant),
dmConversationId,
viewerIdOpt)
def dmConversationHasErasedParticipant(
dmConversationId: DmConversationId,
viewerIdOpt: Option[UserId]
): Stitch[Boolean] =
anyConversationParticipantMatchesCondition(
participant => authorFeatures.authorIsErased(participant),
dmConversationId,
viewerIdOpt)
def viewerIsDmConversationParticipant(
dmConversationId: DmConversationId,
viewerIdOpt: Option[UserId]
): Stitch[Boolean] =
viewerIdOpt match {
case Some(viewerId) =>
dmConversationSource
.getConversationParticipantIds(dmConversationId, viewerId).map {
case Some(participants) => participants.contains(viewerId)
case _ => false
}
case _ => Stitch.exception(InvalidDmConversationFeatureException("Viewer id missing"))
}
}

View File

@ -1,341 +0,0 @@
package com.twitter.visibility.builder.dms
import com.twitter.convosvc.thriftscala.Event
import com.twitter.convosvc.thriftscala.StoredDelete
import com.twitter.convosvc.thriftscala.StoredPerspectivalMessageInfo
import com.twitter.convosvc.thriftscala.PerspectivalSpamState
import com.twitter.stitch.Stitch
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.builder.users.AuthorFeatures
import com.twitter.visibility.common.DmEventId
import com.twitter.visibility.common.dm_sources.DmEventSource
import com.twitter.visibility.common.UserId
import com.twitter.convosvc.thriftscala.EventType
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.stitch.NotFound
import com.twitter.visibility.common.dm_sources.DmConversationSource
import com.twitter.visibility.features._
case class InvalidDmEventFeatureException(message: String) extends Exception(message)
class DmEventFeatures(
dmEventSource: DmEventSource,
dmConversationSource: DmConversationSource,
authorFeatures: AuthorFeatures,
dmConversationFeatures: DmConversationFeatures,
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("dm_event_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
def forDmEventId(
dmEventId: DmEventId,
viewerId: UserId
): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
val dmEventStitchRef: Stitch[Option[Event]] =
Stitch.ref(dmEventSource.getDmEvent(dmEventId, viewerId))
_.withFeature(
DmEventIsMessageCreateEvent,
isDmEventType(dmEventStitchRef, EventType.MessageCreate))
.withFeature(
AuthorIsSuspended,
messageCreateEventHasInactiveInitiatingUser(
dmEventStitchRef,
initiatingUser => authorFeatures.authorIsSuspended(initiatingUser))
)
.withFeature(
AuthorIsDeactivated,
messageCreateEventHasInactiveInitiatingUser(
dmEventStitchRef,
initiatingUser => authorFeatures.authorIsDeactivated(initiatingUser))
)
.withFeature(
AuthorIsErased,
messageCreateEventHasInactiveInitiatingUser(
dmEventStitchRef,
initiatingUser => authorFeatures.authorIsErased(initiatingUser))
)
.withFeature(
DmEventOccurredBeforeLastClearedEvent,
dmEventOccurredBeforeLastClearedEvent(dmEventStitchRef, dmEventId, viewerId)
)
.withFeature(
DmEventOccurredBeforeJoinConversationEvent,
dmEventOccurredBeforeJoinConversationEvent(dmEventStitchRef, dmEventId, viewerId)
)
.withFeature(
ViewerIsDmConversationParticipant,
dmEventViewerIsDmConversationParticipant(dmEventStitchRef, viewerId)
)
.withFeature(
DmEventIsDeleted,
dmEventIsDeleted(dmEventStitchRef, dmEventId)
)
.withFeature(
DmEventIsHidden,
dmEventIsHidden(dmEventStitchRef, dmEventId)
)
.withFeature(
ViewerIsDmEventInitiatingUser,
viewerIsDmEventInitiatingUser(dmEventStitchRef, viewerId)
)
.withFeature(
DmEventInOneToOneConversationWithUnavailableUser,
dmEventInOneToOneConversationWithUnavailableUser(dmEventStitchRef, viewerId)
)
.withFeature(
DmEventIsLastMessageReadUpdateEvent,
isDmEventType(dmEventStitchRef, EventType.LastMessageReadUpdate)
)
.withFeature(
DmEventIsJoinConversationEvent,
isDmEventType(dmEventStitchRef, EventType.JoinConversation)
)
.withFeature(
DmEventIsWelcomeMessageCreateEvent,
isDmEventType(dmEventStitchRef, EventType.WelcomeMessageCreate)
)
.withFeature(
DmEventIsTrustConversationEvent,
isDmEventType(dmEventStitchRef, EventType.TrustConversation)
)
.withFeature(
DmEventIsCsFeedbackSubmitted,
isDmEventType(dmEventStitchRef, EventType.CsFeedbackSubmitted)
)
.withFeature(
DmEventIsCsFeedbackDismissed,
isDmEventType(dmEventStitchRef, EventType.CsFeedbackDismissed)
)
.withFeature(
DmEventIsConversationCreateEvent,
isDmEventType(dmEventStitchRef, EventType.ConversationCreate)
)
.withFeature(
DmEventInOneToOneConversation,
dmEventInOneToOneConversation(dmEventStitchRef, viewerId)
)
.withFeature(
DmEventIsPerspectivalJoinConversationEvent,
dmEventIsPerspectivalJoinConversationEvent(dmEventStitchRef, dmEventId, viewerId))
}
private def isDmEventType(
dmEventOptStitch: Stitch[Option[Event]],
eventType: EventType
): Stitch[Boolean] =
dmEventSource.getEventType(dmEventOptStitch).flatMap {
case Some(_: eventType.type) =>
Stitch.True
case None =>
Stitch.exception(InvalidDmEventFeatureException(s"$eventType event type not found"))
case _ =>
Stitch.False
}
private def dmEventIsPerspectivalJoinConversationEvent(
dmEventOptStitch: Stitch[Option[Event]],
dmEventId: DmEventId,
viewerId: UserId
): Stitch[Boolean] =
Stitch
.join(
dmEventSource.getEventType(dmEventOptStitch),
dmEventSource.getConversationId(dmEventOptStitch)).flatMap {
case (Some(EventType.JoinConversation), conversationIdOpt) =>
conversationIdOpt match {
case Some(conversationId) =>
dmConversationSource
.getParticipantJoinConversationEventId(conversationId, viewerId, viewerId)
.flatMap {
case Some(joinConversationEventId) =>
Stitch.value(joinConversationEventId == dmEventId)
case _ => Stitch.False
}
case _ =>
Stitch.exception(InvalidDmEventFeatureException("Conversation id not found"))
}
case (None, _) =>
Stitch.exception(InvalidDmEventFeatureException("Event type not found"))
case _ => Stitch.False
}
private def messageCreateEventHasInactiveInitiatingUser(
dmEventOptStitch: Stitch[Option[Event]],
condition: UserId => Stitch[Boolean],
): Stitch[Boolean] =
Stitch
.join(
dmEventSource.getEventType(dmEventOptStitch),
dmEventSource.getInitiatingUserId(dmEventOptStitch)).flatMap {
case (Some(EventType.MessageCreate), Some(userId)) =>
condition(userId).rescue {
case NotFound =>
Stitch.exception(InvalidDmEventFeatureException("initiating user not found"))
}
case (None, _) =>
Stitch.exception(InvalidDmEventFeatureException("DmEvent type is missing"))
case (Some(EventType.MessageCreate), _) =>
Stitch.exception(InvalidDmEventFeatureException("initiating user id is missing"))
case _ => Stitch.False
}
private def dmEventOccurredBeforeLastClearedEvent(
dmEventOptStitch: Stitch[Option[Event]],
dmEventId: DmEventId,
viewerId: UserId
): Stitch[Boolean] = {
dmEventSource.getConversationId(dmEventOptStitch).flatMap {
case Some(convoId) =>
val lastClearedEventIdStitch =
dmConversationSource.getParticipantLastClearedEventId(convoId, viewerId, viewerId)
lastClearedEventIdStitch.flatMap {
case Some(lastClearedEventId) => Stitch(dmEventId <= lastClearedEventId)
case _ =>
Stitch.False
}
case _ => Stitch.False
}
}
private def dmEventOccurredBeforeJoinConversationEvent(
dmEventOptStitch: Stitch[Option[Event]],
dmEventId: DmEventId,
viewerId: UserId
): Stitch[Boolean] = {
dmEventSource.getConversationId(dmEventOptStitch).flatMap {
case Some(convoId) =>
val joinConversationEventIdStitch =
dmConversationSource
.getParticipantJoinConversationEventId(convoId, viewerId, viewerId)
joinConversationEventIdStitch.flatMap {
case Some(joinConversationEventId) => Stitch(dmEventId < joinConversationEventId)
case _ => Stitch.False
}
case _ => Stitch.False
}
}
private def dmEventViewerIsDmConversationParticipant(
dmEventOptStitch: Stitch[Option[Event]],
viewerId: UserId
): Stitch[Boolean] = {
dmEventSource.getConversationId(dmEventOptStitch).flatMap {
case Some(convoId) =>
dmConversationFeatures.viewerIsDmConversationParticipant(convoId, Some(viewerId))
case _ => Stitch.True
}
}
private def dmEventIsDeleted(
dmEventOptStitch: Stitch[Option[Event]],
dmEventId: DmEventId
): Stitch[Boolean] =
dmEventSource.getConversationId(dmEventOptStitch).flatMap {
case Some(convoId) =>
dmConversationSource
.getDeleteInfo(convoId, dmEventId).rescue {
case e: java.lang.IllegalArgumentException =>
Stitch.exception(InvalidDmEventFeatureException("Invalid conversation id"))
}.flatMap {
case Some(StoredDelete(None)) => Stitch.True
case _ => Stitch.False
}
case _ => Stitch.False
}
private def dmEventIsHidden(
dmEventOptStitch: Stitch[Option[Event]],
dmEventId: DmEventId
): Stitch[Boolean] =
dmEventSource.getConversationId(dmEventOptStitch).flatMap {
case Some(convoId) =>
dmConversationSource
.getPerspectivalMessageInfo(convoId, dmEventId).rescue {
case e: java.lang.IllegalArgumentException =>
Stitch.exception(InvalidDmEventFeatureException("Invalid conversation id"))
}.flatMap {
case Some(StoredPerspectivalMessageInfo(Some(hidden), _)) if hidden =>
Stitch.True
case Some(StoredPerspectivalMessageInfo(_, Some(spamState)))
if spamState == PerspectivalSpamState.Spam =>
Stitch.True
case _ => Stitch.False
}
case _ => Stitch.False
}
private def viewerIsDmEventInitiatingUser(
dmEventOptStitch: Stitch[Option[Event]],
viewerId: UserId
): Stitch[Boolean] =
Stitch
.join(
dmEventSource.getEventType(dmEventOptStitch),
dmEventSource.getInitiatingUserId(dmEventOptStitch)).flatMap {
case (
Some(
EventType.TrustConversation | EventType.CsFeedbackSubmitted |
EventType.CsFeedbackDismissed | EventType.WelcomeMessageCreate |
EventType.JoinConversation),
Some(userId)) =>
Stitch(viewerId == userId)
case (
Some(
EventType.TrustConversation | EventType.CsFeedbackSubmitted |
EventType.CsFeedbackDismissed | EventType.WelcomeMessageCreate |
EventType.JoinConversation),
None) =>
Stitch.exception(InvalidDmEventFeatureException("Initiating user id is missing"))
case (None, _) =>
Stitch.exception(InvalidDmEventFeatureException("DmEvent type is missing"))
case _ => Stitch.True
}
private def dmEventInOneToOneConversationWithUnavailableUser(
dmEventOptStitch: Stitch[Option[Event]],
viewerId: UserId
): Stitch[Boolean] =
dmEventSource.getConversationId(dmEventOptStitch).flatMap {
case Some(conversationId) =>
dmConversationFeatures
.dmConversationIsOneToOneConversation(conversationId, Some(viewerId)).flatMap {
isOneToOne =>
if (isOneToOne) {
Stitch
.join(
dmConversationFeatures
.dmConversationHasSuspendedParticipant(conversationId, Some(viewerId)),
dmConversationFeatures
.dmConversationHasDeactivatedParticipant(conversationId, Some(viewerId)),
dmConversationFeatures
.dmConversationHasErasedParticipant(conversationId, Some(viewerId))
).flatMap {
case (
convoParticipantIsSuspended,
convoParticipantIsDeactivated,
convoParticipantIsErased) =>
Stitch.value(
convoParticipantIsSuspended || convoParticipantIsDeactivated || convoParticipantIsErased)
}
} else {
Stitch.False
}
}
case _ => Stitch.False
}
private def dmEventInOneToOneConversation(
dmEventOptStitch: Stitch[Option[Event]],
viewerId: UserId
): Stitch[Boolean] =
dmEventSource.getConversationId(dmEventOptStitch).flatMap {
case Some(conversationId) =>
dmConversationFeatures
.dmConversationIsOneToOneConversation(conversationId, Some(viewerId))
case _ => Stitch.False
}
}

View File

@ -1,31 +0,0 @@
scala_library(
sources = ["*.scala"],
compiler_option_sets = ["fatal_warnings"],
platform = "java8",
strict_deps = True,
tags = ["bazel-compatible"],
dependencies = [
"3rdparty/jvm/com/google/guava",
"mediaservices/commons/src/main/thrift:thrift-scala",
"mediaservices/media-util/src/main/scala",
"snowflake/src/main/scala/com/twitter/snowflake/id",
"src/thrift/com/twitter/context:twitter-context-scala",
"src/thrift/com/twitter/gizmoduck:user-thrift-scala",
"src/thrift/com/twitter/spam/rtf:safety-level-scala",
"src/thrift/com/twitter/spam/rtf:tweet-rtf-event-scala",
"src/thrift/com/twitter/tweetypie:service-scala",
"src/thrift/com/twitter/tweetypie:tweet-scala",
"stitch/stitch-core",
"tweetypie/src/scala/com/twitter/tweetypie/additionalfields",
"twitter-context/src/main/scala",
"visibility/common/src/main/scala/com/twitter/visibility/common",
"visibility/lib/src/main/resources/config",
"visibility/lib/src/main/scala/com/twitter/visibility",
"visibility/lib/src/main/scala/com/twitter/visibility/builder/tweets",
"visibility/lib/src/main/scala/com/twitter/visibility/builder/users",
"visibility/lib/src/main/scala/com/twitter/visibility/features",
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/tweets",
"visibility/lib/src/main/scala/com/twitter/visibility/util",
"visibility/lib/src/main/thrift/com/twitter/visibility/safety_label_store:safety-label-store-scala",
],
)

View File

@ -1,90 +0,0 @@
package com.twitter.visibility.builder.media
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.mediaservices.media_util.GenericMediaKey
import com.twitter.stitch.Stitch
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common.MediaSafetyLabelMapSource
import com.twitter.visibility.features.MediaSafetyLabels
import com.twitter.visibility.models.MediaSafetyLabel
import com.twitter.visibility.models.MediaSafetyLabelType
import com.twitter.visibility.models.SafetyLabel
class MediaFeatures(
mediaSafetyLabelMap: StratoMediaLabelMaps,
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("media_features")
private[this] val requests =
scopedStatsReceiver
.counter("requests")
private[this] val mediaSafetyLabelsStats =
scopedStatsReceiver
.scope(MediaSafetyLabels.name)
.counter("requests")
private[this] val nonEmptyMediaStats = scopedStatsReceiver.scope("non_empty_media")
private[this] val nonEmptyMediaRequests = nonEmptyMediaStats.counter("requests")
private[this] val nonEmptyMediaKeysCount = nonEmptyMediaStats.counter("keys")
private[this] val nonEmptyMediaKeysLength = nonEmptyMediaStats.stat("keys_length")
def forMediaKeys(
mediaKeys: Seq[GenericMediaKey],
): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
nonEmptyMediaKeysCount.incr(mediaKeys.size)
mediaSafetyLabelsStats.incr()
if (mediaKeys.nonEmpty) {
nonEmptyMediaRequests.incr()
nonEmptyMediaKeysLength.add(mediaKeys.size)
}
_.withFeature(MediaSafetyLabels, mediaSafetyLabelMap.forGenericMediaKeys(mediaKeys))
}
def forGenericMediaKey(
genericMediaKey: GenericMediaKey
): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
nonEmptyMediaKeysCount.incr()
mediaSafetyLabelsStats.incr()
nonEmptyMediaRequests.incr()
nonEmptyMediaKeysLength.add(1L)
_.withFeature(MediaSafetyLabels, mediaSafetyLabelMap.forGenericMediaKey(genericMediaKey))
}
}
class StratoMediaLabelMaps(source: MediaSafetyLabelMapSource) {
def forGenericMediaKeys(
mediaKeys: Seq[GenericMediaKey],
): Stitch[Seq[MediaSafetyLabel]] = {
Stitch
.collect(
mediaKeys
.map(getFilteredSafetyLabels)
).map(_.flatten)
}
def forGenericMediaKey(
genericMediaKey: GenericMediaKey
): Stitch[Seq[MediaSafetyLabel]] = {
getFilteredSafetyLabels(genericMediaKey)
}
private def getFilteredSafetyLabels(
genericMediaKey: GenericMediaKey,
): Stitch[Seq[MediaSafetyLabel]] =
source
.fetch(genericMediaKey).map(_.flatMap(_.labels.map { stratoSafetyLabelMap =>
stratoSafetyLabelMap
.map(label =>
MediaSafetyLabel(
MediaSafetyLabelType.fromThrift(label._1),
SafetyLabel.fromThrift(label._2)))
}).toSeq.flatten)
}

View File

@ -1,79 +0,0 @@
package com.twitter.visibility.builder.media
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.mediaservices.media_util.GenericMediaKey
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common.MediaMetadataSource
import com.twitter.visibility.features.HasDmcaMediaFeature
import com.twitter.visibility.features.MediaGeoRestrictionsAllowList
import com.twitter.visibility.features.MediaGeoRestrictionsDenyList
import com.twitter.visibility.features.AuthorId
class MediaMetadataFeatures(
mediaMetadataSource: MediaMetadataSource,
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("media_metadata_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val hasDmcaMedia =
scopedStatsReceiver.scope(HasDmcaMediaFeature.name).counter("requests")
private[this] val mediaGeoAllowList =
scopedStatsReceiver.scope(MediaGeoRestrictionsAllowList.name).counter("requests")
private[this] val mediaGeoDenyList =
scopedStatsReceiver.scope(MediaGeoRestrictionsDenyList.name).counter("requests")
private[this] val uploaderId =
scopedStatsReceiver.scope(AuthorId.name).counter("requests")
def forGenericMediaKey(
genericMediaKey: GenericMediaKey
): FeatureMapBuilder => FeatureMapBuilder = { featureMapBuilder =>
requests.incr()
featureMapBuilder.withFeature(
HasDmcaMediaFeature,
mediaIsDmca(genericMediaKey)
)
featureMapBuilder.withFeature(
MediaGeoRestrictionsAllowList,
geoRestrictionsAllowList(genericMediaKey)
)
featureMapBuilder.withFeature(
MediaGeoRestrictionsDenyList,
geoRestrictionsDenyList(genericMediaKey)
)
featureMapBuilder.withFeature(
AuthorId,
mediaUploaderId(genericMediaKey)
)
}
private def mediaIsDmca(genericMediaKey: GenericMediaKey) = {
hasDmcaMedia.incr()
mediaMetadataSource.getMediaIsDmca(genericMediaKey)
}
private def geoRestrictionsAllowList(genericMediaKey: GenericMediaKey) = {
mediaGeoAllowList.incr()
mediaMetadataSource.getGeoRestrictionsAllowList(genericMediaKey).map { allowListOpt =>
allowListOpt.getOrElse(Nil)
}
}
private def geoRestrictionsDenyList(genericMediaKey: GenericMediaKey) = {
mediaGeoDenyList.incr()
mediaMetadataSource.getGeoRestrictionsDenyList(genericMediaKey).map { denyListOpt =>
denyListOpt.getOrElse(Nil)
}
}
private def mediaUploaderId(genericMediaKey: GenericMediaKey) = {
uploaderId.incr()
mediaMetadataSource.getMediaUploaderId(genericMediaKey).map { uploaderIdOpt =>
uploaderIdOpt.map(Set(_)).getOrElse(Set.empty)
}
}
}

View File

@ -1,25 +0,0 @@
scala_library(
sources = ["*.scala"],
compiler_option_sets = ["fatal_warnings"],
platform = "java8",
strict_deps = True,
tags = ["bazel-compatible"],
dependencies = [
"3rdparty/jvm/com/google/guava",
"snowflake/src/main/scala/com/twitter/snowflake/id",
"src/thrift/com/twitter/context:twitter-context-scala",
"src/thrift/com/twitter/gizmoduck:user-thrift-scala",
"src/thrift/com/twitter/spam/rtf:safety-level-scala",
"src/thrift/com/twitter/spam/rtf:tweet-rtf-event-scala",
"stitch/stitch-core",
"twitter-context/src/main/scala",
"visibility/common/src/main/scala/com/twitter/visibility/common",
"visibility/lib/src/main/resources/config",
"visibility/lib/src/main/scala/com/twitter/visibility",
"visibility/lib/src/main/scala/com/twitter/visibility/builder/common",
"visibility/lib/src/main/scala/com/twitter/visibility/builder/users",
"visibility/lib/src/main/scala/com/twitter/visibility/features",
"visibility/lib/src/main/scala/com/twitter/visibility/util",
"visibility/lib/src/main/thrift/com/twitter/visibility/safety_label_store:safety-label-store-scala",
],
)

View File

@ -1,131 +0,0 @@
package com.twitter.visibility.builder.spaces
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.gizmoduck.thriftscala.Label
import com.twitter.gizmoduck.thriftscala.MuteSurface
import com.twitter.stitch.Stitch
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.builder.common.MutedKeywordFeatures
import com.twitter.visibility.builder.users.AuthorFeatures
import com.twitter.visibility.builder.users.RelationshipFeatures
import com.twitter.visibility.common.AudioSpaceSource
import com.twitter.visibility.common.SpaceId
import com.twitter.visibility.common.SpaceSafetyLabelMapSource
import com.twitter.visibility.common.UserId
import com.twitter.visibility.features._
import com.twitter.visibility.models.{MutedKeyword => VfMutedKeyword}
import com.twitter.visibility.models.SafetyLabel
import com.twitter.visibility.models.SpaceSafetyLabel
import com.twitter.visibility.models.SpaceSafetyLabelType
class SpaceFeatures(
spaceSafetyLabelMap: StratoSpaceLabelMaps,
authorFeatures: AuthorFeatures,
relationshipFeatures: RelationshipFeatures,
mutedKeywordFeatures: MutedKeywordFeatures,
audioSpaceSource: AudioSpaceSource) {
def forSpaceAndAuthorIds(
spaceId: SpaceId,
viewerId: Option[UserId],
authorIds: Option[Seq[UserId]]
): FeatureMapBuilder => FeatureMapBuilder = {
_.withFeature(SpaceSafetyLabels, spaceSafetyLabelMap.forSpaceId(spaceId))
.withFeature(AuthorId, getSpaceAuthors(spaceId, authorIds).map(_.toSet))
.withFeature(AuthorUserLabels, allSpaceAuthorLabels(spaceId, authorIds))
.withFeature(ViewerFollowsAuthor, viewerFollowsAnySpaceAuthor(spaceId, authorIds, viewerId))
.withFeature(ViewerMutesAuthor, viewerMutesAnySpaceAuthor(spaceId, authorIds, viewerId))
.withFeature(ViewerBlocksAuthor, viewerBlocksAnySpaceAuthor(spaceId, authorIds, viewerId))
.withFeature(AuthorBlocksViewer, anySpaceAuthorBlocksViewer(spaceId, authorIds, viewerId))
.withFeature(
ViewerMutesKeywordInSpaceTitleForNotifications,
titleContainsMutedKeyword(
audioSpaceSource.getSpaceTitle(spaceId),
audioSpaceSource.getSpaceLanguage(spaceId),
viewerId)
)
}
def titleContainsMutedKeyword(
titleOptStitch: Stitch[Option[String]],
languageOptStitch: Stitch[Option[String]],
viewerId: Option[UserId],
): Stitch[VfMutedKeyword] = {
titleOptStitch.flatMap {
case None => Stitch.value(VfMutedKeyword(None))
case Some(spaceTitle) =>
languageOptStitch.flatMap { languageOpt =>
mutedKeywordFeatures.spaceTitleContainsMutedKeyword(
spaceTitle,
languageOpt,
mutedKeywordFeatures.allMutedKeywords(viewerId),
MuteSurface.Notifications)
}
}
}
def getSpaceAuthors(
spaceId: SpaceId,
authorIdsFromRequest: Option[Seq[UserId]]
): Stitch[Seq[UserId]] = {
authorIdsFromRequest match {
case Some(authorIds) => Stitch.apply(authorIds)
case _ => audioSpaceSource.getAdminIds(spaceId)
}
}
def allSpaceAuthorLabels(
spaceId: SpaceId,
authorIdsFromRequest: Option[Seq[UserId]]
): Stitch[Seq[Label]] = {
getSpaceAuthors(spaceId, authorIdsFromRequest)
.flatMap(authorIds =>
Stitch.collect(authorIds.map(authorId => authorFeatures.authorUserLabels(authorId)))).map(
_.flatten)
}
def viewerMutesAnySpaceAuthor(
spaceId: SpaceId,
authorIdsFromRequest: Option[Seq[UserId]],
viewerId: Option[UserId]
): Stitch[Boolean] = {
getSpaceAuthors(spaceId, authorIdsFromRequest)
.flatMap(authorIds =>
Stitch.collect(authorIds.map(authorId =>
relationshipFeatures.viewerMutesAuthor(authorId, viewerId)))).map(_.contains(true))
}
def anySpaceAuthorBlocksViewer(
spaceId: SpaceId,
authorIdsFromRequest: Option[Seq[UserId]],
viewerId: Option[UserId]
): Stitch[Boolean] = {
getSpaceAuthors(spaceId, authorIdsFromRequest)
.flatMap(authorIds =>
Stitch.collect(authorIds.map(authorId =>
relationshipFeatures.authorBlocksViewer(authorId, viewerId)))).map(_.contains(true))
}
}
class StratoSpaceLabelMaps(
spaceSafetyLabelSource: SpaceSafetyLabelMapSource,
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("space_features")
private[this] val spaceSafetyLabelsStats =
scopedStatsReceiver.scope(SpaceSafetyLabels.name).counter("requests")
def forSpaceId(
spaceId: SpaceId,
): Stitch[Seq[SpaceSafetyLabel]] = {
spaceSafetyLabelSource
.fetch(spaceId).map(_.flatMap(_.labels.map { stratoSafetyLabelMap =>
stratoSafetyLabelMap
.map(label =>
SpaceSafetyLabel(
SpaceSafetyLabelType.fromThrift(label._1),
SafetyLabel.fromThrift(label._2)))
}).toSeq.flatten).ensure(spaceSafetyLabelsStats.incr)
}
}

View File

@ -1,38 +0,0 @@
scala_library(
sources = ["*.scala"],
compiler_option_sets = ["fatal_warnings"],
platform = "java8",
strict_deps = True,
tags = ["bazel-compatible"],
dependencies = [
"3rdparty/jvm/com/google/guava",
"communities/thrift/src/main/thrift/com/twitter/communities:thrift-scala",
"communities/thrift/src/main/thrift/com/twitter/communities/moderation:thrift-scala",
"communities/thrift/src/main/thrift/com/twitter/communities/visibility:thrift-scala",
"escherbird/src/thrift/com/twitter/escherbird/softintervention:softintervention_thrift-scala",
"mediaservices/media-util/src/main/scala",
"notificationservice/common/src/main/scala/com/twitter/notificationservice/model:alias",
"notificationservice/common/src/main/scala/com/twitter/notificationservice/model/notification",
"snowflake/src/main/scala/com/twitter/snowflake/id",
"src/thrift/com/twitter/context:twitter-context-scala",
"src/thrift/com/twitter/escherbird/common:common-scala",
"src/thrift/com/twitter/gizmoduck:user-thrift-scala",
"src/thrift/com/twitter/search/common:constants-scala",
"src/thrift/com/twitter/spam/rtf:safety-level-scala",
"src/thrift/com/twitter/spam/rtf:tweet-rtf-event-scala",
"src/thrift/com/twitter/tweetypie:tweet-scala",
"stitch/stitch-core",
# "tweetypie/src/scala/com/twitter/tweetypie/additionalfields",
"twitter-context/src/main/scala",
"visibility/common/src/main/scala/com/twitter/visibility/common",
"visibility/common/src/main/scala/com/twitter/visibility/common/stitch",
"visibility/lib/src/main/resources/config",
"visibility/lib/src/main/scala/com/twitter/visibility",
"visibility/lib/src/main/scala/com/twitter/visibility/builder/users",
"visibility/lib/src/main/scala/com/twitter/visibility/features",
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/blender",
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/search",
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/tweets",
"visibility/lib/src/main/thrift/com/twitter/visibility/strato:vf-strato-scala",
],
)

View File

@ -1,45 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.search.common.constants.thriftscala.ThriftQuerySource
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.features.SearchCandidateCount
import com.twitter.visibility.features.SearchQueryHasUser
import com.twitter.visibility.features.SearchQuerySource
import com.twitter.visibility.features.SearchResultsPageNumber
import com.twitter.visibility.interfaces.common.blender.BlenderVFRequestContext
@Deprecated
class BlenderContextFeatures(
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("blender_context_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val searchResultsPageNumber =
scopedStatsReceiver.scope(SearchResultsPageNumber.name).counter("requests")
private[this] val searchCandidateCount =
scopedStatsReceiver.scope(SearchCandidateCount.name).counter("requests")
private[this] val searchQuerySource =
scopedStatsReceiver.scope(SearchQuerySource.name).counter("requests")
private[this] val searchQueryHasUser =
scopedStatsReceiver.scope(SearchQueryHasUser.name).counter("requests")
def forBlenderContext(
blenderContext: BlenderVFRequestContext
): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
searchResultsPageNumber.incr()
searchCandidateCount.incr()
searchQuerySource.incr()
searchQueryHasUser.incr()
_.withConstantFeature(SearchResultsPageNumber, blenderContext.resultsPageNumber)
.withConstantFeature(SearchCandidateCount, blenderContext.candidateCount)
.withConstantFeature(
SearchQuerySource,
blenderContext.querySourceOption match {
case Some(querySource) => querySource
case _ => ThriftQuerySource.Unknown
})
.withConstantFeature(SearchQueryHasUser, blenderContext.queryHasUser)
}
}

View File

@ -1,64 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.notificationservice.model.notification.ActivityNotification
import com.twitter.notificationservice.model.notification.MentionNotification
import com.twitter.notificationservice.model.notification.MentionQuoteNotification
import com.twitter.notificationservice.model.notification.Notification
import com.twitter.notificationservice.model.notification.QuoteTweetNotification
import com.twitter.servo.util.Gate
import com.twitter.stitch.Stitch
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common.TweetSource
import com.twitter.visibility.features.NotificationIsOnCommunityTweet
import com.twitter.visibility.models.CommunityTweet
object CommunityNotificationFeatures {
def ForNonCommunityTweetNotification: FeatureMapBuilder => FeatureMapBuilder = {
_.withConstantFeature(NotificationIsOnCommunityTweet, false)
}
}
class CommunityNotificationFeatures(
tweetSource: TweetSource,
enableCommunityTweetHydration: Gate[Long],
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("community_notification_features")
private[this] val requestsCounter = scopedStatsReceiver.counter("requests")
private[this] val hydrationsCounter = scopedStatsReceiver.counter("hydrations")
private[this] val notificationIsOnCommunityTweetCounter =
scopedStatsReceiver.scope(NotificationIsOnCommunityTweet.name).counter("true")
private[this] val notificationIsNotOnCommunityTweetCounter =
scopedStatsReceiver.scope(NotificationIsOnCommunityTweet.name).counter("false")
def forNotification(notification: Notification): FeatureMapBuilder => FeatureMapBuilder = {
requestsCounter.incr()
val isCommunityTweetResult = getTweetIdOption(notification) match {
case Some(tweetId) if enableCommunityTweetHydration(notification.target) =>
hydrationsCounter.incr()
tweetSource
.getTweet(tweetId)
.map {
case Some(tweet) if CommunityTweet(tweet).nonEmpty =>
notificationIsOnCommunityTweetCounter.incr()
true
case _ =>
notificationIsNotOnCommunityTweetCounter.incr()
false
}
case _ => Stitch.False
}
_.withFeature(NotificationIsOnCommunityTweet, isCommunityTweetResult)
}
private[this] def getTweetIdOption(notification: Notification): Option[Long] = {
notification match {
case n: MentionNotification => Some(n.mentioningTweetId)
case n: MentionQuoteNotification => Some(n.mentioningTweetId)
case n: QuoteTweetNotification => Some(n.quotedTweetId)
case n: ActivityNotification[_] if n.visibilityTweets.contains(n.objectId) => Some(n.objectId)
case _ => None
}
}
}

View File

@ -1,70 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.tweetypie.thriftscala.Tweet
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.features.CommunityTweetAuthorIsRemoved
import com.twitter.visibility.features.CommunityTweetCommunityNotFound
import com.twitter.visibility.features.CommunityTweetCommunityDeleted
import com.twitter.visibility.features.CommunityTweetCommunitySuspended
import com.twitter.visibility.features.CommunityTweetCommunityVisible
import com.twitter.visibility.features.CommunityTweetIsHidden
import com.twitter.visibility.features.TweetIsCommunityTweet
import com.twitter.visibility.features.ViewerIsCommunityAdmin
import com.twitter.visibility.features.ViewerIsCommunityMember
import com.twitter.visibility.features.ViewerIsCommunityModerator
import com.twitter.visibility.features.ViewerIsInternalCommunitiesAdmin
import com.twitter.visibility.models.CommunityTweet
import com.twitter.visibility.models.ViewerContext
trait CommunityTweetFeatures {
def forTweet(
tweet: Tweet,
viewerContext: ViewerContext
): FeatureMapBuilder => FeatureMapBuilder
def forTweetOnly(tweet: Tweet): FeatureMapBuilder => FeatureMapBuilder = {
_.withConstantFeature(
TweetIsCommunityTweet,
CommunityTweet(tweet).isDefined
)
}
protected def forNonCommunityTweet(): FeatureMapBuilder => FeatureMapBuilder = { builder =>
builder
.withConstantFeature(
TweetIsCommunityTweet,
false
).withConstantFeature(
CommunityTweetCommunityNotFound,
false
).withConstantFeature(
CommunityTweetCommunitySuspended,
false
).withConstantFeature(
CommunityTweetCommunityDeleted,
false
).withConstantFeature(
CommunityTweetCommunityVisible,
false
).withConstantFeature(
ViewerIsInternalCommunitiesAdmin,
false
).withConstantFeature(
ViewerIsCommunityAdmin,
false
).withConstantFeature(
ViewerIsCommunityModerator,
false
).withConstantFeature(
ViewerIsCommunityMember,
false
).withConstantFeature(
CommunityTweetIsHidden,
false
).withConstantFeature(
CommunityTweetAuthorIsRemoved,
false
)
}
}

View File

@ -1,26 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.servo.util.Gate
import com.twitter.tweetypie.thriftscala.Tweet
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.models.ViewerContext
class CommunityTweetFeaturesPartitioned(
a: CommunityTweetFeatures,
b: CommunityTweetFeatures,
bEnabled: Gate[Unit],
) extends CommunityTweetFeatures {
override def forTweet(
tweet: Tweet,
viewerContext: ViewerContext
): FeatureMapBuilder => FeatureMapBuilder =
bEnabled.pick(
b.forTweet(tweet, viewerContext),
a.forTweet(tweet, viewerContext),
)
override def forTweetOnly(tweet: Tweet): FeatureMapBuilder => FeatureMapBuilder = bEnabled.pick(
b.forTweetOnly(tweet),
a.forTweetOnly(tweet),
)
}

View File

@ -1,129 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.communities.moderation.thriftscala.CommunityTweetModerationState
import com.twitter.communities.moderation.thriftscala.CommunityUserModerationState
import com.twitter.communities.visibility.thriftscala.CommunityVisibilityFeatures
import com.twitter.communities.visibility.thriftscala.CommunityVisibilityFeaturesV1
import com.twitter.communities.visibility.thriftscala.CommunityVisibilityResult
import com.twitter.stitch.Stitch
import com.twitter.tweetypie.thriftscala.Tweet
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common.CommunitiesSource
import com.twitter.visibility.features.CommunityTweetAuthorIsRemoved
import com.twitter.visibility.features.CommunityTweetCommunityNotFound
import com.twitter.visibility.features.CommunityTweetCommunityDeleted
import com.twitter.visibility.features.CommunityTweetCommunitySuspended
import com.twitter.visibility.features.CommunityTweetCommunityVisible
import com.twitter.visibility.features.CommunityTweetIsHidden
import com.twitter.visibility.features.TweetIsCommunityTweet
import com.twitter.visibility.features.ViewerIsCommunityAdmin
import com.twitter.visibility.features.ViewerIsCommunityMember
import com.twitter.visibility.features.ViewerIsCommunityModerator
import com.twitter.visibility.features.ViewerIsInternalCommunitiesAdmin
import com.twitter.visibility.models.CommunityTweet
import com.twitter.visibility.models.ViewerContext
class CommunityTweetFeaturesV2(communitiesSource: CommunitiesSource)
extends CommunityTweetFeatures {
private[this] def forCommunityTweet(
communityTweet: CommunityTweet
): FeatureMapBuilder => FeatureMapBuilder = { builder: FeatureMapBuilder =>
{
val communityVisibilityFeaturesStitch =
communitiesSource.getCommunityVisibilityFeatures(communityTweet.communityId)
val communityTweetModerationStateStitch =
communitiesSource.getTweetModerationState(communityTweet.tweet.id)
val communityTweetAuthorModerationStateStitch =
communitiesSource.getUserModerationState(
communityTweet.authorId,
communityTweet.communityId
)
def getFlagFromFeatures(f: CommunityVisibilityFeaturesV1 => Boolean): Stitch[Boolean] =
communityVisibilityFeaturesStitch.map {
case Some(CommunityVisibilityFeatures.V1(v1)) => f(v1)
case _ => false
}
def getFlagFromCommunityVisibilityResult(
f: CommunityVisibilityResult => Boolean
): Stitch[Boolean] = getFlagFromFeatures { v =>
f(v.communityVisibilityResult)
}
builder
.withConstantFeature(
TweetIsCommunityTweet,
true
)
.withFeature(
CommunityTweetCommunityNotFound,
getFlagFromCommunityVisibilityResult {
case CommunityVisibilityResult.NotFound => true
case _ => false
}
)
.withFeature(
CommunityTweetCommunitySuspended,
getFlagFromCommunityVisibilityResult {
case CommunityVisibilityResult.Suspended => true
case _ => false
}
)
.withFeature(
CommunityTweetCommunityDeleted,
getFlagFromCommunityVisibilityResult {
case CommunityVisibilityResult.Deleted => true
case _ => false
}
)
.withFeature(
CommunityTweetCommunityVisible,
getFlagFromCommunityVisibilityResult {
case CommunityVisibilityResult.Visible => true
case _ => false
}
)
.withFeature(
ViewerIsInternalCommunitiesAdmin,
getFlagFromFeatures { _.viewerIsInternalAdmin }
)
.withFeature(
ViewerIsCommunityAdmin,
getFlagFromFeatures { _.viewerIsCommunityAdmin }
)
.withFeature(
ViewerIsCommunityModerator,
getFlagFromFeatures { _.viewerIsCommunityModerator }
)
.withFeature(
ViewerIsCommunityMember,
getFlagFromFeatures { _.viewerIsCommunityMember }
)
.withFeature(
CommunityTweetIsHidden,
communityTweetModerationStateStitch.map {
case Some(CommunityTweetModerationState.Hidden(_)) => true
case _ => false
}
)
.withFeature(
CommunityTweetAuthorIsRemoved,
communityTweetAuthorModerationStateStitch.map {
case Some(CommunityUserModerationState.Removed(_)) => true
case _ => false
}
)
}
}
def forTweet(
tweet: Tweet,
viewerContext: ViewerContext
): FeatureMapBuilder => FeatureMapBuilder = {
CommunityTweet(tweet) match {
case None => forNonCommunityTweet()
case Some(communityTweet) => forCommunityTweet(communityTweet)
}
}
}

View File

@ -1,178 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.stitch.Stitch
import com.twitter.tweetypie.thriftscala.Tweet
import com.twitter.tweetypie.thriftscala.ConversationControl
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.builder.users.RelationshipFeatures
import com.twitter.visibility.common.InvitedToConversationRepo
import com.twitter.visibility.features.ConversationRootAuthorFollowsViewer
import com.twitter.visibility.features.TweetConversationViewerIsInvited
import com.twitter.visibility.features.TweetConversationViewerIsInvitedViaReplyMention
import com.twitter.visibility.features.TweetConversationViewerIsRootAuthor
import com.twitter.visibility.features.TweetHasByInvitationConversationControl
import com.twitter.visibility.features.TweetHasCommunityConversationControl
import com.twitter.visibility.features.TweetHasFollowersConversationControl
import com.twitter.visibility.features.ViewerFollowsConversationRootAuthor
class ConversationControlFeatures(
relationshipFeatures: RelationshipFeatures,
isInvitedToConversationRepository: InvitedToConversationRepo,
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("conversation_control_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val tweetCommunityConversationRequest =
scopedStatsReceiver.scope(TweetHasCommunityConversationControl.name).counter("requests")
private[this] val tweetByInvitationConversationRequest =
scopedStatsReceiver.scope(TweetHasByInvitationConversationControl.name).counter("requests")
private[this] val tweetFollowersConversationRequest =
scopedStatsReceiver.scope(TweetHasFollowersConversationControl.name).counter("requests")
private[this] val rootAuthorFollowsViewer =
scopedStatsReceiver.scope(ConversationRootAuthorFollowsViewer.name).counter("requests")
private[this] val viewerFollowsRootAuthor =
scopedStatsReceiver.scope(ViewerFollowsConversationRootAuthor.name).counter("requests")
def isCommunityConversation(conversationControl: Option[ConversationControl]): Boolean =
conversationControl
.collect {
case _: ConversationControl.Community =>
tweetCommunityConversationRequest.incr()
true
}.getOrElse(false)
def isByInvitationConversation(conversationControl: Option[ConversationControl]): Boolean =
conversationControl
.collect {
case _: ConversationControl.ByInvitation =>
tweetByInvitationConversationRequest.incr()
true
}.getOrElse(false)
def isFollowersConversation(conversationControl: Option[ConversationControl]): Boolean =
conversationControl
.collect {
case _: ConversationControl.Followers =>
tweetFollowersConversationRequest.incr()
true
}.getOrElse(false)
def conversationRootAuthorId(
conversationControl: Option[ConversationControl]
): Option[Long] =
conversationControl match {
case Some(ConversationControl.Community(community)) =>
Some(community.conversationTweetAuthorId)
case Some(ConversationControl.ByInvitation(byInvitation)) =>
Some(byInvitation.conversationTweetAuthorId)
case Some(ConversationControl.Followers(followers)) =>
Some(followers.conversationTweetAuthorId)
case _ => None
}
def viewerIsRootAuthor(
conversationControl: Option[ConversationControl],
viewerIdOpt: Option[Long]
): Boolean =
(conversationRootAuthorId(conversationControl), viewerIdOpt) match {
case (Some(rootAuthorId), Some(viewerId)) if rootAuthorId == viewerId => true
case _ => false
}
def viewerIsInvited(
conversationControl: Option[ConversationControl],
viewerId: Option[Long]
): Boolean = {
val invitedUserIds = conversationControl match {
case Some(ConversationControl.Community(community)) =>
community.invitedUserIds
case Some(ConversationControl.ByInvitation(byInvitation)) =>
byInvitation.invitedUserIds
case Some(ConversationControl.Followers(followers)) =>
followers.invitedUserIds
case _ => Seq()
}
viewerId.exists(invitedUserIds.contains(_))
}
def conversationAuthorFollows(
conversationControl: Option[ConversationControl],
viewerId: Option[Long]
): Stitch[Boolean] = {
val conversationAuthorId = conversationControl.collect {
case ConversationControl.Community(community) =>
community.conversationTweetAuthorId
}
conversationAuthorId match {
case Some(authorId) =>
rootAuthorFollowsViewer.incr()
relationshipFeatures.authorFollowsViewer(authorId, viewerId)
case None =>
Stitch.False
}
}
def followsConversationAuthor(
conversationControl: Option[ConversationControl],
viewerId: Option[Long]
): Stitch[Boolean] = {
val conversationAuthorId = conversationControl.collect {
case ConversationControl.Followers(followers) =>
followers.conversationTweetAuthorId
}
conversationAuthorId match {
case Some(authorId) =>
viewerFollowsRootAuthor.incr()
relationshipFeatures.viewerFollowsAuthor(authorId, viewerId)
case None =>
Stitch.False
}
}
def viewerIsInvitedViaReplyMention(
tweet: Tweet,
viewerIdOpt: Option[Long]
): Stitch[Boolean] = {
val conversationIdOpt: Option[Long] = tweet.conversationControl match {
case Some(ConversationControl.Community(community))
if community.inviteViaMention.contains(true) =>
tweet.coreData.flatMap(_.conversationId)
case Some(ConversationControl.ByInvitation(invitation))
if invitation.inviteViaMention.contains(true) =>
tweet.coreData.flatMap(_.conversationId)
case Some(ConversationControl.Followers(followers))
if followers.inviteViaMention.contains(true) =>
tweet.coreData.flatMap(_.conversationId)
case _ => None
}
(conversationIdOpt, viewerIdOpt) match {
case (Some(conversationId), Some(viewerId)) =>
isInvitedToConversationRepository(conversationId, viewerId)
case _ => Stitch.False
}
}
def forTweet(tweet: Tweet, viewerId: Option[Long]): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
val cc = tweet.conversationControl
_.withConstantFeature(TweetHasCommunityConversationControl, isCommunityConversation(cc))
.withConstantFeature(TweetHasByInvitationConversationControl, isByInvitationConversation(cc))
.withConstantFeature(TweetHasFollowersConversationControl, isFollowersConversation(cc))
.withConstantFeature(TweetConversationViewerIsRootAuthor, viewerIsRootAuthor(cc, viewerId))
.withConstantFeature(TweetConversationViewerIsInvited, viewerIsInvited(cc, viewerId))
.withFeature(ConversationRootAuthorFollowsViewer, conversationAuthorFollows(cc, viewerId))
.withFeature(ViewerFollowsConversationRootAuthor, followsConversationAuthor(cc, viewerId))
.withFeature(
TweetConversationViewerIsInvitedViaReplyMention,
viewerIsInvitedViaReplyMention(tweet, viewerId))
}
}

View File

@ -1,71 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.tweetypie.thriftscala.EditControl
import com.twitter.tweetypie.thriftscala.Tweet
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.features.TweetIsEditTweet
import com.twitter.visibility.features.TweetIsInitialTweet
import com.twitter.visibility.features.TweetIsLatestTweet
import com.twitter.visibility.features.TweetIsStaleTweet
class EditTweetFeatures(
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("edit_tweet_features")
private[this] val tweetIsEditTweet =
scopedStatsReceiver.scope(TweetIsEditTweet.name).counter("requests")
private[this] val tweetIsStaleTweet =
scopedStatsReceiver.scope(TweetIsStaleTweet.name).counter("requests")
private[this] val tweetIsLatestTweet =
scopedStatsReceiver.scope(TweetIsLatestTweet.name).counter("requests")
private[this] val tweetIsInitialTweet =
scopedStatsReceiver.scope(TweetIsInitialTweet.name).counter("requests")
def forTweet(
tweet: Tweet
): FeatureMapBuilder => FeatureMapBuilder = {
_.withConstantFeature(TweetIsEditTweet, tweetIsEditTweet(tweet))
.withConstantFeature(TweetIsStaleTweet, tweetIsStaleTweet(tweet))
.withConstantFeature(TweetIsLatestTweet, tweetIsLatestTweet(tweet))
.withConstantFeature(TweetIsInitialTweet, tweetIsInitialTweet(tweet))
}
def tweetIsStaleTweet(tweet: Tweet, incrementMetric: Boolean = true): Boolean = {
if (incrementMetric) tweetIsStaleTweet.incr()
tweet.editControl match {
case None => false
case Some(ec) =>
ec match {
case eci: EditControl.Initial => eci.initial.editTweetIds.last != tweet.id
case ece: EditControl.Edit =>
ece.edit.editControlInitial.exists(_.editTweetIds.last != tweet.id)
case _ => false
}
}
}
def tweetIsEditTweet(tweet: Tweet, incrementMetric: Boolean = true): Boolean = {
if (incrementMetric) tweetIsEditTweet.incr()
tweet.editControl match {
case None => false
case Some(ec) =>
ec match {
case _: EditControl.Initial => false
case _ => true
}
}
}
def tweetIsLatestTweet(tweet: Tweet): Boolean = {
tweetIsLatestTweet.incr()
!tweetIsStaleTweet(tweet = tweet, incrementMetric = false)
}
def tweetIsInitialTweet(tweet: Tweet): Boolean = {
tweetIsInitialTweet.incr()
!tweetIsEditTweet(tweet = tweet, incrementMetric = false)
}
}

View File

@ -1,65 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.stitch.Stitch
import com.twitter.tweetypie.thriftscala.Tweet
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.builder.users.ViewerVerbsAuthor
import com.twitter.visibility.common.UserRelationshipSource
import com.twitter.visibility.features.TweetIsExclusiveTweet
import com.twitter.visibility.features.ViewerIsExclusiveTweetRootAuthor
import com.twitter.visibility.features.ViewerSuperFollowsExclusiveTweetRootAuthor
import com.twitter.visibility.models.ViewerContext
class ExclusiveTweetFeatures(
userRelationshipSource: UserRelationshipSource,
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("exclusive_tweet_features")
private[this] val viewerSuperFollowsAuthor =
scopedStatsReceiver.scope(ViewerSuperFollowsExclusiveTweetRootAuthor.name).counter("requests")
def rootAuthorId(tweet: Tweet): Option[Long] =
tweet.exclusiveTweetControl.map(_.conversationAuthorId)
def viewerIsRootAuthor(
tweet: Tweet,
viewerIdOpt: Option[Long]
): Boolean =
(rootAuthorId(tweet), viewerIdOpt) match {
case (Some(rootAuthorId), Some(viewerId)) if rootAuthorId == viewerId => true
case _ => false
}
def viewerSuperFollowsRootAuthor(
tweet: Tweet,
viewerId: Option[Long]
): Stitch[Boolean] =
rootAuthorId(tweet) match {
case Some(authorId) =>
ViewerVerbsAuthor(
authorId,
viewerId,
userRelationshipSource.superFollows,
viewerSuperFollowsAuthor)
case None =>
Stitch.False
}
def forTweet(
tweet: Tweet,
viewerContext: ViewerContext
): FeatureMapBuilder => FeatureMapBuilder = {
val viewerId = viewerContext.userId
_.withConstantFeature(TweetIsExclusiveTweet, tweet.exclusiveTweetControl.isDefined)
.withConstantFeature(ViewerIsExclusiveTweetRootAuthor, viewerIsRootAuthor(tweet, viewerId))
.withFeature(
ViewerSuperFollowsExclusiveTweetRootAuthor,
viewerSuperFollowsRootAuthor(tweet, viewerId))
}
def forTweetOnly(tweet: Tweet): FeatureMapBuilder => FeatureMapBuilder = {
_.withConstantFeature(TweetIsExclusiveTweet, tweet.exclusiveTweetControl.isDefined)
}
}

View File

@ -1,81 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.stitch.Stitch
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.builder.users.ViewerVerbsAuthor
import com.twitter.visibility.common.UserId
import com.twitter.visibility.common.UserRelationshipSource
import com.twitter.visibility.features._
import com.twitter.visibility.models.TweetSafetyLabel
import com.twitter.visibility.models.ViolationLevel
class FosnrPefetchedLabelsRelationshipFeatures(
userRelationshipSource: UserRelationshipSource,
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver =
statsReceiver.scope("fonsr_prefetched_relationship_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val viewerFollowsAuthorOfViolatingTweet =
scopedStatsReceiver.scope(ViewerFollowsAuthorOfViolatingTweet.name).counter("requests")
private[this] val viewerDoesNotFollowAuthorOfViolatingTweet =
scopedStatsReceiver.scope(ViewerDoesNotFollowAuthorOfViolatingTweet.name).counter("requests")
def forNonFosnr(): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
_.withConstantFeature(ViewerFollowsAuthorOfViolatingTweet, false)
.withConstantFeature(ViewerDoesNotFollowAuthorOfViolatingTweet, false)
}
def forTweetWithSafetyLabelsAndAuthorId(
safetyLabels: Seq[TweetSafetyLabel],
authorId: Long,
viewerId: Option[Long]
): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
_.withFeature(
ViewerFollowsAuthorOfViolatingTweet,
viewerFollowsAuthorOfViolatingTweet(safetyLabels, authorId, viewerId))
.withFeature(
ViewerDoesNotFollowAuthorOfViolatingTweet,
viewerDoesNotFollowAuthorOfViolatingTweet(safetyLabels, authorId, viewerId))
}
def viewerFollowsAuthorOfViolatingTweet(
safetyLabels: Seq[TweetSafetyLabel],
authorId: UserId,
viewerId: Option[UserId]
): Stitch[Boolean] = {
if (safetyLabels
.map(ViolationLevel.fromTweetSafetyLabelOpt).collect {
case Some(level) => level
}.isEmpty) {
return Stitch.False
}
ViewerVerbsAuthor(
authorId,
viewerId,
userRelationshipSource.follows,
viewerFollowsAuthorOfViolatingTweet)
}
def viewerDoesNotFollowAuthorOfViolatingTweet(
safetyLabels: Seq[TweetSafetyLabel],
authorId: UserId,
viewerId: Option[UserId]
): Stitch[Boolean] = {
if (safetyLabels
.map(ViolationLevel.fromTweetSafetyLabelOpt).collect {
case Some(level) => level
}.isEmpty) {
return Stitch.False
}
ViewerVerbsAuthor(
authorId,
viewerId,
userRelationshipSource.follows,
viewerDoesNotFollowAuthorOfViolatingTweet).map(following => !following)
}
}

View File

@ -1,82 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.stitch.Stitch
import com.twitter.tweetypie.thriftscala.Tweet
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.builder.users.ViewerVerbsAuthor
import com.twitter.visibility.common.UserId
import com.twitter.visibility.common.UserRelationshipSource
import com.twitter.visibility.features._
import com.twitter.visibility.models.ViolationLevel
class FosnrRelationshipFeatures(
tweetLabels: TweetLabels,
userRelationshipSource: UserRelationshipSource,
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("fonsr_relationship_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val viewerFollowsAuthorOfViolatingTweet =
scopedStatsReceiver.scope(ViewerFollowsAuthorOfViolatingTweet.name).counter("requests")
private[this] val viewerDoesNotFollowAuthorOfViolatingTweet =
scopedStatsReceiver.scope(ViewerDoesNotFollowAuthorOfViolatingTweet.name).counter("requests")
def forTweetAndAuthorId(
tweet: Tweet,
authorId: Long,
viewerId: Option[Long]
): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
_.withFeature(
ViewerFollowsAuthorOfViolatingTweet,
viewerFollowsAuthorOfViolatingTweet(tweet, authorId, viewerId))
.withFeature(
ViewerDoesNotFollowAuthorOfViolatingTweet,
viewerDoesNotFollowAuthorOfViolatingTweet(tweet, authorId, viewerId))
}
def viewerFollowsAuthorOfViolatingTweet(
tweet: Tweet,
authorId: UserId,
viewerId: Option[UserId]
): Stitch[Boolean] =
tweetLabels.forTweet(tweet).flatMap { safetyLabels =>
if (safetyLabels
.map(ViolationLevel.fromTweetSafetyLabelOpt).collect {
case Some(level) => level
}.isEmpty) {
Stitch.False
} else {
ViewerVerbsAuthor(
authorId,
viewerId,
userRelationshipSource.follows,
viewerFollowsAuthorOfViolatingTweet)
}
}
def viewerDoesNotFollowAuthorOfViolatingTweet(
tweet: Tweet,
authorId: UserId,
viewerId: Option[UserId]
): Stitch[Boolean] =
tweetLabels.forTweet(tweet).flatMap { safetyLabels =>
if (safetyLabels
.map(ViolationLevel.fromTweetSafetyLabelOpt).collect {
case Some(level) => level
}.isEmpty) {
Stitch.False
} else {
ViewerVerbsAuthor(
authorId,
viewerId,
userRelationshipSource.follows,
viewerDoesNotFollowAuthorOfViolatingTweet).map(following => !following)
}
}
}

View File

@ -1,86 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.stitch.Stitch
import com.twitter.tweetypie.thriftscala.EscherbirdEntityAnnotations
import com.twitter.tweetypie.thriftscala.Tweet
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common.MisinformationPolicySource
import com.twitter.visibility.features._
import com.twitter.visibility.models.MisinformationPolicy
import com.twitter.visibility.models.SemanticCoreMisinformation
import com.twitter.visibility.models.ViewerContext
class MisinformationPolicyFeatures(
misinformationPolicySource: MisinformationPolicySource,
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver =
statsReceiver.scope("misinformation_policy_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val tweetMisinformationPolicies =
scopedStatsReceiver.scope(TweetMisinformationPolicies.name).counter("requests")
def forTweet(
tweet: Tweet,
viewerContext: ViewerContext
): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
tweetMisinformationPolicies.incr()
_.withFeature(
TweetMisinformationPolicies,
misinformationPolicy(tweet.escherbirdEntityAnnotations, viewerContext))
.withFeature(
TweetEnglishMisinformationPolicies,
misinformationPolicyEnglishOnly(tweet.escherbirdEntityAnnotations))
}
def misinformationPolicyEnglishOnly(
escherbirdEntityAnnotations: Option[EscherbirdEntityAnnotations],
): Stitch[Seq[MisinformationPolicy]] = {
val locale = Some(
MisinformationPolicySource.LanguageAndCountry(
language = Some("en"),
country = Some("us")
))
fetchMisinformationPolicy(escherbirdEntityAnnotations, locale)
}
def misinformationPolicy(
escherbirdEntityAnnotations: Option[EscherbirdEntityAnnotations],
viewerContext: ViewerContext
): Stitch[Seq[MisinformationPolicy]] = {
val locale = viewerContext.requestLanguageCode.map { language =>
MisinformationPolicySource.LanguageAndCountry(
language = Some(language),
country = viewerContext.requestCountryCode
)
}
fetchMisinformationPolicy(escherbirdEntityAnnotations, locale)
}
def fetchMisinformationPolicy(
escherbirdEntityAnnotations: Option[EscherbirdEntityAnnotations],
locale: Option[MisinformationPolicySource.LanguageAndCountry]
): Stitch[Seq[MisinformationPolicy]] = {
Stitch.collect(
escherbirdEntityAnnotations
.map(_.entityAnnotations)
.getOrElse(Seq.empty)
.filter(_.domainId == SemanticCoreMisinformation.domainId)
.map(annotation =>
misinformationPolicySource
.fetch(
annotation,
locale
)
.map(misinformation =>
MisinformationPolicy(
annotation = annotation,
misinformation = misinformation
)))
)
}
}

View File

@ -1,23 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.features.TweetIsModerated
class ModerationFeatures(moderationSource: Long => Boolean, statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver: StatsReceiver =
statsReceiver.scope("moderation_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val tweetIsModerated =
scopedStatsReceiver.scope(TweetIsModerated.name).counter("requests")
def forTweetId(tweetId: Long): FeatureMapBuilder => FeatureMapBuilder = { featureMapBuilder =>
requests.incr()
tweetIsModerated.incr()
featureMapBuilder.withConstantFeature(TweetIsModerated, moderationSource(tweetId))
}
}

View File

@ -1,44 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.search.common.constants.thriftscala.ThriftQuerySource
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.features.SearchCandidateCount
import com.twitter.visibility.features.SearchQueryHasUser
import com.twitter.visibility.features.SearchQuerySource
import com.twitter.visibility.features.SearchResultsPageNumber
import com.twitter.visibility.interfaces.common.search.SearchVFRequestContext
class SearchContextFeatures(
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("search_context_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val searchResultsPageNumber =
scopedStatsReceiver.scope(SearchResultsPageNumber.name).counter("requests")
private[this] val searchCandidateCount =
scopedStatsReceiver.scope(SearchCandidateCount.name).counter("requests")
private[this] val searchQuerySource =
scopedStatsReceiver.scope(SearchQuerySource.name).counter("requests")
private[this] val searchQueryHasUser =
scopedStatsReceiver.scope(SearchQueryHasUser.name).counter("requests")
def forSearchContext(
searchContext: SearchVFRequestContext
): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
searchResultsPageNumber.incr()
searchCandidateCount.incr()
searchQuerySource.incr()
searchQueryHasUser.incr()
_.withConstantFeature(SearchResultsPageNumber, searchContext.resultsPageNumber)
.withConstantFeature(SearchCandidateCount, searchContext.candidateCount)
.withConstantFeature(
SearchQuerySource,
searchContext.querySourceOption match {
case Some(querySource) => querySource
case _ => ThriftQuerySource.Unknown
})
.withConstantFeature(SearchQueryHasUser, searchContext.queryHasUser)
}
}

View File

@ -1,57 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.contenthealth.toxicreplyfilter.thriftscala.FilterState
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.tweetypie.thriftscala.Tweet
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.features.ToxicReplyFilterConversationAuthorIsViewer
import com.twitter.visibility.features.ToxicReplyFilterState
class ToxicReplyFilterFeature(
statsReceiver: StatsReceiver) {
def forTweet(tweet: Tweet, viewerId: Option[Long]): FeatureMapBuilder => FeatureMapBuilder = {
builder =>
requests.incr()
builder
.withConstantFeature(ToxicReplyFilterState, isTweetFilteredFromAuthor(tweet))
.withConstantFeature(
ToxicReplyFilterConversationAuthorIsViewer,
isRootAuthorViewer(tweet, viewerId))
}
private[this] def isRootAuthorViewer(tweet: Tweet, maybeViewerId: Option[Long]): Boolean = {
val maybeAuthorId = tweet.filteredReplyDetails.map(_.conversationAuthorId)
(maybeViewerId, maybeAuthorId) match {
case (Some(viewerId), Some(authorId)) if viewerId == authorId => {
rootAuthorViewerStats.incr()
true
}
case _ => false
}
}
private[this] def isTweetFilteredFromAuthor(
tweet: Tweet,
): FilterState = {
val result = tweet.filteredReplyDetails.map(_.filterState).getOrElse(FilterState.Unfiltered)
if (result == FilterState.FilteredFromAuthor) {
filteredFromAuthorStats.incr()
}
result
}
private[this] val scopedStatsReceiver =
statsReceiver.scope("toxicreplyfilter")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val rootAuthorViewerStats =
scopedStatsReceiver.scope(ToxicReplyFilterConversationAuthorIsViewer.name).counter("requests")
private[this] val filteredFromAuthorStats =
scopedStatsReceiver.scope(ToxicReplyFilterState.name).counter("requests")
}

View File

@ -1,57 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.stitch.Stitch
import com.twitter.tweetypie.thriftscala.Tweet
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common.TrustedFriendsListId
import com.twitter.visibility.common.TrustedFriendsSource
import com.twitter.visibility.features.TweetIsTrustedFriendTweet
import com.twitter.visibility.features.ViewerIsTrustedFriendOfTweetAuthor
import com.twitter.visibility.features.ViewerIsTrustedFriendTweetAuthor
class TrustedFriendsFeatures(trustedFriendsSource: TrustedFriendsSource) {
private[builder] def viewerIsTrustedFriend(
tweet: Tweet,
viewerId: Option[Long]
): Stitch[Boolean] =
(trustedFriendsListId(tweet), viewerId) match {
case (Some(tfListId), Some(userId)) =>
trustedFriendsSource.isTrustedFriend(tfListId, userId)
case _ => Stitch.False
}
private[builder] def viewerIsTrustedFriendListOwner(
tweet: Tweet,
viewerId: Option[Long]
): Stitch[Boolean] =
(trustedFriendsListId(tweet), viewerId) match {
case (Some(tfListId), Some(userId)) =>
trustedFriendsSource.isTrustedFriendListOwner(tfListId, userId)
case _ => Stitch.False
}
private[builder] def trustedFriendsListId(tweet: Tweet): Option[TrustedFriendsListId] =
tweet.trustedFriendsControl.map(_.trustedFriendsListId)
def forTweet(
tweet: Tweet,
viewerId: Option[Long]
): FeatureMapBuilder => FeatureMapBuilder = {
_.withConstantFeature(
TweetIsTrustedFriendTweet,
tweet.trustedFriendsControl.isDefined
).withFeature(
ViewerIsTrustedFriendTweetAuthor,
viewerIsTrustedFriendListOwner(tweet, viewerId)
).withFeature(
ViewerIsTrustedFriendOfTweetAuthor,
viewerIsTrustedFriend(tweet, viewerId)
)
}
def forTweetOnly(tweet: Tweet): FeatureMapBuilder => FeatureMapBuilder = {
_.withConstantFeature(TweetIsTrustedFriendTweet, tweet.trustedFriendsControl.isDefined)
}
}

View File

@ -1,210 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.snowflake.id.SnowflakeId
import com.twitter.stitch.Stitch
import com.twitter.tweetypie.thriftscala.CollabControl
import com.twitter.tweetypie.thriftscala.Tweet
import com.twitter.util.Duration
import com.twitter.util.Time
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common.SafetyLabelMapSource
import com.twitter.visibility.common.TweetId
import com.twitter.visibility.common.UserId
import com.twitter.visibility.features._
import com.twitter.visibility.models.SemanticCoreAnnotation
import com.twitter.visibility.models.TweetSafetyLabel
object TweetFeatures {
def FALLBACK_TIMESTAMP: Time = Time.epoch
def tweetIsSelfReply(tweet: Tweet): Boolean = {
tweet.coreData match {
case Some(coreData) =>
coreData.reply match {
case Some(reply) =>
reply.inReplyToUserId == coreData.userId
case None =>
false
}
case None =>
false
}
}
def tweetReplyToParentTweetDuration(tweet: Tweet): Option[Duration] = for {
coreData <- tweet.coreData
reply <- coreData.reply
inReplyToStatusId <- reply.inReplyToStatusId
replyTime <- SnowflakeId.timeFromIdOpt(tweet.id)
repliedToTime <- SnowflakeId.timeFromIdOpt(inReplyToStatusId)
} yield {
replyTime.diff(repliedToTime)
}
def tweetReplyToRootTweetDuration(tweet: Tweet): Option[Duration] = for {
coreData <- tweet.coreData
if coreData.reply.isDefined
conversationId <- coreData.conversationId
replyTime <- SnowflakeId.timeFromIdOpt(tweet.id)
rootTime <- SnowflakeId.timeFromIdOpt(conversationId)
} yield {
replyTime.diff(rootTime)
}
def tweetTimestamp(tweetId: Long): Time =
SnowflakeId.timeFromIdOpt(tweetId).getOrElse(FALLBACK_TIMESTAMP)
def tweetSemanticCoreAnnotations(tweet: Tweet): Seq[SemanticCoreAnnotation] = {
tweet.escherbirdEntityAnnotations
.map(a =>
a.entityAnnotations.map { annotation =>
SemanticCoreAnnotation(
annotation.groupId,
annotation.domainId,
annotation.entityId
)
}).toSeq.flatten
}
def tweetIsNullcast(tweet: Tweet): Boolean = {
tweet.coreData match {
case Some(coreData) =>
coreData.nullcast
case None =>
false
}
}
def tweetAuthorUserId(tweet: Tweet): Option[UserId] = {
tweet.coreData.map(_.userId)
}
}
sealed trait TweetLabels {
def forTweet(tweet: Tweet): Stitch[Seq[TweetSafetyLabel]]
def forTweetId(tweetId: TweetId): Stitch[Seq[TweetSafetyLabel]]
}
class StratoTweetLabelMaps(safetyLabelSource: SafetyLabelMapSource) extends TweetLabels {
override def forTweet(tweet: Tweet): Stitch[Seq[TweetSafetyLabel]] = {
forTweetId(tweet.id)
}
def forTweetId(tweetId: TweetId): Stitch[Seq[TweetSafetyLabel]] = {
safetyLabelSource
.fetch(tweetId).map(
_.map(
_.labels
.map(
_.map(sl => TweetSafetyLabel.fromTuple(sl._1, sl._2)).toSeq
).getOrElse(Seq())
).getOrElse(Seq()))
}
}
object NilTweetLabelMaps extends TweetLabels {
override def forTweet(tweet: Tweet): Stitch[Seq[TweetSafetyLabel]] = Stitch.Nil
override def forTweetId(tweetId: TweetId): Stitch[Seq[TweetSafetyLabel]] = Stitch.Nil
}
class TweetFeatures(tweetLabels: TweetLabels, statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("tweet_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val tweetSafetyLabels =
scopedStatsReceiver.scope(TweetSafetyLabels.name).counter("requests")
private[this] val tweetTakedownReasons =
scopedStatsReceiver.scope(TweetTakedownReasons.name).counter("requests")
private[this] val tweetIsSelfReply =
scopedStatsReceiver.scope(TweetIsSelfReply.name).counter("requests")
private[this] val tweetTimestamp =
scopedStatsReceiver.scope(TweetTimestamp.name).counter("requests")
private[this] val tweetReplyToParentTweetDuration =
scopedStatsReceiver.scope(TweetReplyToParentTweetDuration.name).counter("requests")
private[this] val tweetReplyToRootTweetDuration =
scopedStatsReceiver.scope(TweetReplyToRootTweetDuration.name).counter("requests")
private[this] val tweetSemanticCoreAnnotations =
scopedStatsReceiver.scope(TweetSemanticCoreAnnotations.name).counter("requests")
private[this] val tweetId =
scopedStatsReceiver.scope(TweetId.name).counter("requests")
private[this] val tweetHasNsfwUser =
scopedStatsReceiver.scope(TweetHasNsfwUser.name).counter("requests")
private[this] val tweetHasNsfwAdmin =
scopedStatsReceiver.scope(TweetHasNsfwAdmin.name).counter("requests")
private[this] val tweetIsNullcast =
scopedStatsReceiver.scope(TweetIsNullcast.name).counter("requests")
private[this] val tweetHasMedia =
scopedStatsReceiver.scope(TweetHasMedia.name).counter("requests")
private[this] val tweetIsCommunity =
scopedStatsReceiver.scope(TweetIsCommunityTweet.name).counter("requests")
private[this] val tweetIsCollabInvitation =
scopedStatsReceiver.scope(TweetIsCollabInvitationTweet.name).counter("requests")
def forTweet(tweet: Tweet): FeatureMapBuilder => FeatureMapBuilder = {
forTweetWithoutSafetyLabels(tweet)
.andThen(_.withFeature(TweetSafetyLabels, tweetLabels.forTweet(tweet)))
}
def forTweetWithoutSafetyLabels(tweet: Tweet): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
tweetTakedownReasons.incr()
tweetIsSelfReply.incr()
tweetTimestamp.incr()
tweetReplyToParentTweetDuration.incr()
tweetReplyToRootTweetDuration.incr()
tweetSemanticCoreAnnotations.incr()
tweetId.incr()
tweetHasNsfwUser.incr()
tweetHasNsfwAdmin.incr()
tweetIsNullcast.incr()
tweetHasMedia.incr()
tweetIsCommunity.incr()
tweetIsCollabInvitation.incr()
_.withConstantFeature(TweetTakedownReasons, tweet.takedownReasons.getOrElse(Seq.empty))
.withConstantFeature(TweetIsSelfReply, TweetFeatures.tweetIsSelfReply(tweet))
.withConstantFeature(TweetTimestamp, TweetFeatures.tweetTimestamp(tweet.id))
.withConstantFeature(
TweetReplyToParentTweetDuration,
TweetFeatures.tweetReplyToParentTweetDuration(tweet))
.withConstantFeature(
TweetReplyToRootTweetDuration,
TweetFeatures.tweetReplyToRootTweetDuration(tweet))
.withConstantFeature(
TweetSemanticCoreAnnotations,
TweetFeatures.tweetSemanticCoreAnnotations(tweet))
.withConstantFeature(TweetId, tweet.id)
.withConstantFeature(TweetHasNsfwUser, tweetHasNsfwUser(tweet))
.withConstantFeature(TweetHasNsfwAdmin, tweetHasNsfwAdmin(tweet))
.withConstantFeature(TweetIsNullcast, TweetFeatures.tweetIsNullcast(tweet))
.withConstantFeature(TweetHasMedia, tweetHasMedia(tweet))
.withConstantFeature(TweetIsCommunityTweet, tweetHasCommunity(tweet))
.withConstantFeature(TweetIsCollabInvitationTweet, tweetIsCollabInvitation(tweet))
}
def tweetHasNsfwUser(tweet: Tweet): Boolean =
tweet.coreData.exists(_.nsfwUser)
def tweetHasNsfwAdmin(tweet: Tweet): Boolean =
tweet.coreData.exists(_.nsfwAdmin)
def tweetHasMedia(tweet: Tweet): Boolean =
tweet.coreData.exists(_.hasMedia.getOrElse(false))
def tweetHasCommunity(tweet: Tweet): Boolean = {
tweet.communities.exists(_.communityIds.nonEmpty)
}
def tweetIsCollabInvitation(tweet: Tweet): Boolean = {
tweet.collabControl.exists(_ match {
case CollabControl.CollabInvitation(_) => true
case _ => false
})
}
}

View File

@ -1,76 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.servo.util.Gate
import com.twitter.spam.rtf.thriftscala.SafetyLabel
import com.twitter.spam.rtf.thriftscala.SafetyLabelType
import com.twitter.spam.rtf.thriftscala.SafetyLabelValue
import com.twitter.stitch.Stitch
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common.stitch.StitchHelpers
import com.twitter.visibility.features.TweetId
import com.twitter.visibility.features.TweetSafetyLabels
import com.twitter.visibility.features.TweetTimestamp
import com.twitter.visibility.models.TweetSafetyLabel
class TweetIdFeatures(
statsReceiver: StatsReceiver,
enableStitchProfiling: Gate[Unit]) {
private[this] val scopedStatsReceiver: StatsReceiver = statsReceiver.scope("tweet_id_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val tweetSafetyLabels =
scopedStatsReceiver.scope(TweetSafetyLabels.name).counter("requests")
private[this] val tweetTimestamp =
scopedStatsReceiver.scope(TweetTimestamp.name).counter("requests")
private[this] val labelFetchScope: StatsReceiver =
scopedStatsReceiver.scope("labelFetch")
private[this] def getTweetLabels(
tweetId: Long,
labelFetcher: Long => Stitch[Map[SafetyLabelType, SafetyLabel]]
): Stitch[Seq[TweetSafetyLabel]] = {
val stitch =
labelFetcher(tweetId).map { labelMap =>
labelMap
.map { case (labelType, label) => SafetyLabelValue(labelType, label) }.toSeq
.map(TweetSafetyLabel.fromThrift)
}
if (enableStitchProfiling()) {
StitchHelpers.profileStitch(
stitch,
Seq(labelFetchScope)
)
} else {
stitch
}
}
def forTweetId(
tweetId: Long,
labelFetcher: Long => Stitch[Map[SafetyLabelType, SafetyLabel]]
): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
tweetSafetyLabels.incr()
tweetTimestamp.incr()
_.withFeature(TweetSafetyLabels, getTweetLabels(tweetId, labelFetcher))
.withConstantFeature(TweetTimestamp, TweetFeatures.tweetTimestamp(tweetId))
.withConstantFeature(TweetId, tweetId)
}
def forTweetId(
tweetId: Long,
constantTweetSafetyLabels: Seq[TweetSafetyLabel]
): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
tweetSafetyLabels.incr()
tweetTimestamp.incr()
_.withConstantFeature(TweetSafetyLabels, constantTweetSafetyLabels)
.withConstantFeature(TweetTimestamp, TweetFeatures.tweetTimestamp(tweetId))
.withConstantFeature(TweetId, tweetId)
}
}

View File

@ -1,130 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.mediaservices.commons.mediainformation.thriftscala.AdditionalMetadata
import com.twitter.mediaservices.media_util.GenericMediaKey
import com.twitter.stitch.Stitch
import com.twitter.tweetypie.thriftscala.Tweet
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common.TweetMediaMetadataSource
import com.twitter.visibility.features.HasDmcaMediaFeature
import com.twitter.visibility.features.MediaGeoRestrictionsAllowList
import com.twitter.visibility.features.MediaGeoRestrictionsDenyList
class TweetMediaMetadataFeatures(
mediaMetadataSource: TweetMediaMetadataSource,
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("tweet_media_metadata_features")
private[this] val reportedStats = scopedStatsReceiver.scope("dmcaStats")
def forTweet(
tweet: Tweet,
mediaKeys: Seq[GenericMediaKey],
enableFetchMediaMetadata: Boolean
): FeatureMapBuilder => FeatureMapBuilder = { featureMapBuilder =>
featureMapBuilder.withFeature(
HasDmcaMediaFeature,
mediaIsDmca(tweet, mediaKeys, enableFetchMediaMetadata))
featureMapBuilder.withFeature(
MediaGeoRestrictionsAllowList,
allowlist(tweet, mediaKeys, enableFetchMediaMetadata))
featureMapBuilder.withFeature(
MediaGeoRestrictionsDenyList,
denylist(tweet, mediaKeys, enableFetchMediaMetadata))
}
private def mediaIsDmca(
tweet: Tweet,
mediaKeys: Seq[GenericMediaKey],
enableFetchMediaMetadata: Boolean
) = getMediaAdditionalMetadata(tweet, mediaKeys, enableFetchMediaMetadata)
.map(_.exists(_.restrictions.exists(_.isDmca)))
private def allowlist(
tweet: Tweet,
mediaKeys: Seq[GenericMediaKey],
enableFetchMediaMetadata: Boolean
) = getMediaGeoRestrictions(tweet, mediaKeys, enableFetchMediaMetadata)
.map(_.flatMap(_.whitelistedCountryCodes))
private def denylist(
tweet: Tweet,
mediaKeys: Seq[GenericMediaKey],
enableFetchMediaMetadata: Boolean
) = getMediaGeoRestrictions(tweet, mediaKeys, enableFetchMediaMetadata)
.map(_.flatMap(_.blacklistedCountryCodes))
private def getMediaGeoRestrictions(
tweet: Tweet,
mediaKeys: Seq[GenericMediaKey],
enableFetchMediaMetadata: Boolean
) = {
getMediaAdditionalMetadata(tweet, mediaKeys, enableFetchMediaMetadata)
.map(additionalMetadatasSeq => {
for {
additionalMetadata <- additionalMetadatasSeq
restrictions <- additionalMetadata.restrictions
geoRestrictions <- restrictions.geoRestrictions
} yield {
geoRestrictions
}
})
}
private def getMediaAdditionalMetadata(
tweet: Tweet,
mediaKeys: Seq[GenericMediaKey],
enableFetchMediaMetadata: Boolean
): Stitch[Seq[AdditionalMetadata]] = {
if (mediaKeys.isEmpty) {
reportedStats.counter("empty").incr()
Stitch.value(Seq.empty)
} else {
tweet.media.flatMap { mediaEntities =>
val alreadyHydratedMetadata = mediaEntities
.filter(_.mediaKey.isDefined)
.flatMap(_.additionalMetadata)
if (alreadyHydratedMetadata.nonEmpty) {
Some(alreadyHydratedMetadata)
} else {
None
}
} match {
case Some(additionalMetadata) =>
reportedStats.counter("already_hydrated").incr()
Stitch.value(additionalMetadata)
case None =>
Stitch
.collect(
mediaKeys.map(fetchAdditionalMetadata(tweet.id, _, enableFetchMediaMetadata))
).map(maybeMetadatas => {
maybeMetadatas
.filter(_.isDefined)
.map(_.get)
})
}
}
}
private def fetchAdditionalMetadata(
tweetId: Long,
genericMediaKey: GenericMediaKey,
enableFetchMediaMetadata: Boolean
): Stitch[Option[AdditionalMetadata]] =
if (enableFetchMediaMetadata) {
genericMediaKey.toThriftMediaKey() match {
case Some(mediaKey) =>
reportedStats.counter("request").incr()
mediaMetadataSource.fetch(tweetId, mediaKey)
case None =>
reportedStats.counter("empty_key").incr()
Stitch.None
}
} else {
reportedStats.counter("light_request").incr()
Stitch.None
}
}

View File

@ -1,54 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.stitch.Stitch
import com.twitter.tweetypie.thriftscala.Tweet
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common.TweetPerspectiveSource
import com.twitter.visibility.features.ViewerReportedTweet
class TweetPerspectiveFeatures(
tweetPerspectiveSource: TweetPerspectiveSource,
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("tweet_perspective_features")
private[this] val reportedStats = scopedStatsReceiver.scope("reported")
def forTweet(
tweet: Tweet,
viewerId: Option[Long],
enableFetchReportedPerspective: Boolean
): FeatureMapBuilder => FeatureMapBuilder =
_.withFeature(
ViewerReportedTweet,
tweetIsReported(tweet, viewerId, enableFetchReportedPerspective))
private[builder] def tweetIsReported(
tweet: Tweet,
viewerId: Option[Long],
enableFetchReportedPerspective: Boolean = true
): Stitch[Boolean] = {
((tweet.perspective, viewerId) match {
case (Some(perspective), _) =>
Stitch.value(perspective.reported).onSuccess { _ =>
reportedStats.counter("already_hydrated").incr()
}
case (None, Some(viewerId)) =>
if (enableFetchReportedPerspective) {
tweetPerspectiveSource.reported(tweet.id, viewerId).onSuccess { _ =>
reportedStats.counter("request").incr()
}
} else {
Stitch.False.onSuccess { _ =>
reportedStats.counter("light_request").incr()
}
}
case _ =>
Stitch.False.onSuccess { _ =>
reportedStats.counter("empty").incr()
}
}).onSuccess { _ =>
reportedStats.counter("").incr()
}
}
}

View File

@ -1,39 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.spam.rtf.thriftscala.SafetyLabelType
import com.twitter.spam.rtf.thriftscala.SafetyLabelType.ExperimentalNudge
import com.twitter.spam.rtf.thriftscala.SafetyLabelType.SemanticCoreMisinformation
import com.twitter.spam.rtf.thriftscala.SafetyLabelType.UnsafeUrl
import com.twitter.visibility.common.LocalizedNudgeSource
import com.twitter.visibility.common.actions.TweetVisibilityNudgeReason
import com.twitter.visibility.common.actions.TweetVisibilityNudgeReason.ExperimentalNudgeSafetyLabelReason
import com.twitter.visibility.common.actions.TweetVisibilityNudgeReason.SemanticCoreMisinformationLabelReason
import com.twitter.visibility.common.actions.TweetVisibilityNudgeReason.UnsafeURLLabelReason
import com.twitter.visibility.rules.LocalizedNudge
class TweetVisibilityNudgeSourceWrapper(localizedNudgeSource: LocalizedNudgeSource) {
def getLocalizedNudge(
reason: TweetVisibilityNudgeReason,
languageCode: String,
countryCode: Option[String]
): Option[LocalizedNudge] =
reason match {
case ExperimentalNudgeSafetyLabelReason =>
fetchNudge(ExperimentalNudge, languageCode, countryCode)
case SemanticCoreMisinformationLabelReason =>
fetchNudge(SemanticCoreMisinformation, languageCode, countryCode)
case UnsafeURLLabelReason =>
fetchNudge(UnsafeUrl, languageCode, countryCode)
}
private def fetchNudge(
safetyLabel: SafetyLabelType,
languageCode: String,
countryCode: Option[String]
): Option[LocalizedNudge] = {
localizedNudgeSource
.fetch(safetyLabel, languageCode, countryCode)
.map(LocalizedNudge.fromStratoThrift)
}
}

View File

@ -1,75 +0,0 @@
package com.twitter.visibility.builder.tweets
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.notificationservice.model.notification._
import com.twitter.servo.util.Gate
import com.twitter.stitch.Stitch
import com.twitter.tweetypie.thriftscala.SettingsUnmentions
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common.TweetSource
import com.twitter.visibility.features.NotificationIsOnUnmentionedViewer
object UnmentionNotificationFeatures {
def ForNonUnmentionNotificationFeatures: FeatureMapBuilder => FeatureMapBuilder = {
_.withConstantFeature(NotificationIsOnUnmentionedViewer, false)
}
}
class UnmentionNotificationFeatures(
tweetSource: TweetSource,
enableUnmentionHydration: Gate[Long],
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver =
statsReceiver.scope("unmention_notification_features")
private[this] val requestsCounter = scopedStatsReceiver.counter("requests")
private[this] val hydrationsCounter = scopedStatsReceiver.counter("hydrations")
private[this] val notificationsUnmentionUserCounter =
scopedStatsReceiver
.scope(NotificationIsOnUnmentionedViewer.name).counter("unmentioned_users")
def forNotification(notification: Notification): FeatureMapBuilder => FeatureMapBuilder = {
requestsCounter.incr()
val isUnmentionNotification = tweetId(notification) match {
case Some(tweetId) if enableUnmentionHydration(notification.target) =>
hydrationsCounter.incr()
tweetSource
.getTweet(tweetId)
.map {
case Some(tweet) =>
tweet.settingsUnmentions match {
case Some(SettingsUnmentions(Some(unmentionedUserIds))) =>
if (unmentionedUserIds.contains(notification.target)) {
notificationsUnmentionUserCounter.incr()
true
} else {
false
}
case _ => false
}
case _ => false
}
case _ => Stitch.False
}
_.withFeature(NotificationIsOnUnmentionedViewer, isUnmentionNotification)
}
private[this] def tweetId(notification: Notification): Option[Long] = {
notification match {
case n: MentionNotification => Some(n.mentioningTweetId)
case n: FavoritedMentioningTweetNotification => Some(n.mentioningTweetId)
case n: FavoritedReplyToYourTweetNotification => Some(n.replyTweetId)
case n: MentionQuoteNotification => Some(n.mentioningTweetId)
case n: ReactionMentioningTweetNotification => Some(n.mentioningTweetId)
case n: ReplyNotification => Some(n.replyingTweetId)
case n: RetweetedMentionNotification => Some(n.mentioningTweetId)
case n: RetweetedReplyToYourTweetNotification => Some(n.replyTweetId)
case n: ReplyToConversationNotification => Some(n.replyingTweetId)
case n: ReactionReplyToYourTweetNotification => Some(n.replyTweetId)
case _ => None
}
}
}

View File

@ -1,39 +0,0 @@
package com.twitter.visibility.builder.users
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.gizmoduck.thriftscala.User
import com.twitter.stitch.Stitch
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common.UserDeviceSource
import com.twitter.visibility.features.AuthorHasConfirmedEmail
import com.twitter.visibility.features.AuthorHasVerifiedPhone
class AuthorDeviceFeatures(userDeviceSource: UserDeviceSource, statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("author_device_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val authorHasConfirmedEmailRequests =
scopedStatsReceiver.scope(AuthorHasConfirmedEmail.name).counter("requests")
private[this] val authorHasVerifiedPhoneRequests =
scopedStatsReceiver.scope(AuthorHasVerifiedPhone.name).counter("requests")
def forAuthor(author: User): FeatureMapBuilder => FeatureMapBuilder = forAuthorId(author.id)
def forAuthorId(authorId: Long): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
_.withFeature(AuthorHasConfirmedEmail, authorHasConfirmedEmail(authorId))
.withFeature(AuthorHasVerifiedPhone, authorHasVerifiedPhone(authorId))
}
def authorHasConfirmedEmail(authorId: Long): Stitch[Boolean] = {
authorHasConfirmedEmailRequests.incr()
userDeviceSource.hasConfirmedEmail(authorId)
}
def authorHasVerifiedPhone(authorId: Long): Stitch[Boolean] = {
authorHasVerifiedPhoneRequests.incr()
userDeviceSource.hasConfirmedPhone(authorId)
}
}

View File

@ -1,221 +0,0 @@
package com.twitter.visibility.builder.users
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.gizmoduck.thriftscala.Label
import com.twitter.gizmoduck.thriftscala.Labels
import com.twitter.gizmoduck.thriftscala.Profile
import com.twitter.gizmoduck.thriftscala.Safety
import com.twitter.gizmoduck.thriftscala.User
import com.twitter.stitch.NotFound
import com.twitter.stitch.Stitch
import com.twitter.tseng.withholding.thriftscala.TakedownReason
import com.twitter.util.Duration
import com.twitter.util.Time
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common.UserSource
import com.twitter.visibility.features._
class AuthorFeatures(userSource: UserSource, statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("author_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val authorUserLabels =
scopedStatsReceiver.scope(AuthorUserLabels.name).counter("requests")
private[this] val authorIsSuspended =
scopedStatsReceiver.scope(AuthorIsSuspended.name).counter("requests")
private[this] val authorIsProtected =
scopedStatsReceiver.scope(AuthorIsProtected.name).counter("requests")
private[this] val authorIsDeactivated =
scopedStatsReceiver.scope(AuthorIsDeactivated.name).counter("requests")
private[this] val authorIsErased =
scopedStatsReceiver.scope(AuthorIsErased.name).counter("requests")
private[this] val authorIsOffboarded =
scopedStatsReceiver.scope(AuthorIsOffboarded.name).counter("requests")
private[this] val authorIsNsfwUser =
scopedStatsReceiver.scope(AuthorIsNsfwUser.name).counter("requests")
private[this] val authorIsNsfwAdmin =
scopedStatsReceiver.scope(AuthorIsNsfwAdmin.name).counter("requests")
private[this] val authorTakedownReasons =
scopedStatsReceiver.scope(AuthorTakedownReasons.name).counter("requests")
private[this] val authorHasDefaultProfileImage =
scopedStatsReceiver.scope(AuthorHasDefaultProfileImage.name).counter("requests")
private[this] val authorAccountAge =
scopedStatsReceiver.scope(AuthorAccountAge.name).counter("requests")
private[this] val authorIsVerified =
scopedStatsReceiver.scope(AuthorIsVerified.name).counter("requests")
private[this] val authorScreenName =
scopedStatsReceiver.scope(AuthorScreenName.name).counter("requests")
private[this] val authorIsBlueVerified =
scopedStatsReceiver.scope(AuthorIsBlueVerified.name).counter("requests")
def forAuthor(author: User): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
_.withConstantFeature(AuthorId, Set(author.id))
.withConstantFeature(AuthorUserLabels, authorUserLabels(author))
.withConstantFeature(AuthorIsProtected, authorIsProtected(author))
.withConstantFeature(AuthorIsSuspended, authorIsSuspended(author))
.withConstantFeature(AuthorIsDeactivated, authorIsDeactivated(author))
.withConstantFeature(AuthorIsErased, authorIsErased(author))
.withConstantFeature(AuthorIsOffboarded, authorIsOffboarded(author))
.withConstantFeature(AuthorTakedownReasons, authorTakedownReasons(author))
.withConstantFeature(AuthorHasDefaultProfileImage, authorHasDefaultProfileImage(author))
.withConstantFeature(AuthorAccountAge, authorAccountAge(author))
.withConstantFeature(AuthorIsNsfwUser, authorIsNsfwUser(author))
.withConstantFeature(AuthorIsNsfwAdmin, authorIsNsfwAdmin(author))
.withConstantFeature(AuthorIsVerified, authorIsVerified(author))
.withConstantFeature(AuthorScreenName, authorScreenName(author))
.withConstantFeature(AuthorIsBlueVerified, authorIsBlueVerified(author))
}
def forAuthorNoDefaults(author: User): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
_.withConstantFeature(AuthorId, Set(author.id))
.withConstantFeature(AuthorUserLabels, authorUserLabelsOpt(author))
.withConstantFeature(AuthorIsProtected, authorIsProtectedOpt(author))
.withConstantFeature(AuthorIsSuspended, authorIsSuspendedOpt(author))
.withConstantFeature(AuthorIsDeactivated, authorIsDeactivatedOpt(author))
.withConstantFeature(AuthorIsErased, authorIsErasedOpt(author))
.withConstantFeature(AuthorIsOffboarded, authorIsOffboarded(author))
.withConstantFeature(AuthorTakedownReasons, authorTakedownReasons(author))
.withConstantFeature(AuthorHasDefaultProfileImage, authorHasDefaultProfileImage(author))
.withConstantFeature(AuthorAccountAge, authorAccountAge(author))
.withConstantFeature(AuthorIsNsfwUser, authorIsNsfwUserOpt(author))
.withConstantFeature(AuthorIsNsfwAdmin, authorIsNsfwAdminOpt(author))
.withConstantFeature(AuthorIsVerified, authorIsVerifiedOpt(author))
.withConstantFeature(AuthorScreenName, authorScreenName(author))
.withConstantFeature(AuthorIsBlueVerified, authorIsBlueVerified(author))
}
def forAuthorId(authorId: Long): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
_.withConstantFeature(AuthorId, Set(authorId))
.withFeature(AuthorUserLabels, authorUserLabels(authorId))
.withFeature(AuthorIsProtected, authorIsProtected(authorId))
.withFeature(AuthorIsSuspended, authorIsSuspended(authorId))
.withFeature(AuthorIsDeactivated, authorIsDeactivated(authorId))
.withFeature(AuthorIsErased, authorIsErased(authorId))
.withFeature(AuthorIsOffboarded, authorIsOffboarded(authorId))
.withFeature(AuthorTakedownReasons, authorTakedownReasons(authorId))
.withFeature(AuthorHasDefaultProfileImage, authorHasDefaultProfileImage(authorId))
.withFeature(AuthorAccountAge, authorAccountAge(authorId))
.withFeature(AuthorIsNsfwUser, authorIsNsfwUser(authorId))
.withFeature(AuthorIsNsfwAdmin, authorIsNsfwAdmin(authorId))
.withFeature(AuthorIsVerified, authorIsVerified(authorId))
.withFeature(AuthorScreenName, authorScreenName(authorId))
.withFeature(AuthorIsBlueVerified, authorIsBlueVerified(authorId))
}
def forNoAuthor(): FeatureMapBuilder => FeatureMapBuilder = {
_.withConstantFeature(AuthorId, Set.empty[Long])
.withConstantFeature(AuthorUserLabels, Seq.empty)
.withConstantFeature(AuthorIsProtected, false)
.withConstantFeature(AuthorIsSuspended, false)
.withConstantFeature(AuthorIsDeactivated, false)
.withConstantFeature(AuthorIsErased, false)
.withConstantFeature(AuthorIsOffboarded, false)
.withConstantFeature(AuthorTakedownReasons, Seq.empty)
.withConstantFeature(AuthorHasDefaultProfileImage, false)
.withConstantFeature(AuthorAccountAge, Duration.Zero)
.withConstantFeature(AuthorIsNsfwUser, false)
.withConstantFeature(AuthorIsNsfwAdmin, false)
.withConstantFeature(AuthorIsVerified, false)
.withConstantFeature(AuthorIsBlueVerified, false)
}
def authorUserLabels(author: User): Seq[Label] =
authorUserLabels(author.labels)
def authorIsSuspended(authorId: Long): Stitch[Boolean] =
userSource.getSafety(authorId).map(safety => authorIsSuspended(Some(safety)))
def authorIsSuspendedOpt(author: User): Option[Boolean] = {
authorIsSuspended.incr()
author.safety.map(_.suspended)
}
private def authorIsSuspended(safety: Option[Safety]): Boolean = {
authorIsSuspended.incr()
safety.exists(_.suspended)
}
def authorIsProtected(author: User): Boolean =
authorIsProtected(author.safety)
def authorIsDeactivated(authorId: Long): Stitch[Boolean] =
userSource.getSafety(authorId).map(safety => authorIsDeactivated(Some(safety)))
def authorIsDeactivatedOpt(author: User): Option[Boolean] = {
authorIsDeactivated.incr()
author.safety.map(_.deactivated)
}
private def authorIsDeactivated(safety: Option[Safety]): Boolean = {
authorIsDeactivated.incr()
safety.exists(_.deactivated)
}
def authorIsErased(author: User): Boolean = {
authorIsErased.incr()
author.safety.exists(_.erased)
}
def authorIsOffboarded(authorId: Long): Stitch[Boolean] = {
userSource.getSafety(authorId).map(safety => authorIsOffboarded(Some(safety)))
}
def authorIsNsfwUser(author: User): Boolean = {
authorIsNsfwUser(author.safety)
}
def authorIsNsfwUser(authorId: Long): Stitch[Boolean] = {
userSource.getSafety(authorId).map(safety => authorIsNsfwUser(Some(safety)))
}
def authorIsNsfwUser(safety: Option[Safety]): Boolean = {
authorIsNsfwUser.incr()
safety.exists(_.nsfwUser)
}
def authorIsNsfwAdminOpt(author: User): Option[Boolean] = {
authorIsNsfwAdmin.incr()
author.safety.map(_.nsfwAdmin)
}
def authorTakedownReasons(authorId: Long): Stitch[Seq[TakedownReason]] = {
authorTakedownReasons.incr()
userSource.getTakedownReasons(authorId)
}
def authorHasDefaultProfileImage(authorId: Long): Stitch[Boolean] =
userSource.getProfile(authorId).map(profile => authorHasDefaultProfileImage(Some(profile)))
def authorAccountAge(authorId: Long): Stitch[Duration] =
userSource.getCreatedAtMsec(authorId).map(authorAccountAgeFromTimestamp)
def authorIsVerified(authorId: Long): Stitch[Boolean] =
userSource.getSafety(authorId).map(safety => authorIsVerified(Some(safety)))
def authorIsVerifiedOpt(author: User): Option[Boolean] = {
authorIsVerified.incr()
author.safety.map(_.verified)
}
private def authorIsVerified(safety: Option[Safety]): Boolean = {
authorIsVerified.incr()
safety.exists(_.verified)
}
def authorScreenName(author: User): Option[String] = {
authorScreenName.incr()
author.profile.map(_.screenName)
}
def authorScreenName(authorId: Long): Stitch[String] = {
authorScreenName.incr()
userSource.getProfile(authorId).map(profile => profile.screenName)
}
}

View File

@ -1,22 +0,0 @@
scala_library(
sources = ["*.scala"],
compiler_option_sets = ["fatal_warnings"],
platform = "java8",
strict_deps = True,
tags = ["bazel-compatible"],
dependencies = [
"src/scala/com/twitter/search/blender/services/strato",
"src/thrift/com/twitter/content-health/sensitivemediasettings:sensitivemediasettings-scala",
"src/thrift/com/twitter/gizmoduck:user-thrift-scala",
"stitch/stitch-core",
"visibility/common/src/main/scala/com/twitter/visibility/common",
"visibility/common/src/main/scala/com/twitter/visibility/common/user_result",
"visibility/common/src/main/thrift/com/twitter/visibility:action-scala",
"visibility/lib/src/main/resources/config",
"visibility/lib/src/main/scala/com/twitter/visibility",
"visibility/lib/src/main/scala/com/twitter/visibility/features",
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/blender",
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/search",
"visibility/lib/src/main/thrift/com/twitter/visibility/context:vf-context-scala",
],
)

View File

@ -1,52 +0,0 @@
package com.twitter.visibility.builder.users
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.features.AuthorBlocksOuterAuthor
import com.twitter.visibility.features.OuterAuthorFollowsAuthor
import com.twitter.visibility.features.OuterAuthorId
import com.twitter.visibility.features.OuterAuthorIsInnerAuthor
class QuotedTweetFeatures(
relationshipFeatures: RelationshipFeatures,
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("quoted_tweet_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val outerAuthorIdStat =
scopedStatsReceiver.scope(OuterAuthorId.name).counter("requests")
private[this] val authorBlocksOuterAuthor =
scopedStatsReceiver.scope(AuthorBlocksOuterAuthor.name).counter("requests")
private[this] val outerAuthorFollowsAuthor =
scopedStatsReceiver.scope(OuterAuthorFollowsAuthor.name).counter("requests")
private[this] val outerAuthorIsInnerAuthor =
scopedStatsReceiver.scope(OuterAuthorIsInnerAuthor.name).counter("requests")
def forOuterAuthor(
outerAuthorId: Long,
innerAuthorId: Long
): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
outerAuthorIdStat.incr()
authorBlocksOuterAuthor.incr()
outerAuthorFollowsAuthor.incr()
outerAuthorIsInnerAuthor.incr()
val viewer = Some(outerAuthorId)
_.withConstantFeature(OuterAuthorId, outerAuthorId)
.withFeature(
AuthorBlocksOuterAuthor,
relationshipFeatures.authorBlocksViewer(innerAuthorId, viewer))
.withFeature(
OuterAuthorFollowsAuthor,
relationshipFeatures.viewerFollowsAuthor(innerAuthorId, viewer))
.withConstantFeature(
OuterAuthorIsInnerAuthor,
innerAuthorId == outerAuthorId
)
}
}

View File

@ -1,176 +0,0 @@
package com.twitter.visibility.builder.users
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.gizmoduck.thriftscala.User
import com.twitter.stitch.Stitch
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common.UserId
import com.twitter.visibility.common.UserRelationshipSource
import com.twitter.visibility.features._
class RelationshipFeatures(
userRelationshipSource: UserRelationshipSource,
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("relationship_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val authorFollowsViewer =
scopedStatsReceiver.scope(AuthorFollowsViewer.name).counter("requests")
private[this] val viewerFollowsAuthor =
scopedStatsReceiver.scope(ViewerFollowsAuthor.name).counter("requests")
private[this] val authorBlocksViewer =
scopedStatsReceiver.scope(AuthorBlocksViewer.name).counter("requests")
private[this] val viewerBlocksAuthor =
scopedStatsReceiver.scope(ViewerBlocksAuthor.name).counter("requests")
private[this] val authorMutesViewer =
scopedStatsReceiver.scope(AuthorMutesViewer.name).counter("requests")
private[this] val viewerMutesAuthor =
scopedStatsReceiver.scope(ViewerMutesAuthor.name).counter("requests")
private[this] val authorHasReportedViewer =
scopedStatsReceiver.scope(AuthorReportsViewerAsSpam.name).counter("requests")
private[this] val viewerHasReportedAuthor =
scopedStatsReceiver.scope(ViewerReportsAuthorAsSpam.name).counter("requests")
private[this] val viewerMutesRetweetsFromAuthor =
scopedStatsReceiver.scope(ViewerMutesRetweetsFromAuthor.name).counter("requests")
def forAuthorId(
authorId: Long,
viewerId: Option[Long]
): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
_.withFeature(AuthorFollowsViewer, authorFollowsViewer(authorId, viewerId))
.withFeature(ViewerFollowsAuthor, viewerFollowsAuthor(authorId, viewerId))
.withFeature(AuthorBlocksViewer, authorBlocksViewer(authorId, viewerId))
.withFeature(ViewerBlocksAuthor, viewerBlocksAuthor(authorId, viewerId))
.withFeature(AuthorMutesViewer, authorMutesViewer(authorId, viewerId))
.withFeature(ViewerMutesAuthor, viewerMutesAuthor(authorId, viewerId))
.withFeature(AuthorReportsViewerAsSpam, authorHasReportedViewer(authorId, viewerId))
.withFeature(ViewerReportsAuthorAsSpam, viewerHasReportedAuthor(authorId, viewerId))
.withFeature(ViewerMutesRetweetsFromAuthor, viewerMutesRetweetsFromAuthor(authorId, viewerId))
}
def forNoAuthor(): FeatureMapBuilder => FeatureMapBuilder = {
_.withConstantFeature(AuthorFollowsViewer, false)
.withConstantFeature(ViewerFollowsAuthor, false)
.withConstantFeature(AuthorBlocksViewer, false)
.withConstantFeature(ViewerBlocksAuthor, false)
.withConstantFeature(AuthorMutesViewer, false)
.withConstantFeature(ViewerMutesAuthor, false)
.withConstantFeature(AuthorReportsViewerAsSpam, false)
.withConstantFeature(ViewerReportsAuthorAsSpam, false)
.withConstantFeature(ViewerMutesRetweetsFromAuthor, false)
}
def forAuthor(author: User, viewerId: Option[Long]): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
_.withFeature(AuthorFollowsViewer, authorFollowsViewer(author, viewerId))
.withFeature(ViewerFollowsAuthor, viewerFollowsAuthor(author, viewerId))
.withFeature(AuthorBlocksViewer, authorBlocksViewer(author, viewerId))
.withFeature(ViewerBlocksAuthor, viewerBlocksAuthor(author, viewerId))
.withFeature(AuthorMutesViewer, authorMutesViewer(author, viewerId))
.withFeature(ViewerMutesAuthor, viewerMutesAuthor(author, viewerId))
.withFeature(AuthorReportsViewerAsSpam, authorHasReportedViewer(author.id, viewerId))
.withFeature(ViewerReportsAuthorAsSpam, viewerHasReportedAuthor(author.id, viewerId))
.withFeature(ViewerMutesRetweetsFromAuthor, viewerMutesRetweetsFromAuthor(author, viewerId))
}
def viewerFollowsAuthor(authorId: UserId, viewerId: Option[UserId]): Stitch[Boolean] =
ViewerVerbsAuthor(authorId, viewerId, userRelationshipSource.follows, viewerFollowsAuthor)
def viewerFollowsAuthor(author: User, viewerId: Option[UserId]): Stitch[Boolean] =
ViewerVerbsAuthor(
author,
viewerId,
p => p.following,
userRelationshipSource.follows,
viewerFollowsAuthor)
def authorFollowsViewer(authorId: UserId, viewerId: Option[UserId]): Stitch[Boolean] =
AuthorVerbsViewer(authorId, viewerId, userRelationshipSource.follows, authorFollowsViewer)
def authorFollowsViewer(author: User, viewerId: Option[UserId]): Stitch[Boolean] =
AuthorVerbsViewer(
author,
viewerId,
p => p.followedBy,
userRelationshipSource.follows,
authorFollowsViewer)
def viewerBlocksAuthor(authorId: UserId, viewerId: Option[UserId]): Stitch[Boolean] =
ViewerVerbsAuthor(authorId, viewerId, userRelationshipSource.blocks, viewerBlocksAuthor)
def viewerBlocksAuthor(author: User, viewerId: Option[UserId]): Stitch[Boolean] =
ViewerVerbsAuthor(
author,
viewerId,
p => p.blocking,
userRelationshipSource.blocks,
viewerBlocksAuthor)
def authorBlocksViewer(authorId: UserId, viewerId: Option[UserId]): Stitch[Boolean] =
ViewerVerbsAuthor(authorId, viewerId, userRelationshipSource.blockedBy, authorBlocksViewer)
def authorBlocksViewer(author: User, viewerId: Option[UserId]): Stitch[Boolean] =
ViewerVerbsAuthor(
author,
viewerId,
p => p.blockedBy,
userRelationshipSource.blockedBy,
authorBlocksViewer)
def viewerMutesAuthor(authorId: UserId, viewerId: Option[UserId]): Stitch[Boolean] =
ViewerVerbsAuthor(authorId, viewerId, userRelationshipSource.mutes, viewerMutesAuthor)
def viewerMutesAuthor(author: User, viewerId: Option[UserId]): Stitch[Boolean] =
ViewerVerbsAuthor(
author,
viewerId,
p => p.muting,
userRelationshipSource.mutes,
viewerMutesAuthor)
def authorMutesViewer(authorId: UserId, viewerId: Option[UserId]): Stitch[Boolean] =
ViewerVerbsAuthor(authorId, viewerId, userRelationshipSource.mutedBy, authorMutesViewer)
def authorMutesViewer(author: User, viewerId: Option[UserId]): Stitch[Boolean] =
ViewerVerbsAuthor(
author,
viewerId,
p => p.mutedBy,
userRelationshipSource.mutedBy,
authorMutesViewer)
def viewerHasReportedAuthor(authorId: UserId, viewerId: Option[UserId]): Stitch[Boolean] =
ViewerVerbsAuthor(
authorId,
viewerId,
userRelationshipSource.reportsAsSpam,
viewerHasReportedAuthor)
def authorHasReportedViewer(authorId: UserId, viewerId: Option[UserId]): Stitch[Boolean] =
ViewerVerbsAuthor(
authorId,
viewerId,
userRelationshipSource.reportedAsSpamBy,
authorHasReportedViewer)
def viewerMutesRetweetsFromAuthor(authorId: UserId, viewerId: Option[UserId]): Stitch[Boolean] =
ViewerVerbsAuthor(
authorId,
viewerId,
userRelationshipSource.noRetweetsFrom,
viewerMutesRetweetsFromAuthor)
def viewerMutesRetweetsFromAuthor(author: User, viewerId: Option[UserId]): Stitch[Boolean] =
ViewerVerbsAuthor(
author,
viewerId,
p => p.noRetweetsFrom,
userRelationshipSource.noRetweetsFrom,
viewerMutesRetweetsFromAuthor)
}

View File

@ -1,79 +0,0 @@
package com.twitter.visibility.builder.users
import com.twitter.finagle.stats.Counter
import com.twitter.gizmoduck.thriftscala.Perspective
import com.twitter.gizmoduck.thriftscala.User
import com.twitter.stitch.Stitch
import com.twitter.visibility.common.UserId
case object ViewerVerbsAuthor {
def apply(
authorId: UserId,
viewerIdOpt: Option[UserId],
relationship: (UserId, UserId) => Stitch[Boolean],
relationshipCounter: Counter
): Stitch[Boolean] = {
relationshipCounter.incr()
viewerIdOpt match {
case Some(viewerId) => relationship(viewerId, authorId)
case _ => Stitch.False
}
}
def apply(
author: User,
viewerId: Option[UserId],
checkPerspective: Perspective => Option[Boolean],
relationship: (UserId, UserId) => Stitch[Boolean],
relationshipCounter: Counter
): Stitch[Boolean] = {
author.perspective match {
case Some(perspective) =>
checkPerspective(perspective) match {
case Some(status) =>
relationshipCounter.incr()
Stitch.value(status)
case None =>
ViewerVerbsAuthor(author.id, viewerId, relationship, relationshipCounter)
}
case None => ViewerVerbsAuthor(author.id, viewerId, relationship, relationshipCounter)
}
}
}
case object AuthorVerbsViewer {
def apply(
authorId: UserId,
viewerIdOpt: Option[UserId],
relationship: (UserId, UserId) => Stitch[Boolean],
relationshipCounter: Counter
): Stitch[Boolean] = {
relationshipCounter.incr()
viewerIdOpt match {
case Some(viewerId) => relationship(authorId, viewerId)
case _ => Stitch.False
}
}
def apply(
author: User,
viewerId: Option[UserId],
checkPerspective: Perspective => Option[Boolean],
relationship: (UserId, UserId) => Stitch[Boolean],
relationshipCounter: Counter
): Stitch[Boolean] = {
author.perspective match {
case Some(perspective) =>
checkPerspective(perspective) match {
case Some(status) =>
relationshipCounter.incr()
Stitch.value(status)
case None =>
AuthorVerbsViewer(author.id, viewerId, relationship, relationshipCounter)
}
case None => AuthorVerbsViewer(author.id, viewerId, relationship, relationshipCounter)
}
}
}

View File

@ -1,26 +0,0 @@
package com.twitter.visibility.builder.users
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.features._
import com.twitter.visibility.context.thriftscala.SearchContext
class SearchFeatures(statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("search_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val rawQueryCounter =
scopedStatsReceiver.scope(RawQuery.name).counter("requests")
def forSearchContext(
searchContext: Option[SearchContext]
): FeatureMapBuilder => FeatureMapBuilder = { builder =>
requests.incr()
searchContext match {
case Some(context: SearchContext) =>
rawQueryCounter.incr()
builder
.withConstantFeature(RawQuery, context.rawQuery)
case _ => builder
}
}
}

View File

@ -1,145 +0,0 @@
package com.twitter.visibility.builder.users
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common.user_result.UserVisibilityResultHelper
import com.twitter.visibility.features.AuthorBlocksViewer
import com.twitter.visibility.features.AuthorIsDeactivated
import com.twitter.visibility.features.AuthorIsErased
import com.twitter.visibility.features.AuthorIsOffboarded
import com.twitter.visibility.features.AuthorIsProtected
import com.twitter.visibility.features.AuthorIsSuspended
import com.twitter.visibility.features.AuthorIsUnavailable
import com.twitter.visibility.features.ViewerBlocksAuthor
import com.twitter.visibility.features.ViewerMutesAuthor
import com.twitter.visibility.models.UserUnavailableStateEnum
case class UserUnavailableFeatures(statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("user_unavailable_features")
private[this] val suspendedAuthorStats = scopedStatsReceiver.scope("suspended_author")
private[this] val deactivatedAuthorStats = scopedStatsReceiver.scope("deactivated_author")
private[this] val offboardedAuthorStats = scopedStatsReceiver.scope("offboarded_author")
private[this] val erasedAuthorStats = scopedStatsReceiver.scope("erased_author")
private[this] val protectedAuthorStats = scopedStatsReceiver.scope("protected_author")
private[this] val authorBlocksViewerStats = scopedStatsReceiver.scope("author_blocks_viewer")
private[this] val viewerBlocksAuthorStats = scopedStatsReceiver.scope("viewer_blocks_author")
private[this] val viewerMutesAuthorStats = scopedStatsReceiver.scope("viewer_mutes_author")
private[this] val unavailableStats = scopedStatsReceiver.scope("unavailable")
def forState(state: UserUnavailableStateEnum): FeatureMapBuilder => FeatureMapBuilder = {
builder =>
builder
.withConstantFeature(AuthorIsSuspended, isSuspended(state))
.withConstantFeature(AuthorIsDeactivated, isDeactivated(state))
.withConstantFeature(AuthorIsOffboarded, isOffboarded(state))
.withConstantFeature(AuthorIsErased, isErased(state))
.withConstantFeature(AuthorIsProtected, isProtected(state))
.withConstantFeature(AuthorBlocksViewer, authorBlocksViewer(state))
.withConstantFeature(ViewerBlocksAuthor, viewerBlocksAuthor(state))
.withConstantFeature(ViewerMutesAuthor, viewerMutesAuthor(state))
.withConstantFeature(AuthorIsUnavailable, isUnavailable(state))
}
private[this] def isSuspended(state: UserUnavailableStateEnum): Boolean =
state match {
case UserUnavailableStateEnum.Suspended =>
suspendedAuthorStats.counter().incr()
true
case UserUnavailableStateEnum.Filtered(result)
if UserVisibilityResultHelper.isDropSuspendedAuthor(result) =>
suspendedAuthorStats.counter().incr()
suspendedAuthorStats.counter("filtered").incr()
true
case _ => false
}
private[this] def isDeactivated(state: UserUnavailableStateEnum): Boolean =
state match {
case UserUnavailableStateEnum.Deactivated =>
deactivatedAuthorStats.counter().incr()
true
case _ => false
}
private[this] def isOffboarded(state: UserUnavailableStateEnum): Boolean =
state match {
case UserUnavailableStateEnum.Offboarded =>
offboardedAuthorStats.counter().incr()
true
case _ => false
}
private[this] def isErased(state: UserUnavailableStateEnum): Boolean =
state match {
case UserUnavailableStateEnum.Erased =>
erasedAuthorStats.counter().incr()
true
case _ => false
}
private[this] def isProtected(state: UserUnavailableStateEnum): Boolean =
state match {
case UserUnavailableStateEnum.Protected =>
protectedAuthorStats.counter().incr()
true
case UserUnavailableStateEnum.Filtered(result)
if UserVisibilityResultHelper.isDropProtectedAuthor(result) =>
protectedAuthorStats.counter().incr()
protectedAuthorStats.counter("filtered").incr()
true
case _ => false
}
private[this] def authorBlocksViewer(state: UserUnavailableStateEnum): Boolean =
state match {
case UserUnavailableStateEnum.AuthorBlocksViewer =>
authorBlocksViewerStats.counter().incr()
true
case UserUnavailableStateEnum.Filtered(result)
if UserVisibilityResultHelper.isDropAuthorBlocksViewer(result) =>
authorBlocksViewerStats.counter().incr()
authorBlocksViewerStats.counter("filtered").incr()
true
case _ => false
}
private[this] def viewerBlocksAuthor(state: UserUnavailableStateEnum): Boolean =
state match {
case UserUnavailableStateEnum.ViewerBlocksAuthor =>
viewerBlocksAuthorStats.counter().incr()
true
case UserUnavailableStateEnum.Filtered(result)
if UserVisibilityResultHelper.isDropViewerBlocksAuthor(result) =>
viewerBlocksAuthorStats.counter().incr()
viewerBlocksAuthorStats.counter("filtered").incr()
true
case _ => false
}
private[this] def viewerMutesAuthor(state: UserUnavailableStateEnum): Boolean =
state match {
case UserUnavailableStateEnum.ViewerMutesAuthor =>
viewerMutesAuthorStats.counter().incr()
true
case UserUnavailableStateEnum.Filtered(result)
if UserVisibilityResultHelper.isDropViewerMutesAuthor(result) =>
viewerMutesAuthorStats.counter().incr()
viewerMutesAuthorStats.counter("filtered").incr()
true
case _ => false
}
private[this] def isUnavailable(state: UserUnavailableStateEnum): Boolean =
state match {
case UserUnavailableStateEnum.Unavailable =>
unavailableStats.counter().incr()
true
case UserUnavailableStateEnum.Filtered(result)
if UserVisibilityResultHelper.isDropUnspecifiedAuthor(result) =>
unavailableStats.counter().incr()
unavailableStats.counter("filtered").incr()
true
case _ => false
}
}

View File

@ -1,92 +0,0 @@
package com.twitter.visibility.builder.users
import com.twitter.finagle.stats.Counter
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.gizmoduck.thriftscala.AdvancedFilters
import com.twitter.gizmoduck.thriftscala.MentionFilter
import com.twitter.stitch.NotFound
import com.twitter.stitch.Stitch
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common.UserSource
import com.twitter.visibility.features.ViewerFiltersDefaultProfileImage
import com.twitter.visibility.features.ViewerFiltersNewUsers
import com.twitter.visibility.features.ViewerFiltersNoConfirmedEmail
import com.twitter.visibility.features.ViewerFiltersNoConfirmedPhone
import com.twitter.visibility.features.ViewerFiltersNotFollowedBy
import com.twitter.visibility.features.ViewerMentionFilter
class ViewerAdvancedFilteringFeatures(userSource: UserSource, statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("viewer_advanced_filtering_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val viewerFiltersNoConfirmedEmail =
scopedStatsReceiver.scope(ViewerFiltersNoConfirmedEmail.name).counter("requests")
private[this] val viewerFiltersNoConfirmedPhone =
scopedStatsReceiver.scope(ViewerFiltersNoConfirmedPhone.name).counter("requests")
private[this] val viewerFiltersDefaultProfileImage =
scopedStatsReceiver.scope(ViewerFiltersDefaultProfileImage.name).counter("requests")
private[this] val viewerFiltersNewUsers =
scopedStatsReceiver.scope(ViewerFiltersNewUsers.name).counter("requests")
private[this] val viewerFiltersNotFollowedBy =
scopedStatsReceiver.scope(ViewerFiltersNotFollowedBy.name).counter("requests")
private[this] val viewerMentionFilter =
scopedStatsReceiver.scope(ViewerMentionFilter.name).counter("requests")
def forViewerId(viewerId: Option[Long]): FeatureMapBuilder => FeatureMapBuilder = {
requests.incr()
_.withFeature(ViewerFiltersNoConfirmedEmail, viewerFiltersNoConfirmedEmail(viewerId))
.withFeature(ViewerFiltersNoConfirmedPhone, viewerFiltersNoConfirmedPhone(viewerId))
.withFeature(ViewerFiltersDefaultProfileImage, viewerFiltersDefaultProfileImage(viewerId))
.withFeature(ViewerFiltersNewUsers, viewerFiltersNewUsers(viewerId))
.withFeature(ViewerFiltersNotFollowedBy, viewerFiltersNotFollowedBy(viewerId))
.withFeature(ViewerMentionFilter, viewerMentionFilter(viewerId))
}
def viewerFiltersNoConfirmedEmail(viewerId: Option[Long]): Stitch[Boolean] =
viewerAdvancedFilters(viewerId, af => af.filterNoConfirmedEmail, viewerFiltersNoConfirmedEmail)
def viewerFiltersNoConfirmedPhone(viewerId: Option[Long]): Stitch[Boolean] =
viewerAdvancedFilters(viewerId, af => af.filterNoConfirmedPhone, viewerFiltersNoConfirmedPhone)
def viewerFiltersDefaultProfileImage(viewerId: Option[Long]): Stitch[Boolean] =
viewerAdvancedFilters(
viewerId,
af => af.filterDefaultProfileImage,
viewerFiltersDefaultProfileImage
)
def viewerFiltersNewUsers(viewerId: Option[Long]): Stitch[Boolean] =
viewerAdvancedFilters(viewerId, af => af.filterNewUsers, viewerFiltersNewUsers)
def viewerFiltersNotFollowedBy(viewerId: Option[Long]): Stitch[Boolean] =
viewerAdvancedFilters(viewerId, af => af.filterNotFollowedBy, viewerFiltersNotFollowedBy)
def viewerMentionFilter(viewerId: Option[Long]): Stitch[MentionFilter] = {
viewerMentionFilter.incr()
viewerId match {
case Some(id) =>
userSource.getMentionFilter(id).handle {
case NotFound =>
MentionFilter.Unfiltered
}
case _ => Stitch.value(MentionFilter.Unfiltered)
}
}
private[this] def viewerAdvancedFilters(
viewerId: Option[Long],
advancedFilterCheck: AdvancedFilters => Boolean,
featureCounter: Counter
): Stitch[Boolean] = {
featureCounter.incr()
val advancedFilters = viewerId match {
case Some(id) => userSource.getAdvancedFilters(id)
case _ => Stitch.value(AdvancedFilters())
}
advancedFilters.map(advancedFilterCheck)
}
}

View File

@ -1,245 +0,0 @@
package com.twitter.visibility.builder.users
import com.twitter.finagle.stats.Counter
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.gizmoduck.thriftscala.Label
import com.twitter.gizmoduck.thriftscala.Safety
import com.twitter.gizmoduck.thriftscala.UniversalQualityFiltering
import com.twitter.gizmoduck.thriftscala.User
import com.twitter.gizmoduck.thriftscala.UserType
import com.twitter.stitch.NotFound
import com.twitter.stitch.Stitch
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common.UserId
import com.twitter.visibility.common.UserSource
import com.twitter.visibility.features._
import com.twitter.visibility.interfaces.common.blender.BlenderVFRequestContext
import com.twitter.visibility.interfaces.common.search.SearchVFRequestContext
import com.twitter.visibility.models.UserAge
import com.twitter.visibility.models.ViewerContext
class ViewerFeatures(userSource: UserSource, statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("viewer_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val viewerIdCount =
scopedStatsReceiver.scope(ViewerId.name).counter("requests")
private[this] val requestCountryCode =
scopedStatsReceiver.scope(RequestCountryCode.name).counter("requests")
private[this] val requestIsVerifiedCrawler =
scopedStatsReceiver.scope(RequestIsVerifiedCrawler.name).counter("requests")
private[this] val viewerUserLabels =
scopedStatsReceiver.scope(ViewerUserLabels.name).counter("requests")
private[this] val viewerIsDeactivated =
scopedStatsReceiver.scope(ViewerIsDeactivated.name).counter("requests")
private[this] val viewerIsProtected =
scopedStatsReceiver.scope(ViewerIsProtected.name).counter("requests")
private[this] val viewerIsSuspended =
scopedStatsReceiver.scope(ViewerIsSuspended.name).counter("requests")
private[this] val viewerRoles =
scopedStatsReceiver.scope(ViewerRoles.name).counter("requests")
private[this] val viewerCountryCode =
scopedStatsReceiver.scope(ViewerCountryCode.name).counter("requests")
private[this] val viewerAge =
scopedStatsReceiver.scope(ViewerAge.name).counter("requests")
private[this] val viewerHasUniversalQualityFilterEnabled =
scopedStatsReceiver.scope(ViewerHasUniversalQualityFilterEnabled.name).counter("requests")
private[this] val viewerIsSoftUserCtr =
scopedStatsReceiver.scope(ViewerIsSoftUser.name).counter("requests")
def forViewerBlenderContext(
blenderContext: BlenderVFRequestContext,
viewerContext: ViewerContext
): FeatureMapBuilder => FeatureMapBuilder =
forViewerContext(viewerContext)
.andThen(
_.withConstantFeature(
ViewerOptInBlocking,
blenderContext.userSearchSafetySettings.optInBlocking)
.withConstantFeature(
ViewerOptInFiltering,
blenderContext.userSearchSafetySettings.optInFiltering)
)
def forViewerSearchContext(
searchContext: SearchVFRequestContext,
viewerContext: ViewerContext
): FeatureMapBuilder => FeatureMapBuilder =
forViewerContext(viewerContext)
.andThen(
_.withConstantFeature(
ViewerOptInBlocking,
searchContext.userSearchSafetySettings.optInBlocking)
.withConstantFeature(
ViewerOptInFiltering,
searchContext.userSearchSafetySettings.optInFiltering)
)
def forViewerContext(viewerContext: ViewerContext): FeatureMapBuilder => FeatureMapBuilder =
forViewerId(viewerContext.userId)
.andThen(
_.withConstantFeature(RequestCountryCode, requestCountryCode(viewerContext))
).andThen(
_.withConstantFeature(RequestIsVerifiedCrawler, requestIsVerifiedCrawler(viewerContext))
)
def forViewerId(viewerId: Option[UserId]): FeatureMapBuilder => FeatureMapBuilder = { builder =>
requests.incr()
val builderWithFeatures = builder
.withConstantFeature(ViewerId, viewerId)
.withFeature(ViewerIsProtected, viewerIsProtected(viewerId))
.withFeature(
ViewerHasUniversalQualityFilterEnabled,
viewerHasUniversalQualityFilterEnabled(viewerId)
)
.withFeature(ViewerIsSuspended, viewerIsSuspended(viewerId))
.withFeature(ViewerIsDeactivated, viewerIsDeactivated(viewerId))
.withFeature(ViewerUserLabels, viewerUserLabels(viewerId))
.withFeature(ViewerRoles, viewerRoles(viewerId))
.withFeature(ViewerAge, viewerAgeInYears(viewerId))
.withFeature(ViewerIsSoftUser, viewerIsSoftUser(viewerId))
viewerId match {
case Some(_) =>
viewerIdCount.incr()
builderWithFeatures
.withFeature(ViewerCountryCode, viewerCountryCode(viewerId))
case _ =>
builderWithFeatures
}
}
def forViewerNoDefaults(viewerOpt: Option[User]): FeatureMapBuilder => FeatureMapBuilder = {
builder =>
requests.incr()
viewerOpt match {
case Some(viewer) =>
builder
.withConstantFeature(ViewerId, viewer.id)
.withConstantFeature(ViewerIsProtected, viewerIsProtectedOpt(viewer))
.withConstantFeature(ViewerIsSuspended, viewerIsSuspendedOpt(viewer))
.withConstantFeature(ViewerIsDeactivated, viewerIsDeactivatedOpt(viewer))
.withConstantFeature(ViewerCountryCode, viewerCountryCode(viewer))
case None =>
builder
.withConstantFeature(ViewerIsProtected, false)
.withConstantFeature(ViewerIsSuspended, false)
.withConstantFeature(ViewerIsDeactivated, false)
}
}
private def checkSafetyValue(
viewerId: Option[UserId],
safetyCheck: Safety => Boolean,
featureCounter: Counter
): Stitch[Boolean] =
viewerId match {
case Some(id) =>
userSource.getSafety(id).map(safetyCheck).ensure {
featureCounter.incr()
}
case None => Stitch.False
}
private def checkSafetyValue(
viewer: User,
safetyCheck: Safety => Boolean
): Boolean = {
viewer.safety.exists(safetyCheck)
}
def viewerIsProtected(viewerId: Option[UserId]): Stitch[Boolean] =
checkSafetyValue(viewerId, s => s.isProtected, viewerIsProtected)
def viewerIsProtected(viewer: User): Boolean =
checkSafetyValue(viewer, s => s.isProtected)
def viewerIsProtectedOpt(viewer: User): Option[Boolean] =
viewer.safety.map(_.isProtected)
def viewerIsDeactivated(viewerId: Option[UserId]): Stitch[Boolean] =
checkSafetyValue(viewerId, s => s.deactivated, viewerIsDeactivated)
def viewerIsDeactivated(viewer: User): Boolean =
checkSafetyValue(viewer, s => s.deactivated)
def viewerIsDeactivatedOpt(viewer: User): Option[Boolean] =
viewer.safety.map(_.deactivated)
def viewerHasUniversalQualityFilterEnabled(viewerId: Option[UserId]): Stitch[Boolean] =
checkSafetyValue(
viewerId,
s => s.universalQualityFiltering == UniversalQualityFiltering.Enabled,
viewerHasUniversalQualityFilterEnabled
)
def viewerUserLabels(viewerIdOpt: Option[UserId]): Stitch[Seq[Label]] =
viewerIdOpt match {
case Some(viewerId) =>
userSource
.getLabels(viewerId).map(_.labels)
.handle {
case NotFound => Seq.empty
}.ensure {
viewerUserLabels.incr()
}
case _ => Stitch.value(Seq.empty)
}
def viewerAgeInYears(viewerId: Option[UserId]): Stitch[UserAge] =
(viewerId match {
case Some(id) =>
userSource
.getExtendedProfile(id).map(_.ageInYears)
.handle {
case NotFound => None
}.ensure {
viewerAge.incr()
}
case _ => Stitch.value(None)
}).map(UserAge)
def viewerIsSoftUser(viewerId: Option[UserId]): Stitch[Boolean] = {
viewerId match {
case Some(id) =>
userSource
.getUserType(id).map { userType =>
userType == UserType.Soft
}.ensure {
viewerIsSoftUserCtr.incr()
}
case _ => Stitch.False
}
}
def requestCountryCode(viewerContext: ViewerContext): Option[String] = {
requestCountryCode.incr()
viewerContext.requestCountryCode
}
def requestIsVerifiedCrawler(viewerContext: ViewerContext): Boolean = {
requestIsVerifiedCrawler.incr()
viewerContext.isVerifiedCrawler
}
def viewerCountryCode(viewerId: Option[UserId]): Stitch[String] =
viewerId match {
case Some(id) =>
userSource
.getAccount(id).map(_.countryCode).flatMap {
case Some(countryCode) => Stitch.value(countryCode.toLowerCase)
case _ => Stitch.NotFound
}.ensure {
viewerCountryCode.incr()
}
case _ => Stitch.NotFound
}
def viewerCountryCode(viewer: User): Option[String] =
viewer.account.flatMap(_.countryCode)
}

View File

@ -1,49 +0,0 @@
package com.twitter.visibility.builder.users
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.stitch.Stitch
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common.UserId
import com.twitter.visibility.common.UserSearchSafetySource
import com.twitter.visibility.features.ViewerId
import com.twitter.visibility.features.ViewerOptInBlocking
import com.twitter.visibility.features.ViewerOptInFiltering
class ViewerSearchSafetyFeatures(
userSearchSafetySource: UserSearchSafetySource,
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver = statsReceiver.scope("viewer_search_safety_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
private[this] val viewerOptInBlockingRequests =
scopedStatsReceiver.scope(ViewerOptInBlocking.name).counter("requests")
private[this] val viewerOptInFilteringRequests =
scopedStatsReceiver.scope(ViewerOptInFiltering.name).counter("requests")
def forViewerId(viewerId: Option[UserId]): FeatureMapBuilder => FeatureMapBuilder = { builder =>
requests.incr()
builder
.withConstantFeature(ViewerId, viewerId)
.withFeature(ViewerOptInBlocking, viewerOptInBlocking(viewerId))
.withFeature(ViewerOptInFiltering, viewerOptInFiltering(viewerId))
}
def viewerOptInBlocking(viewerId: Option[UserId]): Stitch[Boolean] = {
viewerOptInBlockingRequests.incr()
viewerId match {
case Some(userId) => userSearchSafetySource.optInBlocking(userId)
case _ => Stitch.False
}
}
def viewerOptInFiltering(viewerId: Option[UserId]): Stitch[Boolean] = {
viewerOptInFilteringRequests.incr()
viewerId match {
case Some(userId) => userSearchSafetySource.optInFiltering(userId)
case _ => Stitch.False
}
}
}

View File

@ -1,41 +0,0 @@
package com.twitter.visibility.builder.users
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.stitch.Stitch
import com.twitter.stitch.NotFound
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.common.UserId
import com.twitter.visibility.common.UserSensitiveMediaSettingsSource
import com.twitter.visibility.features.ViewerId
import com.twitter.visibility.features.ViewerSensitiveMediaSettings
import com.twitter.visibility.models.UserSensitiveMediaSettings
class ViewerSensitiveMediaSettingsFeatures(
userSensitiveMediaSettingsSource: UserSensitiveMediaSettingsSource,
statsReceiver: StatsReceiver) {
private[this] val scopedStatsReceiver =
statsReceiver.scope("viewer_sensitive_media_settings_features")
private[this] val requests = scopedStatsReceiver.counter("requests")
def forViewerId(viewerId: Option[UserId]): FeatureMapBuilder => FeatureMapBuilder = { builder =>
requests.incr()
builder
.withConstantFeature(ViewerId, viewerId)
.withFeature(ViewerSensitiveMediaSettings, viewerSensitiveMediaSettings(viewerId))
}
def viewerSensitiveMediaSettings(viewerId: Option[UserId]): Stitch[UserSensitiveMediaSettings] = {
(viewerId match {
case Some(userId) =>
userSensitiveMediaSettingsSource
.userSensitiveMediaSettings(userId)
.handle {
case NotFound => None
}
case _ => Stitch.value(None)
}).map(UserSensitiveMediaSettings)
}
}

View File

@ -1,19 +0,0 @@
scala_library(
sources = ["*.scala"],
compiler_option_sets = ["fatal_warnings"],
platform = "java8",
strict_deps = True,
tags = ["bazel-compatible"],
dependencies = [
"abdecider/src/main/scala",
"configapi/configapi-abdecider",
"configapi/configapi-core",
"configapi/configapi-featureswitches:v2",
"decider",
"featureswitches/featureswitches-core/src/main/scala",
"finagle/finagle-stats",
"servo/decider/src/main/scala",
"visibility/lib/src/main/scala/com/twitter/visibility/configapi/configs",
"visibility/lib/src/main/scala/com/twitter/visibility/models",
],
)

View File

@ -1,43 +0,0 @@
package com.twitter.visibility.configapi
import com.twitter.decider.Decider
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.logging.Logger
import com.twitter.servo.decider.DeciderGateBuilder
import com.twitter.timelines.configapi.CompositeConfig
import com.twitter.timelines.configapi.Config
import com.twitter.util.Memoize
import com.twitter.visibility.configapi.configs.VisibilityDeciders
import com.twitter.visibility.configapi.configs.VisibilityExperimentsConfig
import com.twitter.visibility.configapi.configs.VisibilityFeatureSwitches
import com.twitter.visibility.models.SafetyLevel
object ConfigBuilder {
def apply(statsReceiver: StatsReceiver, decider: Decider, logger: Logger): ConfigBuilder = {
val deciderGateBuilder: DeciderGateBuilder =
new DeciderGateBuilder(decider)
new ConfigBuilder(
deciderGateBuilder,
statsReceiver,
logger
)
}
}
class ConfigBuilder(
deciderGateBuilder: DeciderGateBuilder,
statsReceiver: StatsReceiver,
logger: Logger) {
def buildMemoized: SafetyLevel => Config = Memoize(build)
def build(safetyLevel: SafetyLevel): Config = {
new CompositeConfig(
VisibilityExperimentsConfig.config(safetyLevel) :+
VisibilityDeciders.config(deciderGateBuilder, logger, statsReceiver, safetyLevel) :+
VisibilityFeatureSwitches.config(statsReceiver, logger)
)
}
}

View File

@ -1,61 +0,0 @@
package com.twitter.visibility.configapi
import com.twitter.abdecider.LoggingABDecider
import com.twitter.decider.Decider
import com.twitter.featureswitches.v2.FeatureSwitches
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.logging.Logger
import com.twitter.servo.util.MemoizingStatsReceiver
import com.twitter.timelines.configapi.Params
import com.twitter.visibility.models.SafetyLevel
import com.twitter.visibility.models.UnitOfDiversion
import com.twitter.visibility.models.ViewerContext
object VisibilityParams {
def apply(
log: Logger,
statsReceiver: StatsReceiver,
decider: Decider,
abDecider: LoggingABDecider,
featureSwitches: FeatureSwitches
): VisibilityParams =
new VisibilityParams(log, statsReceiver, decider, abDecider, featureSwitches)
}
class VisibilityParams(
log: Logger,
statsReceiver: StatsReceiver,
decider: Decider,
abDecider: LoggingABDecider,
featureSwitches: FeatureSwitches) {
private[this] val contextFactory = new VisibilityRequestContextFactory(
abDecider,
featureSwitches
)
private[this] val configBuilder = ConfigBuilder(statsReceiver.scope("config"), decider, log)
private[this] val paramStats: MemoizingStatsReceiver = new MemoizingStatsReceiver(
statsReceiver.scope("configapi_params"))
def apply(
viewerContext: ViewerContext,
safetyLevel: SafetyLevel,
unitsOfDiversion: Seq[UnitOfDiversion] = Seq.empty
): Params = {
val config = configBuilder.build(safetyLevel)
val requestContext = contextFactory(viewerContext, safetyLevel, unitsOfDiversion)
config.apply(requestContext, paramStats)
}
def memoized(
viewerContext: ViewerContext,
safetyLevel: SafetyLevel,
unitsOfDiversion: Seq[UnitOfDiversion] = Seq.empty
): Params = {
val config = configBuilder.buildMemoized(safetyLevel)
val requestContext = contextFactory(viewerContext, safetyLevel, unitsOfDiversion)
config.apply(requestContext, paramStats)
}
}

View File

@ -1,14 +0,0 @@
package com.twitter.visibility.configapi
import com.twitter.timelines.configapi._
case class VisibilityRequestContext(
userId: Option[Long],
guestId: Option[Long],
experimentContext: ExperimentContext = NullExperimentContext,
featureContext: FeatureContext = NullFeatureContext)
extends BaseRequestContext
with WithUserId
with WithGuestId
with WithExperimentContext
with WithFeatureContext

View File

@ -1,64 +0,0 @@
package com.twitter.visibility.configapi
import com.twitter.abdecider.LoggingABDecider
import com.twitter.featureswitches.FSRecipient
import com.twitter.featureswitches.v2.FeatureSwitches
import com.twitter.timelines.configapi.abdecider.UserRecipientExperimentContextFactory
import com.twitter.timelines.configapi.featureswitches.v2.FeatureSwitchResultsFeatureContext
import com.twitter.timelines.configapi.FeatureContext
import com.twitter.timelines.configapi.NullExperimentContext
import com.twitter.timelines.configapi.UseFeatureContextExperimentContext
import com.twitter.visibility.models.SafetyLevel
import com.twitter.visibility.models.UnitOfDiversion
import com.twitter.visibility.models.ViewerContext
class VisibilityRequestContextFactory(
loggingABDecider: LoggingABDecider,
featureSwitches: FeatureSwitches) {
private val userExperimentContextFactory = new UserRecipientExperimentContextFactory(
loggingABDecider
)
private[this] def getFeatureContext(
context: ViewerContext,
safetyLevel: SafetyLevel,
unitsOfDiversion: Seq[UnitOfDiversion]
): FeatureContext = {
val uodCustomFields = unitsOfDiversion.map(_.apply)
val recipient = FSRecipient(
userId = context.userId,
guestId = context.guestId,
userAgent = context.fsUserAgent,
clientApplicationId = context.clientApplicationId,
countryCode = context.requestCountryCode,
deviceId = context.deviceId,
languageCode = context.requestLanguageCode,
isTwoffice = Some(context.isTwOffice),
userRoles = context.userRoles,
).withCustomFields(("safety_level", safetyLevel.name), uodCustomFields: _*)
val results = featureSwitches.matchRecipient(recipient)
new FeatureSwitchResultsFeatureContext(results)
}
def apply(
context: ViewerContext,
safetyLevel: SafetyLevel,
unitsOfDiversion: Seq[UnitOfDiversion] = Seq.empty
): VisibilityRequestContext = {
val experimentContextBase =
context.userId
.map(userId => userExperimentContextFactory.apply(userId)).getOrElse(NullExperimentContext)
val featureContext = getFeatureContext(context, safetyLevel, unitsOfDiversion)
val experimentContext =
UseFeatureContextExperimentContext(experimentContextBase, featureContext)
VisibilityRequestContext(
context.userId,
context.guestId,
experimentContext,
featureContext
)
}
}

View File

@ -1,17 +0,0 @@
scala_library(
sources = ["*.scala"],
compiler_option_sets = ["fatal_warnings"],
platform = "java8",
strict_deps = True,
tags = ["bazel-compatible"],
dependencies = [
"configapi/configapi-core",
"configapi/configapi-decider",
"decider",
"finagle/finagle-stats",
"servo/decider",
"servo/decider/src/main/scala",
"visibility/lib/src/main/scala/com/twitter/visibility/configapi/params",
"visibility/lib/src/main/scala/com/twitter/visibility/models",
],
)

View File

@ -1,26 +0,0 @@
package com.twitter.visibility.configapi.configs
import com.twitter.timelines.configapi.Config
import com.twitter.timelines.configapi.ExperimentConfigBuilder
import com.twitter.timelines.configapi.Param
import com.twitter.visibility.configapi.params.VisibilityExperiment
import com.twitter.visibility.models.SafetyLevel
object ExperimentsHelper {
def mkABExperimentConfig(experiment: VisibilityExperiment, param: Param[Boolean]): Config = {
ExperimentConfigBuilder(experiment)
.addBucket(
experiment.ControlBucket,
param := true
)
.addBucket(
experiment.TreatmentBucket,
param := false
)
.build
}
def mkABExperimentConfig(experiment: VisibilityExperiment, safetyLevel: SafetyLevel): Config =
mkABExperimentConfig(experiment, safetyLevel.enabledParam)
}

View File

@ -1,73 +0,0 @@
package com.twitter.visibility.configapi.configs
import com.twitter.decider.Decider
import com.twitter.servo.gate.DeciderGate
import com.twitter.servo.util.Gate
case class VisibilityDeciderGates(decider: Decider) {
import DeciderKey._
private[this] def feature(deciderKey: Value) = decider.feature(deciderKey.toString)
val enableFetchTweetReportedPerspective: Gate[Unit] =
DeciderGate.linear(feature(DeciderKey.EnableFetchTweetReportedPerspective))
val enableFetchTweetMediaMetadata: Gate[Unit] =
DeciderGate.linear(feature(DeciderKey.EnableFetchTweetMediaMetadata))
val enableFollowCheckInMutedKeyword: Gate[Unit] =
DeciderGate.linear(feature(DeciderKey.VisibilityLibraryEnableFollowCheckInMutedKeyword))
val enableMediaInterstitialComposition: Gate[Unit] =
DeciderGate.linear(feature(DeciderKey.VisibilityLibraryEnableMediaInterstitialComposition))
val enableExperimentalRuleEngine: Gate[Unit] =
DeciderGate.linear(feature(DeciderKey.EnableExperimentalRuleEngine))
val enableLocalizedInterstitialGenerator: Gate[Unit] =
DeciderGate.linear(feature(DeciderKey.EnableLocalizedInterstitialGenerator))
val enableShortCircuitingTVL: Gate[Unit] =
DeciderGate.linear(feature(EnableShortCircuitingFromTweetVisibilityLibrary))
val enableVerdictLoggerTVL = DeciderGate.linear(
feature(DeciderKey.EnableVerdictLoggerEventPublisherInstantiationFromTweetVisibilityLibrary))
val enableVerdictScribingTVL =
DeciderGate.linear(feature(DeciderKey.EnableVerdictScribingFromTweetVisibilityLibrary))
val enableBackendLimitedActions =
DeciderGate.linear(feature(DeciderKey.EnableBackendLimitedActions))
val enableMemoizeSafetyLevelParams: Gate[Unit] =
DeciderGate.linear(feature(DeciderKey.EnableMemoizeSafetyLevelParams))
val enableShortCircuitingBVL: Gate[Unit] =
DeciderGate.linear(feature(EnableShortCircuitingFromBlenderVisibilityLibrary))
val enableVerdictLoggerBVL = DeciderGate.linear(
feature(DeciderKey.EnableVerdictLoggerEventPublisherInstantiationFromBlenderVisibilityLibrary))
val enableVerdictScribingBVL =
DeciderGate.linear(feature(DeciderKey.EnableVerdictScribingFromBlenderVisibilityLibrary))
val enableShortCircuitingSVL: Gate[Unit] =
DeciderGate.linear(feature(EnableShortCircuitingFromSearchVisibilityLibrary))
val enableVerdictLoggerSVL = DeciderGate.linear(
feature(DeciderKey.EnableVerdictLoggerEventPublisherInstantiationFromSearchVisibilityLibrary))
val enableVerdictScribingSVL =
DeciderGate.linear(feature(DeciderKey.EnableVerdictScribingFromSearchVisibilityLibrary))
val enableShortCircuitingTCVL: Gate[Unit] =
DeciderGate.linear(feature(EnableShortCircuitingFromTimelineConversationsVisibilityLibrary))
val enableVerdictLoggerTCVL = DeciderGate.linear(feature(
DeciderKey.EnableVerdictLoggerEventPublisherInstantiationFromTimelineConversationsVisibilityLibrary))
val enableVerdictScribingTCVL =
DeciderGate.linear(
feature(DeciderKey.EnableVerdictScribingFromTimelineConversationsVisibilityLibrary))
val enableCardVisibilityLibraryCardUriParsing =
DeciderGate.linear(feature(DeciderKey.EnableCardVisibilityLibraryCardUriParsing))
val enableConvosLocalizedInterstitial: Gate[Unit] =
DeciderGate.linear(feature(DeciderKey.EnableConvosLocalizedInterstitial))
val enableConvosLegacyInterstitial: Gate[Unit] =
DeciderGate.linear(feature(DeciderKey.EnableConvosLegacyInterstitial))
val disableLegacyInterstitialFilteredReason: Gate[Unit] =
DeciderGate.linear(feature(DeciderKey.DisableLegacyInterstitialFilteredReason))
val enableLocalizedInterstitialInUserStateLibrary: Gate[Unit] =
DeciderGate.linear(feature(DeciderKey.EnableLocalizedInterstitialInUserStateLib))
}

View File

@ -1,390 +0,0 @@
package com.twitter.visibility.configapi.configs
import com.twitter.decider.Recipient
import com.twitter.decider.SimpleRecipient
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.logging.Logger
import com.twitter.servo.decider.DeciderGateBuilder
import com.twitter.timelines.configapi.BaseConfigBuilder
import com.twitter.timelines.configapi.BaseRequestContext
import com.twitter.timelines.configapi.Config
import com.twitter.timelines.configapi.Param
import com.twitter.timelines.configapi.WithGuestId
import com.twitter.timelines.configapi.WithUserId
import com.twitter.timelines.configapi.decider.DeciderSwitchOverrideValue
import com.twitter.timelines.configapi.decider.GuestRecipient
import com.twitter.timelines.configapi.decider.RecipientBuilder
import com.twitter.visibility.configapi.params.RuleParams
import com.twitter.visibility.configapi.params.TimelineConversationsDownrankingSpecificParams
import com.twitter.visibility.models.SafetyLevel
import com.twitter.visibility.models.SafetyLevel._
private[visibility] object VisibilityDeciders {
val SafetyLevelToDeciderMap: Map[SafetyLevel, DeciderKey.Value] = Map(
AllSubscribedLists -> DeciderKey.EnableAllSubscribedListsSafetyLevel,
AccessInternalPromotedContent -> DeciderKey.EnableAccessInternalPromotedContentSafetyLevel,
AdsBusinessSettings -> DeciderKey.EnableAdsBusinessSettingsSafetyLevel,
AdsCampaign -> DeciderKey.EnableAdsCampaignSafetyLevel,
AdsManager -> DeciderKey.EnableAdsManagerSafetyLevel,
AdsReportingDashboard -> DeciderKey.EnableAdsReportingDashboardSafetyLevel,
Appeals -> DeciderKey.EnableAppealsSafetyLevel,
ArticleTweetTimeline -> DeciderKey.EnableArticleTweetTimelineSafetyLevel,
BaseQig -> DeciderKey.EnableBaseQig,
BirdwatchNoteAuthor -> DeciderKey.EnableBirdwatchNoteAuthorSafetyLevel,
BirdwatchNoteTweetsTimeline -> DeciderKey.EnableBirdwatchNoteTweetsTimelineSafetyLevel,
BirdwatchNeedsYourHelpNotifications -> DeciderKey.EnableBirdwatchNeedsYourHelpNotificationsSafetyLevel,
BlockMuteUsersTimeline -> DeciderKey.EnableBlockMuteUsersTimelineSafetyLevel,
BrandSafety -> DeciderKey.EnableBrandSafetySafetyLevel,
CardPollVoting -> DeciderKey.EnableCardPollVotingSafetyLevel,
CardsService -> DeciderKey.EnableCardsServiceSafetyLevel,
Communities -> DeciderKey.EnableCommunitiesSafetyLevel,
ContentControlToolInstall -> DeciderKey.EnableContentControlToolInstallSafetyLevel,
ConversationFocalPrehydration -> DeciderKey.EnableConversationFocalPrehydrationSafetyLevel,
ConversationFocalTweet -> DeciderKey.EnableConversationFocalTweetSafetyLevel,
ConversationInjectedTweet -> DeciderKey.EnableConversationInjectedTweetSafetyLevel,
ConversationReply -> DeciderKey.EnableConversationReplySafetyLevel,
CuratedTrendsRepresentativeTweet -> DeciderKey.EnableCuratedTrendsRepresentativeTweet,
CurationPolicyViolations -> DeciderKey.EnableCurationPolicyViolations,
DeprecatedSafetyLevel -> DeciderKey.EnableDeprecatedSafetyLevelSafetyLevel,
DevPlatformGetListTweets -> DeciderKey.EnableDevPlatformGetListTweetsSafetyLevel,
DesFollowingAndFollowersUserList -> DeciderKey.EnableDesFollowingAndFollowersUserListSafetyLevel,
DesHomeTimeline -> DeciderKey.EnableDesHomeTimelineSafetyLevel,
DesQuoteTweetTimeline -> DeciderKey.EnableDesQuoteTweetTimelineSafetyLevel,
DesRealtime -> DeciderKey.EnableDesRealtimeSafetyLevel,
DesRealtimeSpamEnrichment -> DeciderKey.EnableDesRealtimeSpamEnrichmentSafetyLevel,
DesRealtimeTweetFilter -> DeciderKey.EnableDesRealtimeTweetFilterSafetyLevel,
DesRetweetingUsers -> DeciderKey.EnableDesRetweetingUsersSafetyLevel,
DesTweetDetail -> DeciderKey.EnableDesTweetDetailSafetyLevel,
DesTweetLikingUsers -> DeciderKey.EnableDesTweetLikingUsersSafetyLevel,
DesUserBookmarks -> DeciderKey.EnableDesUserBookmarksSafetyLevel,
DesUserLikedTweets -> DeciderKey.EnableDesUserLikedTweetsSafetyLevel,
DesUserMentions -> DeciderKey.EnableDesUserMentionsSafetyLevel,
DesUserTweets -> DeciderKey.EnableDesUserTweetsSafetyLevel,
DevPlatformComplianceStream -> DeciderKey.EnableDevPlatformComplianceStreamSafetyLevel,
DirectMessages -> DeciderKey.EnableDirectMessagesSafetyLevel,
DirectMessagesConversationList -> DeciderKey.EnableDirectMessagesConversationListSafetyLevel,
DirectMessagesConversationTimeline -> DeciderKey.EnableDirectMessagesConversationTimelineSafetyLevel,
DirectMessagesInbox -> DeciderKey.EnableDirectMessagesInboxSafetyLevel,
DirectMessagesMutedUsers -> DeciderKey.EnableDirectMessagesMutedUsersSafetyLevel,
DirectMessagesPinned -> DeciderKey.EnableDirectMessagesPinnedSafetyLevel,
DirectMessagesSearch -> DeciderKey.EnableDirectMessagesSearchSafetyLevel,
EditHistoryTimeline -> DeciderKey.EnableEditHistoryTimelineSafetyLevel,
ElevatedQuoteTweetTimeline -> DeciderKey.EnableElevatedQuoteTweetTimelineSafetyLevel,
EmbeddedTweet -> DeciderKey.EnableEmbeddedTweetSafetyLevel,
EmbedsPublicInterestNotice -> DeciderKey.EnableEmbedsPublicInterestNoticeSafetyLevel,
EmbedTweetMarkup -> DeciderKey.EnableEmbedTweetMarkupSafetyLevel,
FilterAll -> DeciderKey.EnableFilterAllSafetyLevel,
FilterAllPlaceholder -> DeciderKey.EnableFilterAllPlaceholderSafetyLevel,
FilterNone -> DeciderKey.EnableFilterNoneSafetyLevel,
FilterDefault -> DeciderKey.EnableFilterDefaultSafetyLevel,
FollowedTopicsTimeline -> DeciderKey.EnableFollowedTopicsTimelineSafetyLevel,
FollowerConnections -> DeciderKey.EnableFollowerConnectionsSafetyLevel,
FollowingAndFollowersUserList -> DeciderKey.EnableFollowingAndFollowersUserListSafetyLevel,
ForDevelopmentOnly -> DeciderKey.EnableForDevelopmentOnlySafetyLevel,
FriendsFollowingList -> DeciderKey.EnableFriendsFollowingListSafetyLevel,
GraphqlDefault -> DeciderKey.EnableGraphqlDefaultSafetyLevel,
GryphonDecksAndColumns -> DeciderKey.EnableGryphonDecksAndColumnsSafetyLevel,
HumanizationNudge -> DeciderKey.EnableHumanizationNudgeSafetyLevel,
KitchenSinkDevelopment -> DeciderKey.EnableKitchenSinkDevelopmentSafetyLevel,
ListHeader -> DeciderKey.EnableListHeaderSafetyLevel,
ListMemberships -> DeciderKey.EnableListMembershipsSafetyLevel,
ListOwnerships -> DeciderKey.EnableListOwnershipsSafetyLevel,
ListRecommendations -> DeciderKey.EnableListRecommendationsSafetyLevel,
ListSearch -> DeciderKey.EnableListSearchSafetyLevel,
ListSubscriptions -> DeciderKey.EnableListSubscriptionsSafetyLevel,
LiveVideoTimeline -> DeciderKey.EnableLiveVideoTimelineSafetyLevel,
LivePipelineEngagementCounts -> DeciderKey.EnableLivePipelineEngagementCountsSafetyLevel,
MagicRecs -> DeciderKey.EnableMagicRecsSafetyLevel,
MagicRecsAggressive -> DeciderKey.EnableMagicRecsAggressiveSafetyLevel,
MagicRecsAggressiveV2 -> DeciderKey.EnableMagicRecsAggressiveV2SafetyLevel,
MagicRecsV2 -> DeciderKey.EnableMagicRecsV2SafetyLevel,
Minimal -> DeciderKey.EnableMinimalSafetyLevel,
ModeratedTweetsTimeline -> DeciderKey.EnableModeratedTweetsTimelineSafetyLevel,
Moments -> DeciderKey.EnableMomentsSafetyLevel,
NearbyTimeline -> DeciderKey.EnableNearbyTimelineSafetyLevel,
NewUserExperience -> DeciderKey.EnableNewUserExperienceSafetyLevel,
NotificationsIbis -> DeciderKey.EnableNotificationsIbisSafetyLevel,
NotificationsPlatform -> DeciderKey.EnableNotificationsPlatformSafetyLevel,
NotificationsPlatformPush -> DeciderKey.EnableNotificationsPlatformPushSafetyLevel,
NotificationsQig -> DeciderKey.EnableNotificationsQig,
NotificationsRead -> DeciderKey.EnableNotificationsReadSafetyLevel,
NotificationsTimelineDeviceFollow -> DeciderKey.EnableNotificationsTimelineDeviceFollowSafetyLevel,
NotificationsWrite -> DeciderKey.EnableNotificationsWriteSafetyLevel,
NotificationsWriterV2 -> DeciderKey.EnableNotificationsWriterV2SafetyLevel,
NotificationsWriterTweetHydrator -> DeciderKey.EnableNotificationsWriterTweetHydratorSafetyLevel,
ProfileMixerMedia -> DeciderKey.EnableProfileMixeMediaSafetyLevel,
ProfileMixerFavorites -> DeciderKey.EnableProfileMixerFavoritesSafetyLevel,
QuickPromoteTweetEligibility -> DeciderKey.EnableQuickPromoteTweetEligibilitySafetyLevel,
QuoteTweetTimeline -> DeciderKey.EnableQuoteTweetTimelineSafetyLevel,
QuotedTweetRules -> DeciderKey.EnableQuotedTweetRulesSafetyLevel,
Recommendations -> DeciderKey.EnableRecommendationsSafetyLevel,
RecosVideo -> DeciderKey.EnableRecosVideoSafetyLevel,
RecosWritePath -> DeciderKey.EnableRecosWritePathSafetyLevel,
RepliesGrouping -> DeciderKey.EnableRepliesGroupingSafetyLevel,
ReportCenter -> DeciderKey.EnableReportCenterSafetyLevel,
ReturningUserExperience -> DeciderKey.EnableReturningUserExperienceSafetyLevel,
ReturningUserExperienceFocalTweet -> DeciderKey.EnableReturningUserExperienceFocalTweetSafetyLevel,
Revenue -> DeciderKey.EnableRevenueSafetyLevel,
RitoActionedTweetTimeline -> DeciderKey.EnableRitoActionedTweetTimelineSafetyLevel,
SafeSearchMinimal -> DeciderKey.EnableSafeSearchMinimalSafetyLevel,
SafeSearchStrict -> DeciderKey.EnableSafeSearchStrictSafetyLevel,
SearchMixerSrpMinimal -> DeciderKey.EnableSearchMixerSrpMinimalSafetyLevel,
SearchMixerSrpStrict -> DeciderKey.EnableSearchMixerSrpStrictSafetyLevel,
SearchHydration -> DeciderKey.EnableSearchHydration,
SearchLatest -> DeciderKey.EnableSearchLatest,
SearchPeopleSrp -> DeciderKey.EnableSearchPeopleSrp,
SearchPeopleTypeahead -> DeciderKey.EnableSearchPeopleTypeahead,
SearchPhoto -> DeciderKey.EnableSearchPhoto,
SearchTrendTakeoverPromotedTweet -> DeciderKey.EnableSearchTrendTakeoverPromotedTweet,
SearchTop -> DeciderKey.EnableSearchTop,
SearchTopQig -> DeciderKey.EnableSearchTopQig,
SearchVideo -> DeciderKey.EnableSearchVideo,
SearchBlenderUserRules -> DeciderKey.EnableSearchLatestUserRules,
SearchLatestUserRules -> DeciderKey.EnableSearchLatestUserRules,
ShoppingManagerSpyMode -> DeciderKey.EnableShoppingManagerSpyModeSafetyLevel,
SignalsReactions -> DeciderKey.EnableSignalsReactions,
SignalsTweetReactingUsers -> DeciderKey.EnableSignalsTweetReactingUsers,
SocialProof -> DeciderKey.EnableSocialProof,
SoftInterventionPivot -> DeciderKey.EnableSoftInterventionPivot,
SpaceFleetline -> DeciderKey.EnableSpaceFleetlineSafetyLevel,
SpaceHomeTimelineUpranking -> DeciderKey.EnableSpaceHomeTimelineUprankingSafetyLevel,
SpaceJoinScreen -> DeciderKey.EnableSpaceJoinScreenSafetyLevel,
SpaceNotifications -> DeciderKey.EnableSpaceNotificationsSafetyLevel,
Spaces -> DeciderKey.EnableSpacesSafetyLevel,
SpacesParticipants -> DeciderKey.EnableSpacesParticipantsSafetyLevel,
SpacesSellerApplicationStatus -> DeciderKey.EnableSpacesSellerApplicationStatus,
SpacesSharing -> DeciderKey.EnableSpacesSharingSafetyLevel,
SpaceTweetAvatarHomeTimeline -> DeciderKey.EnableSpaceTweetAvatarHomeTimelineSafetyLevel,
StickersTimeline -> DeciderKey.EnableStickersTimelineSafetyLevel,
StratoExtLimitedEngagements -> DeciderKey.EnableStratoExtLimitedEngagementsSafetyLevel,
StreamServices -> DeciderKey.EnableStreamServicesSafetyLevel,
SuperFollowerConnections -> DeciderKey.EnableSuperFollowerConnectionsSafetyLevel,
SuperLike -> DeciderKey.EnableSuperLikeSafetyLevel,
Test -> DeciderKey.EnableTestSafetyLevel,
TimelineContentControls -> DeciderKey.EnableTimelineContentControlsSafetyLevel,
TimelineConversations -> DeciderKey.EnableTimelineConversationsSafetyLevel,
TimelineConversationsDownranking -> DeciderKey.EnableTimelineConversationsDownrankingSafetyLevel,
TimelineConversationsDownrankingMinimal -> DeciderKey.EnableTimelineConversationsDownrankingMinimalSafetyLevel,
TimelineFollowingActivity -> DeciderKey.EnableTimelineFollowingActivitySafetyLevel,
TimelineHome -> DeciderKey.EnableTimelineHomeSafetyLevel,
TimelineHomeCommunities -> DeciderKey.EnableTimelineHomeCommunitiesSafetyLevel,
TimelineHomeHydration -> DeciderKey.EnableTimelineHomeHydrationSafetyLevel,
TimelineHomePromotedHydration -> DeciderKey.EnableTimelineHomePromotedHydrationSafetyLevel,
TimelineHomeRecommendations -> DeciderKey.EnableTimelineHomeRecommendationsSafetyLevel,
TimelineHomeTopicFollowRecommendations -> DeciderKey.EnableTimelineHomeTopicFollowRecommendationsSafetyLevel,
TimelineScorer -> DeciderKey.EnableTimelineScorerSafetyLevel,
TopicsLandingPageTopicRecommendations -> DeciderKey.EnableTopicsLandingPageTopicRecommendationsSafetyLevel,
ExploreRecommendations -> DeciderKey.EnableExploreRecommendationsSafetyLevel,
TimelineInjection -> DeciderKey.EnableTimelineInjectionSafetyLevel,
TimelineMentions -> DeciderKey.EnableTimelineMentionsSafetyLevel,
TimelineModeratedTweetsHydration -> DeciderKey.EnableTimelineModeratedTweetsHydrationSafetyLevel,
TimelineHomeLatest -> DeciderKey.EnableTimelineHomeLatestSafetyLevel,
TimelineLikedBy -> DeciderKey.EnableTimelineLikedBySafetyLevel,
TimelineRetweetedBy -> DeciderKey.EnableTimelineRetweetedBySafetyLevel,
TimelineSuperLikedBy -> DeciderKey.EnableTimelineSuperLikedBySafetyLevel,
TimelineBookmark -> DeciderKey.EnableTimelineBookmarkSafetyLevel,
TimelineMedia -> DeciderKey.EnableTimelineMediaSafetyLevel,
TimelineReactiveBlending -> DeciderKey.EnableTimelineReactiveBlendingSafetyLevel,
TimelineFavorites -> DeciderKey.EnableTimelineFavoritesSafetyLevel,
TimelineFavoritesSelfView -> DeciderKey.EnableSelfViewTimelineFavoritesSafetyLevel,
TimelineLists -> DeciderKey.EnableTimelineListsSafetyLevel,
TimelineProfile -> DeciderKey.EnableTimelineProfileSafetyLevel,
TimelineProfileAll -> DeciderKey.EnableTimelineProfileAllSafetyLevel,
TimelineProfileSpaces -> DeciderKey.EnableTimelineProfileSpacesSafetyLevel,
TimelineProfileSuperFollows -> DeciderKey.EnableTimelineProfileSuperFollowsSafetyLevel,
TimelineFocalTweet -> DeciderKey.EnableTweetTimelineFocalTweetSafetyLevel,
TweetDetailWithInjectionsHydration -> DeciderKey.EnableTweetDetailWithInjectionsHydrationSafetyLevel,
Tombstoning -> DeciderKey.EnableTombstoningSafetyLevel,
TopicRecommendations -> DeciderKey.EnableTopicRecommendationsSafetyLevel,
TrendsRepresentativeTweet -> DeciderKey.EnableTrendsRepresentativeTweetSafetyLevel,
TrustedFriendsUserList -> DeciderKey.EnableTrustedFriendsUserListSafetyLevel,
TwitterDelegateUserList -> DeciderKey.EnableTwitterDelegateUserListSafetyLevel,
TweetDetail -> DeciderKey.EnableTweetDetailSafetyLevel,
TweetDetailNonToo -> DeciderKey.EnableTweetDetailNonTooSafetyLevel,
TweetEngagers -> DeciderKey.EnableTweetEngagersSafetyLevel,
TweetReplyNudge -> DeciderKey.EnableTweetReplyNudgeSafetyLevel,
TweetScopedTimeline -> DeciderKey.EnableTweetScopedTimelineSafetyLevel,
TweetWritesApi -> DeciderKey.EnableTweetWritesApiSafetyLevel,
TwitterArticleCompose -> DeciderKey.EnableTwitterArticleComposeSafetyLevel,
TwitterArticleProfileTab -> DeciderKey.EnableTwitterArticleProfileTabSafetyLevel,
TwitterArticleRead -> DeciderKey.EnableTwitterArticleReadSafetyLevel,
UserProfileHeader -> DeciderKey.EnableUserProfileHeaderSafetyLevel,
UserMilestoneRecommendation -> DeciderKey.EnableUserMilestoneRecommendationSafetyLevel,
UserScopedTimeline -> DeciderKey.EnableUserScopedTimelineSafetyLevel,
UserSearchSrp -> DeciderKey.EnableUserSearchSrpSafetyLevel,
UserSearchTypeahead -> DeciderKey.EnableUserSearchTypeaheadSafetyLevel,
UserSelfViewOnly -> DeciderKey.EnableUserSelfViewOnlySafetyLevel,
UserSettings -> DeciderKey.EnableUserSettingsSafetyLevel,
VideoAds -> DeciderKey.EnableVideoAdsSafetyLevel,
WritePathLimitedActionsEnforcement -> DeciderKey.EnableWritePathLimitedActionsEnforcementSafetyLevel,
ZipbirdConsumerArchives -> DeciderKey.EnableZipbirdConsumerArchivesSafetyLevel,
TweetAward -> DeciderKey.EnableTweetAwardSafetyLevel,
)
val BoolToDeciderMap: Map[Param[Boolean], DeciderKey.Value] = Map(
RuleParams.TweetConversationControlEnabledParam ->
DeciderKey.EnableTweetConversationControlRules,
RuleParams.CommunityTweetsEnabledParam ->
DeciderKey.EnableCommunityTweetsControlRules,
RuleParams.DropCommunityTweetWithUndefinedCommunityRuleEnabledParam ->
DeciderKey.EnableDropCommunityTweetWithUndefinedCommunityRule,
TimelineConversationsDownrankingSpecificParams.EnablePSpammyTweetDownrankConvosLowQualityParam ->
DeciderKey.EnablePSpammyTweetDownrankConvosLowQuality,
RuleParams.EnableHighPSpammyTweetScoreSearchTweetLabelDropRuleParam ->
DeciderKey.EnableHighPSpammyTweetScoreSearchTweetLabelDropRule,
TimelineConversationsDownrankingSpecificParams.EnableRitoActionedTweetDownrankConvosLowQualityParam ->
DeciderKey.EnableRitoActionedTweetDownrankConvosLowQuality,
RuleParams.EnableSmyteSpamTweetRuleParam ->
DeciderKey.EnableSmyteSpamTweetRule,
RuleParams.EnableHighSpammyTweetContentScoreSearchLatestTweetLabelDropRuleParam ->
DeciderKey.EnableHighSpammyTweetContentScoreSearchLatestTweetLabelDropRule,
RuleParams.EnableHighSpammyTweetContentScoreSearchTopTweetLabelDropRuleParam ->
DeciderKey.EnableHighSpammyTweetContentScoreSearchTopTweetLabelDropRule,
RuleParams.EnableHighSpammyTweetContentScoreTrendsTopTweetLabelDropRuleParam ->
DeciderKey.EnableHighSpammyTweetContentScoreTrendsTopTweetLabelDropRule,
RuleParams.EnableHighSpammyTweetContentScoreTrendsLatestTweetLabelDropRuleParam ->
DeciderKey.EnableHighSpammyTweetContentScoreTrendsLatestTweetLabelDropRule,
TimelineConversationsDownrankingSpecificParams.EnableHighSpammyTweetContentScoreConvoDownrankAbusiveQualityRuleParam ->
DeciderKey.EnableHighSpammyTweetContentScoreConvoDownrankAbusiveQualityRule,
TimelineConversationsDownrankingSpecificParams.EnableHighCryptospamScoreConvoDownrankAbusiveQualityRuleParam ->
DeciderKey.EnableHighCryptospamScoreConvoDownrankAbusiveQualityRule,
RuleParams.EnableGoreAndViolenceTopicHighRecallTweetLabelRule ->
DeciderKey.EnableGoreAndViolenceTopicHighRecallTweetLabelRule,
RuleParams.EnableLimitRepliesFollowersConversationRule ->
DeciderKey.EnableLimitRepliesFollowersConversationRule,
RuleParams.EnableSearchBasicBlockMuteRulesParam -> DeciderKey.EnableSearchBasicBlockMuteRules,
RuleParams.EnableBlinkBadDownrankingRuleParam ->
DeciderKey.EnableBlinkBadDownrankingRule,
RuleParams.EnableBlinkWorstDownrankingRuleParam ->
DeciderKey.EnableBlinkWorstDownrankingRule,
RuleParams.EnableCopypastaSpamDownrankConvosAbusiveQualityRule ->
DeciderKey.EnableCopypastaSpamDownrankConvosAbusiveQualityRule,
RuleParams.EnableCopypastaSpamSearchDropRule ->
DeciderKey.EnableCopypastaSpamSearchDropRule,
RuleParams.EnableSpammyUserModelTweetDropRuleParam ->
DeciderKey.EnableSpammyUserModelHighPrecisionDropTweetRule,
RuleParams.EnableAvoidNsfwRulesParam ->
DeciderKey.EnableAvoidNsfwRules,
RuleParams.EnableReportedTweetInterstitialRule ->
DeciderKey.EnableReportedTweetInterstitialRule,
RuleParams.EnableReportedTweetInterstitialSearchRule ->
DeciderKey.EnableReportedTweetInterstitialSearchRule,
RuleParams.EnableDropExclusiveTweetContentRule ->
DeciderKey.EnableDropExclusiveTweetContentRule,
RuleParams.EnableDropExclusiveTweetContentRuleFailClosed ->
DeciderKey.EnableDropExclusiveTweetContentRuleFailClosed,
RuleParams.EnableTombstoneExclusiveQtProfileTimelineParam ->
DeciderKey.EnableTombstoneExclusiveQtProfileTimelineParam,
RuleParams.EnableDropAllExclusiveTweetsRuleParam -> DeciderKey.EnableDropAllExclusiveTweetsRule,
RuleParams.EnableDropAllExclusiveTweetsRuleFailClosedParam -> DeciderKey.EnableDropAllExclusiveTweetsRuleFailClosed,
RuleParams.EnableDownrankSpamReplySectioningRuleParam ->
DeciderKey.EnableDownrankSpamReplySectioningRule,
RuleParams.EnableNsfwTextSectioningRuleParam ->
DeciderKey.EnableNsfwTextSectioningRule,
RuleParams.EnableSearchIpiSafeSearchWithoutUserInQueryDropRule -> DeciderKey.EnableSearchIpiSafeSearchWithoutUserInQueryDropRule,
RuleParams.EnableTimelineHomePromotedTweetHealthEnforcementRules -> DeciderKey.EnableTimelineHomePromotedTweetHealthEnforcementRules,
RuleParams.EnableMutedKeywordFilteringSpaceTitleNotificationsRuleParam -> DeciderKey.EnableMutedKeywordFilteringSpaceTitleNotificationsRule,
RuleParams.EnableDropTweetsWithGeoRestrictedMediaRuleParam -> DeciderKey.EnableDropTweetsWithGeoRestrictedMediaRule,
RuleParams.EnableDropAllTrustedFriendsTweetsRuleParam -> DeciderKey.EnableDropAllTrustedFriendsTweetsRule,
RuleParams.EnableDropTrustedFriendsTweetContentRuleParam -> DeciderKey.EnableDropTrustedFriendsTweetContentRule,
RuleParams.EnableDropAllCollabInvitationTweetsRuleParam -> DeciderKey.EnableDropCollabInvitationTweetsRule,
RuleParams.EnableNsfwTextHighPrecisionDropRuleParam -> DeciderKey.EnableNsfwTextHighPrecisionDropRule,
RuleParams.EnableLikelyIvsUserLabelDropRule -> DeciderKey.EnableLikelyIvsUserLabelDropRule,
RuleParams.EnableCardUriRootDomainCardDenylistRule -> DeciderKey.EnableCardUriRootDomainDenylistRule,
RuleParams.EnableCommunityNonMemberPollCardRule -> DeciderKey.EnableCommunityNonMemberPollCardRule,
RuleParams.EnableCommunityNonMemberPollCardRuleFailClosed -> DeciderKey.EnableCommunityNonMemberPollCardRuleFailClosed,
RuleParams.EnableExperimentalNudgeEnabledParam -> DeciderKey.EnableExperimentalNudgeLabelRule,
RuleParams.NsfwHighPrecisionUserLabelAvoidTweetRuleEnabledParam -> DeciderKey.NsfwHighPrecisionUserLabelAvoidTweetRuleEnabledParam,
RuleParams.EnableNewAdAvoidanceRulesParam -> DeciderKey.EnableNewAdAvoidanceRules,
RuleParams.EnableNsfaHighRecallAdAvoidanceParam -> DeciderKey.EnableNsfaHighRecallAdAvoidanceParam,
RuleParams.EnableNsfaKeywordsHighPrecisionAdAvoidanceParam -> DeciderKey.EnableNsfaKeywordsHighPrecisionAdAvoidanceParam,
RuleParams.EnableStaleTweetDropRuleParam -> DeciderKey.EnableStaleTweetDropRuleParam,
RuleParams.EnableStaleTweetDropRuleFailClosedParam -> DeciderKey.EnableStaleTweetDropRuleFailClosedParam,
RuleParams.EnableDeleteStateTweetRulesParam -> DeciderKey.EnableDeleteStateTweetRules,
RuleParams.EnableSpacesSharingNsfwDropRulesParam -> DeciderKey.EnableSpacesSharingNsfwDropRulesParam,
RuleParams.EnableViewerIsSoftUserDropRuleParam -> DeciderKey.EnableViewerIsSoftUserDropRuleParam,
RuleParams.EnablePdnaQuotedTweetTombstoneRuleParam -> DeciderKey.EnablePdnaQuotedTweetTombstoneRule,
RuleParams.EnableSpamQuotedTweetTombstoneRuleParam -> DeciderKey.EnableSpamQuotedTweetTombstoneRule,
RuleParams.EnableNsfwHpQuotedTweetDropRuleParam -> DeciderKey.EnableNsfwHpQuotedTweetDropRule,
RuleParams.EnableNsfwHpQuotedTweetTombstoneRuleParam -> DeciderKey.EnableNsfwHpQuotedTweetTombstoneRule,
RuleParams.EnableInnerQuotedTweetViewerBlocksAuthorInterstitialRuleParam -> DeciderKey.EnableInnerQuotedTweetViewerBlocksAuthorInterstitialRule,
RuleParams.EnableInnerQuotedTweetViewerMutesAuthorInterstitialRuleParam -> DeciderKey.EnableInnerQuotedTweetViewerMutesAuthorInterstitialRule,
RuleParams.EnableToxicReplyFilteringConversationRulesParam -> DeciderKey.VisibilityLibraryEnableToxicReplyFilterConversation,
RuleParams.EnableToxicReplyFilteringNotificationsRulesParam -> DeciderKey.VisibilityLibraryEnableToxicReplyFilterNotifications,
RuleParams.EnableLegacySensitiveMediaHomeTimelineRulesParam -> DeciderKey.EnableLegacySensitiveMediaRulesHomeTimeline,
RuleParams.EnableNewSensitiveMediaSettingsInterstitialsHomeTimelineRulesParam -> DeciderKey.EnableNewSensitiveMediaSettingsInterstitialRulesHomeTimeline,
RuleParams.EnableLegacySensitiveMediaConversationRulesParam -> DeciderKey.EnableLegacySensitiveMediaRulesConversation,
RuleParams.EnableNewSensitiveMediaSettingsInterstitialsConversationRulesParam -> DeciderKey.EnableNewSensitiveMediaSettingsInterstitialRulesConversation,
RuleParams.EnableLegacySensitiveMediaProfileTimelineRulesParam -> DeciderKey.EnableLegacySensitiveMediaRulesProfileTimeline,
RuleParams.EnableNewSensitiveMediaSettingsInterstitialsProfileTimelineRulesParam -> DeciderKey.EnableNewSensitiveMediaSettingsInterstitialRulesProfileTimeline,
RuleParams.EnableLegacySensitiveMediaTweetDetailRulesParam -> DeciderKey.EnableLegacySensitiveMediaRulesTweetDetail,
RuleParams.EnableNewSensitiveMediaSettingsInterstitialsTweetDetailRulesParam -> DeciderKey.EnableNewSensitiveMediaSettingsInterstitialRulesTweetDetail,
RuleParams.EnableLegacySensitiveMediaDirectMessagesRulesParam -> DeciderKey.EnableLegacySensitiveMediaRulesDirectMessages,
RuleParams.EnableAbusiveBehaviorDropRuleParam -> DeciderKey.EnableAbusiveBehaviorDropRule,
RuleParams.EnableAbusiveBehaviorInterstitialRuleParam -> DeciderKey.EnableAbusiveBehaviorInterstitialRule,
RuleParams.EnableAbusiveBehaviorLimitedEngagementsRuleParam -> DeciderKey.EnableAbusiveBehaviorLimitedEngagementsRule,
RuleParams.EnableNotGraduatedDownrankConvosAbusiveQualityRuleParam -> DeciderKey.EnableNotGraduatedDownrankConvosAbusiveQualityRule,
RuleParams.EnableNotGraduatedSearchDropRuleParam -> DeciderKey.EnableNotGraduatedSearchDropRule,
RuleParams.EnableNotGraduatedDropRuleParam -> DeciderKey.EnableNotGraduatedDropRule,
RuleParams.EnableFosnrRuleParam -> DeciderKey.EnableFosnrRules,
RuleParams.EnableAuthorBlocksViewerDropRuleParam -> DeciderKey.EnableAuthorBlocksViewerDropRule
)
def config(
deciderGateBuilder: DeciderGateBuilder,
logger: Logger,
statsReceiver: StatsReceiver,
SafetyLevel: SafetyLevel
): Config = {
object UserOrGuestOrRequest extends RecipientBuilder {
private val scopedStats = statsReceiver.scope("decider_recipient")
private val userIdDefinedCounter = scopedStats.counter("user_id_defined")
private val userIdNotDefinedCounter = scopedStats.counter("user_id_undefined")
private val guestIdDefinedCounter = scopedStats.counter("guest_id_defined")
private val guestIdNotDefinedCounter = scopedStats.counter("guest_id_undefined")
private val noIdCounter = scopedStats.counter("no_id_defined")
def apply(requestContext: BaseRequestContext): Option[Recipient] = requestContext match {
case c: WithUserId if c.userId.isDefined =>
userIdDefinedCounter.incr()
c.userId.map(SimpleRecipient)
case c: WithGuestId if c.guestId.isDefined =>
guestIdDefinedCounter.incr()
c.guestId.map(GuestRecipient)
case c: WithGuestId =>
guestIdNotDefinedCounter.incr()
RecipientBuilder.Request(c)
case _: WithUserId =>
userIdNotDefinedCounter.incr()
None
case _ =>
logger.warning("Request Context with no user or guest id trait found: " + requestContext)
noIdCounter.incr()
None
}
}
val boolOverrides = BoolToDeciderMap.map {
case (param, deciderKey) =>
param.optionalOverrideValue(
DeciderSwitchOverrideValue(
feature = deciderGateBuilder.keyToFeature(deciderKey),
enabledValue = true,
disabledValueOption = Some(false),
recipientBuilder = UserOrGuestOrRequest
)
)
}.toSeq
val safetyLevelOverride = SafetyLevel.enabledParam.optionalOverrideValue(
DeciderSwitchOverrideValue(
feature = deciderGateBuilder.keyToFeature(SafetyLevelToDeciderMap(SafetyLevel)),
enabledValue = true,
recipientBuilder = UserOrGuestOrRequest
)
)
BaseConfigBuilder(boolOverrides :+ safetyLevelOverride).build("VisibilityDeciders")
}
}

View File

@ -1,33 +0,0 @@
package com.twitter.visibility.configapi.configs
import com.twitter.timelines.configapi.Config
import com.twitter.visibility.configapi.params.RuleParams._
import com.twitter.visibility.configapi.params.VisibilityExperiments._
import com.twitter.visibility.models.SafetyLevel
import com.twitter.visibility.models.SafetyLevel._
private[visibility] object VisibilityExperimentsConfig {
import ExperimentsHelper._
val TestExperimentConfig: Config = mkABExperimentConfig(TestExperiment, TestHoldbackParam)
val NotGraduatedUserLabelRuleHoldbackExperimentConfig: Config =
mkABExperimentConfig(
NotGraduatedUserLabelRuleExperiment,
NotGraduatedUserLabelRuleHoldbackExperimentParam
)
def config(safetyLevel: SafetyLevel): Seq[Config] = {
val experimentConfigs = safetyLevel match {
case Test =>
Seq(TestExperimentConfig)
case _ => Seq(NotGraduatedUserLabelRuleHoldbackExperimentConfig)
}
experimentConfigs
}
}

View File

@ -1,74 +0,0 @@
package com.twitter.visibility.configapi.configs
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.logging.Logger
import com.twitter.timelines.configapi._
import com.twitter.util.Time
import com.twitter.visibility.configapi.params.FSEnumRuleParam
import com.twitter.visibility.configapi.params.FSRuleParams._
private[visibility] object VisibilityFeatureSwitches {
val booleanFsOverrides: Seq[OptionalOverride[Boolean]] =
FeatureSwitchOverrideUtil.getBooleanFSOverrides(
AgeGatingAdultContentExperimentRuleEnabledParam,
CommunityTweetCommunityUnavailableLimitedActionsRulesEnabledParam,
CommunityTweetDropProtectedRuleEnabledParam,
CommunityTweetDropRuleEnabledParam,
CommunityTweetLimitedActionsRulesEnabledParam,
CommunityTweetMemberRemovedLimitedActionsRulesEnabledParam,
CommunityTweetNonMemberLimitedActionsRuleEnabledParam,
NsfwAgeBasedDropRulesHoldbackParam,
SkipTweetDetailLimitedEngagementRuleEnabledParam,
StaleTweetLimitedActionsRulesEnabledParam,
TrustedFriendsTweetLimitedEngagementsRuleEnabledParam,
FosnrFallbackDropRulesEnabledParam,
FosnrRulesEnabledParam
)
val doubleFsOverrides: Seq[OptionalOverride[Double]] =
FeatureSwitchOverrideUtil.getBoundedDoubleFSOverrides(
HighSpammyTweetContentScoreSearchTopProdTweetLabelDropRuleThresholdParam,
HighSpammyTweetContentScoreSearchLatestProdTweetLabelDropRuleThresholdParam,
HighSpammyTweetContentScoreTrendTopTweetLabelDropRuleThresholdParam,
HighSpammyTweetContentScoreTrendLatestTweetLabelDropRuleThresholdParam,
HighSpammyTweetContentScoreConvoDownrankAbusiveQualityThresholdParam,
HighToxicityModelScoreSpaceThresholdParam,
AdAvoidanceHighToxicityModelScoreThresholdParam,
AdAvoidanceReportedTweetModelScoreThresholdParam,
)
val timeFsOverrides: Seq[OptionalOverride[Time]] =
FeatureSwitchOverrideUtil.getTimeFromStringFSOverrides()
val stringSeqFeatureSwitchOverrides: Seq[OptionalOverride[Seq[String]]] =
FeatureSwitchOverrideUtil.getStringSeqFSOverrides(
CountrySpecificNsfwContentGatingCountriesParam,
AgeGatingAdultContentExperimentCountriesParam,
CardUriRootDomainDenyListParam
)
val enumFsParams: Seq[FSEnumRuleParam[_ <: Enumeration]] = Seq()
val mkOptionalEnumFsOverrides: (StatsReceiver, Logger) => Seq[OptionalOverride[_]] = {
(statsReceiver: StatsReceiver, logger: Logger) =>
FeatureSwitchOverrideUtil.getEnumFSOverrides(
statsReceiver,
logger,
enumFsParams: _*
)
}
def overrides(statsReceiver: StatsReceiver, logger: Logger): Seq[OptionalOverride[_]] = {
val enumOverrides = mkOptionalEnumFsOverrides(statsReceiver, logger)
booleanFsOverrides ++
doubleFsOverrides ++
timeFsOverrides ++
stringSeqFeatureSwitchOverrides ++
enumOverrides
}
def config(statsReceiver: StatsReceiver, logger: Logger): BaseConfig =
BaseConfigBuilder(overrides(statsReceiver.scope("features_switches"), logger))
.build("VisibilityFeatureSwitches")
}

View File

@ -1,9 +0,0 @@
scala_library(
sources = ["*.scala"],
compiler_option_sets = ["fatal_warnings"],
strict_deps = True,
tags = ["bazel-compatible"],
dependencies = [
"decider",
],
)

View File

@ -1,24 +0,0 @@
package com.twitter.visibility.configapi.configs.overrides
import com.twitter.decider.LocalOverrides
object VisibilityLibraryDeciderOverrides
extends LocalOverrides.Namespace("visibility-library", "") {
val EnableLocalizedTombstoneOnVisibilityResults = feature(
"visibility_library_enable_localized_tombstones_on_visibility_results")
val EnableLocalizedInterstitialGenerator: LocalOverrides.Override =
feature("visibility_library_enable_localized_interstitial_generator")
val EnableInnerQuotedTweetViewerBlocksAuthorInterstitialRule: LocalOverrides.Override =
feature("visibility_library_enable_inner_quoted_tweet_viewer_blocks_author_interstitial_rule")
val EnableInnerQuotedTweetViewerMutesAuthorInterstitialRule: LocalOverrides.Override =
feature("visibility_library_enable_inner_quoted_tweet_viewer_mutes_author_interstitial_rule")
val EnableBackendLimitedActions: LocalOverrides.Override =
feature("visibility_library_enable_backend_limited_actions")
val disableLegacyInterstitialFilteredReason: LocalOverrides.Override = feature(
"visibility_library_disable_legacy_interstitial_filtered_reason")
}

View File

@ -1,15 +0,0 @@
scala_library(
sources = ["*.scala"],
compiler_option_sets = ["fatal_warnings"],
platform = "java8",
strict_deps = True,
tags = ["bazel-compatible"],
dependencies = [
"configapi/configapi-core",
"finagle/finagle-stats",
"visibility/common/src/main/scala/com/twitter/visibility/common:model_thresholds",
],
exports = [
"visibility/common/src/main/scala/com/twitter/visibility/common:model_thresholds",
],
)

View File

@ -1,213 +0,0 @@
package com.twitter.visibility.configapi.params
import com.twitter.timelines.configapi.Bounded
import com.twitter.timelines.configapi.FSBoundedParam
import com.twitter.timelines.configapi.FSName
import com.twitter.timelines.configapi.FeatureName
import com.twitter.timelines.configapi.HasTimeConversion
import com.twitter.timelines.configapi.TimeConversion
import com.twitter.util.Time
import com.twitter.visibility.common.ModelScoreThresholds
private[visibility] object FeatureSwitchKey extends Enumeration {
type FeatureSwitchKey = String
final val HighSpammyTweetContentScoreSearchTopProdTweetLabelDropFuleThreshold =
"high_spammy_tweet_content_score_search_top_prod_tweet_label_drop_rule_threshold"
final val HighSpammyTweetContentScoreSearchLatestProdTweetLabelDropRuleThreshold =
"high_spammy_tweet_content_score_search_latest_prod_tweet_label_drop_rule_threshold"
final val HighSpammyTweetContentScoreTrendTopTweetLabelDropRuleThreshold =
"high_spammy_tweet_content_score_trend_top_tweet_label_drop_rule_threshold"
final val HighSpammyTweetContentScoreTrendLatestTweetLabelDropRuleThreshold =
"high_spammy_tweet_content_score_trend_latest_tweet_label_drop_rule_threshold"
final val HighSpammyTweetContentScoreConvoDownrankAbusiveQualityThreshold =
"high_spammy_tweet_content_score_convos_downranking_abusive_quality_threshold"
final val NsfwAgeBasedDropRulesHoldbackParam =
"nsfw_age_based_drop_rules_holdback"
final val CommunityTweetDropRuleEnabled =
"community_tweet_drop_rule_enabled"
final val CommunityTweetDropProtectedRuleEnabled =
"community_tweet_drop_protected_rule_enabled"
final val CommunityTweetLimitedActionsRulesEnabled =
"community_tweet_limited_actions_rules_enabled"
final val CommunityTweetMemberRemovedLimitedActionsRulesEnabled =
"community_tweet_member_removed_limited_actions_rules_enabled"
final val CommunityTweetCommunityUnavailableLimitedActionsRulesEnabled =
"community_tweet_community_unavailable_limited_actions_rules_enabled"
final val CommunityTweetNonMemberLimitedActionsRuleEnabled =
"community_tweet_non_member_limited_actions_rule_enabled"
final val TrustedFriendsTweetLimitedEngagementsRuleEnabled =
"trusted_friends_tweet_limited_engagements_rule_enabled"
final val CountrySpecificNsfwContentGatingCountries =
"country_specific_nsfw_content_gating_countries"
final val AgeGatingAdultContentExperimentCountries =
"age_gating_adult_content_experiment_countries"
final val AgeGatingAdultContentExperimentEnabled =
"age_gating_adult_content_experiment_enabled"
final val HighToxicityModelScoreSpaceThreshold =
"high_toxicity_model_score_space_threshold"
final val CardUriRootDomainDenyList = "card_uri_root_domain_deny_list"
final val SkipTweetDetailLimitedEngagementsRuleEnabled =
"skip_tweet_detail_limited_engagements_rule_enabled"
final val AdAvoidanceHighToxicityModelScoreThreshold =
"ad_avoidance_model_thresholds_high_toxicity_model"
final val AdAvoidanceReportedTweetModelScoreThreshold =
"ad_avoidance_model_thresholds_reported_tweet_model"
final val StaleTweetLimitedActionsRulesEnabled =
"stale_tweet_limited_actions_rules_enabled"
final val FosnrFallbackDropRulesEnabled =
"freedom_of_speech_not_reach_fallback_drop_rules_enabled"
final val FosnrRulesEnabled =
"freedom_of_speech_not_reach_rules_enabled"
}
abstract class FSRuleParam[T](override val name: FeatureName, override val default: T)
extends RuleParam(default)
with FSName
abstract class FSBoundedRuleParam[T](
override val name: FeatureName,
override val default: T,
override val min: T,
override val max: T
)(
implicit override val ordering: Ordering[T])
extends RuleParam(default)
with Bounded[T]
with FSName
abstract class FSTimeRuleParam[T](
override val name: FeatureName,
override val default: Time,
override val timeConversion: TimeConversion[T])
extends RuleParam(default)
with HasTimeConversion[T]
with FSName
abstract class FSEnumRuleParam[T <: Enumeration](
override val name: FeatureName,
override val default: T#Value,
override val enum: T)
extends EnumRuleParam(default, enum)
with FSName
private[visibility] object FSRuleParams {
object HighSpammyTweetContentScoreSearchTopProdTweetLabelDropRuleThresholdParam
extends FSBoundedParam(
FeatureSwitchKey.HighSpammyTweetContentScoreSearchTopProdTweetLabelDropFuleThreshold,
default = ModelScoreThresholds.HighSpammyTweetContentScoreDefaultThreshold,
min = 0,
max = 1)
object HighSpammyTweetContentScoreSearchLatestProdTweetLabelDropRuleThresholdParam
extends FSBoundedParam(
FeatureSwitchKey.HighSpammyTweetContentScoreSearchLatestProdTweetLabelDropRuleThreshold,
default = ModelScoreThresholds.HighSpammyTweetContentScoreDefaultThreshold,
min = 0,
max = 1)
object HighSpammyTweetContentScoreTrendTopTweetLabelDropRuleThresholdParam
extends FSBoundedParam(
FeatureSwitchKey.HighSpammyTweetContentScoreTrendTopTweetLabelDropRuleThreshold,
default = ModelScoreThresholds.HighSpammyTweetContentScoreDefaultThreshold,
min = 0,
max = 1)
object HighSpammyTweetContentScoreTrendLatestTweetLabelDropRuleThresholdParam
extends FSBoundedParam(
FeatureSwitchKey.HighSpammyTweetContentScoreTrendLatestTweetLabelDropRuleThreshold,
default = ModelScoreThresholds.HighSpammyTweetContentScoreDefaultThreshold,
min = 0,
max = 1)
object HighSpammyTweetContentScoreConvoDownrankAbusiveQualityThresholdParam
extends FSBoundedParam(
FeatureSwitchKey.HighSpammyTweetContentScoreConvoDownrankAbusiveQualityThreshold,
default = ModelScoreThresholds.HighSpammyTweetContentScoreDefaultThreshold,
min = 0,
max = 1)
object CommunityTweetDropRuleEnabledParam
extends FSRuleParam(FeatureSwitchKey.CommunityTweetDropRuleEnabled, true)
object CommunityTweetDropProtectedRuleEnabledParam
extends FSRuleParam(FeatureSwitchKey.CommunityTweetDropProtectedRuleEnabled, true)
object CommunityTweetLimitedActionsRulesEnabledParam
extends FSRuleParam(FeatureSwitchKey.CommunityTweetLimitedActionsRulesEnabled, false)
object CommunityTweetMemberRemovedLimitedActionsRulesEnabledParam
extends FSRuleParam(
FeatureSwitchKey.CommunityTweetMemberRemovedLimitedActionsRulesEnabled,
false)
object CommunityTweetCommunityUnavailableLimitedActionsRulesEnabledParam
extends FSRuleParam(
FeatureSwitchKey.CommunityTweetCommunityUnavailableLimitedActionsRulesEnabled,
false)
object CommunityTweetNonMemberLimitedActionsRuleEnabledParam
extends FSRuleParam(FeatureSwitchKey.CommunityTweetNonMemberLimitedActionsRuleEnabled, false)
object TrustedFriendsTweetLimitedEngagementsRuleEnabledParam
extends FSRuleParam(FeatureSwitchKey.TrustedFriendsTweetLimitedEngagementsRuleEnabled, false)
object SkipTweetDetailLimitedEngagementRuleEnabledParam
extends FSRuleParam(FeatureSwitchKey.SkipTweetDetailLimitedEngagementsRuleEnabled, false)
object NsfwAgeBasedDropRulesHoldbackParam
extends FSRuleParam(FeatureSwitchKey.NsfwAgeBasedDropRulesHoldbackParam, true)
object CountrySpecificNsfwContentGatingCountriesParam
extends FSRuleParam[Seq[String]](
FeatureSwitchKey.CountrySpecificNsfwContentGatingCountries,
default = Seq("au"))
object AgeGatingAdultContentExperimentCountriesParam
extends FSRuleParam[Seq[String]](
FeatureSwitchKey.AgeGatingAdultContentExperimentCountries,
default = Seq.empty)
object AgeGatingAdultContentExperimentRuleEnabledParam
extends FSRuleParam(FeatureSwitchKey.AgeGatingAdultContentExperimentEnabled, default = false)
object HighToxicityModelScoreSpaceThresholdParam
extends FSBoundedParam(
FeatureSwitchKey.HighToxicityModelScoreSpaceThreshold,
default = ModelScoreThresholds.HighToxicityModelScoreSpaceDefaultThreshold,
min = 0,
max = 1)
object CardUriRootDomainDenyListParam
extends FSRuleParam[Seq[String]](
FeatureSwitchKey.CardUriRootDomainDenyList,
default = Seq.empty)
object AdAvoidanceHighToxicityModelScoreThresholdParam
extends FSBoundedParam(
FeatureSwitchKey.AdAvoidanceHighToxicityModelScoreThreshold,
default = ModelScoreThresholds.AdAvoidanceHighToxicityModelScoreDefaultThreshold,
min = 0,
max = 1)
object AdAvoidanceReportedTweetModelScoreThresholdParam
extends FSBoundedParam(
FeatureSwitchKey.AdAvoidanceReportedTweetModelScoreThreshold,
default = ModelScoreThresholds.AdAvoidanceReportedTweetModelScoreDefaultThreshold,
min = 0,
max = 1)
object StaleTweetLimitedActionsRulesEnabledParam
extends FSRuleParam(FeatureSwitchKey.StaleTweetLimitedActionsRulesEnabled, false)
object FosnrFallbackDropRulesEnabledParam
extends FSRuleParam(FeatureSwitchKey.FosnrFallbackDropRulesEnabled, false)
object FosnrRulesEnabledParam extends FSRuleParam(FeatureSwitchKey.FosnrRulesEnabled, true)
}

View File

@ -1,11 +0,0 @@
package com.twitter.visibility.configapi.params
import com.twitter.timelines.configapi.Param
abstract class GlobalParam[T](override val default: T) extends Param(default) {
override val statName: String = s"GlobalParam/${this.getClass.getSimpleName}"
}
private[visibility] object GlobalParams {
object EnableFetchingLabelMap extends GlobalParam(false)
}

View File

@ -1,15 +0,0 @@
package com.twitter.visibility.configapi.params
import com.twitter.timelines.configapi.Param
abstract class LabelSourceParam(override val default: Boolean) extends Param(default) {
override val statName: String = s"LabelSourceParam/${this.getClass.getSimpleName}"
}
private[visibility] object LabelSourceParams {
object FilterLabelsFromBot7174Param extends LabelSourceParam(false)
object FilterTweetsSmyteAutomationParamA extends LabelSourceParam(false)
object FilterTweetsSmyteAutomationParamB extends LabelSourceParam(false)
object FilterTweetsSmyteAutomationParamAB extends LabelSourceParam(false)
}

View File

@ -1,164 +0,0 @@
package com.twitter.visibility.configapi.params
import com.twitter.timelines.configapi.EnumParam
import com.twitter.timelines.configapi.Param
abstract class RuleParam[T](override val default: T) extends Param(default) {
override val statName: String = s"RuleParam/${this.getClass.getSimpleName}"
}
abstract class EnumRuleParam[T <: Enumeration](override val default: T#Value, override val enum: T)
extends EnumParam(default, enum) {
override val statName: String = s"RuleParam/${this.getClass.getSimpleName}"
}
private[visibility] object RuleParams {
object True extends RuleParam(true)
object False extends RuleParam(false)
object TestHoldbackParam extends RuleParam(true)
object TweetConversationControlEnabledParam extends RuleParam(default = false)
object EnableLimitRepliesFollowersConversationRule extends RuleParam(default = false)
object CommunityTweetsEnabledParam extends RuleParam(default = false)
object DropCommunityTweetWithUndefinedCommunityRuleEnabledParam extends RuleParam(default = false)
object EnableHighPSpammyTweetScoreSearchTweetLabelDropRuleParam extends RuleParam(false)
object EnableSmyteSpamTweetRuleParam extends RuleParam(false)
object EnableHighSpammyTweetContentScoreSearchLatestTweetLabelDropRuleParam
extends RuleParam(false)
object EnableHighSpammyTweetContentScoreSearchTopTweetLabelDropRuleParam extends RuleParam(false)
object NotGraduatedUserLabelRuleHoldbackExperimentParam extends RuleParam(default = false)
object EnableGoreAndViolenceTopicHighRecallTweetLabelRule extends RuleParam(default = false)
object EnableBlinkBadDownrankingRuleParam extends RuleParam(false)
object EnableBlinkWorstDownrankingRuleParam extends RuleParam(false)
object EnableHighSpammyTweetContentScoreTrendsTopTweetLabelDropRuleParam
extends RuleParam(default = false)
object EnableHighSpammyTweetContentScoreTrendsLatestTweetLabelDropRuleParam
extends RuleParam(default = false)
object EnableCopypastaSpamDownrankConvosAbusiveQualityRule extends RuleParam(default = false)
object EnableCopypastaSpamSearchDropRule extends RuleParam(default = false)
object EnableSpammyUserModelTweetDropRuleParam extends RuleParam(default = false)
object EnableAvoidNsfwRulesParam extends RuleParam(false)
object EnableReportedTweetInterstitialRule extends RuleParam(default = false)
object EnableReportedTweetInterstitialSearchRule extends RuleParam(default = false)
object EnableDropExclusiveTweetContentRule extends RuleParam(default = false)
object EnableDropExclusiveTweetContentRuleFailClosed extends RuleParam(default = false)
object EnableTombstoneExclusiveQtProfileTimelineParam extends RuleParam(default = false)
object EnableDropAllExclusiveTweetsRuleParam extends RuleParam(false)
object EnableDropAllExclusiveTweetsRuleFailClosedParam extends RuleParam(false)
object EnableDownrankSpamReplySectioningRuleParam extends RuleParam(default = false)
object EnableNsfwTextSectioningRuleParam extends RuleParam(default = false)
object EnableSearchIpiSafeSearchWithoutUserInQueryDropRule extends RuleParam(false)
object PromotedTweetHealthEnforcementHoldback extends RuleParam(default = true)
object EnableTimelineHomePromotedTweetHealthEnforcementRules extends RuleParam(default = false)
object EnableMutedKeywordFilteringSpaceTitleNotificationsRuleParam extends RuleParam(false)
object EnableDropTweetsWithGeoRestrictedMediaRuleParam extends RuleParam(default = false)
object EnableDropAllTrustedFriendsTweetsRuleParam extends RuleParam(false)
object EnableDropTrustedFriendsTweetContentRuleParam extends RuleParam(false)
object EnableDropAllCollabInvitationTweetsRuleParam extends RuleParam(false)
object EnableNsfwTextHighPrecisionDropRuleParam extends RuleParam(false)
object EnableLikelyIvsUserLabelDropRule extends RuleParam(false)
object EnableCardUriRootDomainCardDenylistRule extends RuleParam(false)
object EnableCommunityNonMemberPollCardRule extends RuleParam(false)
object EnableCommunityNonMemberPollCardRuleFailClosed extends RuleParam(false)
object EnableExperimentalNudgeEnabledParam extends RuleParam(false)
object NsfwHighPrecisionUserLabelAvoidTweetRuleEnabledParam extends RuleParam(default = false)
object EnableNewAdAvoidanceRulesParam extends RuleParam(false)
object EnableNsfaHighRecallAdAvoidanceParam extends RuleParam(false)
object EnableNsfaKeywordsHighPrecisionAdAvoidanceParam extends RuleParam(false)
object EnableStaleTweetDropRuleParam extends RuleParam(false)
object EnableStaleTweetDropRuleFailClosedParam extends RuleParam(false)
object EnableDeleteStateTweetRulesParam extends RuleParam(default = false)
object EnableSpacesSharingNsfwDropRulesParam extends RuleParam(default = true)
object EnableViewerIsSoftUserDropRuleParam extends RuleParam(default = false)
object EnablePdnaQuotedTweetTombstoneRuleParam extends RuleParam(default = true)
object EnableSpamQuotedTweetTombstoneRuleParam extends RuleParam(default = true)
object EnableNsfwHpQuotedTweetDropRuleParam extends RuleParam(default = false)
object EnableNsfwHpQuotedTweetTombstoneRuleParam extends RuleParam(default = false)
object EnableInnerQuotedTweetViewerBlocksAuthorInterstitialRuleParam
extends RuleParam(default = false)
object EnableInnerQuotedTweetViewerMutesAuthorInterstitialRuleParam
extends RuleParam(default = false)
object EnableNewSensitiveMediaSettingsInterstitialsHomeTimelineRulesParam extends RuleParam(false)
object EnableNewSensitiveMediaSettingsInterstitialsConversationRulesParam extends RuleParam(false)
object EnableNewSensitiveMediaSettingsInterstitialsProfileTimelineRulesParam
extends RuleParam(false)
object EnableNewSensitiveMediaSettingsInterstitialsTweetDetailRulesParam extends RuleParam(false)
object EnableLegacySensitiveMediaHomeTimelineRulesParam extends RuleParam(true)
object EnableLegacySensitiveMediaConversationRulesParam extends RuleParam(true)
object EnableLegacySensitiveMediaProfileTimelineRulesParam extends RuleParam(true)
object EnableLegacySensitiveMediaTweetDetailRulesParam extends RuleParam(true)
object EnableLegacySensitiveMediaDirectMessagesRulesParam extends RuleParam(true)
object EnableToxicReplyFilteringConversationRulesParam extends RuleParam(false)
object EnableToxicReplyFilteringNotificationsRulesParam extends RuleParam(false)
object EnableSearchQueryMatchesTweetAuthorConditionParam extends RuleParam(default = false)
object EnableSearchBasicBlockMuteRulesParam extends RuleParam(default = false)
object EnableAbusiveBehaviorDropRuleParam extends RuleParam(default = false)
object EnableAbusiveBehaviorInterstitialRuleParam extends RuleParam(default = false)
object EnableAbusiveBehaviorLimitedEngagementsRuleParam extends RuleParam(default = false)
object EnableNotGraduatedDownrankConvosAbusiveQualityRuleParam extends RuleParam(default = false)
object EnableNotGraduatedSearchDropRuleParam extends RuleParam(default = false)
object EnableNotGraduatedDropRuleParam extends RuleParam(default = false)
object EnableFosnrRuleParam extends RuleParam(default = false)
object EnableAuthorBlocksViewerDropRuleParam extends RuleParam(default = false)
}

View File

@ -1,215 +0,0 @@
package com.twitter.visibility.configapi.params
import com.twitter.timelines.configapi.Param
abstract class SafetyLevelParam(override val default: Boolean) extends Param(default) {
override val statName: String = s"SafetyLevelParam/${this.getClass.getSimpleName}"
}
private[visibility] object SafetyLevelParams {
object EnableAccessInternalPromotedContentSafetyLevelParam extends SafetyLevelParam(false)
object EnableAdsBusinessSettingsSafetyLevelParam extends SafetyLevelParam(false)
object EnableAdsCampaignSafetyLevelParam extends SafetyLevelParam(false)
object EnableAdsManagerSafetyLevelParam extends SafetyLevelParam(false)
object EnableAdsReportingDashboardSafetyLevelParam extends SafetyLevelParam(false)
object EnableAllSubscribedListsSafetyLevelParam extends SafetyLevelParam(false)
object EnableAppealsSafetyLevelParam extends SafetyLevelParam(false)
object EnableArticleTweetTimelineSafetyLevelParam extends SafetyLevelParam(false)
object EnableBaseQigSafetyLevelParam extends SafetyLevelParam(false)
object EnableBirdwatchNoteAuthorSafetyLevel extends SafetyLevelParam(false)
object EnableBirdwatchNoteTweetsTimelineSafetyLevel extends SafetyLevelParam(false)
object EnableBirdwatchNeedsYourHelpNotificationsSafetyLevel extends SafetyLevelParam(false)
object EnableBlockMuteUsersTimelineSafetyLevelParam extends SafetyLevelParam(false)
object EnableBrandSafetySafetyLevelParam extends SafetyLevelParam(false)
object EnableCardPollVotingSafetyLevelParam extends SafetyLevelParam(false)
object EnableCardsServiceSafetyLevelParam extends SafetyLevelParam(false)
object EnableCommunitiesSafetyLevelParam extends SafetyLevelParam(false)
object EnableContentControlToolInstallSafetyLevelParam extends SafetyLevelParam(false)
object EnableConversationFocalPrehydrationSafetyLevelParam extends SafetyLevelParam(false)
object EnableConversationFocalTweetSafetyLevelParam extends SafetyLevelParam(false)
object EnableConversationInjectedTweetSafetyLevelParam extends SafetyLevelParam(false)
object EnableConversationReplySafetyLevelParam extends SafetyLevelParam(false)
object EnableCuratedTrendsRepresentativeTweet extends SafetyLevelParam(default = false)
object EnableCurationPolicyViolations extends SafetyLevelParam(default = false)
object EnableDevPlatformGetListTweetsSafetyLevelParam extends SafetyLevelParam(false)
object EnableDESFollowingAndFollowersUserListSafetyLevelParam extends SafetyLevelParam(false)
object EnableDESHomeTimelineSafetyLevelParam extends SafetyLevelParam(false)
object EnableDESQuoteTweetTimelineSafetyLevelParam extends SafetyLevelParam(false)
object EnableDESRealtimeSafetyLevelParam extends SafetyLevelParam(false)
object EnableDESRealtimeSpamEnrichmentSafetyLevelParam extends SafetyLevelParam(false)
object EnableDESRealtimeTweetFilterSafetyLevelParam extends SafetyLevelParam(false)
object EnableDESRetweetingUsersSafetyLevelParam extends SafetyLevelParam(false)
object EnableDesTweetDetailSafetyLevelParam extends SafetyLevelParam(false)
object EnableDESTweetLikingUsersSafetyLevelParam extends SafetyLevelParam(false)
object EnableDESUserBookmarksSafetyLevelParam extends SafetyLevelParam(false)
object EnableDESUserLikedTweetSafetyLevelParam extends SafetyLevelParam(false)
object EnableDESUserMentionsSafetyLevelParam extends SafetyLevelParam(false)
object EnableDESUserTweetsSafetyLevelParam extends SafetyLevelParam(false)
object EnableDevPlatformComplianceStreamSafetyLevelParam extends SafetyLevelParam(false)
object EnableDirectMessagesSafetyLevelParam extends SafetyLevelParam(false)
object EnableDirectMessagesConversationListSafetyLevelParam extends SafetyLevelParam(false)
object EnableDirectMessagesConversationTimelineSafetyLevelParam extends SafetyLevelParam(false)
object EnableDirectMessagesInboxSafetyLevelParam extends SafetyLevelParam(false)
object EnableDirectMessagesMutedUsersSafetyLevelParam extends SafetyLevelParam(false)
object EnableDirectMessagesPinnedSafetyLevelParam extends SafetyLevelParam(false)
object EnableDirectMessagesSearchSafetyLevelParam extends SafetyLevelParam(false)
object EnableEditHistoryTimelineSafetyLevelParam extends SafetyLevelParam(false)
object EnableElevatedQuoteTweetTimelineSafetyLevelParam extends SafetyLevelParam(false)
object EnableEmbeddedTweetSafetyLevelParam extends SafetyLevelParam(false)
object EnableEmbedsPublicInterestNoticeSafetyLevelParam extends SafetyLevelParam(false)
object EnableEmbedTweetMarkupSafetyLevelParam extends SafetyLevelParam(false)
object EnableWritePathLimitedActionsEnforcementSafetyLevelParam extends SafetyLevelParam(false)
object EnableFilterAllSafetyLevelParam extends SafetyLevelParam(false)
object EnableFilterAllPlaceholderSafetyLevelParam extends SafetyLevelParam(false)
object EnableFilterDefaultSafetyLevelParam extends SafetyLevelParam(false)
object EnableFilterNoneSafetyLevelParam extends SafetyLevelParam(false)
object EnableFollowedTopicsTimelineSafetyLevelParam extends SafetyLevelParam(false)
object EnableFollowerConnectionsSafetyLevelParam extends SafetyLevelParam(false)
object EnableFollowingAndFollowersUserListSafetyLevelParam extends SafetyLevelParam(false)
object EnableForDevelopmentOnlySafetyLevelParam extends SafetyLevelParam(false)
object EnableFriendsFollowingListSafetyLevelParam extends SafetyLevelParam(false)
object EnableGraphqlDefaultSafetyLevelParam extends SafetyLevelParam(false)
object EnableGryphonDecksAndColumnsSafetyLevelParam extends SafetyLevelParam(false)
object EnableHumanizationNudgeSafetyLevelParam extends SafetyLevelParam(false)
object EnableKitchenSinkDevelopmentSafetyLevelParam extends SafetyLevelParam(false)
object EnableListHeaderSafetyLevelParam extends SafetyLevelParam(false)
object EnableListMembershipsSafetyLevelParam extends SafetyLevelParam(false)
object EnableListOwnershipsSafetyLevelParam extends SafetyLevelParam(false)
object EnableListRecommendationsSafetyLevelParam extends SafetyLevelParam(false)
object EnableListSearchSafetyLevelParam extends SafetyLevelParam(false)
object EnableListSubscriptionsSafetyLevelParam extends SafetyLevelParam(false)
object EnableLivePipelineEngagementCountsSafetyLevelParam extends SafetyLevelParam(false)
object EnableLiveVideoTimelineSafetyLevelParam extends SafetyLevelParam(false)
object EnableMagicRecsAggressiveSafetyLevelParam extends SafetyLevelParam(false)
object EnableMagicRecsAggressiveV2SafetyLevelParam extends SafetyLevelParam(false)
object EnableMagicRecsSafetyLevelParam extends SafetyLevelParam(false)
object EnableMagicRecsV2SafetyLevelParam extends SafetyLevelParam(false)
object EnableMinimalSafetyLevelParam extends SafetyLevelParam(false)
object EnableModeratedTweetsTimelineSafetyLevelParam extends SafetyLevelParam(false)
object EnableMomentsSafetyLevelParam extends SafetyLevelParam(false)
object EnableNearbySafetyLevelParam extends SafetyLevelParam(false)
object EnableNewUserExperienceSafetyLevelParam extends SafetyLevelParam(false)
object EnableNotificationsIbisSafetyLevelParam extends SafetyLevelParam(false)
object EnableNotificationsPlatformSafetyLevelParam extends SafetyLevelParam(false)
object EnableNotificationsPlatformPushSafetyLevelParam extends SafetyLevelParam(false)
object EnableNotificationsQigSafetyLevelParam extends SafetyLevelParam(false)
object EnableNotificationsReadSafetyLevelParam extends SafetyLevelParam(false)
object EnableNotificationsTimelineDeviceFollowSafetyLevelParam extends SafetyLevelParam(false)
object EnableNotificationsWriteSafetyLevelParam extends SafetyLevelParam(false)
object EnableNotificationsWriterTweetHydratorSafetyLevelParam extends SafetyLevelParam(false)
object EnableNotificationsWriterV2SafetyLevelParam extends SafetyLevelParam(false)
object EnableQuickPromoteTweetEligibilitySafetyLevelParam extends SafetyLevelParam(false)
object EnableQuoteTweetTimelineSafetyLevelParam extends SafetyLevelParam(false)
object EnableRecommendationsSafetyLevelParam extends SafetyLevelParam(false)
object EnableRecommendationsWithoutNsfaSafetyLevelParam extends SafetyLevelParam(false)
object EnableRecosVideoSafetyLevelParam extends SafetyLevelParam(false)
object EnableRecosWritePathSafetyLevelParam extends SafetyLevelParam(false)
object EnableRepliesGroupingSafetyLevelParam extends SafetyLevelParam(false)
object EnableReportCenterSafetyLevelParam extends SafetyLevelParam(false)
object EnableReturningUserExperienceFocalTweetSafetyLevelParam extends SafetyLevelParam(false)
object EnableReturningUserExperienceSafetyLevelParam extends SafetyLevelParam(false)
object EnableRevenueSafetyLevelParam extends SafetyLevelParam(false)
object EnableRitoActionedTweetTimelineParam extends SafetyLevelParam(false)
object EnableSafeSearchMinimalSafetyLevelParam extends SafetyLevelParam(false)
object EnableSafeSearchStrictSafetyLevelParam extends SafetyLevelParam(false)
object EnableSearchHydrationSafetyLevelParam extends SafetyLevelParam(false)
object EnableSearchLatestSafetyLevelParam extends SafetyLevelParam(false)
object EnableSearchTopSafetyLevelParam extends SafetyLevelParam(false)
object EnableSearchTopQigSafetyLevelParam extends SafetyLevelParam(false)
object EnableSearchPhotoSafetyLevelParam extends SafetyLevelParam(false)
object SearchTrendTakeoverPromotedTweetSafetyLevelParam extends SafetyLevelParam(false)
object EnableSearchVideoSafetyLevelParam extends SafetyLevelParam(false)
object EnableSearchBlenderUserRulesSafetyLevelParam extends SafetyLevelParam(false)
object EnableSearchLatestUserRulesSafetyLevelParam extends SafetyLevelParam(false)
object EnableSearchPeopleSearchResultPageSafetyLevelParam extends SafetyLevelParam(false)
object EnableSearchPeopleTypeaheadSafetyLevelParam extends SafetyLevelParam(false)
object EnableUserSearchSrpSafetyLevelParam extends SafetyLevelParam(false)
object EnableUserSearchTypeaheadSafetyLevelParam extends SafetyLevelParam(false)
object EnableSearchMixerSrpMinimalSafetyLevelParam extends SafetyLevelParam(false)
object EnableSearchMixerSrpStrictSafetyLevelParam extends SafetyLevelParam(false)
object EnableShoppingManagerSpyModeSafetyLevelParam extends SafetyLevelParam(false)
object EnableSignalsReactionsSafetyLevelParam extends SafetyLevelParam(false)
object EnableSignalsTweetReactingUsersSafetyLevelParam extends SafetyLevelParam(false)
object EnableSocialProofSafetyLevelParam extends SafetyLevelParam(false)
object EnableSoftInterventionPivotSafetyLevelParam extends SafetyLevelParam(false)
object EnableSpaceFleetlineSafetyLevelParam extends SafetyLevelParam(false)
object EnableSpaceHomeTimelineUprankingSafetyLevelParam extends SafetyLevelParam(false)
object EnableSpaceJoinScreenSafetyLevelParam extends SafetyLevelParam(false)
object EnableSpaceNotificationsSafetyLevelParam extends SafetyLevelParam(false)
object EnableSpacesSafetyLevelParam extends SafetyLevelParam(false)
object EnableSpacesParticipantsSafetyLevelParam extends SafetyLevelParam(false)
object EnableSpaceNotificationSafetyLevelParam extends SafetyLevelParam(false)
object EnableSpacesSellerApplicationStatusSafetyLevelParam extends SafetyLevelParam(false)
object EnableSpacesSharingSafetyLevelParam extends SafetyLevelParam(false)
object EnableSpaceTweetAvatarHomeTimelineSafetyLevelParam extends SafetyLevelParam(false)
object EnableStickersTimelineSafetyLevelParam extends SafetyLevelParam(false)
object EnableStratoExtLimitedEngagementsSafetyLevelParam extends SafetyLevelParam(false)
object EnableStreamServicesSafetyLevelParam extends SafetyLevelParam(false)
object EnableSuperFollowerConnectionsSafetyLevelParam extends SafetyLevelParam(false)
object EnableSuperLikeSafetyLevelParam extends SafetyLevelParam(false)
object EnableTestSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineBookmarkSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineConversationsDownrankingSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineContentControlsSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineConversationsDownrankingMinimalSafetyLevelParam
extends SafetyLevelParam(false)
object EnableTimelineConversationsSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineFavoritesSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineFavoritesSelfViewSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineFocalTweetSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineFollowingActivitySafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineHomeCommunitiesSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineHomeHydrationSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineHomeLatestSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineHomePromotedHydrationSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineHomeRecommendationsSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineHomeSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineHomeTopicFollowRecommendationsSafetyLevelParam
extends SafetyLevelParam(false)
object EnableTimelineScorerSafetyLevelParam extends SafetyLevelParam(false)
object EnableTopicsLandingPageTopicRecommendationsSafetyLevelParam extends SafetyLevelParam(false)
object EnableExploreRecommendationsSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineInjectionSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineLikedBySafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineListsSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineMediaSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineMentionsSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineModeratedTweetsHydrationSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineProfileSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineProfileAllSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineProfileSpacesSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineProfileSuperFollowsSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineReactiveBlendingSafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineRetweetedBySafetyLevelParam extends SafetyLevelParam(false)
object EnableTimelineSuperLikedBySafetyLevelParam extends SafetyLevelParam(false)
object EnableTombstoningSafetyLevelParam extends SafetyLevelParam(false)
object EnableTopicRecommendationsSafetyLevelParam extends SafetyLevelParam(false)
object EnableTrendsRepresentativeTweetSafetyLevelParam extends SafetyLevelParam(false)
object EnableTrustedFriendsUserListSafetyLevelParam extends SafetyLevelParam(false)
object EnableTwitterDelegateUserListSafetyLevelParam extends SafetyLevelParam(false)
object EnableTweetDetailSafetyLevelParam extends SafetyLevelParam(false)
object EnableTweetDetailNonTooSafetyLevelParam extends SafetyLevelParam(false)
object EnableTweetDetailWithInjectionsHydrationSafetyLevelParam extends SafetyLevelParam(false)
object EnableTweetEngagersSafetyLevelParam extends SafetyLevelParam(false)
object EnableTweetReplyNudgeParam extends SafetyLevelParam(false)
object EnableTweetScopedTimelineSafetyLevelParam extends SafetyLevelParam(false)
object EnableTweetWritesApiSafetyLevelParam extends SafetyLevelParam(false)
object EnableTwitterArticleComposeSafetyLevelParam extends SafetyLevelParam(false)
object EnableTwitterArticleProfileTabSafetyLevelParam extends SafetyLevelParam(false)
object EnableTwitterArticleReadSafetyLevelParam extends SafetyLevelParam(false)
object EnableUserProfileHeaderSafetyLevelParam extends SafetyLevelParam(false)
object EnableProfileMixerMediaSafetyLevelParam extends SafetyLevelParam(false)
object EnableProfileMixerFavoritesSafetyLevelParam extends SafetyLevelParam(false)
object EnableUserMilestoneRecommendationSafetyLevelParam extends SafetyLevelParam(false)
object EnableUserScopedTimelineSafetyLevelParam extends SafetyLevelParam(false)
object EnableUserSelfViewOnlySafetyLevelParam extends SafetyLevelParam(false)
object EnableUserSettingsSafetyLevelParam extends SafetyLevelParam(false)
object EnableVideoAdsSafetyLevelParam extends SafetyLevelParam(false)
object EnableZipbirdConsumerArchivesSafetyLevelParam extends SafetyLevelParam(false)
object EnableTweetAwardSafetyLevelParam extends SafetyLevelParam(false)
object EnableDeprecatedSafetyLevel extends SafetyLevelParam(true)
object EnableQuotedTweetRulesParam extends SafetyLevelParam(true)
object EnableUnsupportedSafetyLevel extends SafetyLevelParam(true)
object EnableUnknownSafetyLevel$ extends SafetyLevelParam(true)
}

View File

@ -1,13 +0,0 @@
package com.twitter.visibility.configapi.params
private[visibility] object TimelineConversationsDownrankingSpecificParams {
object EnablePSpammyTweetDownrankConvosLowQualityParam extends RuleParam(false)
object EnableRitoActionedTweetDownrankConvosLowQualityParam extends RuleParam(false)
object EnableHighSpammyTweetContentScoreConvoDownrankAbusiveQualityRuleParam
extends RuleParam(false)
object EnableHighCryptospamScoreConvoDownrankAbusiveQualityRuleParam extends RuleParam(false)
}

View File

@ -1,19 +0,0 @@
package com.twitter.visibility.configapi.params
import com.twitter.timelines.configapi.BucketName
import com.twitter.timelines.configapi.Experiment
import com.twitter.timelines.configapi.UseFeatureContext
object VisibilityExperiment {
val Control = "control"
val Treatment = "treatment"
}
abstract class VisibilityExperiment(experimentKey: String)
extends Experiment(experimentKey)
with UseFeatureContext {
val TreatmentBucket: String = VisibilityExperiment.Treatment
override def experimentBuckets: Set[BucketName] = Set(TreatmentBucket)
val ControlBucket: String = VisibilityExperiment.Control
override def controlBuckets: Set[BucketName] = Set(ControlBucket)
}

View File

@ -1,16 +0,0 @@
package com.twitter.visibility.configapi.params
private[visibility] object VisibilityExperiments {
case object TestExperiment extends VisibilityExperiment("vf_test_ddg_7727")
object CommonBucketId extends Enumeration {
type CommonBucketId = Value
val Control = Value("control")
val Treatment = Value("treatment")
val None = Value("none")
}
case object NotGraduatedUserLabelRuleExperiment
extends VisibilityExperiment("not_graduated_user_holdback_16332")
}

View File

@ -1,22 +0,0 @@
scala_library(
sources = ["*.scala"],
compiler_option_sets = ["fatal_warnings"],
platform = "java8",
strict_deps = True,
tags = ["bazel-compatible"],
dependencies = [
"abdecider/src/main/scala",
"configapi/configapi-core",
"servo/util/src/main/scala",
"src/thrift/com/twitter/search/common:constants-scala",
"src/thrift/com/twitter/spam/rtf:safety-level-scala",
"src/thrift/com/twitter/tweetypie:tweet-scala",
"stitch/stitch-core",
"visibility/lib/src/main/scala/com/twitter/visibility/builder",
"visibility/lib/src/main/scala/com/twitter/visibility/configapi/params",
"visibility/lib/src/main/scala/com/twitter/visibility/features",
"visibility/lib/src/main/scala/com/twitter/visibility/models",
"visibility/lib/src/main/scala/com/twitter/visibility/rules",
"visibility/lib/src/main/scala/com/twitter/visibility/rules/providers",
],
)

View File

@ -1,26 +0,0 @@
package com.twitter.visibility.engine
import com.twitter.servo.util.Gate
import com.twitter.spam.rtf.thriftscala.{SafetyLevel => ThriftSafetyLevel}
import com.twitter.stitch.Stitch
import com.twitter.visibility.builder.VisibilityResult
import com.twitter.visibility.builder.VisibilityResultBuilder
import com.twitter.visibility.models.SafetyLevel
import com.twitter.visibility.rules.EvaluationContext
import com.twitter.visibility.rules.Rule
trait DeciderableVisibilityRuleEngine {
def apply(
evaluationContext: EvaluationContext,
safetyLevel: SafetyLevel,
visibilityResultBuilder: VisibilityResultBuilder,
enableShortCircuiting: Gate[Unit] = Gate.True,
preprocessedRules: Option[Seq[Rule]] = None
): Stitch[VisibilityResult]
def apply(
evaluationContext: EvaluationContext,
thriftSafetyLevel: ThriftSafetyLevel,
visibilityResultBuilder: VisibilityResultBuilder
): Stitch[VisibilityResult]
}

View File

@ -1,179 +0,0 @@
package com.twitter.visibility.engine
import com.twitter.finagle.stats.NullStatsReceiver
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.finagle.stats.Verbosity
import com.twitter.servo.util.Gate
import com.twitter.servo.util.MemoizingStatsReceiver
import com.twitter.visibility.builder.VisibilityResult
import com.twitter.visibility.features.Feature
import com.twitter.visibility.models.SafetyLevel
import com.twitter.visibility.rules.NotEvaluated
import com.twitter.visibility.rules.RuleResult
import com.twitter.visibility.rules.State
import com.twitter.visibility.rules.State.Disabled
import com.twitter.visibility.rules.State.FeatureFailed
import com.twitter.visibility.rules.State.MissingFeature
import com.twitter.visibility.rules.State.RuleFailed
import com.twitter.visibility.rules.Action
case class VisibilityResultsMetricRecorder(
statsReceiver: StatsReceiver,
captureDebugStats: Gate[Unit]) {
private val scopedStatsReceiver = new MemoizingStatsReceiver(
statsReceiver.scope("visibility_rule_engine")
)
private val actionStats: StatsReceiver = scopedStatsReceiver.scope("by_action")
private val featureFailureReceiver: StatsReceiver =
scopedStatsReceiver.scope("feature_failed")
private val safetyLevelStatsReceiver: StatsReceiver =
scopedStatsReceiver.scope("from_safety_level")
private val ruleStatsReceiver: StatsReceiver = scopedStatsReceiver.scope("for_rule")
private val ruleFailureReceiver: StatsReceiver =
scopedStatsReceiver.scope("rule_failures")
private val failClosedReceiver: StatsReceiver =
scopedStatsReceiver.scope("fail_closed")
private val ruleStatsBySafetyLevelReceiver: StatsReceiver =
scopedStatsReceiver.scope("for_rule_by_safety_level")
def recordSuccess(
safetyLevel: SafetyLevel,
result: VisibilityResult
): Unit = {
recordAction(safetyLevel, result.verdict.fullName)
val isFeatureFailure = result.ruleResultMap.values
.collectFirst {
case RuleResult(_, FeatureFailed(_)) =>
ruleFailureReceiver.counter("feature_failed").incr()
true
}.getOrElse(false)
val isMissingFeature = result.ruleResultMap.values
.collectFirst {
case RuleResult(_, MissingFeature(_)) =>
ruleFailureReceiver.counter("missing_feature").incr()
true
}.getOrElse(false)
val isRuleFailed = result.ruleResultMap.values
.collectFirst {
case RuleResult(_, RuleFailed(_)) =>
ruleFailureReceiver.counter("rule_failed").incr()
true
}.getOrElse(false)
if (isFeatureFailure || isMissingFeature || isRuleFailed) {
ruleFailureReceiver.counter().incr()
}
if (captureDebugStats()) {
val ruleBySafetyLevelStat =
ruleStatsBySafetyLevelReceiver.scope(safetyLevel.name)
result.ruleResultMap.foreach {
case (rule, ruleResult) => {
ruleBySafetyLevelStat
.scope(rule.name)
.scope("action")
.counter(Verbosity.Debug, ruleResult.action.fullName).incr()
ruleBySafetyLevelStat
.scope(rule.name)
.scope("state")
.counter(Verbosity.Debug, ruleResult.state.name).incr()
}
}
}
}
def recordFailedFeature(
failedFeature: Feature[_],
exception: Throwable
): Unit = {
featureFailureReceiver.counter().incr()
val featureStat = featureFailureReceiver.scope(failedFeature.name)
featureStat.counter().incr()
featureStat.counter(exception.getClass.getName).incr()
}
def recordAction(
safetyLevel: SafetyLevel,
action: String
): Unit = {
safetyLevelStatsReceiver.scope(safetyLevel.name).counter(action).incr()
actionStats.counter(action).incr()
}
def recordUnknownSafetyLevel(
safetyLevel: SafetyLevel
): Unit = {
safetyLevelStatsReceiver
.scope("unknown_safety_level")
.counter(safetyLevel.name.toLowerCase).incr()
}
def recordRuleMissingFeatures(
ruleName: String,
missingFeatures: Set[Feature[_]]
): Unit = {
val ruleStat = ruleStatsReceiver.scope(ruleName)
missingFeatures.foreach { featureId =>
ruleStat.scope("missing_feature").counter(featureId.name).incr()
}
ruleStat.scope("action").counter(NotEvaluated.fullName).incr()
ruleStat.scope("state").counter(MissingFeature(missingFeatures).name).incr()
}
def recordRuleFailedFeatures(
ruleName: String,
failedFeatures: Map[Feature[_], Throwable]
): Unit = {
val ruleStat = ruleStatsReceiver.scope(ruleName)
ruleStat.scope("action").counter(NotEvaluated.fullName).incr()
ruleStat.scope("state").counter(FeatureFailed(failedFeatures).name).incr()
}
def recordFailClosed(rule: String, state: State) {
failClosedReceiver.scope(state.name).counter(rule).incr();
}
def recordRuleEvaluation(
ruleName: String,
action: Action,
state: State
): Unit = {
val ruleStat = ruleStatsReceiver.scope(ruleName)
ruleStat.scope("action").counter(action.fullName).incr()
ruleStat.scope("state").counter(state.name).incr()
}
def recordRuleFallbackAction(
ruleName: String
): Unit = {
val ruleStat = ruleStatsReceiver.scope(ruleName)
ruleStat.counter("fallback_action").incr()
}
def recordRuleHoldBack(
ruleName: String
): Unit = {
ruleStatsReceiver.scope(ruleName).counter("heldback").incr()
}
def recordRuleFailed(
ruleName: String
): Unit = {
ruleStatsReceiver.scope(ruleName).counter("failed").incr()
}
def recordDisabledRule(
ruleName: String
): Unit = recordRuleEvaluation(ruleName, NotEvaluated, Disabled)
}
object NullVisibilityResultsMetricsRecorder
extends VisibilityResultsMetricRecorder(NullStatsReceiver, Gate.False)

View File

@ -1,266 +0,0 @@
package com.twitter.visibility.engine
import com.twitter.servo.util.Gate
import com.twitter.spam.rtf.thriftscala.{SafetyLevel => ThriftSafetyLevel}
import com.twitter.stitch.Stitch
import com.twitter.visibility.builder.VisibilityResult
import com.twitter.visibility.builder.VisibilityResultBuilder
import com.twitter.visibility.features._
import com.twitter.visibility.models.SafetyLevel
import com.twitter.visibility.models.SafetyLevel.DeprecatedSafetyLevel
import com.twitter.visibility.rules.EvaluationContext
import com.twitter.visibility.rules.State._
import com.twitter.visibility.rules._
import com.twitter.visibility.rules.providers.ProvidedEvaluationContext
import com.twitter.visibility.rules.providers.PolicyProvider
class VisibilityRuleEngine private[VisibilityRuleEngine] (
rulePreprocessor: VisibilityRulePreprocessor,
metricsRecorder: VisibilityResultsMetricRecorder,
enableComposableActions: Gate[Unit],
enableFailClosed: Gate[Unit],
policyProviderOpt: Option[PolicyProvider] = None)
extends DeciderableVisibilityRuleEngine {
private[visibility] def apply(
evaluationContext: ProvidedEvaluationContext,
visibilityPolicy: VisibilityPolicy,
visibilityResultBuilder: VisibilityResultBuilder,
enableShortCircuiting: Gate[Unit],
preprocessedRules: Option[Seq[Rule]]
): Stitch[VisibilityResult] = {
val (resultBuilder, rules) = preprocessedRules match {
case Some(r) =>
(visibilityResultBuilder, r)
case None =>
rulePreprocessor.evaluate(evaluationContext, visibilityPolicy, visibilityResultBuilder)
}
evaluate(evaluationContext, resultBuilder, rules, enableShortCircuiting)
}
def apply(
evaluationContext: EvaluationContext,
safetyLevel: SafetyLevel,
visibilityResultBuilder: VisibilityResultBuilder,
enableShortCircuiting: Gate[Unit] = Gate.True,
preprocessedRules: Option[Seq[Rule]] = None
): Stitch[VisibilityResult] = {
val visibilityPolicy = policyProviderOpt match {
case Some(policyProvider) =>
policyProvider.policyForSurface(safetyLevel)
case None => RuleBase.RuleMap(safetyLevel)
}
if (evaluationContext.params(safetyLevel.enabledParam)) {
apply(
ProvidedEvaluationContext.injectRuntimeRulesIntoEvaluationContext(
evaluationContext = evaluationContext,
safetyLevel = Some(safetyLevel),
policyProviderOpt = policyProviderOpt
),
visibilityPolicy,
visibilityResultBuilder,
enableShortCircuiting,
preprocessedRules
).onSuccess { result =>
metricsRecorder.recordSuccess(safetyLevel, result)
}
.onFailure { _ =>
metricsRecorder.recordAction(safetyLevel, "failure")
}
} else {
metricsRecorder.recordAction(safetyLevel, "disabled")
val rules: Seq[Rule] = visibilityPolicy.forContentId(visibilityResultBuilder.contentId)
Stitch.value(
visibilityResultBuilder
.withRuleResultMap(rules.map(r => r -> RuleResult(Allow, Skipped)).toMap)
.withVerdict(verdict = Allow)
.withFinished(finished = true)
.build
)
}
}
def apply(
evaluationContext: EvaluationContext,
thriftSafetyLevel: ThriftSafetyLevel,
visibilityResultBuilder: VisibilityResultBuilder
): Stitch[VisibilityResult] = {
val safetyLevel: SafetyLevel = SafetyLevel.fromThrift(thriftSafetyLevel)
safetyLevel match {
case DeprecatedSafetyLevel =>
metricsRecorder.recordUnknownSafetyLevel(safetyLevel)
Stitch.value(
visibilityResultBuilder
.withVerdict(verdict = Allow)
.withFinished(finished = true)
.build
)
case thriftSafetyLevel: SafetyLevel =>
this(
ProvidedEvaluationContext.injectRuntimeRulesIntoEvaluationContext(
evaluationContext = evaluationContext,
safetyLevel = Some(safetyLevel),
policyProviderOpt = policyProviderOpt
),
thriftSafetyLevel,
visibilityResultBuilder
)
}
}
private[visibility] def evaluateRules(
evaluationContext: ProvidedEvaluationContext,
resolvedFeatureMap: Map[Feature[_], Any],
failedFeatures: Map[Feature[_], Throwable],
resultBuilderWithoutFailedFeatures: VisibilityResultBuilder,
preprocessedRules: Seq[Rule],
enableShortCircuiting: Gate[Unit]
): VisibilityResultBuilder = {
preprocessedRules
.foldLeft(resultBuilderWithoutFailedFeatures) { (builder, rule) =>
builder.ruleResults.get(rule) match {
case Some(RuleResult(_, state)) if state == Evaluated || state == ShortCircuited =>
builder
case _ =>
val failedFeatureDependencies: Map[Feature[_], Throwable] =
failedFeatures.filterKeys(key => rule.featureDependencies.contains(key))
val shortCircuit =
builder.finished && enableShortCircuiting() &&
!(enableComposableActions() && builder.isVerdictComposable())
if (failedFeatureDependencies.nonEmpty && rule.fallbackActionBuilder.isEmpty) {
metricsRecorder.recordRuleFailedFeatures(rule.name, failedFeatureDependencies)
builder.withRuleResult(
rule,
RuleResult(NotEvaluated, FeatureFailed(failedFeatureDependencies)))
} else if (shortCircuit) {
metricsRecorder.recordRuleEvaluation(rule.name, NotEvaluated, ShortCircuited)
builder.withRuleResult(rule, RuleResult(builder.verdict, ShortCircuited))
} else {
if (failedFeatureDependencies.nonEmpty && rule.fallbackActionBuilder.nonEmpty) {
metricsRecorder.recordRuleFallbackAction(rule.name)
}
val ruleResult =
rule.evaluate(evaluationContext, resolvedFeatureMap)
metricsRecorder
.recordRuleEvaluation(rule.name, ruleResult.action, ruleResult.state)
val nextBuilder = (ruleResult.action, builder.finished) match {
case (NotEvaluated | Allow, _) =>
ruleResult.state match {
case Heldback =>
metricsRecorder.recordRuleHoldBack(rule.name)
case RuleFailed(_) =>
metricsRecorder.recordRuleFailed(rule.name)
case _ =>
}
builder.withRuleResult(rule, ruleResult)
case (_, true) =>
builder
.withRuleResult(rule, ruleResult)
.withSecondaryVerdict(ruleResult.action, rule)
case _ =>
builder
.withRuleResult(rule, ruleResult)
.withVerdict(ruleResult.action, Some(rule))
.withFinished(true)
}
nextBuilder
}
}
}.withResolvedFeatureMap(resolvedFeatureMap)
}
private[visibility] def evaluateFailClosed(
evaluationContext: ProvidedEvaluationContext
): VisibilityResultBuilder => Stitch[VisibilityResultBuilder] = { builder =>
builder.failClosedException(evaluationContext) match {
case Some(e: FailClosedException) if enableFailClosed() =>
metricsRecorder.recordFailClosed(e.getRuleName, e.getState);
Stitch.exception(e)
case _ => Stitch.value(builder)
}
}
private[visibility] def checkMarkFinished(
builder: VisibilityResultBuilder
): VisibilityResult = {
val allRulesEvaluated: Boolean = builder.ruleResults.values.forall {
case RuleResult(_, state) =>
state == Evaluated || state == Disabled || state == Skipped
case _ =>
false
}
if (allRulesEvaluated) {
builder.withFinished(true).build
} else {
builder.build
}
}
private[visibility] def evaluate(
evaluationContext: ProvidedEvaluationContext,
visibilityResultBuilder: VisibilityResultBuilder,
preprocessedRules: Seq[Rule],
enableShortCircuiting: Gate[Unit] = Gate.True
): Stitch[VisibilityResult] = {
val finalBuilder =
FeatureMap.resolve(visibilityResultBuilder.features, evaluationContext.statsReceiver).map {
resolvedFeatureMap =>
val (failedFeatureMap, successfulFeatureMap) = resolvedFeatureMap.constantMap.partition({
case (_, _: FeatureFailedPlaceholderObject) => true
case _ => false
})
val failedFeatures: Map[Feature[_], Throwable] =
failedFeatureMap.mapValues({
case failurePlaceholder: FeatureFailedPlaceholderObject =>
failurePlaceholder.throwable
})
val resultBuilderWithoutFailedFeatures =
visibilityResultBuilder.withFeatureMap(ResolvedFeatureMap(successfulFeatureMap))
evaluateRules(
evaluationContext,
successfulFeatureMap,
failedFeatures,
resultBuilderWithoutFailedFeatures,
preprocessedRules,
enableShortCircuiting
)
}
finalBuilder.flatMap(evaluateFailClosed(evaluationContext)).map(checkMarkFinished)
}
}
object VisibilityRuleEngine {
def apply(
rulePreprocessor: Option[VisibilityRulePreprocessor] = None,
metricsRecorder: VisibilityResultsMetricRecorder = NullVisibilityResultsMetricsRecorder,
enableComposableActions: Gate[Unit] = Gate.False,
enableFailClosed: Gate[Unit] = Gate.False,
policyProviderOpt: Option[PolicyProvider] = None,
): VisibilityRuleEngine = {
new VisibilityRuleEngine(
rulePreprocessor.getOrElse(VisibilityRulePreprocessor(metricsRecorder)),
metricsRecorder,
enableComposableActions,
enableFailClosed,
policyProviderOpt = policyProviderOpt)
}
}

View File

@ -1,156 +0,0 @@
package com.twitter.visibility.engine
import com.twitter.abdecider.NullABDecider
import com.twitter.util.Return
import com.twitter.util.Throw
import com.twitter.util.Try
import com.twitter.visibility.builder.VisibilityResultBuilder
import com.twitter.visibility.features._
import com.twitter.visibility.models.SafetyLevel
import com.twitter.visibility.rules.Rule.DisabledRuleResult
import com.twitter.visibility.rules.Rule.EvaluatedRuleResult
import com.twitter.visibility.rules.State._
import com.twitter.visibility.rules._
import com.twitter.visibility.rules.providers.ProvidedEvaluationContext
import com.twitter.visibility.rules.providers.PolicyProvider
class VisibilityRulePreprocessor private (
metricsRecorder: VisibilityResultsMetricRecorder,
policyProviderOpt: Option[PolicyProvider] = None) {
private[engine] def filterEvaluableRules(
evaluationContext: ProvidedEvaluationContext,
resultBuilder: VisibilityResultBuilder,
rules: Seq[Rule]
): (VisibilityResultBuilder, Seq[Rule]) = {
val (builder, ruleList) = rules.foldLeft((resultBuilder, Seq.empty[Rule])) {
case ((builder, nextPassRules), rule) =>
if (evaluationContext.ruleEnabledInContext(rule)) {
val missingFeatures: Set[Feature[_]] = rule.featureDependencies.collect {
case feature: Feature[_] if !builder.featureMap.contains(feature) => feature
}
if (missingFeatures.isEmpty) {
(builder, nextPassRules :+ rule)
} else {
metricsRecorder.recordRuleMissingFeatures(rule.name, missingFeatures)
(
builder.withRuleResult(
rule,
RuleResult(NotEvaluated, MissingFeature(missingFeatures))
),
nextPassRules
)
}
} else {
(builder.withRuleResult(rule, DisabledRuleResult), nextPassRules)
}
}
(builder, ruleList)
}
private[visibility] def preFilterRules(
evaluationContext: ProvidedEvaluationContext,
resolvedFeatureMap: Map[Feature[_], Any],
resultBuilder: VisibilityResultBuilder,
rules: Seq[Rule]
): (VisibilityResultBuilder, Seq[Rule]) = {
val isResolvedFeatureMap = resultBuilder.featureMap.isInstanceOf[ResolvedFeatureMap]
val (builder, ruleList) = rules.foldLeft((resultBuilder, Seq.empty[Rule])) {
case ((builder, nextPassRules), rule) =>
rule.preFilter(evaluationContext, resolvedFeatureMap, NullABDecider) match {
case NeedsFullEvaluation =>
(builder, nextPassRules :+ rule)
case NotFiltered =>
(builder, nextPassRules :+ rule)
case Filtered if isResolvedFeatureMap =>
(builder, nextPassRules :+ rule)
case Filtered =>
(builder.withRuleResult(rule, EvaluatedRuleResult), nextPassRules)
}
}
(builder, ruleList)
}
private[visibility] def evaluate(
evaluationContext: ProvidedEvaluationContext,
safetyLevel: SafetyLevel,
resultBuilder: VisibilityResultBuilder
): (VisibilityResultBuilder, Seq[Rule]) = {
val visibilityPolicy = policyProviderOpt match {
case Some(policyProvider) =>
policyProvider.policyForSurface(safetyLevel)
case None => RuleBase.RuleMap(safetyLevel)
}
if (evaluationContext.params(safetyLevel.enabledParam)) {
evaluate(evaluationContext, visibilityPolicy, resultBuilder)
} else {
metricsRecorder.recordAction(safetyLevel, "disabled")
val rules: Seq[Rule] = visibilityPolicy.forContentId(resultBuilder.contentId)
val skippedResultBuilder = resultBuilder
.withRuleResultMap(rules.map(r => r -> RuleResult(Allow, Skipped)).toMap)
.withVerdict(verdict = Allow)
.withFinished(finished = true)
(skippedResultBuilder, rules)
}
}
private[visibility] def evaluate(
evaluationContext: ProvidedEvaluationContext,
visibilityPolicy: VisibilityPolicy,
resultBuilder: VisibilityResultBuilder,
): (VisibilityResultBuilder, Seq[Rule]) = {
val rules: Seq[Rule] = visibilityPolicy.forContentId(resultBuilder.contentId)
val (secondPassBuilder, secondPassRules) =
filterEvaluableRules(evaluationContext, resultBuilder, rules)
val secondPassFeatureMap = secondPassBuilder.featureMap
val secondPassConstantFeatures: Set[Feature[_]] = RuleBase
.getFeaturesForRules(secondPassRules)
.filter(secondPassFeatureMap.containsConstant(_))
val secondPassFeatureValues: Set[(Feature[_], Any)] = secondPassConstantFeatures.map {
feature =>
Try(secondPassFeatureMap.getConstant(feature)) match {
case Return(value) => (feature, value)
case Throw(ex) =>
metricsRecorder.recordFailedFeature(feature, ex)
(feature, FeatureFailedPlaceholderObject(ex))
}
}
val resolvedFeatureMap: Map[Feature[_], Any] =
secondPassFeatureValues.filterNot {
case (_, value) => value.isInstanceOf[FeatureFailedPlaceholderObject]
}.toMap
val (preFilteredResultBuilder, preFilteredRules) = preFilterRules(
evaluationContext,
resolvedFeatureMap,
secondPassBuilder,
secondPassRules
)
val preFilteredFeatureMap =
RuleBase.removeUnusedFeaturesFromFeatureMap(
preFilteredResultBuilder.featureMap,
preFilteredRules)
(preFilteredResultBuilder.withFeatureMap(preFilteredFeatureMap), preFilteredRules)
}
}
object VisibilityRulePreprocessor {
def apply(
metricsRecorder: VisibilityResultsMetricRecorder,
policyProviderOpt: Option[PolicyProvider] = None
): VisibilityRulePreprocessor = {
new VisibilityRulePreprocessor(metricsRecorder, policyProviderOpt)
}
}

View File

@ -1,24 +0,0 @@
package com.twitter.visibility.features
import com.twitter.gizmoduck.thriftscala.MentionFilter
import com.twitter.util.Duration
case object ViewerFiltersNoConfirmedEmail extends Feature[Boolean]
case object ViewerFiltersNoConfirmedPhone extends Feature[Boolean]
case object ViewerFiltersDefaultProfileImage extends Feature[Boolean]
case object ViewerFiltersNewUsers extends Feature[Boolean]
case object ViewerFiltersNotFollowedBy extends Feature[Boolean]
case object ViewerMentionFilter extends Feature[MentionFilter]
case object AuthorHasConfirmedEmail extends Feature[Boolean]
case object AuthorHasVerifiedPhone extends Feature[Boolean]
case object AuthorHasDefaultProfileImage extends Feature[Boolean]
case object AuthorAccountAge extends Feature[Duration]

View File

@ -1,17 +0,0 @@
scala_library(
sources = ["*.scala"],
compiler_option_sets = ["fatal_warnings"],
platform = "java8",
strict_deps = True,
tags = ["bazel-compatible"],
dependencies = [
"3rdparty/jvm/com/squareup/okhttp:okhttp3",
"finagle/finagle-mux/src/main/scala",
"src/thrift/com/twitter/gizmoduck:user-thrift-scala",
"src/thrift/com/twitter/search/common:constants-scala",
"src/thrift/com/twitter/tweetypie:tweet-scala",
"stitch/stitch-core",
"visibility/lib/src/main/scala/com/twitter/visibility/models",
"visibility/lib/src/main/scala/com/twitter/visibility/util",
],
)

View File

@ -1,11 +0,0 @@
package com.twitter.visibility.features
import com.twitter.visibility.util.NamingUtils
abstract class Feature[T] protected ()(implicit val manifest: Manifest[T]) {
lazy val name: String = NamingUtils.getFriendlyName(this)
override lazy val toString: String =
"Feature[%s](name=%s)".format(manifest, getClass.getSimpleName)
}

View File

@ -1,121 +0,0 @@
package com.twitter.visibility.features
import com.twitter.finagle.mux.ClientDiscardedRequestException
import com.twitter.finagle.stats.NullStatsReceiver
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.stitch.Stitch
import scala.language.existentials
class MissingFeatureException(feature: Feature[_]) extends Exception("Missing value for " + feature)
case class FeatureFailedException(feature: Feature[_], exception: Throwable) extends Exception
private[visibility] case class FeatureFailedPlaceholderObject(throwable: Throwable)
class FeatureMap(
val map: Map[Feature[_], Stitch[_]],
val constantMap: Map[Feature[_], Any]) {
def contains[T](feature: Feature[T]): Boolean =
constantMap.contains(feature) || map.contains(feature)
def containsConstant[T](feature: Feature[T]): Boolean = constantMap.contains(feature)
lazy val size: Int = keys.size
lazy val keys: Set[Feature[_]] = constantMap.keySet ++ map.keySet
def get[T](feature: Feature[T]): Stitch[T] = {
map.get(feature) match {
case _ if constantMap.contains(feature) =>
Stitch.value(getConstant(feature))
case Some(x) =>
x.asInstanceOf[Stitch[T]]
case _ =>
Stitch.exception(new MissingFeatureException(feature))
}
}
def getConstant[T](feature: Feature[T]): T = {
constantMap.get(feature) match {
case Some(x) =>
x.asInstanceOf[T]
case _ =>
throw new MissingFeatureException(feature)
}
}
def -[T](key: Feature[T]): FeatureMap = new FeatureMap(map - key, constantMap - key)
override def toString: String = "FeatureMap(%s, %s)".format(map, constantMap)
}
object FeatureMap {
def empty: FeatureMap = new FeatureMap(Map.empty, Map.empty)
def resolve(
featureMap: FeatureMap,
statsReceiver: StatsReceiver = NullStatsReceiver
): Stitch[ResolvedFeatureMap] = {
val featureMapHydrationStatsReceiver = statsReceiver.scope("feature_map_hydration")
Stitch
.traverse(featureMap.map.toSeq) {
case (feature, value: Stitch[_]) =>
val featureStatsReceiver = featureMapHydrationStatsReceiver.scope(feature.name)
lazy val featureFailureStat = featureStatsReceiver.scope("failures")
val featureStitch: Stitch[(Feature[_], Any)] = value
.map { resolvedValue =>
featureStatsReceiver.counter("success").incr()
(feature, resolvedValue)
}
featureStitch
.handle {
case ffe: FeatureFailedException =>
featureFailureStat.counter().incr()
featureFailureStat.counter(ffe.exception.getClass.getName).incr()
(feature, FeatureFailedPlaceholderObject(ffe.exception))
}
.ensure {
featureStatsReceiver.counter("requests").incr()
}
}
.map { resolvedFeatures: Seq[(Feature[_], Any)] =>
new ResolvedFeatureMap(resolvedFeatures.toMap ++ featureMap.constantMap)
}
}
def rescueFeatureTuple(kv: (Feature[_], Stitch[_])): (Feature[_], Stitch[_]) = {
val (k, v) = kv
val rescueValue = v.rescue {
case e =>
e match {
case cdre: ClientDiscardedRequestException => Stitch.exception(cdre)
case _ => Stitch.exception(FeatureFailedException(k, e))
}
}
(k, rescueValue)
}
}
class ResolvedFeatureMap(private[visibility] val resolvedMap: Map[Feature[_], Any])
extends FeatureMap(Map.empty, resolvedMap) {
override def equals(other: Any): Boolean = other match {
case otherResolvedFeatureMap: ResolvedFeatureMap =>
this.resolvedMap.equals(otherResolvedFeatureMap.resolvedMap)
case _ => false
}
override def toString: String = "ResolvedFeatureMap(%s)".format(resolvedMap)
}
object ResolvedFeatureMap {
def apply(resolvedMap: Map[Feature[_], Any]): ResolvedFeatureMap = {
new ResolvedFeatureMap(resolvedMap)
}
}

View File

@ -1,269 +0,0 @@
package com.twitter.visibility.features
import com.twitter.contenthealth.toxicreplyfilter.thriftscala.FilterState
import com.twitter.gizmoduck.thriftscala.Label
import com.twitter.search.common.constants.thriftscala.ThriftQuerySource
import com.twitter.tseng.withholding.thriftscala.TakedownReason
import com.twitter.util.Duration
import com.twitter.util.Time
import com.twitter.visibility.models.TweetDeleteReason.TweetDeleteReason
import com.twitter.visibility.models._
case object AuthorId extends Feature[Set[Long]]
case object ViewerId extends Feature[Long]
case object AuthorIsProtected extends Feature[Boolean]
case object AuthorIsSuspended extends Feature[Boolean]
case object AuthorIsUnavailable extends Feature[Boolean]
case object AuthorIsDeactivated extends Feature[Boolean]
case object AuthorIsErased extends Feature[Boolean]
case object AuthorIsOffboarded extends Feature[Boolean]
case object AuthorIsVerified extends Feature[Boolean]
case object AuthorIsBlueVerified extends Feature[Boolean]
case object ViewerIsSuspended extends Feature[Boolean]
case object ViewerIsDeactivated extends Feature[Boolean]
case object AuthorFollowsViewer extends Feature[Boolean]
case object AuthorUserLabels extends Feature[Seq[Label]]
case object ViewerFollowsAuthorOfViolatingTweet extends Feature[Boolean]
case object ViewerDoesNotFollowAuthorOfViolatingTweet extends Feature[Boolean]
case object ViewerFollowsAuthor extends Feature[Boolean]
case object ViewerBlocksAuthor extends Feature[Boolean]
case object AuthorBlocksViewer extends Feature[Boolean]
case object AuthorMutesViewer extends Feature[Boolean]
case object ViewerMutesAuthor extends Feature[Boolean]
case object AuthorReportsViewerAsSpam extends Feature[Boolean]
case object ViewerReportsAuthorAsSpam extends Feature[Boolean]
case object ViewerReportedTweet extends Feature[Boolean]
case object ViewerMutesRetweetsFromAuthor extends Feature[Boolean]
case object ViewerHasUniversalQualityFilterEnabled extends Feature[Boolean]
case object ViewerIsProtected extends Feature[Boolean]
case object ViewerIsSoftUser extends Feature[Boolean]
case object TweetSafetyLabels extends Feature[Seq[TweetSafetyLabel]]
case object SpaceSafetyLabels extends Feature[Seq[SpaceSafetyLabel]]
case object MediaSafetyLabels extends Feature[Seq[MediaSafetyLabel]]
case object TweetTakedownReasons extends Feature[Seq[TakedownReason]]
case object AuthorTakedownReasons extends Feature[Seq[TakedownReason]]
case object AuthorIsNsfwUser extends Feature[Boolean]
case object AuthorIsNsfwAdmin extends Feature[Boolean]
case object TweetHasNsfwUser extends Feature[Boolean]
case object TweetHasNsfwAdmin extends Feature[Boolean]
case object TweetHasMedia extends Feature[Boolean]
case object CardHasMedia extends Feature[Boolean]
case object TweetHasCard extends Feature[Boolean]
case object ViewerMutesKeywordInTweetForHomeTimeline extends Feature[MutedKeyword]
case object ViewerMutesKeywordInTweetForTweetReplies extends Feature[MutedKeyword]
case object ViewerMutesKeywordInTweetForNotifications extends Feature[MutedKeyword]
case object ViewerMutesKeywordInSpaceTitleForNotifications extends Feature[MutedKeyword]
case object ViewerMutesKeywordInTweetForAllSurfaces extends Feature[MutedKeyword]
case object ViewerUserLabels extends Feature[Seq[Label]]
case object RequestCountryCode extends Feature[String]
case object RequestIsVerifiedCrawler extends Feature[Boolean]
case object ViewerCountryCode extends Feature[String]
case object TweetIsSelfReply extends Feature[Boolean]
case object TweetIsNullcast extends Feature[Boolean]
case object TweetTimestamp extends Feature[Time]
case object TweetIsInnerQuotedTweet extends Feature[Boolean]
case object TweetIsRetweet extends Feature[Boolean]
case object TweetIsSourceTweet extends Feature[Boolean]
case object TweetDeleteReason extends Feature[TweetDeleteReason]
case object TweetReplyToParentTweetDuration extends Feature[Duration]
case object TweetReplyToRootTweetDuration extends Feature[Duration]
case object TweetHasCommunityConversationControl extends Feature[Boolean]
case object TweetHasByInvitationConversationControl extends Feature[Boolean]
case object TweetHasFollowersConversationControl extends Feature[Boolean]
case object TweetConversationViewerIsInvited extends Feature[Boolean]
case object TweetConversationViewerIsInvitedViaReplyMention extends Feature[Boolean]
case object TweetConversationViewerIsRootAuthor extends Feature[Boolean]
case object ConversationRootAuthorFollowsViewer extends Feature[Boolean]
case object ViewerFollowsConversationRootAuthor extends Feature[Boolean]
case object TweetIsExclusiveTweet extends Feature[Boolean]
case object ViewerIsExclusiveTweetRootAuthor extends Feature[Boolean]
case object ViewerSuperFollowsExclusiveTweetRootAuthor extends Feature[Boolean]
case object TweetIsCommunityTweet extends Feature[Boolean]
case object CommunityTweetCommunityNotFound extends Feature[Boolean]
case object CommunityTweetCommunityDeleted extends Feature[Boolean]
case object CommunityTweetCommunitySuspended extends Feature[Boolean]
case object CommunityTweetCommunityVisible extends Feature[Boolean]
case object CommunityTweetIsHidden extends Feature[Boolean]
case object ViewerIsInternalCommunitiesAdmin extends Feature[Boolean]
case object ViewerIsCommunityAdmin extends Feature[Boolean]
case object ViewerIsCommunityModerator extends Feature[Boolean]
case object ViewerIsCommunityMember extends Feature[Boolean]
case object CommunityTweetAuthorIsRemoved extends Feature[Boolean]
case object NotificationIsOnCommunityTweet extends Feature[Boolean]
case object NotificationIsOnUnmentionedViewer extends Feature[Boolean]
case object SearchResultsPageNumber extends Feature[Int]
case object SearchCandidateCount extends Feature[Int]
case object SearchQuerySource extends Feature[ThriftQuerySource]
case object SearchQueryHasUser extends Feature[Boolean]
case object TweetSemanticCoreAnnotations extends Feature[Seq[SemanticCoreAnnotation]]
case object OuterAuthorId extends Feature[Long]
case object AuthorBlocksOuterAuthor extends Feature[Boolean]
case object OuterAuthorFollowsAuthor extends Feature[Boolean]
case object OuterAuthorIsInnerAuthor extends Feature[Boolean]
case object TweetIsModerated extends Feature[Boolean]
case object FocalTweetId extends Feature[Long]
case object TweetId extends Feature[Long]
case object TweetConversationId extends Feature[Long]
case object TweetParentId extends Feature[Long]
case object ConversationRootAuthorIsVerified extends Feature[Boolean]
case object ViewerOptInBlocking extends Feature[Boolean]
case object ViewerOptInFiltering extends Feature[Boolean]
case object ViewerRoles extends Feature[Seq[String]] {
val EmployeeRole = "employee"
}
case object TweetMisinformationPolicies extends Feature[Seq[MisinformationPolicy]]
case object TweetEnglishMisinformationPolicies extends Feature[Seq[MisinformationPolicy]]
case object HasInnerCircleOfFriendsRelationship extends Feature[Boolean]
case object ViewerAge extends Feature[UserAge]
case object HasDmcaMediaFeature extends Feature[Boolean]
case object MediaGeoRestrictionsAllowList extends Feature[Seq[String]]
case object MediaGeoRestrictionsDenyList extends Feature[Seq[String]]
case object TweetIsTrustedFriendTweet extends Feature[Boolean]
case object ViewerIsTrustedFriendTweetAuthor extends Feature[Boolean]
case object ViewerIsTrustedFriendOfTweetAuthor extends Feature[Boolean]
case object DmConversationIsOneToOneConversation extends Feature[Boolean]
case object DmConversationHasEmptyTimeline extends Feature[Boolean]
case object DmConversationHasValidLastReadableEventId extends Feature[Boolean]
case object DmConversationInfoExists extends Feature[Boolean]
case object DmConversationTimelineExists extends Feature[Boolean]
case object ViewerIsDmConversationParticipant extends Feature[Boolean]
case object DmEventIsMessageCreateEvent extends Feature[Boolean]
case object DmEventIsWelcomeMessageCreateEvent extends Feature[Boolean]
case object DmEventIsLastMessageReadUpdateEvent extends Feature[Boolean]
case object DmEventIsDeleted extends Feature[Boolean]
case object DmEventIsHidden extends Feature[Boolean]
case object ViewerIsDmEventInitiatingUser extends Feature[Boolean]
case object DmEventInOneToOneConversationWithUnavailableUser extends Feature[Boolean]
case object DmEventIsJoinConversationEvent extends Feature[Boolean]
case object DmEventIsConversationCreateEvent extends Feature[Boolean]
case object DmEventInOneToOneConversation extends Feature[Boolean]
case object DmEventIsTrustConversationEvent extends Feature[Boolean]
case object DmEventIsCsFeedbackSubmitted extends Feature[Boolean]
case object DmEventIsCsFeedbackDismissed extends Feature[Boolean]
case object DmEventIsPerspectivalJoinConversationEvent extends Feature[Boolean]
case object DmEventOccurredBeforeLastClearedEvent extends Feature[Boolean]
case object DmEventOccurredBeforeJoinConversationEvent extends Feature[Boolean]
case object CardUriHost extends Feature[String]
case object CardIsPoll extends Feature[Boolean]
case object TweetIsStaleTweet extends Feature[Boolean]
case object TweetIsEditTweet extends Feature[Boolean]
case object TweetIsLatestTweet extends Feature[Boolean]
case object TweetIsInitialTweet extends Feature[Boolean]
case object TweetIsCollabInvitationTweet extends Feature[Boolean]
case object ViewerSensitiveMediaSettings extends Feature[UserSensitiveMediaSettings]
case object ToxicReplyFilterState extends Feature[FilterState]
case object ToxicReplyFilterConversationAuthorIsViewer extends Feature[Boolean]
case object RawQuery extends Feature[String]
case object AuthorScreenName extends Feature[String]
case object TweetIsInternalPromotedContent extends Feature[Boolean]

View File

@ -1,30 +0,0 @@
scala_library(
sources = ["*.scala"],
compiler_option_sets = ["fatal_warnings"],
platform = "java8",
strict_deps = True,
tags = ["bazel-compatible"],
dependencies = [
"3rdparty/jvm/com/ibm/icu:icu4j",
"configapi/configapi-core",
"decider/src/main/scala",
"src/thrift/com/twitter/gizmoduck:thrift-scala",
"src/thrift/com/twitter/gizmoduck:user-thrift-scala",
"src/thrift/com/twitter/spam/rtf:safety-result-scala",
"stitch/stitch-core",
"strato/src/main/scala/com/twitter/strato/client",
"twitter-config/yaml",
"visibility/common/src/main/scala/com/twitter/visibility/common",
"visibility/common/src/main/scala/com/twitter/visibility/common/actions",
"visibility/common/src/main/scala/com/twitter/visibility/common/user_result",
"visibility/common/src/main/thrift/com/twitter/visibility:action-scala",
"visibility/lib/src/main/scala/com/twitter/visibility/builder",
"visibility/lib/src/main/scala/com/twitter/visibility/configapi",
"visibility/lib/src/main/scala/com/twitter/visibility/configapi/configs",
"visibility/lib/src/main/scala/com/twitter/visibility/features",
"visibility/lib/src/main/scala/com/twitter/visibility/models",
"visibility/lib/src/main/scala/com/twitter/visibility/rules",
"visibility/results/src/main/scala/com/twitter/visibility/results/richtext",
"visibility/results/src/main/scala/com/twitter/visibility/results/translation",
],
)

View File

@ -1,58 +0,0 @@
package com.twitter.visibility.generators
import com.ibm.icu.util.ULocale
import com.twitter.config.yaml.YamlMap
import com.twitter.finagle.stats.StatsReceiver
object CountryNameGenerator {
private val AuroraFilesystemPath = "/usr/local/twitter-config/twitter/config/"
private val ContentBlockingSupportedCountryList = "takedown_countries.yml"
def providesFromConfigBus(statsReceiver: StatsReceiver): CountryNameGenerator = {
fromFile(AuroraFilesystemPath + ContentBlockingSupportedCountryList, statsReceiver)
}
def providesWithCustomMap(countryCodeMap: Map[String, String], statsReceiver: StatsReceiver) = {
new CountryNameGenerator(countryCodeMap, statsReceiver)
}
private def fromFile(fileName: String, statsReceiver: StatsReceiver) = {
val yamlConfig = YamlMap.load(fileName)
val countryCodeMap: Map[String, String] = yamlConfig.keySet.map { countryCode: String =>
val normalizedCode = countryCode.toUpperCase
val countryName: Option[String] =
yamlConfig.get(Seq(countryCode, "name")).asInstanceOf[Option[String]]
(normalizedCode, countryName.getOrElse(normalizedCode))
}.toMap
new CountryNameGenerator(countryCodeMap, statsReceiver)
}
}
class CountryNameGenerator(countryCodeMap: Map[String, String], statsReceiver: StatsReceiver) {
private val scopedStatsReceiver = statsReceiver.scope("country_name_generator")
private val foundCountryReceiver = scopedStatsReceiver.counter("found")
private val missingCountryReceiver = scopedStatsReceiver.counter("missing")
def getCountryName(code: String): String = {
val normalizedCode = code.toUpperCase
countryCodeMap.get(normalizedCode) match {
case Some(retrievedName) => {
foundCountryReceiver.incr()
retrievedName
}
case _ => {
missingCountryReceiver.incr()
val fallbackName =
new ULocale("", normalizedCode).getDisplayCountry(ULocale.forLanguageTag("en"))
if (fallbackName == "")
normalizedCode
else
fallbackName
}
}
}
}

View File

@ -1,66 +0,0 @@
package com.twitter.visibility.generators
import com.twitter.visibility.common.actions.LocalizedMessage
import com.twitter.visibility.common.actions.MessageLink
import com.twitter.visibility.results.translation.Translator
import com.twitter.visibility.results.richtext.EpitaphToRichText
import com.twitter.visibility.results.translation.Resource
import com.twitter.visibility.results.translation.LearnMoreLink
import com.twitter.visibility.rules.Epitaph
import com.twitter.visibility.results.richtext.EpitaphToRichText.Copy
object EpitaphToLocalizedMessage {
def apply(
epitaph: Epitaph,
languageTag: String,
): LocalizedMessage = {
val copy =
EpitaphToRichText.EpitaphToPolicyMap.getOrElse(epitaph, EpitaphToRichText.FallbackPolicy)
val text = Translator.translate(
copy.resource,
languageTag
)
localizeWithCopyAndText(copy, languageTag, text)
}
def apply(
epitaph: Epitaph,
languageTag: String,
applicableCountries: Seq[String],
): LocalizedMessage = {
val copy =
EpitaphToRichText.EpitaphToPolicyMap.getOrElse(epitaph, EpitaphToRichText.FallbackPolicy)
val text = Translator.translateWithSimplePlaceholderReplacement(
copy.resource,
languageTag,
Map((Resource.ApplicableCountriesPlaceholder -> applicableCountries.mkString(", ")))
)
localizeWithCopyAndText(copy, languageTag, text)
}
private def localizeWithCopyAndText(
copy: Copy,
languageTag: String,
text: String
): LocalizedMessage = {
val learnMore = Translator.translate(LearnMoreLink, languageTag)
val links = copy.additionalLinks match {
case links if links.nonEmpty =>
MessageLink(Resource.LearnMorePlaceholder, learnMore, copy.link) +:
links.map {
case EpitaphToRichText.Link(placeholder, copyResource, link) =>
val copyText = Translator.translate(copyResource, languageTag)
MessageLink(placeholder, copyText, link)
}
case _ =>
Seq(
MessageLink(
key = Resource.LearnMorePlaceholder,
displayText = learnMore,
uri = copy.link))
}
LocalizedMessage(message = text, language = languageTag, links = links)
}
}

View File

@ -1,47 +0,0 @@
package com.twitter.visibility.generators
import com.twitter.visibility.common.actions.InterstitialReason
import com.twitter.visibility.common.actions.LocalizedMessage
import com.twitter.visibility.common.actions.MessageLink
import com.twitter.visibility.results.richtext.InterstitialReasonToRichText
import com.twitter.visibility.results.richtext.InterstitialReasonToRichText.InterstitialCopy
import com.twitter.visibility.results.richtext.InterstitialReasonToRichText.InterstitialLink
import com.twitter.visibility.results.translation.LearnMoreLink
import com.twitter.visibility.results.translation.Resource
import com.twitter.visibility.results.translation.Translator
object InterstitialReasonToLocalizedMessage {
def apply(
reason: InterstitialReason,
languageTag: String,
): Option[LocalizedMessage] = {
InterstitialReasonToRichText.reasonToCopy(reason).map { copy =>
val text = Translator.translate(
copy.resource,
languageTag
)
localizeWithCopyAndText(copy, languageTag, text)
}
}
private def localizeWithCopyAndText(
copy: InterstitialCopy,
languageTag: String,
text: String
): LocalizedMessage = {
val learnMore = Translator.translate(LearnMoreLink, languageTag)
val learnMoreLinkOpt =
copy.link.map { link =>
MessageLink(key = Resource.LearnMorePlaceholder, displayText = learnMore, uri = link)
}
val additionalLinks = copy.additionalLinks.map {
case InterstitialLink(placeholder, copyResource, link) =>
val copyText = Translator.translate(copyResource, languageTag)
MessageLink(key = placeholder, displayText = copyText, uri = link)
}
val links = learnMoreLinkOpt.toSeq ++ additionalLinks
LocalizedMessage(message = text, language = languageTag, links = links)
}
}

View File

@ -1,151 +0,0 @@
package com.twitter.visibility.generators
import com.twitter.decider.Decider
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.visibility.builder.VisibilityResult
import com.twitter.visibility.common.actions.LocalizedMessage
import com.twitter.visibility.common.actions.MessageLink
import com.twitter.visibility.configapi.configs.VisibilityDeciderGates
import com.twitter.visibility.results.richtext.PublicInterestReasonToRichText
import com.twitter.visibility.results.translation.LearnMoreLink
import com.twitter.visibility.results.translation.Resource
import com.twitter.visibility.results.translation.SafetyResultReasonToResource
import com.twitter.visibility.results.translation.Translator
import com.twitter.visibility.rules.EmergencyDynamicInterstitial
import com.twitter.visibility.rules.Interstitial
import com.twitter.visibility.rules.InterstitialLimitedEngagements
import com.twitter.visibility.rules.PublicInterest
import com.twitter.visibility.rules.Reason
import com.twitter.visibility.rules.TweetInterstitial
object LocalizedInterstitialGenerator {
def apply(
visibilityDecider: Decider,
baseStatsReceiver: StatsReceiver,
): LocalizedInterstitialGenerator = {
new LocalizedInterstitialGenerator(visibilityDecider, baseStatsReceiver)
}
}
class LocalizedInterstitialGenerator private (
val visibilityDecider: Decider,
val baseStatsReceiver: StatsReceiver) {
private val visibilityDeciderGates = VisibilityDeciderGates(visibilityDecider)
private val localizationStatsReceiver = baseStatsReceiver.scope("interstitial_localization")
private val publicInterestInterstitialStats =
localizationStatsReceiver.scope("public_interest_copy")
private val emergencyDynamicInterstitialStats =
localizationStatsReceiver.scope("emergency_dynamic_copy")
private val regularInterstitialStats = localizationStatsReceiver.scope("interstitial_copy")
def apply(visibilityResult: VisibilityResult, languageTag: String): VisibilityResult = {
if (!visibilityDeciderGates.enableLocalizedInterstitialGenerator()) {
return visibilityResult
}
visibilityResult.verdict match {
case ipi: InterstitialLimitedEngagements if PublicInterest.Reasons.contains(ipi.reason) =>
visibilityResult.copy(
verdict = ipi.copy(
localizedMessage = Some(localizePublicInterestCopyInResult(ipi, languageTag))
))
case edi: EmergencyDynamicInterstitial =>
visibilityResult.copy(
verdict = EmergencyDynamicInterstitial(
edi.copy,
edi.linkOpt,
Some(localizeEmergencyDynamicCopyInResult(edi, languageTag))
))
case interstitial: Interstitial =>
visibilityResult.copy(
verdict = interstitial.copy(
localizedMessage = localizeInterstitialCopyInResult(interstitial, languageTag)
))
case tweetInterstitial: TweetInterstitial if tweetInterstitial.interstitial.isDefined =>
tweetInterstitial.interstitial.get match {
case ipi: InterstitialLimitedEngagements if PublicInterest.Reasons.contains(ipi.reason) =>
visibilityResult.copy(
verdict = tweetInterstitial.copy(
interstitial = Some(
ipi.copy(
localizedMessage = Some(localizePublicInterestCopyInResult(ipi, languageTag))
))
))
case edi: EmergencyDynamicInterstitial =>
visibilityResult.copy(
verdict = tweetInterstitial.copy(
interstitial = Some(
EmergencyDynamicInterstitial(
edi.copy,
edi.linkOpt,
Some(localizeEmergencyDynamicCopyInResult(edi, languageTag))
))
))
case interstitial: Interstitial =>
visibilityResult.copy(
verdict = tweetInterstitial.copy(
interstitial = Some(
interstitial.copy(
localizedMessage = localizeInterstitialCopyInResult(interstitial, languageTag)
))
))
case _ => visibilityResult
}
case _ => visibilityResult
}
}
private def localizeEmergencyDynamicCopyInResult(
edi: EmergencyDynamicInterstitial,
languageTag: String
): LocalizedMessage = {
val text = edi.linkOpt
.map(_ => s"${edi.copy} {${Resource.LearnMorePlaceholder}}")
.getOrElse(edi.copy)
val messageLinks = edi.linkOpt
.map { link =>
val learnMoreText = Translator.translate(LearnMoreLink, languageTag)
Seq(MessageLink(Resource.LearnMorePlaceholder, learnMoreText, link))
}.getOrElse(Seq.empty)
emergencyDynamicInterstitialStats.counter("localized").incr()
LocalizedMessage(text, languageTag, messageLinks)
}
private def localizePublicInterestCopyInResult(
ipi: InterstitialLimitedEngagements,
languageTag: String
): LocalizedMessage = {
val safetyResultReason = PublicInterest.ReasonToSafetyResultReason(ipi.reason)
val text = Translator.translate(
SafetyResultReasonToResource.resource(safetyResultReason),
languageTag,
)
val learnMoreLink = PublicInterestReasonToRichText.toLearnMoreLink(safetyResultReason)
val learnMoreText = Translator.translate(LearnMoreLink, languageTag)
val messageLinks = Seq(MessageLink(Resource.LearnMorePlaceholder, learnMoreText, learnMoreLink))
publicInterestInterstitialStats.counter("localized").incr()
LocalizedMessage(text, languageTag, messageLinks)
}
private def localizeInterstitialCopyInResult(
interstitial: Interstitial,
languageTag: String
): Option[LocalizedMessage] = {
val localizedMessageOpt = Reason
.toInterstitialReason(interstitial.reason)
.flatMap(InterstitialReasonToLocalizedMessage(_, languageTag))
if (localizedMessageOpt.isDefined) {
regularInterstitialStats.counter("localized").incr()
localizedMessageOpt
} else {
regularInterstitialStats.counter("empty").incr()
None
}
}
}

View File

@ -1,94 +0,0 @@
package com.twitter.visibility.generators
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.servo.util.MemoizingStatsReceiver
import com.twitter.visibility.builder.VisibilityResult
import com.twitter.visibility.common.actions.TombstoneReason
import com.twitter.visibility.configapi.VisibilityParams
import com.twitter.visibility.rules.Epitaph
import com.twitter.visibility.rules.LocalizedTombstone
import com.twitter.visibility.rules.Tombstone
object TombstoneGenerator {
def apply(
visibilityParams: VisibilityParams,
countryNameGenerator: CountryNameGenerator,
statsReceiver: StatsReceiver
): TombstoneGenerator = {
new TombstoneGenerator(visibilityParams, countryNameGenerator, statsReceiver)
}
}
class TombstoneGenerator(
paramsFactory: VisibilityParams,
countryNameGenerator: CountryNameGenerator,
baseStatsReceiver: StatsReceiver) {
private[this] val statsReceiver = new MemoizingStatsReceiver(
baseStatsReceiver.scope("tombstone_generator"))
private[this] val deletedReceiver = statsReceiver.scope("deleted_state")
private[this] val authorStateReceiver = statsReceiver.scope("tweet_author_state")
private[this] val visResultReceiver = statsReceiver.scope("visibility_result")
def apply(
result: VisibilityResult,
language: String
): VisibilityResult = {
result.verdict match {
case tombstone: Tombstone =>
val epitaph = tombstone.epitaph
visResultReceiver.scope("tombstone").counter(epitaph.name.toLowerCase())
val overriddenLanguage = epitaph match {
case Epitaph.LegalDemandsWithheldMedia | Epitaph.LocalLawsWithheldMedia => "en"
case _ => language
}
tombstone.applicableCountryCodes match {
case Some(countryCodes) => {
val countryNames = countryCodes.map(countryNameGenerator.getCountryName(_))
result.copy(verdict = LocalizedTombstone(
reason = epitaphToTombstoneReason(epitaph),
message = EpitaphToLocalizedMessage(epitaph, overriddenLanguage, countryNames)))
}
case _ => {
result.copy(verdict = LocalizedTombstone(
reason = epitaphToTombstoneReason(epitaph),
message = EpitaphToLocalizedMessage(epitaph, overriddenLanguage)))
}
}
case _ =>
result
}
}
private def epitaphToTombstoneReason(epitaph: Epitaph): TombstoneReason = {
epitaph match {
case Epitaph.Deleted => TombstoneReason.Deleted
case Epitaph.Bounced => TombstoneReason.Bounced
case Epitaph.BounceDeleted => TombstoneReason.BounceDeleted
case Epitaph.Protected => TombstoneReason.ProtectedAuthor
case Epitaph.Suspended => TombstoneReason.SuspendedAuthor
case Epitaph.BlockedBy => TombstoneReason.AuthorBlocksViewer
case Epitaph.SuperFollowsContent => TombstoneReason.ExclusiveTweet
case Epitaph.Underage => TombstoneReason.NsfwViewerIsUnderage
case Epitaph.NoStatedAge => TombstoneReason.NsfwViewerHasNoStatedAge
case Epitaph.LoggedOutAge => TombstoneReason.NsfwLoggedOut
case Epitaph.Deactivated => TombstoneReason.DeactivatedAuthor
case Epitaph.CommunityTweetHidden => TombstoneReason.CommunityTweetHidden
case Epitaph.CommunityTweetCommunityIsSuspended =>
TombstoneReason.CommunityTweetCommunityIsSuspended
case Epitaph.DevelopmentOnly => TombstoneReason.DevelopmentOnly
case Epitaph.AdultMedia => TombstoneReason.AdultMedia
case Epitaph.ViolentMedia => TombstoneReason.ViolentMedia
case Epitaph.OtherSensitiveMedia => TombstoneReason.OtherSensitiveMedia
case Epitaph.DmcaWithheldMedia => TombstoneReason.DmcaWithheldMedia
case Epitaph.LegalDemandsWithheldMedia => TombstoneReason.LegalDemandsWithheldMedia
case Epitaph.LocalLawsWithheldMedia => TombstoneReason.LocalLawsWithheldMedia
case Epitaph.ToxicReplyFiltered => TombstoneReason.ReplyFiltered
case _ => TombstoneReason.Unspecified
}
}
}

View File

@ -1,34 +0,0 @@
scala_library(
sources = ["*.scala"],
compiler_option_sets = ["fatal_warnings"],
platform = "java8",
strict_deps = True,
tags = ["bazel-compatible"],
dependencies = [
"3rdparty/jvm/com/twitter/src/java/com/twitter/logpipeline/client:logpipeline-event-publisher-thin",
"decider/src/main/scala",
"mediaservices/media-util/src/main/scala",
"servo/decider/src/main/scala",
"src/thrift/com/twitter/escherbird:media-annotation-structs-scala",
"src/thrift/com/twitter/spam/rtf:safety-level-scala",
"src/thrift/com/twitter/spam/rtf:tweet-rtf-event-scala",
"src/thrift/com/twitter/tweetypie:tweet-scala",
"stitch/stitch-core",
"strato/src/main/scala/com/twitter/strato/client",
"visibility/common/src/main/scala/com/twitter/visibility/common",
"visibility/lib/src/main/resources/config",
"visibility/lib/src/main/scala/com/twitter/visibility",
"visibility/lib/src/main/scala/com/twitter/visibility/builder/media",
"visibility/lib/src/main/scala/com/twitter/visibility/builder/tweets",
"visibility/lib/src/main/scala/com/twitter/visibility/builder/users",
"visibility/lib/src/main/scala/com/twitter/visibility/configapi/configs",
"visibility/lib/src/main/scala/com/twitter/visibility/features",
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/blender",
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/tweets",
"visibility/lib/src/main/thrift/com/twitter/visibility/logging:vf-logging-scala",
],
exports = [
"visibility/common/src/main/scala/com/twitter/visibility/common",
"visibility/lib/src/main/scala/com/twitter/visibility",
],
)

View File

@ -1,416 +0,0 @@
package com.twitter.visibility.interfaces.blender
import com.twitter.decider.Decider
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.mediaservices.media_util.GenericMediaKey
import com.twitter.servo.util.Gate
import com.twitter.stitch.Stitch
import com.twitter.strato.client.{Client => StratoClient}
import com.twitter.tweetypie.thriftscala.Tweet
import com.twitter.util.Stopwatch
import com.twitter.visibility.VisibilityLibrary
import com.twitter.visibility.builder.VerdictLogger
import com.twitter.visibility.builder.VisibilityResult
import com.twitter.visibility.builder.media.MediaFeatures
import com.twitter.visibility.builder.media.StratoMediaLabelMaps
import com.twitter.visibility.builder.tweets._
import com.twitter.visibility.builder.users.AuthorFeatures
import com.twitter.visibility.builder.users.RelationshipFeatures
import com.twitter.visibility.builder.users.ViewerFeatures
import com.twitter.visibility.common.MediaSafetyLabelMapSource
import com.twitter.visibility.common.MisinformationPolicySource
import com.twitter.visibility.common.SafetyLabelMapSource
import com.twitter.visibility.common.TrustedFriendsSource
import com.twitter.visibility.common.UserRelationshipSource
import com.twitter.visibility.common.UserSource
import com.twitter.visibility.rules.ComposableActions.ComposableActionsWithInterstitial
import com.twitter.visibility.configapi.configs.VisibilityDeciderGates
import com.twitter.visibility.features.FeatureMap
import com.twitter.visibility.features.TweetIsInnerQuotedTweet
import com.twitter.visibility.features.TweetIsRetweet
import com.twitter.visibility.features.TweetIsSourceTweet
import com.twitter.visibility.logging.thriftscala.VFLibType
import com.twitter.visibility.models.ContentId
import com.twitter.visibility.models.ContentId.BlenderTweetId
import com.twitter.visibility.models.ContentId.TweetId
import com.twitter.visibility.models.SafetyLevel
import com.twitter.visibility.models.SafetyLevel.toThrift
import com.twitter.visibility.rules.Action
import com.twitter.visibility.rules.Allow
import com.twitter.visibility.rules.Drop
import com.twitter.visibility.rules.Interstitial
import com.twitter.visibility.rules.TweetInterstitial
object TweetType extends Enumeration {
type TweetType = Value
val ORIGINAL, SOURCE, QUOTED = Value
}
import com.twitter.visibility.interfaces.blender.TweetType._
object BlenderVisibilityLibrary {
def buildWithStratoClient(
visibilityLibrary: VisibilityLibrary,
decider: Decider,
stratoClient: StratoClient,
userSource: UserSource,
userRelationshipSource: UserRelationshipSource
): BlenderVisibilityLibrary = new BlenderVisibilityLibrary(
visibilityLibrary,
decider,
stratoClient,
userSource,
userRelationshipSource,
None
)
def buildWithSafetyLabelMapSource(
visibilityLibrary: VisibilityLibrary,
decider: Decider,
stratoClient: StratoClient,
userSource: UserSource,
userRelationshipSource: UserRelationshipSource,
safetyLabelMapSource: SafetyLabelMapSource
): BlenderVisibilityLibrary = new BlenderVisibilityLibrary(
visibilityLibrary,
decider,
stratoClient,
userSource,
userRelationshipSource,
Some(safetyLabelMapSource)
)
def createVerdictLogger(
enableVerdictLogger: Gate[Unit],
decider: Decider,
statsReceiver: StatsReceiver
): VerdictLogger = {
if (enableVerdictLogger()) {
VerdictLogger(statsReceiver, decider)
} else {
VerdictLogger.Empty
}
}
def scribeVisibilityVerdict(
result: CombinedVisibilityResult,
enableVerdictScribing: Gate[Unit],
verdictLogger: VerdictLogger,
viewerId: Option[Long],
safetyLevel: SafetyLevel
): Unit = if (enableVerdictScribing()) {
verdictLogger.scribeVerdict(
visibilityResult = result.tweetVisibilityResult,
viewerId = viewerId,
safetyLevel = toThrift(safetyLevel),
vfLibType = VFLibType.BlenderVisibilityLibrary)
result.quotedTweetVisibilityResult.map(quotedTweetVisibilityResult =>
verdictLogger.scribeVerdict(
visibilityResult = quotedTweetVisibilityResult,
viewerId = viewerId,
safetyLevel = toThrift(safetyLevel),
vfLibType = VFLibType.BlenderVisibilityLibrary))
}
}
class BlenderVisibilityLibrary(
visibilityLibrary: VisibilityLibrary,
decider: Decider,
stratoClient: StratoClient,
userSource: UserSource,
userRelationshipSource: UserRelationshipSource,
safetyLabelMapSourceOption: Option[SafetyLabelMapSource]) {
val libraryStatsReceiver = visibilityLibrary.statsReceiver
val stratoClientStatsReceiver = visibilityLibrary.statsReceiver.scope("strato")
val vfEngineCounter = libraryStatsReceiver.counter("vf_engine_requests")
val bvlRequestCounter = libraryStatsReceiver.counter("bvl_requests")
val vfLatencyOverallStat = libraryStatsReceiver.stat("vf_latency_overall")
val vfLatencyStitchBuildStat = libraryStatsReceiver.stat("vf_latency_stitch_build")
val vfLatencyStitchRunStat = libraryStatsReceiver.stat("vf_latency_stitch_run")
val visibilityDeciderGates = VisibilityDeciderGates(decider)
val verdictLogger = BlenderVisibilityLibrary.createVerdictLogger(
visibilityDeciderGates.enableVerdictLoggerBVL,
decider,
libraryStatsReceiver)
val tweetLabels = safetyLabelMapSourceOption match {
case Some(safetyLabelMapSource) =>
new StratoTweetLabelMaps(safetyLabelMapSource)
case None =>
new StratoTweetLabelMaps(
SafetyLabelMapSource.fromStrato(stratoClient, stratoClientStatsReceiver))
}
val mediaLabelMaps = new StratoMediaLabelMaps(
MediaSafetyLabelMapSource.fromStrato(stratoClient, stratoClientStatsReceiver))
val tweetFeatures = new TweetFeatures(tweetLabels, libraryStatsReceiver)
val blenderContextFeatures = new BlenderContextFeatures(libraryStatsReceiver)
val authorFeatures = new AuthorFeatures(userSource, libraryStatsReceiver)
val viewerFeatures = new ViewerFeatures(userSource, libraryStatsReceiver)
val relationshipFeatures =
new RelationshipFeatures(userRelationshipSource, libraryStatsReceiver)
val fonsrRelationshipFeatures =
new FosnrRelationshipFeatures(
tweetLabels = tweetLabels,
userRelationshipSource = userRelationshipSource,
statsReceiver = libraryStatsReceiver)
val misinfoPolicySource =
MisinformationPolicySource.fromStrato(stratoClient, stratoClientStatsReceiver)
val misinfoPolicyFeatures =
new MisinformationPolicyFeatures(misinfoPolicySource, stratoClientStatsReceiver)
val exclusiveTweetFeatures =
new ExclusiveTweetFeatures(userRelationshipSource, libraryStatsReceiver)
val mediaFeatures = new MediaFeatures(mediaLabelMaps, libraryStatsReceiver)
val trustedFriendsTweetFeatures = new TrustedFriendsFeatures(
trustedFriendsSource = TrustedFriendsSource.fromStrato(stratoClient, stratoClientStatsReceiver))
val editTweetFeatures = new EditTweetFeatures(libraryStatsReceiver)
def getCombinedVisibilityResult(
bvRequest: BlenderVisibilityRequest
): Stitch[CombinedVisibilityResult] = {
val elapsed = Stopwatch.start()
bvlRequestCounter.incr()
val (
requestTweetVisibilityResult,
quotedTweetVisibilityResultOption,
sourceTweetVisibilityResultOption
) = getAllVisibilityResults(bvRequest: BlenderVisibilityRequest)
val response: Stitch[CombinedVisibilityResult] = {
(
requestTweetVisibilityResult,
quotedTweetVisibilityResultOption,
sourceTweetVisibilityResultOption) match {
case (requestTweetVisResult, Some(quotedTweetVisResult), Some(sourceTweetVisResult)) => {
Stitch
.join(
requestTweetVisResult,
quotedTweetVisResult,
sourceTweetVisResult
).map {
case (requestTweetVisResult, quotedTweetVisResult, sourceTweetVisResult) => {
requestTweetVisResult.verdict match {
case Allow =>
CombinedVisibilityResult(sourceTweetVisResult, Some(quotedTweetVisResult))
case _ =>
CombinedVisibilityResult(requestTweetVisResult, Some(quotedTweetVisResult))
}
}
}
}
case (requestTweetVisResult, None, Some(sourceTweetVisResult)) => {
Stitch
.join(
requestTweetVisResult,
sourceTweetVisResult
).map {
case (requestTweetVisResult, sourceTweetVisResult) => {
requestTweetVisResult.verdict match {
case Allow =>
CombinedVisibilityResult(sourceTweetVisResult, None)
case _ =>
CombinedVisibilityResult(requestTweetVisResult, None)
}
}
}
}
case (requestTweetVisResult, Some(quotedTweetVisResult), None) => {
Stitch
.join(
requestTweetVisResult,
quotedTweetVisResult
).map {
case (requestTweetVisResult, quotedTweetVisResult) => {
CombinedVisibilityResult(requestTweetVisResult, Some(quotedTweetVisResult))
}
}
}
case (requestTweetVisResult, None, None) => {
requestTweetVisResult.map {
CombinedVisibilityResult(_, None)
}
}
}
}
val runStitchStartMs = elapsed().inMilliseconds
val buildStitchStatMs = elapsed().inMilliseconds
vfLatencyStitchBuildStat.add(buildStitchStatMs)
response
.onSuccess(_ => {
val overallMs = elapsed().inMilliseconds
vfLatencyOverallStat.add(overallMs)
val stitchRunMs = elapsed().inMilliseconds - runStitchStartMs
vfLatencyStitchRunStat.add(stitchRunMs)
})
.onSuccess(
BlenderVisibilityLibrary.scribeVisibilityVerdict(
_,
visibilityDeciderGates.enableVerdictScribingBVL,
verdictLogger,
bvRequest.viewerContext.userId,
bvRequest.safetyLevel))
}
def getContentId(viewerId: Option[Long], authorId: Long, tweet: Tweet): ContentId = {
if (viewerId.contains(authorId))
TweetId(tweet.id)
else BlenderTweetId(tweet.id)
}
def getAllVisibilityResults(bvRequest: BlenderVisibilityRequest): (
Stitch[VisibilityResult],
Option[Stitch[VisibilityResult]],
Option[Stitch[VisibilityResult]]
) = {
val tweetContentId = getContentId(
viewerId = bvRequest.viewerContext.userId,
authorId = bvRequest.tweet.coreData.get.userId,
tweet = bvRequest.tweet)
val tweetFeatureMap =
buildFeatureMap(bvRequest, bvRequest.tweet, ORIGINAL)
vfEngineCounter.incr()
val requestTweetVisibilityResult = visibilityLibrary
.runRuleEngine(
tweetContentId,
tweetFeatureMap,
bvRequest.viewerContext,
bvRequest.safetyLevel
).map(handleComposableVisibilityResult)
val quotedTweetVisibilityResultOption = bvRequest.quotedTweet.map(quotedTweet => {
val quotedTweetContentId = getContentId(
viewerId = bvRequest.viewerContext.userId,
authorId = quotedTweet.coreData.get.userId,
tweet = quotedTweet)
val quotedInnerTweetFeatureMap =
buildFeatureMap(bvRequest, quotedTweet, QUOTED)
vfEngineCounter.incr()
visibilityLibrary
.runRuleEngine(
quotedTweetContentId,
quotedInnerTweetFeatureMap,
bvRequest.viewerContext,
bvRequest.safetyLevel
)
.map(handleComposableVisibilityResult)
.map(handleInnerQuotedTweetVisibilityResult)
})
val sourceTweetVisibilityResultOption = bvRequest.retweetSourceTweet.map(sourceTweet => {
val sourceTweetContentId = getContentId(
viewerId = bvRequest.viewerContext.userId,
authorId = sourceTweet.coreData.get.userId,
tweet = sourceTweet)
val sourceTweetFeatureMap =
buildFeatureMap(bvRequest, sourceTweet, SOURCE)
vfEngineCounter.incr()
visibilityLibrary
.runRuleEngine(
sourceTweetContentId,
sourceTweetFeatureMap,
bvRequest.viewerContext,
bvRequest.safetyLevel
)
.map(handleComposableVisibilityResult)
})
(
requestTweetVisibilityResult,
quotedTweetVisibilityResultOption,
sourceTweetVisibilityResultOption)
}
def buildFeatureMap(
bvRequest: BlenderVisibilityRequest,
tweet: Tweet,
tweetType: TweetType
): FeatureMap = {
val authorId = tweet.coreData.get.userId
val viewerId = bvRequest.viewerContext.userId
val isRetweet = if (tweetType.equals(ORIGINAL)) bvRequest.isRetweet else false
val isSourceTweet = tweetType.equals(SOURCE)
val isQuotedTweet = tweetType.equals(QUOTED)
val tweetMediaKeys: Seq[GenericMediaKey] = tweet.media
.getOrElse(Seq.empty)
.flatMap(_.mediaKey.map(GenericMediaKey.apply))
visibilityLibrary.featureMapBuilder(
Seq(
viewerFeatures
.forViewerBlenderContext(bvRequest.blenderVFRequestContext, bvRequest.viewerContext),
relationshipFeatures.forAuthorId(authorId, viewerId),
fonsrRelationshipFeatures
.forTweetAndAuthorId(tweet = tweet, authorId = authorId, viewerId = viewerId),
tweetFeatures.forTweet(tweet),
mediaFeatures.forMediaKeys(tweetMediaKeys),
authorFeatures.forAuthorId(authorId),
blenderContextFeatures.forBlenderContext(bvRequest.blenderVFRequestContext),
_.withConstantFeature(TweetIsRetweet, isRetweet),
misinfoPolicyFeatures.forTweet(tweet, bvRequest.viewerContext),
exclusiveTweetFeatures.forTweet(tweet, bvRequest.viewerContext),
trustedFriendsTweetFeatures.forTweet(tweet, viewerId),
editTweetFeatures.forTweet(tweet),
_.withConstantFeature(TweetIsInnerQuotedTweet, isQuotedTweet),
_.withConstantFeature(TweetIsSourceTweet, isSourceTweet),
)
)
}
def handleComposableVisibilityResult(result: VisibilityResult): VisibilityResult = {
if (result.secondaryVerdicts.nonEmpty) {
result.copy(verdict = composeActions(result.verdict, result.secondaryVerdicts))
} else {
result
}
}
private def composeActions(primary: Action, secondary: Seq[Action]): Action = {
if (primary.isComposable && secondary.nonEmpty) {
val actions = Seq[Action] { primary } ++ secondary
val interstitialOpt = Action.getFirstInterstitial(actions: _*)
val softInterventionOpt = Action.getFirstSoftIntervention(actions: _*)
val limitedEngagementsOpt = Action.getFirstLimitedEngagements(actions: _*)
val avoidOpt = Action.getFirstAvoid(actions: _*)
val numActions =
Seq[Option[_]](interstitialOpt, softInterventionOpt, limitedEngagementsOpt, avoidOpt)
.count(_.isDefined)
if (numActions > 1) {
TweetInterstitial(
interstitialOpt,
softInterventionOpt,
limitedEngagementsOpt,
None,
avoidOpt
)
} else {
primary
}
} else {
primary
}
}
def handleInnerQuotedTweetVisibilityResult(
result: VisibilityResult
): VisibilityResult = {
val newVerdict: Action =
result.verdict match {
case interstitial: Interstitial => Drop(interstitial.reason)
case ComposableActionsWithInterstitial(tweetInterstitial) => Drop(tweetInterstitial.reason)
case verdict => verdict
}
result.copy(verdict = newVerdict)
}
}

View File

@ -1,42 +0,0 @@
package com.twitter.visibility.interfaces.blender
import com.twitter.tweetypie.thriftscala.Tweet
import com.twitter.visibility.models.SafetyLevel
import com.twitter.visibility.models.ViewerContext
import com.twitter.visibility.interfaces.common.blender.BlenderVFRequestContext
case class BlenderVisibilityRequest(
tweet: Tweet,
quotedTweet: Option[Tweet],
retweetSourceTweet: Option[Tweet] = None,
isRetweet: Boolean,
safetyLevel: SafetyLevel,
viewerContext: ViewerContext,
blenderVFRequestContext: BlenderVFRequestContext) {
def getTweetID: Long = tweet.id
def hasQuotedTweet: Boolean = {
quotedTweet.nonEmpty
}
def hasSourceTweet: Boolean = {
retweetSourceTweet.nonEmpty
}
def getQuotedTweetId: Long = {
quotedTweet match {
case Some(qTweet) =>
qTweet.id
case None =>
-1
}
}
def getSourceTweetId: Long = {
retweetSourceTweet match {
case Some(sourceTweet) =>
sourceTweet.id
case None =>
-1
}
}
}

View File

@ -1,7 +0,0 @@
package com.twitter.visibility.interfaces.blender
import com.twitter.visibility.builder.VisibilityResult
case class CombinedVisibilityResult(
tweetVisibilityResult: VisibilityResult,
quotedTweetVisibilityResult: Option[VisibilityResult])

View File

@ -1,17 +0,0 @@
scala_library(
sources = ["*.scala"],
compiler_option_sets = ["fatal_warnings"],
platform = "java8",
strict_deps = False,
tags = ["bazel-compatible"],
dependencies = [
"appsec/sanitization-lib/src/main/scala",
"src/thrift/com/twitter/expandodo:cards-scala",
"stitch/stitch-core",
"visibility/lib/src/main/resources/config",
"visibility/lib/src/main/scala/com/twitter/visibility",
"visibility/lib/src/main/scala/com/twitter/visibility/builder/tweets",
"visibility/lib/src/main/scala/com/twitter/visibility/builder/users",
"visibility/lib/src/main/scala/com/twitter/visibility/features",
],
)

View File

@ -1,187 +0,0 @@
package com.twitter.visibility.interfaces.cards
import com.twitter.appsec.sanitization.URLSafety
import com.twitter.decider.Decider
import com.twitter.servo.util.Gate
import com.twitter.stitch.Stitch
import com.twitter.tweetypie.{thriftscala => tweetypiethrift}
import com.twitter.util.Stopwatch
import com.twitter.visibility.VisibilityLibrary
import com.twitter.visibility.builder.FeatureMapBuilder
import com.twitter.visibility.builder.VisibilityResult
import com.twitter.visibility.builder.tweets.CommunityTweetFeatures
import com.twitter.visibility.builder.tweets.CommunityTweetFeaturesV2
import com.twitter.visibility.builder.tweets.NilTweetLabelMaps
import com.twitter.visibility.builder.tweets.TweetFeatures
import com.twitter.visibility.builder.users.AuthorFeatures
import com.twitter.visibility.builder.users.RelationshipFeatures
import com.twitter.visibility.builder.users.ViewerFeatures
import com.twitter.visibility.common.CommunitiesSource
import com.twitter.visibility.common.UserId
import com.twitter.visibility.common.UserRelationshipSource
import com.twitter.visibility.common.UserSource
import com.twitter.visibility.configapi.configs.VisibilityDeciderGates
import com.twitter.visibility.features.CardIsPoll
import com.twitter.visibility.features.CardUriHost
import com.twitter.visibility.features.FeatureMap
import com.twitter.visibility.models.ContentId.CardId
import com.twitter.visibility.models.ViewerContext
object CardVisibilityLibrary {
type Type = CardVisibilityRequest => Stitch[VisibilityResult]
private[this] def getAuthorFeatures(
authorIdOpt: Option[Long],
authorFeatures: AuthorFeatures
): FeatureMapBuilder => FeatureMapBuilder = {
authorIdOpt match {
case Some(authorId) => authorFeatures.forAuthorId(authorId)
case _ => authorFeatures.forNoAuthor()
}
}
private[this] def getCardUriFeatures(
cardUri: String,
enableCardVisibilityLibraryCardUriParsing: Boolean,
trackCardUriHost: Option[String] => Unit
): FeatureMapBuilder => FeatureMapBuilder = {
if (enableCardVisibilityLibraryCardUriParsing) {
val safeCardUriHost = URLSafety.getHostSafe(cardUri)
trackCardUriHost(safeCardUriHost)
_.withConstantFeature(CardUriHost, safeCardUriHost)
} else {
identity
}
}
private[this] def getRelationshipFeatures(
authorIdOpt: Option[Long],
viewerIdOpt: Option[Long],
relationshipFeatures: RelationshipFeatures
): FeatureMapBuilder => FeatureMapBuilder = {
authorIdOpt match {
case Some(authorId) => relationshipFeatures.forAuthorId(authorId, viewerIdOpt)
case _ => relationshipFeatures.forNoAuthor()
}
}
private[this] def getTweetFeatures(
tweetOpt: Option[tweetypiethrift.Tweet],
tweetFeatures: TweetFeatures
): FeatureMapBuilder => FeatureMapBuilder = {
tweetOpt match {
case Some(tweet) => tweetFeatures.forTweet(tweet)
case _ => identity
}
}
private[this] def getCommunityFeatures(
tweetOpt: Option[tweetypiethrift.Tweet],
viewerContext: ViewerContext,
communityTweetFeatures: CommunityTweetFeatures
): FeatureMapBuilder => FeatureMapBuilder = {
tweetOpt match {
case Some(tweet) => communityTweetFeatures.forTweet(tweet, viewerContext)
case _ => identity
}
}
def apply(
visibilityLibrary: VisibilityLibrary,
userSource: UserSource = UserSource.empty,
userRelationshipSource: UserRelationshipSource = UserRelationshipSource.empty,
communitiesSource: CommunitiesSource = CommunitiesSource.empty,
enableVfParityTest: Gate[Unit] = Gate.False,
enableVfFeatureHydration: Gate[Unit] = Gate.False,
decider: Decider
): Type = {
val libraryStatsReceiver = visibilityLibrary.statsReceiver
val vfLatencyOverallStat = libraryStatsReceiver.stat("vf_latency_overall")
val vfLatencyStitchBuildStat = libraryStatsReceiver.stat("vf_latency_stitch_build")
val vfLatencyStitchRunStat = libraryStatsReceiver.stat("vf_latency_stitch_run")
val cardUriStats = libraryStatsReceiver.scope("card_uri")
val visibilityDeciderGates = VisibilityDeciderGates(decider)
val authorFeatures = new AuthorFeatures(userSource, libraryStatsReceiver)
val viewerFeatures = new ViewerFeatures(userSource, libraryStatsReceiver)
val tweetFeatures = new TweetFeatures(NilTweetLabelMaps, libraryStatsReceiver)
val communityTweetFeatures = new CommunityTweetFeaturesV2(
communitiesSource = communitiesSource,
)
val relationshipFeatures =
new RelationshipFeatures(userRelationshipSource, libraryStatsReceiver)
val parityTest = new CardVisibilityLibraryParityTest(libraryStatsReceiver)
{ r: CardVisibilityRequest =>
val elapsed = Stopwatch.start()
var runStitchStartMs = 0L
val viewerId: Option[UserId] = r.viewerContext.userId
val featureMap =
visibilityLibrary
.featureMapBuilder(
Seq(
viewerFeatures.forViewerId(viewerId),
getAuthorFeatures(r.authorId, authorFeatures),
getCardUriFeatures(
cardUri = r.cardUri,
enableCardVisibilityLibraryCardUriParsing =
visibilityDeciderGates.enableCardVisibilityLibraryCardUriParsing(),
trackCardUriHost = { safeCardUriHost: Option[String] =>
if (safeCardUriHost.isEmpty) {
cardUriStats.counter("empty").incr()
}
}
),
getCommunityFeatures(r.tweetOpt, r.viewerContext, communityTweetFeatures),
getRelationshipFeatures(r.authorId, r.viewerContext.userId, relationshipFeatures),
getTweetFeatures(r.tweetOpt, tweetFeatures),
_.withConstantFeature(CardIsPoll, r.isPollCardType)
)
)
val response = visibilityLibrary
.runRuleEngine(
CardId(r.cardUri),
featureMap,
r.viewerContext,
r.safetyLevel
)
.onSuccess(_ => {
val overallStatMs = elapsed().inMilliseconds
vfLatencyOverallStat.add(overallStatMs)
val runStitchEndMs = elapsed().inMilliseconds
vfLatencyStitchRunStat.add(runStitchEndMs - runStitchStartMs)
})
runStitchStartMs = elapsed().inMilliseconds
val buildStitchStatMs = elapsed().inMilliseconds
vfLatencyStitchBuildStat.add(buildStitchStatMs)
lazy val hydratedFeatureResponse: Stitch[VisibilityResult] =
FeatureMap.resolve(featureMap, libraryStatsReceiver).flatMap { resolvedFeatureMap =>
visibilityLibrary.runRuleEngine(
CardId(r.cardUri),
resolvedFeatureMap,
r.viewerContext,
r.safetyLevel
)
}
val isVfParityTestEnabled = enableVfParityTest()
val isVfFeatureHydrationEnabled = enableVfFeatureHydration()
if (!isVfParityTestEnabled && !isVfFeatureHydrationEnabled) {
response
} else if (isVfParityTestEnabled && !isVfFeatureHydrationEnabled) {
response.applyEffect { resp =>
Stitch.async(parityTest.runParityTest(hydratedFeatureResponse, resp))
}
} else {
hydratedFeatureResponse
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More