<commit_before># This file uses code from Websocket::Client::Simple, licensed under the following license:
#
# Copyright (c) 2013-2014 <NAME>
#
# MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
#                                  distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

require 'thread'

module Discordrb
  # Gateway packet opcodes
  module Opcodes
    # **Received** when Discord dispatches an event to the gateway (like MESSAGE_CREATE, PRESENCE_UPDATE or whatever).
    # The vast majority of received packets will have this opcode.
    DISPATCH = 0

    # **Two-way**: The client has to send a packet with this opcode every ~40 seconds (actual interval specified in
    # READY or RESUMED) and the current sequence number, otherwise it will be disconnected from the gateway. In certain
    # cases Discord may also send one, specifically if two clients are connected at once.
    HEARTBEAT = 1

    # **Sent**: This is one of the two possible ways to initiate a session after connecting to the gateway. It
    # should contain the authentication token along with other stuff the server has to know right from the start, such
    # as large_threshold and, for older gateway versions, the desired version.
    IDENTIFY = 2

    # **Sent**: Packets with this opcode are used to change the user's status and played game. (Sending this is never
    # necessary for a gateway client to behave correctly)
    PRESENCE = 3

    # **Sent**: Packets with this opcode are used to change a user's voice state (mute/deaf/unmute/undeaf/etc.). It is
    # also used to connect to a voice server in the first place. (Sending this is never necessary for a gateway client
    # to behave correctly)
    VOICE_STATE = 4

    # **Sent**: This opcode is used to ping a voice server, whatever that means. The functionality of this opcode isn't
    # known well but non-user clients should never send it.
    VOICE_PING = 5

    # **Sent**: This is the other of two possible ways to initiate a gateway session (other than {IDENTIFY}). Rather
    # than starting an entirely new session, it resumes an existing session by replaying all events from a given
    # sequence number. It should be used to recover from a connection error or anything like that when the session is
    # still valid - sending this with an invalid session will cause an error to occur.
    RESUME = 6

    # **Received**: Discord sends this opcode to indicate that the client should reconnect to a different gateway
    # server because the old one is currently being decommissioned. Counterintuitively, this opcode also invalidates the
    # session - the client has to create an entirely new session with the new gateway instead of resuming the old one.
    RECONNECT = 7

    # **Sent**: This opcode identifies packets used to retrieve a list of members from a particular server. There is
    # also a REST endpoint available for this, but it is inconvenient to use because the client has to implement
    # pagination itself, whereas sending this opcode lets Discord handle the pagination and the client can just add
    # members when it receives them. (Sending this is never necessary for a gateway client to behave correctly)
    REQUEST_MEMBERS = 8

    # **Received**: The functionality of this opcode is less known than the others but it appears to specifically
    # tell the client to invalidate its local session and continue by {IDENTIFY}ing.
    INVALIDATE_SESSION = 9

    # **Received**: Sent immediately for any opened connection; tells the client to start heartbeating early on, so the
    # server can safely search for a session server to handle the connection without the connection being terminated.
    # As a side-effect, large bots are less likely to disconnect because of very large READY parse times.
    HELLO = 10

    # **Received**: Returned after a heartbeat was sent to the server. This allows clients to identify and deal with
    # zombie connections that don't dispatch any events anymore.
    HEARTBEAT_ACK = 11
  end

  # This class stores the data of an active gateway session. Note that this is different from a websocket connection -
  # there may be multiple sessions per connection or one session may persist over multiple connections.
  class Session
    attr_reader :session_id
    attr_accessor :sequence

    def initialize(session_id)
      @id = session_id
      @sequence = 0
      @suspended = false
      @invalid = false
    end

    def suspend
      @suspended = true
    end

    def suspended?
      @suspended
    end

    def invalidate
      @invalid = true
    end

    def invalid?
      @invalid
    end

    def should_resume?
      suspended? && !invalid?
    end
  end

  # Client for the Discord gateway protocol
  class Gateway
    # How many members there need to be in a server for it to count as "large"
    LARGE_THRESHOLD = 100

    # The version of the gateway that's supposed to be used.
    GATEWAY_VERSION = 6

    def initialize(bot, token, shard_key = nil)
      @token = token
      @bot = bot

      @shard_key = shard_key

      @getc_mutex = Mutex.new

      # Whether the connection to the gateway has succeeded yet
      @ws_success = false
    end

    # Connect to the gateway server in a separate thread
    def run_async
      @ws_thread = Thread.new do
        Thread.current[:discordrb_name] = 'websocket'
        connect_loop
        LOGGER.warn('The WS loop exited! Not sure if this is a good thing')
      end

      LOGGER.debug('WS thread created! Now waiting for confirmation that everything worked')
      sleep(0.5) until @ws_success
      LOGGER.debug('Confirmation received! Exiting run.')
    end

    # Prevents all further execution until the websocket thread stops (e. g. through a closed connection).
    def sync
      @ws_thread.join
    end

    # Whether the WebSocket connection to the gateway is currently open
    def open?
      @handshake && @handshake.finished? && !@closed
    end

    # Stops the bot gracefully, disconnecting the websocket without immediately killing the thread. This means that
    # Discord is immediately aware of the closed connection and makes the bot appear offline instantly.
    #
    # If this method doesn't work or you're looking for something more drastic, use {#kill} instead.
    def stop(no_sync = false)
      @should_reconnect = false
      close(no_sync)
    end

    # Kills the websocket thread, stopping all connections to Discord.
    def kill
      @ws_thread.kill
    end

    # Notifies the {#run_async} method that everything is ready and the caller can now continue (i.e. with syncing,
    # or with doing processing and then syncing)
    def notify_ready
      @ws_success = true
    end

    # Injects a reconnect event (op 7) into the event processor, causing Discord to reconnect to the given gateway URL.
    # If the URL is set to nil, it will reconnect and get an entirely new gateway URL. This method has not much use
    # outside of testing and implementing highly custom reconnect logic.
    # @param url [String, nil] the URL to connect to or nil if one should be obtained from Discord.
    def inject_reconnect(url = nil)
      # When no URL is specified, the data should be nil, as is the case with Discord-sent packets.
      data = url ? { url: url } : nil

      handle_message({
        op: Opcodes::RECONNECT,
        d: data
      }.to_json)
    end

    # Injects a resume packet (op 6) into the gateway. If this is done with a running connection, it will cause an
    # error. It has no use outside of testing stuff that I know of, but if you want to use it anyway for some reason,
    # here it is.
    # @param seq [Integer, nil] The sequence ID to inject, or nil if the currently tracked one should be used.
    def inject_resume(seq)
      send_resume(raw_token, @session_id, seq || @sequence)
    end

    # Injects a terminal gateway error into the handler. Useful for testing the reconnect logic.
    # @param e [Exception] The exception object to inject.
    def inject_error(e)
      handle_internal_close(e)
    end

    # Sends a heartbeat with the last received packet's seq (to acknowledge that we have received it and all packets
    # before it), or if none have been received yet, with 0.
    # @see #send_heartbeat
    def heartbeat
      send_heartbeat(@session ? @session.sequence : 0)
    end

    # Sends a heartbeat packet (op 1). This tells Discord that the current connection is still active and that the last
    # packets until the given sequence have been processed (in case of a resume).
    # @param sequence [Integer] The sequence number for which to send a heartbeat.
    def send_heartbeat(sequence)
      send_packet(Opcodes::HEARTBEAT, sequence)
    end

    # Identifies to Discord with the default parameters.
    # @see #send_identify
    def identify
      send_identify(@token, {
                      :'$os' => RUBY_PLATFORM,
                      :'$browser' => 'discordrb',
                      :'$device' => 'discordrb',
                      :'$referrer' => '',
                      :'$referring_domain' => ''
                    }, true, 100, @shard_key)
    end

    # Sends an identify packet (op 2). This starts a new session on the current connection and tells Discord who we are.
    # This can only be done once a connection.
    # @param token [String] The token with which to authorise the session. If it belongs to a bot account, it must be
    #   prefixed with "Bot ".
    # @param properties [Hash<Symbol => String>] A list of properties for Discord to use in analytics. The following
    #   keys are recognised:
    #
    #    - "$os" (recommended value: the operating system the bot is running on)
    #    - "$browser" (recommended value: library name)
    #    - "$device" (recommended value: library name)
    #    - "$referrer" (recommended value: empty)
    #    - "$referring_domain" (recommended value: empty)
    #
    # @param compress [true, false] Whether certain large packets should be compressed using zlib.
    # @param large_threshold [Integer] The member threshold after which a server counts as large and will have to have
    #   its member list chunked.
    # @param shard_key [Array(Integer, Integer), nil] The shard key to use for sharding, represented as
    #   [shard_id, num_shards], or nil if the bot should not be sharded.
    def send_identify(token, properties, compress, large_threshold, shard_key = nil)
      data = {
        # Don't send a v anymore as it's entirely determined by the URL now
        token: token,
        properties: properties,
        compress: compress,
        large_threshold: large_threshold
      }

      # Don't include the shard key at all if it is nil as Discord checks for its mere existence
      data[:shard_key] = shard_key if shard_key

      send_packet(Opcodes::IDENTIFY, data)
    end

    # Sends a status update packet (op 3). This sets the bot user's status (online/idle/...) and game playing/streaming.
    # @param status [String] The status that should be set (`online`, `idle`, `dnd`, `invisible`).
    # @param since [Integer] The unix timestamp in milliseconds when the status was set. Should only be provided when
    #   `afk` is true.
    # @param game [Hash<Symbol => Object>, nil] `nil` if no game should be played, or a hash of `:game => "name"` if a
    #   game should be played. The hash can also contain additional attributes for streaming statuses.
    # @param afk [true, false] Whether the status was set due to inactivity on the user's part.
    def send_status_update(status, since, game, afk)
      data = {
        status: status,
        since: since,
        game: game,
        afk: afk
      }

      send_packet(Opcodes::PRESENCE, data)
    end
<commit_msg>Fix the naming of the shard property in op2<commit_after># This file uses code from Websocket::Client::Simple, licensed under the following license:
#
# Copyright (c) 2013-2014 <NAME>
#
# MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
#                                  distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

require 'thread'

module Discordrb
  # Gateway packet opcodes
  module Opcodes
    # **Received** when Discord dispatches an event to the gateway (like MESSAGE_CREATE, PRESENCE_UPDATE or whatever).
    # The vast majority of received packets will have this opcode.
    DISPATCH = 0

    # **Two-way**: The client has to send a packet with this opcode every ~40 seconds (actual interval specified in
    # READY or RESUMED) and the current sequence number, otherwise it will be disconnected from the gateway. In certain
    # cases Discord may also send one, specifically if two clients are connected at once.
    HEARTBEAT = 1

    # **Sent**: This is one of the two possible ways to initiate a session after connecting to the gateway. It
    # should contain the authentication token along with other stuff the server has to know right from the start, such
    # as large_threshold and, for older gateway versions, the desired version.
    IDENTIFY = 2

    # **Sent**: Packets with this opcode are used to change the user's status and played game. (Sending this is never
    # necessary for a gateway client to behave correctly)
    PRESENCE = 3

    # **Sent**: Packets with this opcode are used to change a user's voice state (mute/deaf/unmute/undeaf/etc.). It is
    # also used to connect to a voice server in the first place. (Sending this is never necessary for a gateway client
    # to behave correctly)
    VOICE_STATE = 4

    # **Sent**: This opcode is used to ping a voice server, whatever that means. The functionality of this opcode isn't
    # known well but non-user clients should never send it.
    VOICE_PING = 5

    # **Sent**: This is the other of two possible ways to initiate a gateway session (other than {IDENTIFY}). Rather
    # than starting an entirely new session, it resumes an existing session by replaying all events from a given
    # sequence number. It should be used to recover from a connection error or anything like that when the session is
    # still valid - sending this with an invalid session will cause an error to occur.
    RESUME = 6

    # **Received**: Discord sends this opcode to indicate that the client should reconnect to a different gateway
    # server because the old one is currently being decommissioned. Counterintuitively, this opcode also invalidates the
    # session - the client has to create an entirely new session with the new gateway instead of resuming the old one.
    RECONNECT = 7

    # **Sent**: This opcode identifies packets used to retrieve a list of members from a particular server. There is
    # also a REST endpoint available for this, but it is inconvenient to use because the client has to implement
    # pagination itself, whereas sending this opcode lets Discord handle the pagination and the client can just add
    # members when it receives them. (Sending this is never necessary for a gateway client to behave correctly)
    REQUEST_MEMBERS = 8

    # **Received**: The functionality of this opcode is less known than the others but it appears to specifically
    # tell the client to invalidate its local session and continue by {IDENTIFY}ing.
    INVALIDATE_SESSION = 9

    # **Received**: Sent immediately for any opened connection; tells the client to start heartbeating early on, so the
    # server can safely search for a session server to handle the connection without the connection being terminated.
    # As a side-effect, large bots are less likely to disconnect because of very large READY parse times.
    HELLO = 10

    # **Received**: Returned after a heartbeat was sent to the server. This allows clients to identify and deal with
    # zombie connections that don't dispatch any events anymore.
    HEARTBEAT_ACK = 11
  end

  # This class stores the data of an active gateway session. Note that this is different from a websocket connection -
  # there may be multiple sessions per connection or one session may persist over multiple connections.
  class Session
    attr_reader :session_id
    attr_accessor :sequence

    def initialize(session_id)
      @id = session_id
      @sequence = 0
      @suspended = false
      @invalid = false
    end

    def suspend
      @suspended = true
    end

    def suspended?
      @suspended
    end

    def invalidate
      @invalid = true
    end

    def invalid?
      @invalid
    end

    def should_resume?
      suspended? && !invalid?
    end
  end

  # Client for the Discord gateway protocol
  class Gateway
    # How many members there need to be in a server for it to count as "large"
    LARGE_THRESHOLD = 100

    # The version of the gateway that's supposed to be used.
    GATEWAY_VERSION = 6

    def initialize(bot, token, shard_key = nil)
      @token = token
      @bot = bot

      @shard_key = shard_key

      @getc_mutex = Mutex.new

      # Whether the connection to the gateway has succeeded yet
      @ws_success = false
    end

    # Connect to the gateway server in a separate thread
    def run_async
      @ws_thread = Thread.new do
        Thread.current[:discordrb_name] = 'websocket'
        connect_loop
        LOGGER.warn('The WS loop exited! Not sure if this is a good thing')
      end

      LOGGER.debug('WS thread created! Now waiting for confirmation that everything worked')
      sleep(0.5) until @ws_success
      LOGGER.debug('Confirmation received! Exiting run.')
    end

    # Prevents all further execution until the websocket thread stops (e. g. through a closed connection).
    def sync
      @ws_thread.join
    end

    # Whether the WebSocket connection to the gateway is currently open
    def open?
      @handshake && @handshake.finished? && !@closed
    end

    # Stops the bot gracefully, disconnecting the websocket without immediately killing the thread. This means that
    # Discord is immediately aware of the closed connection and makes the bot appear offline instantly.
    #
    # If this method doesn't work or you're looking for something more drastic, use {#kill} instead.
    def stop(no_sync = false)
      @should_reconnect = false
      close(no_sync)
    end

    # Kills the websocket thread, stopping all connections to Discord.
    def kill
      @ws_thread.kill
    end

    # Notifies the {#run_async} method that everything is ready and the caller can now continue (i.e. with syncing,
    # or with doing processing and then syncing)
    def notify_ready
      @ws_success = true
    end

    # Injects a reconnect event (op 7) into the event processor, causing Discord to reconnect to the given gateway URL.
    # If the URL is set to nil, it will reconnect and get an entirely new gateway URL. This method has not much use
    # outside of testing and implementing highly custom reconnect logic.
    # @param url [String, nil] the URL to connect to or nil if one should be obtained from Discord.
    def inject_reconnect(url = nil)
      # When no URL is specified, the data should be nil, as is the case with Discord-sent packets.
      data = url ? { url: url } : nil

      handle_message({
        op: Opcodes::RECONNECT,
        d: data
      }.to_json)
    end

    # Injects a resume packet (op 6) into the gateway. If this is done with a running connection, it will cause an
    # error. It has no use outside of testing stuff that I know of, but if you want to use it anyway for some reason,
    # here it is.
    # @param seq [Integer, nil] The sequence ID to inject, or nil if the currently tracked one should be used.
    def inject_resume(seq)
      send_resume(raw_token, @session_id, seq || @sequence)
    end

    # Injects a terminal gateway error into the handler. Useful for testing the reconnect logic.
    # @param e [Exception] The exception object to inject.
    def inject_error(e)
      handle_internal_close(e)
    end

    # Sends a heartbeat with the last received packet's seq (to acknowledge that we have received it and all packets
    # before it), or if none have been received yet, with 0.
    # @see #send_heartbeat
    def heartbeat
      send_heartbeat(@session ? @session.sequence : 0)
    end

    # Sends a heartbeat packet (op 1). This tells Discord that the current connection is still active and that the last
    # packets until the given sequence have been processed (in case of a resume).
    # @param sequence [Integer] The sequence number for which to send a heartbeat.
    def send_heartbeat(sequence)
      send_packet(Opcodes::HEARTBEAT, sequence)
    end

    # Identifies to Discord with the default parameters.
    # @see #send_identify
    def identify
      send_identify(@token, {
                      :'$os' => RUBY_PLATFORM,
                      :'$browser' => 'discordrb',
                      :'$device' => 'discordrb',
                      :'$referrer' => '',
                      :'$referring_domain' => ''
                    }, true, 100, @shard_key)
    end

    # Sends an identify packet (op 2). This starts a new session on the current connection and tells Discord who we are.
    # This can only be done once a connection.
    # @param token [String] The token with which to authorise the session. If it belongs to a bot account, it must be
    #   prefixed with "Bot ".
    # @param properties [Hash<Symbol => String>] A list of properties for Discord to use in analytics. The following
    #   keys are recognised:
    #
    #    - "$os" (recommended value: the operating system the bot is running on)
    #    - "$browser" (recommended value: library name)
    #    - "$device" (recommended value: library name)
    #    - "$referrer" (recommended value: empty)
    #    - "$referring_domain" (recommended value: empty)
    #
    # @param compress [true, false] Whether certain large packets should be compressed using zlib.
    # @param large_threshold [Integer] The member threshold after which a server counts as large and will have to have
    #   its member list chunked.
    # @param shard_key [Array(Integer, Integer), nil] The shard key to use for sharding, represented as
    #   [shard_id, num_shards], or nil if the bot should not be sharded.
    def send_identify(token, properties, compress, large_threshold, shard_key = nil)
      data = {
        # Don't send a v anymore as it's entirely determined by the URL now
        token: token,
        properties: properties,
        compress: compress,
        large_threshold: large_threshold
      }

      # Don't include the shard key at all if it is nil as Discord checks for its mere existence
      data[:shard] = shard_key if shard_key

      send_packet(Opcodes::IDENTIFY, data)
    end

    # Sends a status update packet (op 3). This sets the bot user's status (online/idle/...) and game playing/streaming.
    # @param status [String] The status that should be set (`online`, `idle`, `dnd`, `invisible`).
    # @param since [Integer] The unix timestamp in milliseconds when the status was set. Should only be provided when
    #   `afk` is true.
    # @param game [Hash<Symbol => Object>, nil] `nil` if no game should be played, or a hash of `:game => "name"` if a
    #   game should be played. The hash can also contain additional attributes for streaming statuses.
    # @param afk [true, false] Whether the status was set due to inactivity on the user's part.
    def send_status_update(status, since, game, afk)
      data = {
        status: status,
        since: since,
        game: game,
        afk: afk
      }

      send_packet(Opcodes::PRESENCE, data)
    end

{'fraction_non_alphanumeric': 0.061936495491963936, 'fraction_numerical': 0.003253626029008232, 'mean_word_length': 2.982982045277127, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1325576', 'n_tokens_mistral': 6926, 'n_tokens_neox': 6570, 'n_words': 3388}