text:
# frozen_string_literal: true

# Use this hook to configure devise mailer, warden hooks and so forth.
# Many of these configuration options can be set straight in your model.
Devise.setup do |config|
  # The secret key used by Devise. Devise uses this key to generate
  # random tokens. Changing this key will render invalid all existing
  # confirmation, reset password and unlock tokens in the database.
  # rubocop:disable Metrics/LineLength
  # config.secret_key = '39ce439262a99bf92d00257a2ac32c8199cf961537c2bf65a313e7fb63085a20abd1e9527e8f92dd2c04777d20cf0258c50befc8fd7b6772548d8b1240660369'
  # rubocop:enable Metrics/LineLength

  # ==> Mailer Configuration
  # Configure the e-mail address which will be shown in Devise::Mailer,
  # note that it will be overwritten if you use your own mailer class
  # with default 'from' parameter.
  config.mailer_sender = 'metasmoke@erwaysoftware.com'

  # Configure the class responsible to send e-mails.
  # config.mailer = 'Devise::Mailer'

  # ==> ORM configuration
  # Load and configure the ORM. Supports :active_record (default) and
  # :mongoid (bson_ext recommended) by default. Other ORMs may be
  # available as additional gems.
  require 'devise/orm/active_record'

  # ==> Configuration for any authentication mechanism
  # Configure which keys are used when authenticating a user. The default is
  # just :email. You can configure it to use [:username, :subdomain], so for
  # authenticating a user, both parameters are required. Remember that those
  # parameters are used only when authenticating and not when retrieving from
  # session. If you need permissions, you should implement that in a before filter.
  # You can also supply a hash where the value is a boolean determining whether
  # or not authentication should be aborted when the value is not present.
  # config.authentication_keys = [ :email ]

  # Configure parameters from the request object used for authentication. Each entry
  # given should be a request method and it will automatically be passed to the
  # find_for_authentication method and considered in your model lookup. For instance,
  # if you set :request_keys to [:subdomain], :subdomain will be used on authentication.
  # The same considerations mentioned for authentication_keys also apply to request_keys.
  # config.request_keys = []

  # Configure which authentication keys should be case-insensitive.
  # These keys will be downcased upon creating or modifying a user and when used
  # to authenticate or find a user. Default is :email.
  config.case_insensitive_keys = [:email]

  # Configure which authentication keys should have whitespace stripped.
  # These keys will have whitespace before and after removed upon creating or
  # modifying a user and when used to authenticate or find a user. Default is :email.
  config.strip_whitespace_keys = [:email]

  # Tell if authentication through request.params is enabled. True by default.
  # It can be set to an array that will enable params authentication only for the
  # given strategies, for example, `config.params_authenticatable = [:database]` will
  # enable it only for database (email + password) authentication.
  # config.params_authenticatable = true

  # Tell if authentication through HTTP Auth is enabled. False by default.
  # It can be set to an array that will enable http authentication only for the
  # given strategies, for example, `config.http_authenticatable = [:database]` will
  # enable it only for database authentication. The supported strategies are:
  # :database      = Support basic authentication with authentication key + password
  # config.http_authenticatable = false

  # If 401 status code should be returned for AJAX requests. True by default.
  # config.http_authenticatable_on_xhr = true

  # The realm used in Http Basic Authentication. 'Application' by default.
  # config.http_authentication_realm = 'Application'

  # It will change confirmation, password recovery and other workflows
  # to behave the same regardless if the e-mail provided was right or wrong.
  # Does not affect registerable.
  # config.paranoid = true

  # By default Devise will store the user in session. You can skip storage for
  # particular strategies by setting this option.
  # Notice that if you are skipping storage for all authentication paths, you
  # may want to disable generating routes to Devise's sessions controller by
  # passing skip: :sessions to `devise_for` in your config/routes.rb
  config.skip_session_storage = [:http_auth]

  # By default, Devise cleans up the CSRF token on authentication to
  # avoid CSRF token fixation attacks. This means that, when using AJAX
  # requests for sign in and sign up, you need to get a new CSRF token
  # from the server. You can disable this option at your own risk.
  # config.clean_up_csrf_token_on_authentication = true

  # ==> Configuration for :database_authenticatable
  # For bcrypt, this is the cost for hashing the password and defaults to 10. If
  # using other encryptors, it sets how many times you want the password re-encrypted.
  #
  # Limiting the stretches to just one in testing will increase the performance of
  # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use
  # a value less than 10 in other environments. Note that, for bcrypt (the default
  # encryptor), the cost increases exponentially with the number of stretches (e.g.
  # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation).
  config.stretches = Rails.env.test? ? 1 : 10

  # Setup a pepper to generate the encrypted password.
  # config.pepper = 'ff5a3a7c04c1e977dc5002044107f04389b38814fc8b8f2293e99a01b7f7a0099fc85e35b981631fad532bba675e567f77f3606a0daab6a8f6794121cc8e299a'

  # ==> Configuration for :confirmable
  # A period that the user is allowed to access the website even without
  # confirming their account. For instance, if set to 2.days, the user will be
  # able to access the website for two days without confirming their account,
  # access will be blocked just in the third day. Default is 0.days, meaning
  # the user cannot access the website without confirming their account.
  # config.allow_unconfirmed_access_for = 2.days

  # A period that the user is allowed to confirm their account before their
  # token becomes invalid. For example, if set to 3.days, the user can confirm
  # their account within 3 days after the mail was sent, but on the fourth day
  # their account can't be confirmed with the token any more.
  # Default is nil, meaning there is no restriction on how long a user can take
  # before confirming their account.
  # config.confirm_within = 3.days

  # If true, requires any email changes to be confirmed (exactly the same way as
  # initial account confirmation) to be applied. Requires additional unconfirmed_email
  # db field (see migrations). Until confirmed, new email is stored in
  # unconfirmed_email column, and copied to email column on successful confirmation.
  config.reconfirmable = false

  # Defines which key will be used when confirming an account
  # config.confirmation_keys = [ :email ]

  # ==> Configuration for :rememberable
  # The time the user will be remembered without asking for credentials again.
  # config.remember_for = 2.weeks

  # Invalidates all the remember me tokens when the user signs out.
  config.expire_all_remember_me_on_sign_out = true

  # If true, extends the user's remember period when remembered via cookie.
  # config.extend_remember_period = false

  # Options to be passed to the created cookie. For instance, you can set
  # secure: true in order to force SSL only cookies.
  # config.rememberable_options = {}

  # ==> Configuration for :validatable
  # Range for password length.
  config.password_length = 8..128

  # Email regex used to validate email formats. It simply asserts that
  # one (and only one) @ exists in the given string. This is mainly
  # to give user feedback and not to assert the e-mail validity.
  # config.email_regexp = /\A[^@]+@[^@]+\z/

  # ==> Configuration for :timeoutable
  # The time you want to timeout the user session without activity. After this
  # time the user will be asked for credentials again. Default is 30 minutes.
  # config.timeout_in = 30.minutes

  # If true, expires auth token on session timeout.
  # config.expire_auth_token_on_timeout = false

  # ==> Configuration for :lockable
  # Defines which strategy will be used to lock an account.
  # :failed_attempts = Locks an account after a number of failed attempts to sign in.
  # :none            = No lock strategy. You should handle locking by yourself.
  # config.lock_strategy = :failed_attempts

  # Defines which key will be used when locking and unlocking an account
  # config.unlock_keys = [ :email ]

  # Defines which strategy will be used to unlock an account.
  # :email = Sends an unlock link to the user email
  # :time  = Re-enables login after a certain amount of time (see :unlock_in below)
  # :both  = Enables both strategies
  # :none  = No unlock strategy. You should handle unlocking by yourself.
  # config.unlock_strategy = :both

  # Number of authentication tries before locking an account if lock_strategy
  # is failed attempts.
  # config.maximum_attempts = 20

  # Time interval to unlock the account if :time is enabled as unlock_strategy.
  # config.unlock_in = 1.hour

  # Warn on the last attempt before the account is locked.
  # config.last_attempt_warning = true

  # ==> Configuration for :recoverable
  #
  # Defines which key will be used when recovering the password for an account
  # config.reset_password_keys = [ :email ]

  # Time interval you can reset your password with a reset password key.
  # Don't put a too small interval or your users won't have the time to
  # change their passwords.
  config.reset_password_within = 6.hours

  # ==> Configuration for :encryptable
  # Allow you to use another encryption algorithm besides bcrypt (default). You can use
  # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1,
  # :authlogic_sha512 (then you should set stretches above to 20 for default behavior)
  # and :restful_authentication_sha1 (then you should set stretches to 10, and copy
  # REST_AUTH_SITE_KEY to pepper).
  #
  # Require the `devise-encryptable` gem when using anything other than bcrypt
  # config.encryptor = :sha512

  # ==> Scopes configuration
  # Turn scoped views on. Before rendering 'sessions/new', it will first check for
  # 'users/sessions/new'. It's turned off by default because it's slower if you
  # are using only default views.
  # config.scoped_views = false

  # Configure the default scope given to Warden. By default it's the first
  # devise role declared in your routes (usually :user).
  # config.default_scope = :user

  # Set this configuration to false if you want /users/sign_out to sign out
  # only the current scope. By default, Devise signs out all scopes.
  # config.sign_out_all_scopes = true

  # ==> Navigation configuration
  # Lists the formats that should be treated as navigational. Formats like
  # :html, should redirect to the sign in page when the user does not have
  # access, but formats like :xml or :json, should return 401.
  #
  # If you have any extra navigational formats, like :iphone or :mobile, you
  # should add them to the navigational formats lists.
  #
  # The '*/*' below is required to match Internet Explorer requests.
  # config.navigational_formats = ['*/*', :html]

  # The default HTTP method used to sign out a resource. Default is :delete.
  config.sign_out_via = :delete

  # ==> OmniAuth
  # Add a new OmniAuth provider. Check the wiki for more information on setting
  # up on your models and hooks.
  # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'

  # ==> Warden configuration
  # If you want to use other strategies, that are not supported by Devise, or
  # change the failure app, you can configure them inside the config.warden block.
  #
  # config.warden do |manager|
  #   manager.intercept_401 = false
  #   manager.default_strategies(scope: :user).unshift :some_external_strategy
  # end

  # ==> Mountable engine configurations
  # When using Devise inside an engine, let's call it `MyEngine`, and this engine
  # is mountable, there are some extra configurations to be taken into account.
  # The following options are available, assuming the engine is mounted as:
  #
  #     mount MyEngine, at: '/my_engine'
  #
  # The router that invoked `devise_for`, in the example above, would be:
  # config.router_name = :my_engine
  #
  # When using omniauth, Devise cannot automatically set Omniauth path,
  # so you need to do it manually. For the users scope, it would be:
  # config.omniauth_path_prefix = '/my_engine/users/auth'
end


text:
# frozen_string_literal: true

# Be sure to restart your server when you modify this file.

Rails.application.config.session_store :cookie_store, key: '_metasmoke_session'

repo_name: Charcoal-SE/metasmoke

repo_language: Ruby

mime_type: text/plain


text:
# frozen_string_literal: true

# Be sure to restart your server when you modify this file.

# Configure sensitive parameters which will be filtered from the log file.
Rails.application.config.filter_parameters += [:password]


text:
# Files in the config/locales directory are used for internationalization
# and are automatically loaded by Rails. If you want to use locales other
# than English, add the necessary files in this directory.
#
# To use the locales, use `I18n.t`:
#
#     I18n.t 'hello'
#
# In views, this is aliased to just `t`:
#
#     <%= t('hello') %>
#
# To use a different locale, set it with `I18n.locale`:
#
#     I18n.locale = :es
#
# This would use the information in config/locales/es.yml.
#
# To learn more, please read the Rails Internationalization guide
# available at http://guides.rubyonrails.org/i18n.html.

en:
  hello: "Hello world"


text:
# Additional translations at https://github.com/plataformatec/devise/wiki/I18n

en:
  devise:
    confirmations:
      confirmed: "Your email address has been successfully confirmed."
      send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes."
      send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
    failure:
      already_authenticated: "You are already signed in."
      inactive: "Your account is not activated yet."
      invalid: "Invalid %{authentication_keys} or password."
      locked: "Your account is locked."
      last_attempt: "You have one more attempt before your account is locked."
      not_found_in_database: "Invalid %{authentication_keys} or password."
      timeout: "Your session expired. Please sign in again to continue."
      unauthenticated: "You need to sign in or sign up before continuing."
      unconfirmed: "You have to confirm your email address before continuing."
      not_approved: "Accounts require manual activation. Ping a metasmoke admin in Charcoal HQ (room 11540)."
    mailer:
      confirmation_instructions:
        subject: "Confirmation instructions"
      reset_password_instructions:
        subject: "Reset password instructions"
      unlock_instructions:
        subject: "Unlock instructions"
    omniauth_callbacks:
      failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
      success: "Successfully authenticated from %{kind} account."
    passwords:
      no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
      send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
      send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
      updated: "Your password has been changed successfully. You are now signed in."
      updated_not_active: "Your password has been changed successfully."
    registrations:
      destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon."
      signed_up: "Welcome! You have signed up successfully."
      signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
      signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
      signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
      signed_up_but_not_approved: "Accounts require manual activation to access /review. Ping @Undo in http://chat.stackexchange.com/rooms/11540/charcoal-hq"
      update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address."
      updated: "Your account has been updated successfully."
    sessions:
      signed_in: "Signed in successfully."
      signed_out: "Signed out successfully."
      already_signed_out: "Signed out successfully."
    unlocks:
      send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes."
      send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes."
      unlocked: "Your account has been unlocked successfully. Please sign in to continue."
  errors:
    messages:
      already_confirmed: "was already confirmed, please try signing in"
      confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
      expired: "has expired, please request a new one"
      not_found: "not found"
      not_locked: "was not locked"
      not_saved:
        one: "1 error prohibited this %{resource} from being saved:"
        other: "%{count} errors prohibited this %{resource} from being saved:"


text:
# Sample localization file for English. Add more files in this directory for other locales.
# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.

en:
  breadcrumbs:
    application:
      root: "Index"
    pages:
      pages: "Pages"
  helpers:
    actions: "Actions"
    links:
      back: "Back"
      cancel: "Cancel"
      confirm: "Are you sure?"
      destroy: "Delete"
      new: "New"
      edit: "Edit"
    titles:
      edit: "Edit %{model}"
      save: "Save %{model}"
      new: "New %{model}"
      delete: "Delete %{model}"


text:
<!DOCTYPE html>

  <title>The page you were looking for doesn't exist (404)</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">

The page you were looking for doesn't exist.

You may have mistyped the address or the page may have moved.

If you are the application owner check the logs for more information.


text:
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">
  <ShortName>metasmoke</ShortName>
  <Description>Searches the content of all post bodies in metasmoke.</Description>
  <Url type="text/html" template="https://metasmoke.erwaysoftware.com/search?body={searchTerms}"/>
  <Image height="32" width="32" type="image/vnd.microsoft.icon">https://metasmoke.erwaysoftware.com/favicon.ico</Image>
  <LongName>metasmoke post bodies search</LongName>
  <SyndicationRight>limited</SyndicationRight>
  <moz:SearchForm>https://metasmoke.erwaysoftware.com/search</moz:SearchForm>
</OpenSearchDescription>


text:
# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
#
# To ban all spiders from the entire site uncomment the next two lines:
User-agent: *
Disallow: /


text:
// :( :( :( :( :( :( :( :( :( :( :( :( :(  //
// This file DOES NOT use webpack/babel.  //
// This means that `require` and async/  //
// await are not supported. Using them  //
// leads to a Fun™ issue with browser  //
// globals. Sorry :( :( :( :( :( :(   //
// :( :( :( :( :( :( :( :( :( :( :(  //

/* globals application */

application.setInterface({
  eval({ code, store }, cb) {
    Promise.resolve()
           .then(() => (0, eval)(code)) // eslint-disable-line no-eval
           .then(fn => fn(store))
           .then(res => cb(null, res))
           .catch(err => cb({ stack: err.stack, message: err.message }));
  }
});


text:
<!DOCTYPE html>

  <title>The change you wanted was rejected (422)</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">

The change you wanted was rejected.

Maybe you tried to change something you didn't have access to.

If you are the application owner check the logs for more information.


text:
<!DOCTYPE html>

  <title>We're sorry, but something went wrong (500)</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">

We're sorry, but something went wrong.

If you are the application owner check the logs for more information.


text:
<!DOCTYPE html>
<html>
<head>
    <script src="https://cdn.jsdelivr.net/gh/ofirdagan/cross-domain-local-storage@d779a81a6383475a1bf88595a98b10a8bd5bb4ae/dist/scripts/xdLocalStoragePostMessageApi.min.js"></script>
</head>
<body>
    I bet you never think of me when you're out there, flagging away. I'm the bit that makes it all work. You're welcome.
</body>
</html>


text:
Contact: security@charcoal-se.org
Encryption: https://metasmoke.erwaysoftware.com/security-pubkey.txt
Acknowledgements: https://charcoal-se.org/security
Policy: https://charcoal-se.org/security


text:
# Spotify API

* Create a [Spotify Application](https://developer.spotify.com/dashboard/applications)
* Take note of:
    * `Client ID`
    * `Client Secret`
* Click on **Edit Settings**
* In **Redirect URIs**:
    * Add `http://localhost/callback/`

# Refresh Token

* Navigate to the following URL:

```
https://accounts.spotify.com/authorize?client_id={SPOTIFY_CLIENT_ID}&response_type=code&scope=user-read-currently-playing,user-read-recently-played&redirect_uri=http://localhost/callback/
```

* After logging in, save the {CODE} portion of: `http://localhost/callback/?code={CODE}`

* Create a string combining `{SPOTIFY_CLIENT_ID}:{SPOTIFY_CLIENT_SECRET}` (e.g. `5n7o4v5a3t7o5r2e3m1:5a8n7d3r4e2w5n8o2v3a7c5`) and **encode** into [Base64](https://base64.io/).

* Then run a [curl command](https://httpie.org/run) in the form of:
```sh
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -H "Authorization: Basic {BASE64}" -d "grant_type=authorization_code&redirect_uri=http://localhost/callback/&code={CODE}" https://accounts.spotify.com/api/token
```

* Save the Refresh token

# Deployment

## Deploy to Vercel

* Register on [Vercel](https://vercel.com/)

* Fork this repo, then create a vercel project linked to it

* Add Environment Variables:
    * `https://vercel.com/<YourName>/<ProjectName>/settings/environment-variables`
        * `SPOTIFY_REFRESH_TOKEN`
        * `SPOTIFY_CLIENT_ID`
        * `SPOTIFY_SECRET_ID`

* Deploy!

## Deploy to Heroku

[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://dashboard.heroku.com/new?template=https%3A%2F%2Fgithub.com%2Fnovatorem%2Fnovatorem)
- Create a Heroku application via the Heroku CLI or via the Heroku Dashboard. Connect the app with your GitHub repository and enable automatic builds

    `PS. automatic build means that everytime you push changes to remote, heroku will rebuild and redeploy the app.`
    - To start the Flask server execute `heroku ps:scale web=1` once the build is completed.
- Or click the `Deploy to Heroku` button above to automatically start the deployment process.

## Run locally with Docker

* You need to have [Docker](https://docs.docker.com/get-docker/) installed.

* Add Environment Variables:
    * `SPOTIFY_REFRESH_TOKEN`
    * `SPOTIFY_CLIENT_ID`
    * `SPOTIFY_SECRET_ID`

* To run the service, open a terminal in the root folder of the repo:

    Execute:
    ```
    docker compose up
    ```
* When finished, navigate to [http://localhost:5000/](http://localhost:5000/)

* To stop the service, open a terminal in the root folder of the repo:

    Execute:
    ```
    docker compose down
    ```

# Readme

You can now use the following in your readme:

```[![Spotify](https://USER_NAME.vercel.app/api/spotify)](https://open.spotify.com/user/USER_NAME)```

# Customization

## Hide the EQ bar

Remove the `#` in front of `contentBar` in [line 81](https://github.com/novatorem/novatorem/blob/98ba4a8489ad86f5f73e95088e620e8859d28e71/api/spotify.py#L81) of current master, then the EQ bar will be hidden when you're in not currently playing anything.

## Status String

Have a string saying either "Vibing to:" or "Last seen playing:".

* Change [`height` to `height + 40`](https://github.com/novatorem/novatorem/blob/5194a689253ee4c89a9d365260d6050923d93dd5/api/templates/spotify.html.j2#L1-L2) (or whatever `margin-top` is set to)
* Uncomment [**.main**'s `margin-top`](https://github.com/novatorem/novatorem/blob/5194a689253ee4c89a9d365260d6050923d93dd5/api/templates/spotify.html.j2#L10)
* Uncomment [currentStatus](https://github.com/novatorem/novatorem/blob/5194a689253ee4c89a9d365260d6050923d93dd5/api/templates/spotify.html.j2#L93)

## Theme Templates

If you want to change the widget theme, you can do so by the changing the `current-theme` property in the `templates.json` file.

Themes:
* `light`
* `dark`

If you wish to customize farther, you can add your own customized `spotify.html.j2` file to the templates folder, and add the theme and file name to the `templates` dictionary in the `templates.json` file.

## Color

You can customize the appearance of your `Card` however you wish with URL params.

### Common Options:

- `background_color` - Card's background color _(hex color)_ without `#`
- `border_color` - Card border color _(hex color)_ without `#`

Use `/?background_color=8b0000&border_color=ffffff` parameter like so:

 [![Spotify](https://novatorem.vercel.app/api/spotify?background_color=0d1117&border_color=ffffff)]()

## Spotify Logo

You can add the spotify logo by removing the commented out code, seen below:
```html
<a href="{{songURI}}" class="spotify-logo">
    <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Spotify</title><path d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z"/></svg>
</a>
```

## Requests

Customization requests can be submitted as an issue, like https://github.com/novatorem/novatorem/issues/2

If you want to share your own customization options, open a PR if it's done or open an issue if you want it implemented by someone else.

# Debugging
If you have issues setting up, try following this [guide](https://youtu.be/n6d4KHSKqGk?t=615).

Followed the guide and still having problems?
Try checking out the functions tab in vercel, linked as:
```https://vercel.com/{name}/spotify/{build}/functions```

Which looks like-

![image](https://user-images.githubusercontent.com/16753077/91338931-b0326680-e7a3-11ea-8178-5499e0e73250.png)

You will see a log there, and most issues can be resolved by ensuring you have the correct variables from setup.

repo_name: novatorem/novatorem

repo_language: Jinja


text:
version: '3.8'
services:
    spotify-readme:
        env_file:
            - .env
        build: .
        ports:
            - "5000:5000"
        volumes:
            - ./api:/api
volumes:
  persistent:


text: web: gunicorn --workers=1 api.spotify:app


text:
autopep8==1.6.0
certifi==2020.6.20
chardet==3.0.4
click==7.1.2
Flask==1.1.4
gunicorn==20.1.0
idna==2.10
itsdangerous==1.1.0
Jinja2==2.11.3
MarkupSafe==2.0.1
pycodestyle==2.8.0
python-dotenv==0.14.0
requests==2.24.0
toml==0.10.2
urllib3==1.25.11
Werkzeug==1.0.1
colorthief==0.2.1


text:
# syntax=docker/dockerfile:1

FROM python:3.10.0

WORKDIR /api

COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt --no-cache-dir

COPY api/ .

CMD ["gunicorn", "--workers=1", "--bind", "0.0.0.0:5000", "spotify:app"]


text:
  [![Spotify](https://novatorem.vercel.app/api/spotify?background_color=0d1117&border_color=ffffff)](https://open.spotify.com/user/omnitenebris)

  [Fork](https://github.com/novatorem/novatorem/blob/main/SetUp.md) this [unlicensed](https://choosealicense.com/licenses/unlicense/) repository to recreate!

  [![Linkedin](https://img.shields.io/badge/linked-in-369?style=flat-square&logo=linkedin&logoColor=white&color=blue)](https://www.linkedin.com/in/andrew-novac)
  [![E-Mail](https://img.shields.io/badge/email-reveal-2a8?style=flat-square&logo=gmail&logoColor=white)](https://mail.novac.dev/)
  [![Visits](https://komarev.com/ghpvc/?username=novatorem&logo=GitHub&label=github%20visits&color=336699&logoColor=white&style=flat-square)](https://github.com/novatorem)


text:
from io import BytesIO
import os
import json
import random
import requests

from colorthief import ColorThief
from base64 import b64encode
from dotenv import load_dotenv, find_dotenv
from flask import Flask, Response, render_template, request

load_dotenv(find_dotenv())

# Spotify scopes:
#   user-read-currently-playing
#   user-read-recently-played
PLACEHOLDER_IMAGE = "[encoded data, 10728 characters]"
PLACEHOLDER_URL = "https://source.unsplash.com/random/300x300/?aerial"
SPOTIFY_CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID")
SPOTIFY_SECRET_ID = os.getenv("SPOTIFY_SECRET_ID")
SPOTIFY_REFRESH_TOKEN = os.getenv("SPOTIFY_REFRESH_TOKEN")
SPOTIFY_TOKEN = ""

FALLBACK_THEME = "spotify.html.j2"

REFRESH_TOKEN_URL = "https://accounts.spotify.com/api/token"
NOW_PLAYING_URL = "https://api.spotify.com/v1/me/player/currently-playing"
RECENTLY_PLAYING_URL = (
    "https://api.spotify.com/v1/me/player/recently-played?limit=10"
)

app = Flask(__name__)

def getAuth():
    return b64encode(f"{SPOTIFY_CLIENT_ID}:{SPOTIFY_SECRET_ID}".encode()).decode(
        "ascii"
    )

def refreshToken():
    data = {
        "grant_type": "refresh_token",
        "refresh_token": SPOTIFY_REFRESH_TOKEN,
    }

    headers = {"Authorization": "Basic {}".format(getAuth())}
    response = requests.post(
        REFRESH_TOKEN_URL, data=data, headers=headers).json()

    try:
        return response["access_token"]
    except KeyError:
        print(json.dumps(response))
        print("\n---\n")
        raise KeyError(str(response))

def get(url):
    global SPOTIFY_TOKEN

    if (SPOTIFY_TOKEN == ""):
        SPOTIFY_TOKEN = refreshToken()

    response = requests.get(
        url, headers={"Authorization": f"Bearer {SPOTIFY_TOKEN}"})

    if response.status_code == 401:
        SPOTIFY_TOKEN = refreshToken()
        response = requests.get(
            url, headers={"Authorization": f"Bearer {SPOTIFY_TOKEN}"}).json()
        return response
    elif response.status_code == 204:
        raise Exception(f"{url} returned no data.")
    else:
        return response.json()

def barGen(barCount):
    barCSS = ""
    left = 1
    for i in range(1, barCount + 1):
        anim = random.randint(500, 1000)
        # below code generates random cubic-bezier values
        x1 = random.random()
        y1 = random.random()*2
        x2 = random.random()
        y2 = random.random()*2
        barCSS += (
            ".bar:nth-child({})  {{ left: {}px; animation-duration: 15s, {}ms; animation-timing-function: ease, cubic-bezier({},{},{},{}); }}".format(
                i, left, anim, x1, y1, x2, y2
            )
        )
        left += 4
    return barCSS

def gradientGen(albumArtURL, color_count):
    colortheif = ColorThief(BytesIO(requests.get(albumArtURL).content))
    palette = colortheif.get_palette(color_count)
    return palette

def getTemplate():
    try:
        file = open("api/templates.json", "r")
        templates = json.loads(file.read())
        return templates["templates"][templates["current-theme"]]
    except Exception as e:
        print(f"Failed to load templates.\r\n```{e}```")
        return FALLBACK_THEME

def loadImageB64(url):
    response = requests.get(url)
    return b64encode(response.content).decode("ascii")

def makeSVG(data, background_color, border_color):
    barCount = 84
    contentBar = "".join(["

" for _ in range(barCount)])
    barCSS = barGen(barCount)

    if not "is_playing" in data:
        #contentBar = "" #Shows/Hides the EQ bar if no song is currently playing
        currentStatus = "Recently played:"
        recentPlays = get(RECENTLY_PLAYING_URL)
        recentPlaysLength = len(recentPlays["items"])
        itemIndex = random.randint(0, recentPlaysLength - 1)
        item = recentPlays["items"][itemIndex]["track"]
    else:
        item = data["item"]
        currentStatus = "Vibing to:"

    if item["album"]["images"] == []:
        image = PLACEHOLDER_IMAGE
        barPalette = gradientGen(PLACEHOLDER_URL, 4)
        songPalette = gradientGen(PLACEHOLDER_URL, 2)
    else:
        image = loadImageB64(item["album"]["images"][1]["url"])
        barPalette = gradientGen(item["album"]["images"][1]["url"], 4)
        songPalette = gradientGen(item["album"]["images"][1]["url"], 2)

    artistName = item["artists"][0]["name"].replace("&", "&")
    songName = item["name"].replace("&", "&")
    songURI = item["external_urls"]["spotify"]
    artistURI = item["artists"][0]["external_urls"]["spotify"]

    dataDict = {
        "contentBar": contentBar,
        "barCSS": barCSS,
        "artistName": artistName,
        "songName": songName,
        "songURI": songURI,
        "artistURI": artistURI,
        "image": image,
        "status": currentStatus,
        "background_color": background_color,
        "border_color": border_color,
        "barPalette": barPalette,
        "songPalette": songPalette
    }

    return render_template(getTemplate(), **dataDict)

@app.route("/", defaults={"path": ""})
@app.route("/<path:path>")
@app.route('/with_parameters')
def catch_all(path):
    background_color = request.args.get('background_color') or "181414"
    border_color = request.args.get('border_color') or "181414"

    try:
        data = get(NOW_PLAYING_URL)
    except Exception:
        data = get(RECENTLY_PLAYING_URL)

    svg = makeSVG(data, background_color, border_color)

    resp = Response(svg, mimetype="image/svg+xml")
    resp.headers["Cache-Control"] = "s-maxage=1"

    return resp

if __name__ == "__main__":
    app.run(host="0.0.0.0", debug=True, port=os.getenv("PORT") or 5000)


text:
/*
    pixel-perfect upscales bdf bitmap fonts

    ![](http://hnng.moe/f/ZsR)
    ![](http://hnng.moe/f/Zsa)

    rationale
    -----------------------------------------------------------
    I love the pixel art aesthetics of small bitmap
    fonts, but they're way too small for a 1080p screen and
    bigger fonts don't look quite as good, so I figured I'd
    simply take the small fonts and make every pixel a 2x2
    block

    for now I've only implemented 2x scaling, and while it's
    trivial to add 3x, I'm not gonna bother as this was made
    out of necessity. feel free to PR it though!

    usage
    -----------------------------------------------------------
    ```sh
    curl -O https://raw.githubusercontent.com/Francesco149/bdf2x/master/bdf2x.c
    sudo gcc bdf2x.c -o /usr/bin/bdf2x

    # copy your desired font and convert it to bdf
    cat /usr/share/fonts/X11/misc/ProFont_r400-11.pcf \
    | pcf2bdf -o ProFont_r400-11.bdf

    bdf2x ProFont_r400-11.bdf > 2xProFont_r400-11.bdf

    bdftopcf 2xProFont_r400-11.bdf \
    | gzip \
    | sudo tee /usr/share/fonts/X11/misc/2xProFont_r400-11.pcf.gz \
    > /dev/null

    sudo mkfontdir /usr/share/fonts/X11/misc
    xset fp rehash

    # check your font in xfontsel
    ```

    license
    -----------------------------------------------------------
    this is free and unencumbered software released into the
    public domain.

    refer to the attached UNLICENSE or http://unlicense.org/
*/

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdint.h>

#define WHOAMI "bdf2x"
#define VERSION_MAJOR 1
#define VERSION_MINOR 0
#define VERSION_PATCH 0

#define WHITESPACE " \r\n\t\f"

static inline
void printver(FILE* f)
{
    fprintf(f, WHOAMI "-%d.%d.%d\n",
        VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
}

static inline
void bdf2x_font_name(char* start)
{
    int i;
    char* end;

    printf("FONT ");
    start += strspn(start, WHITESPACE);

    for (i = 0; i < 15; ++i)
    {
        end = start + strcspn(start, "-");

        switch (i)
        {
        case 7:
        case 8:
        case 12:
            printf("%d-", atoi(start) * 2);
            break;

        case 2:
            printf("2x"); /* fallthrough to print rest of name */
        default:
            fwrite(start, 1, end - start + 1, stdout);
            break;
        case 14:
            fwrite(start, 1, end - start, stdout);
        }

        start = end + 1;
    }

    puts("");
}

static inline
void bdf2x_double_bits(char* start)
{
    char buf[1024];
    char* p = buf;

    for (;isxdigit(*start);)
    {
        uint8_t byte;
        uint16_t doubled;
        char bytestr[3];

        bytestr[0] = *start++;
        bytestr[1] = *start++;
        bytestr[2] = 0;

        byte = (uint8_t)strtol(bytestr, 0, 16);

        doubled = (uint16_t)(
            ((byte & 0x01)<<0) |
            ((byte & 0x01)<<1) |
            ((byte & 0x02)<<1) |
            ((byte & 0x02)<<2) |
            ((byte & 0x04)<<2) |
            ((byte & 0x04)<<3) |
            ((byte & 0x08)<<3) |
            ((byte & 0x08)<<4) |
            ((byte & 0x10)<<4) |
            ((byte & 0x10)<<5) |
            ((byte & 0x20)<<5) |
            ((byte & 0x20)<<6) |
            ((byte & 0x40)<<6) |
            ((byte & 0x40)<<7) |
            ((byte & 0x80)<<7) |
            ((byte & 0x80)<<8)
        );

        p += snprintf(p, buf + 1024 - p - 1, "%04X", doubled);
    }

    puts(buf);
    puts(buf);
}

int main(int argc, char* argv[])
{
    FILE* f;
    char* line = 0;
    size_t line_size = 0;
    int in_bitmap = 0;

    if (argc != 2) {
        printver(stderr);
        fprintf(stderr, "usage: %s file.bdf > file2x.bdf\n",
            argv[0]);
        return 1;
    }

    if (!strcmp(argv[1], "-v")) {
        printver(stdout);
        return 0;
    }

    f = fopen(argv[1], "r");
    if (!f) {
        perror("fopen");
        return 1;
    }

    while (getline(&line, &line_size, f) >= 0)
    {
        char* start;
        char* end;
        size_t n;

        start = line + strspn(line, WHITESPACE);
        end = start + strcspn(line, WHITESPACE);
        n = end - start;

        if (start >= end) {
            goto continueloop;
        }

        if (!strncmp(start, "FONT", n)) {
            bdf2x_font_name(end);
            continue;
        }

        if (!strncmp(start, "CAP_HEIGHT", n) ||
            !strncmp(start, "POINT_SIZE", n) ||
            !strncmp(start, "X_HEIGHT", n) ||
            !strncmp(start, "QUAD_WIDTH", n) ||
            !strncmp(start, "FONT_DESCENT", n) ||
            !strncmp(start, "FONT_ASCENT", n) ||
            !strncmp(start, "SWIDTH", n) ||
            !strncmp(start, "DWIDTH", n) ||
            !strncmp(start, "BBX", n) ||
            !strncmp(start, "SIZE", n) ||
            !strncmp(start, "FONTBOUNDINGBOX", n))
        {
            int only_first;
            int multiplier = 2;

            only_first = !strncmp(start, "SIZE", n);

            fwrite(start, n, 1, stdout);
            start += strcspn(start, WHITESPACE);
            start += strspn(start, WHITESPACE);

            while (start < line + strlen(line))
            {
                printf(" %d", atoi(start) * multiplier);
                start += strcspn(start, WHITESPACE);
                start += strspn(start, WHITESPACE);

                if (only_first) {
                    multiplier = 1;
                }
            }

            puts("");
            continue;
        }

        if (!strncmp(start, "BITMAP", n)) {
            in_bitmap = 1;
        }

        else if (!strncmp(start, "ENDCHAR", n)) {
            in_bitmap = 0;
        }

        else if (in_bitmap) {
            bdf2x_double_bits(start);
            continue;
        }

continueloop:
        printf("%s", line);
    }

    return 0;
}

repo_language: C


text:
#!/usr/bin/env python

import sys
print_lines = False

for line in sys.stdin:
    if line.strip().endswith("*/"):
        sys.exit(0);
    if print_lines:
        print(line[4:-1])
    elif line.strip().startswith("/*"):
        print_lines = True


text:
pixel-perfect upscales bdf bitmap fonts

![](http://hnng.moe/f/ZsR)
![](http://hnng.moe/f/Zsa)

rationale
-----------------------------------------------------------
I love the pixel art aesthetics of small bitmap
fonts, but they're way too small for a 1080p screen and
bigger fonts don't look quite as good, so I figured I'd
simply take the small fonts and make every pixel a 2x2
block

for now I've only implemented 2x scaling, and while it's
trivial to add 3x, I'm not gonna bother as this was made
out of necessity. feel free to PR it though!

usage
-----------------------------------------------------------
```sh
curl -O https://raw.githubusercontent.com/Francesco149/bdf2x/master/bdf2x.c
sudo gcc bdf2x.c -o /usr/bin/bdf2x

# copy your desired font and convert it to bdf
cat /usr/share/fonts/X11/misc/ProFont_r400-11.pcf \
| pcf2bdf -o ProFont_r400-11.bdf

bdf2x ProFont_r400-11.bdf > 2xProFont_r400-11.bdf

bdftopcf 2xProFont_r400-11.bdf \
| gzip \
| sudo tee /usr/share/fonts/X11/misc/2xProFont_r400-11.pcf.gz \
> /dev/null

sudo mkfontdir /usr/share/fonts/X11/misc
xset fp rehash

# check your font in xfontsel
```

license
-----------------------------------------------------------
this is free and unencumbered software released into the
public domain.

refer to the attached UNLICENSE or http://unlicense.org/


text:
# Python for Geospatial Data Analysis

## Introduction

This includes short and minimalistic few sessions covering fundamentals of Python programing language for geospatial data analysis including vector and raster data.

Each chapter includes several Python Jupyter Notebooks with example codes. And data used in example codes are also included in chapter folders.

## Libraries Used

* numpy
* gdal
* matplotlib
* geopandas

## Content

Content of this tutorial is as follows,

* Chapter 0 - Installation

* Chapter 1 - Introduction of Python
  * Session 1.1 - Fundamentals of Python
  * Session 1.2 - Built-in Data Structures
  * Session 1.3 - Control Program Flow
  * Session 1.4 - Functions and Libraries

* Chapter 2 - Working with Raster Data in Python
  * Session 2.1 - Matrix (Images) in Python
  * Session 2.2 - Geo Referenced Images in Python
  * Session 2.3 - Plotting, Visualizations in Python
  * Session 2.4 - Analysis - Raster Operations (Case Studies)

* Chapter 3 - Working with Vector Data in Python
  * Session 3.1 - Read, Write and Visualize Shapefiles
  * Session 3.2 - Working with Attribute Table
  * Session 3.3 - Working with Geometries (Vector Operations)

## Acknowledgements
Created by [N. Lakmal Deshapriya](https://github.com/lakmalnd) for activites of Geoinformatics Center of Asian Institute of Technology, Thailand.

## References for Sample Data Used
* Farr, T. G., et al. (2007), The Shuttle Radar Topography Mission, Rev. Geophys., 45, RG2004, doi:10.1029/2005RG000183.
* Hijmans, R.J., Guarino, L., Jarvis, A., O’Brien, R., Mathur, P., Bussink, C., Cruz, M., Barrantes, I. & Rojas, E. DIVA-GIS. Available at: www.diva-gis.org
* Gorelick, N., Hancher, M., Dixon, M., Ilyushchenko, S., Thau, D., & Moore, R. (2017). Google Earth Engine: Planetary-scale geospatial analysis for everyone. Remote Sensing of Environment.

repo_language: Jupyter Notebook


text:
/*
* Author: Michael Galetzka, 2016
*
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* 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 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.
*
* For more information, please refer to <http://unlicense.org>
*/

#pragma once

#include <vector>
#include <unordered_map>
#include <cmath>
#include <random>

# define PI2 6.28318530717958647692

namespace sw {

    enum class DistanceType {
        LINEAR, INVERSE_LINEAR, QUADRATIC, INVERSE_QUADRATIC
    };

    struct Vec3 {
        float X;
        float Y;
        float Z;

        explicit Vec3(float x = 0, float y = 0, float z = 0) : X(x), Y(y), Z(z) {
        }

        float DistanceTo(const Vec3 &other) const {
            return std::sqrt(std::pow(other.X - X, 2) + std::pow(other.Y - Y, 2) + std::pow(other.Z - Z, 2));
        }

        float Length() const {
            return std::sqrtf(std::powf(X, 2) + std::powf(Y, 2) + std::powf(Z, 2));
        }

        float DotProduct(const Vec3 &v) const {
            return X * v.X + Y * v.Y + Z * v.Z;
        }

        float AngleTo(const Vec3 &v) const {
            float l1 = Length();
            float l2 = v.Length();
            if (l1 == 0 || l2 == 0) {
                return 0;
            }
            return static_cast<float>(std::acos(DotProduct(v) / (l1 * l2)) * 360 / PI2);
        }

        Vec3 Normalized() const {
            float length = Length();
            if (length == 0) {
                return Vec3();
            }
            return Vec3(X / length, Y / length, Z / length);
        }

        Vec3 Negative() const {
            return Vec3(-X, -Y, -Z);
        }

        Vec3 &operator+=(const Vec3 &other) {
            X += other.X;
            Y += other.Y;
            Z += other.Z;
            return *this;
        }

        Vec3 operator/(float scalar) const {
            return Vec3(X / scalar, Y / scalar, Z / scalar);
        }

        Vec3 operator*(float scalar) const {
            return Vec3(X * scalar, Y * scalar, Z * scalar);
        }

        Vec3 operator+(const Vec3 &other) const {
            return Vec3(X + other.X, Y + other.Y, Z + other.Z);
        }

        Vec3 operator-(const Vec3 &other) const {
            return Vec3(X - other.X, Y - other.Y, Z - other.Z);
        }

        bool operator==(const Vec3 &rhs) const
        {
            return X == rhs.X && Y == rhs.Y && Z == rhs.Z;
        }

        Vec3 ClampLength(float length) const {
            float l = Length();
            if (l > length) {
                return Normalized() * length;
            }
            return *this;
        }

        static Vec3 GetRandomUniform(std::mt19937 &engine) {
            std::uniform_real_distribution<float> thetaRange(0.0f, PI2);
            std::uniform_real_distribution<float> oneRange(0, 1);
            float theta = thetaRange(engine);
            float r = sqrt(oneRange(engine));
            float z = sqrt(1.0f - r * r) * (oneRange(engine) > 0.5f ? -1.0f : 1.0f);
            return Vec3(r * cos(theta), r * sin(theta), z);
        }
    };

    struct Vec3Hasher
    {
        typedef std::size_t result_type;

        result_type operator()(sw::Vec3 const &v) const {
            result_type const h1(std::hash<float>()(v.X));
            result_type const h2(std::hash<float>()(v.Y));
            result_type const h3(std::hash<float>()(v.Z));
            return (h1 * 31 + h2) * 31 + h3;
        }
    };

    struct Boid {
        Vec3 Position;
        Vec3 Velocity;
        Vec3 Acceleration;

        explicit Boid(Vec3 pos, Vec3 vel) : Position(pos), Velocity(vel) {
        }
    };

    struct NearbyBoid {
        Boid *boid;
        Vec3 direction;
        float distance;
    };

    class Swarm {
    public:
        float PerceptionRadius = 30;

        float SeparationWeight = 1;
        DistanceType SeparationType = DistanceType::INVERSE_QUADRATIC;

        float AlignmentWeight = 1;
        float CohesionWeight = 1;

        float SteeringWeight = 0.1;
        std::vector<Vec3> SteeringTargets;
        DistanceType SteeringTargetType = DistanceType::LINEAR;

        float BlindspotAngleDeg = 20;
        float MaxAcceleration = 10;
        float MaxVelocity = 20;

        explicit Swarm(std::vector<Boid> *entities) : boids(entities) {
            std::random_device rd;
            eng = std::mt19937(rd());
        }

        void Update(float delta) {
            BlindspotAngleDegCompareValue = cosf(PI2 * BlindspotAngleDeg / 360.0f);
            UpdateAcceleration();

            for (auto &b : *boids) {
                b.Velocity = (b.Velocity + b.Acceleration * delta).ClampLength(MaxVelocity);
                b.Position += b.Velocity * delta;
            }
        }

        void UpdateAcceleration() {
            if (PerceptionRadius == 0) {
                PerceptionRadius = 1;
            }
            buildVoxelCache();
            for (auto &b : *boids) {
                updateBoid(b);
            }
        }

    private:
        std::vector<Boid> *boids;
        std::unordered_map<Vec3, std::vector<Boid *>, Vec3Hasher> voxelCache;
        std::mt19937 eng;
        float BlindspotAngleDegCompareValue = 0; // = cos(PI2 * BlindspotAngleDeg / 360)

        struct NearbyBoidsInformation
        {
            Vec3 separationSum;
            Vec3 headingSum;
            Vec3 positionSum;
            int count;
        };

        void updateBoid(Boid &b) {
            Vec3 separationSum;
            Vec3 headingSum;
            Vec3 positionSum;
            Vec3 po = b.Position;

            auto nearby = getNearbyBoids(b);

            for (NearbyBoid &closeBoid : nearby) {
                if (closeBoid.distance == 0) {
                    separationSum += Vec3::GetRandomUniform(eng) * 1000;
                }
                else {
                    float separationFactor = TransformDistance(closeBoid.distance, SeparationType);
                    separationSum += closeBoid.direction.Negative() * separationFactor;
                }
                headingSum += closeBoid.boid->Velocity;
                positionSum += closeBoid.boid->Position;
            }

            Vec3 steeringTarget = b.Position;
            float targetDistance = -1;
            for (auto &target : SteeringTargets) {
                float distance = TransformDistance(target.DistanceTo(b.Position), SteeringTargetType);
                if (targetDistance < 0 || distance < targetDistance) {
                    steeringTarget = target;
                    targetDistance = distance;
                }
            }

            // Separation: steer to avoid crowding local flockmates
            Vec3 separation = nearby.size() > 0 ? separationSum / nearby.size() : separationSum;

            // Alignment: steer towards the average heading of local flockmates
            Vec3 alignment = nearby.size() > 0 ? headingSum / nearby.size() : headingSum;

            // Cohesion: steer to move toward the average position of local flockmates
            Vec3 avgPosition = nearby.size() > 0 ? positionSum / nearby.size() : b.Position;
            Vec3 cohesion = avgPosition - b.Position;

            // Steering: steer towards the nearest target location (like a moth to the light)
            Vec3 steering = (steeringTarget - b.Position).Normalized() * targetDistance;

            // calculate boid acceleration
            Vec3 acceleration;
            acceleration += separation * SeparationWeight;
            acceleration += alignment * AlignmentWeight;
            acceleration += cohesion * CohesionWeight;
            acceleration += steering * SteeringWeight;
            b.Acceleration = acceleration.ClampLength(MaxAcceleration);
        }

        std::vector<NearbyBoid> getNearbyBoids(const Boid &b) const {
            std::vector<NearbyBoid> result;
            result.reserve(boids->size());

            Vec3 voxelPos = getVoxelForBoid(b);
            voxelPos.X -= 1;
            voxelPos.Y -= 1;
            voxelPos.Z -= 1;
            for (int x = 0; x < 3; x++) {
                for (int y = 0; y < 3; y++) {
                    for (int z = 0; z < 3; z++) {
                        checkVoxelForBoids(b, result, voxelPos);
                        voxelPos.Z++;
                    }
                    voxelPos.Z -= 3;
                    voxelPos.Y++;
                }
                voxelPos.Y -= 3;
                voxelPos.X++;
            }
            return result;
        }

        void checkVoxelForBoids(const Boid &b, std::vector<NearbyBoid> &result, const Vec3 &voxelPos) const {
            auto iter = voxelCache.find(voxelPos);
            if (iter != voxelCache.end()) {
                for (Boid *test : iter->second) {
                    const Vec3 &p1 = b.Position;
                    const Vec3 &p2 = test->Position;
                    Vec3 vec = p2 - p1;
                    float distance = vec.Length();

                    float compareValue = 0;
                    float l1 = vec.Length();
                    float l2 = b.Velocity.Length();
                    if (l1 != 0 && l2 != 0) {
                        compareValue = b.Velocity.Negative().DotProduct(vec) / (l1 * l2);
                    }

                    if ((&b) != test && distance <= PerceptionRadius && (BlindspotAngleDegCompareValue > compareValue || b.Velocity.Length() == 0)) {
                        NearbyBoid nb;
                        nb.boid = test;
                        nb.distance = distance;
                        nb.direction = vec;
                        result.push_back(nb);
                    }
                }
            }
        }

        void buildVoxelCache() {
            voxelCache.clear();
            voxelCache.reserve(boids->size());
            for (auto &b : *boids) {
                voxelCache[getVoxelForBoid(b)].push_back(&b);
            }
        }

        Vec3 getVoxelForBoid(const Boid &b) const {
            float r = std::abs(PerceptionRadius);
            const Vec3 &p = b.Position;
            Vec3 voxelPos;
            voxelPos.X = static_cast<int>(p.X / r);
            voxelPos.Y = static_cast<int>(p.Y / r);
            voxelPos.Z = static_cast<int>(p.Z / r);
            return voxelPos;
        }

        float TransformDistance(float distance, DistanceType type) {
            if (type == DistanceType::LINEAR) {
                return distance;
            }
            else if (type == DistanceType::INVERSE_LINEAR) {
                return distance == 0 ? 0 : 1 / distance;
            }
            else if (type == DistanceType::QUADRATIC) {
                return std::pow(distance, 2);
            }
            else if (type == DistanceType::INVERSE_QUADRATIC) {
                float quad = std::pow(distance, 2);
                return quad == 0 ? 0 : 1 / quad;
            }
            else {
                return distance; // throw exception instead?
            }
        }
    };
}

repo_name: Cultrarius/Swarmz

mime_type: text/x-c


text:
cmake_minimum_required(VERSION 3.5)
project(Swarmz)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall")

set(SOURCE_FILES debugging/main.cpp swarmz.h debugging/timing.h)
add_executable(Swarmz ${SOURCE_FILES})


text:
# Swarmz

A free, header-only C++ swarming (flocking) library for real-time applications.

An example usage can be seen in [this particle editor plugin](https://cultrarius.github.io/UE4ParticleEditorPlugin#swarm) for the Unreal Engine 4.

# Usage

Just include the header file to start:
```
#include "swarmz.h"

using namespace sw;
```

To create a new swarm, a pointer to the list of `Boids` is required.
A boid is a single member of the swarm.
Each boid has a position and a velocity:

```
// define our swarm members
vector<Boid> boids;
boids.push_back(Boid(Vec3(1, 0, 0), Vec3(1, 0, 0)));
boids.push_back(Boid(Vec3(1.5, 0, 0), Vec3(1, 1, 0)));
boids.push_back(Boid(Vec3(1, 0.5, 0.5), Vec3(0, 1, 0)));
boids.push_back(Boid(Vec3(4, 4, -2), Vec3(1, 0, 0)));

//create the swarm!
Swarm swarm(&boids);
```

The swarm has a lot of different settings, but the most important ones are the following:
* `PerceptionRadius` - determines the vision radius of each boid. Only boids within this distance influence each other.
* `SeparationWeight` - how much boids repel each other
* `AlignmentWeight` - how much boids want go in the same direction
* `CohesionWeight` - how much boids want to be in the center of the swarm
* `MaxAcceleration` - how fast each boid can change its direction
* `MaxVelocity` - how fast each boid can move

Each setting has a sensible default parameter, but playing around with them is half the fun:

```
// configure swarm
swarm.PerceptionRadius = 100;
swarm.CohesionWeight = 0.2;
```

To calculate the swarm movement, you usually update the swarm a few times a second in a loop.
The swarm does not automatically move the boids, it just updates the velocity and acceleration for each boid.
The position has to be updated by you.

```
// your tick/update loop
while (true) {
  swarm.Update(deltaSeconds);

  // each boid now has an updated velocity
  for (auto& boid : boids) {
    // update position
    boid.Position += boid.Velocity * deltaSeconds;

    // draw boid
  }
}
```

If you do not want the swarm to even update the boids velocity automatically (because you want to debug the result or you want to scale the acceleration per boid), then you can just call `UpdateAcceleration()` instead of the `Update(deltaSeconds)` on the swarm.

You can of course adjust the swarm parameters as well as the boid list during the loop to change the swarm behaviour over time.
The library is, however, *not* thread-safe (so please no changes during the swarm update)!

That's basically it, have fun! :)


text:
#include <iostream>
#include <random>
#include "../swarmz.h"
#include "timing.h"

using namespace std;
using namespace sw;

int main() {
    cout << "Hello, Swarmz World!" << endl;
    std::random_device rd;
    std::mt19937 eng(rd());

    vector<Boid> boids;
    boids.push_back(Boid(Vec3(1, 0, 0), Vec3(1, 0, 0)));
    boids.push_back(Boid(Vec3(1.5, 0, 0), Vec3(1, 1, 0)));
    boids.push_back(Boid(Vec3(1, 0.5, 0.5), Vec3(0, 1, 0)));
    boids.push_back(Boid(Vec3(4, 4, -2), Vec3(1, 0, 0)));
    Swarm swarm(&boids);
    swarm.SteeringTargets.push_back(Vec3(0, 0, 0));
    swarm.SteeringTargets.push_back(Vec3(4, 0, 0));
    swarm.UpdateAcceleration();

    for (int j = 0; j < boids.size(); ++j) {
        auto p = boids[j].Position;
        auto a = boids[j].Acceleration;
        cout << "B " << p.X << ", " << p.Y << ", " << p.Z << " => " << a.X << ", " << a.Y << ", " << a.Z << "\n";
    }

    exit(0);
    std::uniform_real_distribution<float> dist(0, 20);
    for (int k = 1; k < 100; k++) {
        int size = k * 100;
        boids.clear();
        for (int i = 0; i < size; i++) {
            boids.push_back(Boid(Vec3(dist(eng), dist(eng), dist(eng)), Vec3::GetRandomUniform(eng)));
        }
        auto start = GetTimeMs64();
        Swarm swarm100(&boids);
        swarm100.Update(1);
        cout << size << ";" << (GetTimeMs64() - start) << "\n";
    }
    cout << endl;

    return 0;
}


text:
//
// Created by Michael on 28.06.2016.
//

#pragma once

#ifdef _WIN32
#include <Windows.h>
#else
#include <sys/time.h>
#include <ctime>
#endif

/* Remove if already defined */
typedef long long int64; typedef unsigned long long uint64;

/* Returns the amount of milliseconds elapsed since the UNIX epoch. Works on both
 * windows and linux. */

uint64 GetTimeMs64()
{
#ifdef _WIN32
 /* Windows */
 FILETIME ft;
 LARGE_INTEGER li;

 /* Get the amount of 100 nano seconds intervals elapsed since January 1, 1601 (UTC) and copy it
  * to a LARGE_INTEGER structure. */
 GetSystemTimeAsFileTime(&ft);
 li.LowPart = ft.dwLowDateTime;
 li.HighPart = ft.dwHighDateTime;

 uint64 ret = li.QuadPart;
 ret -= 116444736000000000LL; /* Convert from file time to UNIX epoch time. */
 ret /= 10000; /* From 100 nano seconds (10^-7) to 1 millisecond (10^-3) intervals */

 return ret;
#else
 /* Linux */
 struct timeval tv;

 gettimeofday(&tv, NULL);

 uint64 ret = tv.tv_usec;
 /* Convert from micro seconds (10^-6) to milliseconds (10^-3) */
 ret /= 1000;

 /* Adds the seconds (10^0) after converting them to milliseconds (10^-3) */
 ret += (tv.tv_sec * 1000);

 return ret;
#endif
}


text:
# Contributing guidelines

We’d love you to help us improve this project. To help us keep this collection
high quality, we request that contributions adhere to the following guidelines.

- **Provide a link to the application or project’s homepage**. Unless it’s
  extremely popular, there’s a chance the maintainers don’t know about or use
  the language, framework, editor, app, or project your change applies to.

- **Provide links to documentation** supporting the change you’re making.
  Current, canonical documentation mentioning the files being ignored is best.
  If documentation isn’t available to support your change, do the best you can
  to explain what the files being ignored are for.

- **Explain why you’re making a change**. Even if it seems self-evident, please
  take a sentence or two to tell us why your change or addition should happen.
  It’s especially helpful to articulate why this change applies to *everyone*
  who works with the applicable technology, rather than just you or your team.

- **Please consider the scope of your change**. If your change specific to a
  certain language or framework, then make sure the change is made to the
  template for that language or framework, rather than to the template for an
  editor, tool, or operating system.

- **Please only modify *one template* per pull request**. This helps keep pull
  requests and feedback focused on a specific project or technology.

In general, the more you can do to help us understand the change you’re making,
the more likely we’ll be to accept your contribution quickly.

If a template is mostly a list of files installed by a particular version of
some software (e.g. a PHP framework) then it's brittle and probably no more
helpful than a simple `ls`. If it's not possible to curate a small set of
useful rules, then the template might not be a good fit for this collection.

Please also understand that we can’t list every tool that ever existed.
Our aim is to curate a collection of the *most common and helpful* templates,
not to make sure we cover every project possible. If we choose not to
include your language, tool, or project, it’s not because it’s not awesome.

repo_name: github/gitignore


text:
# A collection of `.gitignore` templates

This is GitHub’s collection of [`.gitignore`][man] file templates.
We use this list to populate the `.gitignore` template choosers available
in the GitHub.com interface when creating new repositories and files.

For more information about how `.gitignore` files work, and how to use them,
the following resources are a great place to start:

- The [Ignoring Files chapter][chapter] of the [Pro Git][progit] book.
- The [Ignoring Files article][help] on the GitHub Help site.
- The [gitignore(5)][man] manual page.

[man]: http://git-scm.com/docs/gitignore
[help]: https://help.github.com/articles/ignoring-files
[chapter]: https://git-scm.com/book/en/Git-Basics-Recording-Changes-to-the-Repository#_ignoring
[progit]: http://git-scm.com/book

## Folder structure

We support a collection of templates, organized in this way:

- The root folder contains templates in common use, to help people get started
  with popular programming languages and technologies. These define a meaningful
  set of rules to help get started, and ensure you are not committing
  unimportant files into your repository.
- [`Global`](./Global) contains templates for various editors, tools and
  operating systems that can be used in different situations. It is recommended
  that you either [add these to your global template](https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files#configuring-ignored-files-for-all-repositories-on-your-computer)
  or merge these rules into your project-specific templates if you want to use
  them permanently.
- [`community`](./community) contains specialized templates for other popular
  languages, tools and project, which don't currently belong in the mainstream
  templates. These should be added to your project-specific templates when you
  decide to adopt the framework or tool.

## What makes a good template?

A template should contain a set of rules to help Git repositories work with a
specific programming language, framework, tool or environment.

If it's not possible to curate a small set of useful rules for this situation,
then the template is not a good fit for this collection.

If a template is mostly a list of files installed by a particular version of
some software (e.g. a PHP framework), it could live under the `community`
directory. See [versioned templates](#versioned-templates) for more details.

If you have a small set of rules, or want to support a technology that is not
widely in use, and still believe this will be helpful to others, please read the
section about [specialized templates](#specialized-templates) for more details.

Include details when opening pull request if the template is important and visible. We
may not accept it immediately, but we can promote it to the root at a later date
based on interest.

Please also understand that we can’t list every tool that ever existed.
Our aim is to curate a collection of the _most common and helpful_ templates,
not to make sure we cover every project possible. If we choose not to
include your language, tool, or project, it’s not because it’s not awesome.

## Contributing guidelines

We’d love for you to help us improve this project. To help us keep this collection
high quality, we request that contributions adhere to the following guidelines.

- **Provide a link to the application or project’s homepage**. Unless it’s
  extremely popular, there’s a chance the maintainers don’t know about or use
  the language, framework, editor, app, or project your change applies to.

- **Provide links to documentation** supporting the change you’re making.
  Current, canonical documentation mentioning the files being ignored is best.
  If documentation isn’t available to support your change, do the best you can
  to explain what the files being ignored are for.

- **Explain why you’re making a change**. Even if it seems self-evident, please
  take a sentence or two to tell us why your change or addition should happen.
  It’s especially helpful to articulate why this change applies to _everyone_
  who works with the applicable technology, rather than just you or your team.

- **Please consider the scope of your change**. If your change is specific to a
  certain language or framework, then make sure the change is made to the
  template for that language or framework, rather than to the template for an
  editor, tool, or operating system.

- **Please only modify _one template_ per pull request**. This helps keep pull
  requests and feedback focused on a specific project or technology.

In general, the more you can do to help us understand the change you’re making,
the more likely we’ll be to accept your contribution quickly.

## Versioned templates

Some templates can change greatly between versions, and if you wish to contribute
to this repository we need to follow this specific flow:

- the template at the root should be the current supported version
- the template at the root should not have a version in the filename (i.e.
  "evergreen")
- previous versions of templates should live under `community/`
- previous versions of the template should embed the version in the filename,
  for readability

This helps ensure users get the latest version (because they'll use whatever is
at the root) but helps maintainers support older versions still in the wild.

## Specialized templates

If you have a template that you would like to contribute, but it isn't quite
mainstream, please consider adding this to the `community` directory under a
folder that best suits where it belongs.

The rules in your specialized template should be specific to the framework or
tool, and any additional templates should be mentioned in a comment in the
header of the template.

For example, this template might live at `community/DotNet/InforCRM.gitignore`:

```
# gitignore template for InforCRM (formerly SalesLogix)
# website: https://www.infor.com/product-summary/cx/infor-crm/
#
# Recommended: VisualStudio.gitignore

# Ignore model files that are auto-generated
ModelIndex.xml
ExportedFiles.xml

# Ignore deployment files
[Mm]odel/[Dd]eployment

# Force include portal SupportFiles
!Model/Portal/*/SupportFiles/[Bb]in/
!Model/Portal/PortalTemplates/*/SupportFiles/[Bb]in
```

## Contributing workflow

Here’s how we suggest you go about proposing a change to this project:

1. [Fork this project][fork] to your account.
2. [Create a branch][branch] for the change you intend to make.
3. Make your changes to your fork.
4. [Send a pull request][pr] from your fork’s branch to our `main` branch.

Using the web-based interface to make changes is fine too, and will help you
by automatically forking the project and prompting to send a pull request too.

[fork]: https://help.github.com/articles/fork-a-repo/
[branch]: https://help.github.com/articles/creating-and-deleting-branches-within-your-repository
[pr]: https://help.github.com/articles/using-pull-requests/

## License

[CC0-1.0](./LICENSE).


text:
**Reasons for making this change:**
<!-- Include your relationship to the project and what you expect to get from this change. -->

_TODO_

**Links to documentation supporting these rule changes:**

_TODO_

If this is a new template:

 - **Link to application or project’s homepage**: _TODO_


text:
## Globally Useful gitignores

This directory contains globally useful gitignores,
e.g. OS-specific and editor specific.

For more on global gitignores:
<https://help.github.com/en/github/using-git/ignoring-files#configuring-ignored-files-for-all-repositories-on-your-computer>

And a good blog post about 'em:
<http://augustl.com/blog/2009/global_gitignores>


text:
# Contribution Guidelines

Please ensure your pull request adheres to the following guidelines:

- Search previous suggestions before making a new one, as yours may be a duplicate.
- Suggested tools/tweaks should be tested and documented.
- Make an individual pull request for each suggestion.
- Use the following format: `[tool/tweak](link) - Description.`
- New categories, or improvements to the existing categorization are welcome.
- Keep descriptions short and simple, but descriptive.
- End all descriptions with a full stop/period.
- Check your spelling and grammar.
- Make sure your text editor is set to remove trailing whitespace.
- The pull request should include a link to the tool/tweak and why it should be included.

Thank you for your suggestions!


text:
# Awesome iOS Security

> []() A curated list of awesome iOS application security resources.

A collection of awesome tools, books, courses, blog posts, and cool stuff about iOS Application Security and Penetration Testing.

---

## Contents

- [Tools](#tools)
  - [Reverse Engineering Tools](#reverse-engineering-tools)
  - [Static Analysis Tools](#static-analysis-tools)
  - [Dynamic Analysis Tools](#dynamic-analysis-tools)
- [Tweaks](#tweaks)
  - [Reverse Engineering Tweaks](#reverse-engineering-tweaks)
  - [Jailbrek Detection Bypass Tweaks](#jailbrek-detection-bypass-tweaks)
  - [SSL Pinning Bypass Tweaks](#ssl-pinning-bypass-tweaks)
- [Frida Scripts](#frida-scripts)
- [Courses](#courses)
- [Books](#books)
- [Sessions & Workshops](#sessions--workshops)
- [Articles & Tutorials](#articles--tutorials)
  - [Penetration Testing Articles](#penetration-testing-articles)
  - [Reverse Engineering Articles](#reverse-engineering-articles)
  - [Jailbrek Detection Bypass Articles](#jailbrek-detection-bypass-articles)
  - [SSL Pinning Bypass Articles](#ssl-pinning-bypass-articles)
- [Checklists & Cheatsheets](#checklists--cheatsheets)
- [Labs](#labs)
- [CTF](#ctf)
- [Writeups](#writeups)
- [Misc](#misc)

## Tools

### Reverse Engineering Tools
- [Hopper](https://www.hopperapp.com/) - A reverse engineering tool that will assist you in your static analysis of executable files.
- [Ghidra](https://github.com/NationalSecurityAgency/ghidra) - A software reverse engineering (SRE) framework created and maintained by the National Security Agency Research Directorate.
- [Radare2](https://github.com/radareorg/radare2) - UNIX-like reverse engineering framework and command-line toolset.
- [Cutter](https://github.com/rizinorg/cutter) - Free and Open Source Reverse Engineering Platform powered by rizin.
- [frida-ios-dump](https://github.com/AloneMonkey/frida-ios-dump) - A tool to pull a decrypted IPA from a jailbroken device.
- [bagbak](https://github.com/ChiChou/bagbak) - Yet another frida based App decryptor. Requires jailbroken iOS device and frida.re.
- [flexdecrypt](https://github.com/JohnCoates/flexdecrypt) - An iOS App & Mach-O binary decryptor.
- [bfdecrypt](https://github.com/BishopFox/bfdecrypt) - Utility to decrypt App Store apps on jailbroken iOS 11.x.
- [bfinject](https://github.com/BishopFox/bfinject) - Easy dylib injection for jailbroken 64-bit iOS 11.0 - 11.1.2. Compatible with Electra and LiberiOS jailbreaks.
- [r2flutch](https://github.com/as0ler/r2flutch) - Yet another tool to decrypt iOS apps using r2frida.
- [Clutch](https://github.com/KJCracks/Clutch) - A high-speed iOS decryption tool.
- [dsdump](https://github.com/DerekSelander/dsdump) - An improved nm + objc/swift class-dump tool.
- [class-dump](https://github.com/nygard/class-dump) - A command-line utility for examining the Objective-C segment of Mach-O files.
- [SwiftDump](https://github.com/neil-wu/SwiftDump/) - A command-line tool for retriving the Swift Object info from Mach-O file.
- [jtool](http://www.newosxbook.com/tools/jtool.html) - An app inspector, disassembler, and signing utility for the macOS, iOS.
- [Sideloadly](https://sideloadly.io/) - An app to sideload your favorite games and apps to Jailbroken & Non-Jailbroken iOS devices.
- [Cydia Impactor](http://www.cydiaimpactor.com/) - A GUI tool for sideloading iOS application.
- [AltStore](https://altstore.io/) - Allows to sideload other apps (.ipa files) onto iOS device.
- [iOS App Signer](https://github.com/DanTheMan827/ios-app-signer) - An app for macOS that can (re)sign apps and bundle them into ipa files that are ready to be installed on an iOS device.

### Static Analysis Tools
- [iLEAPP](https://github.com/abrignoni/iLEAPP) - An iOS Logs, Events, And Plist Parser.
- [Keychain Dumper](https://github.com/ptoomey3/Keychain-Dumper) - A tool to check which keychain items are available to an attacker once an iOS device has been jailbroken.
- [BinaryCookieReader](https://github.com/as0ler/BinaryCookieReader) - A tool to read the binarycookie format of Cookies on iOS applications.
- [PList Viewer](https://github.com/TingPing/plist-viewer) - Gtk application to view property list files.
- [XMachOViewer](https://github.com/horsicq/XMachOViewer) - A Mach-O viewer for Windows, Linux and macOS.
- [MachO-Explorer](https://github.com/DeVaukz/MachO-Explorer) - A graphical Mach-O viewer for macOS. Powered by Mach-O Kit.
- [iFunbox](https://www.i-funbox.com/en/index.html) - A general file management software for iPhone and other Apple products.
- [3uTools](http://www.3u.com/) - An All-in-One management software for iOS devices.
- [iTools](https://www.thinkskysoft.com/itools/) - An All-in-One solution for iOS devices management.

### Dynamic Analysis Tools
- [Corellium](https://www.corellium.com/) - The only platform offering ARM-based mobile device virtualization using a custom-built hypervisor for real-world accuracy and high performance.
- [Frida](https://github.com/frida/frida) - Dynamic instrumentation toolkit for developers, reverse-engineers, and security researchers.
- [frida-gum](https://github.com/frida/frida-gum) - Cross-platform instrumentation and introspection library written in C.
- [Fridax](https://github.com/NorthwaveSecurity/fridax) - Fridax enables you to read variables and intercept/hook functions in Xamarin/Mono JIT and AOT compiled iOS/Android applications.
- [r2frida](https://github.com/nowsecure/r2frida) - Radare2 and Frida better together.
- [r2ghidra](https://github.com/radareorg/r2ghidra) - An integration of the Ghidra decompiler for radare2.
- [iproxy](https://github.com/libimobiledevice/libusbmuxd) - A utility allows binding local TCP ports so that a connection to one (or more) of the local ports will be forwarded to the specified port (or ports) on a usbmux device.
- [itunnel](https://code.google.com/archive/p/iphonetunnel-usbmuxconnectbyport/downloads) - Use to forward SSH via USB.
- [objection](https://github.com/sensepost/objection) - A runtime mobile exploration toolkit, powered by Frida, built to help you assess the security posture of your mobile applications, without needing a jailbreak.
- [Grapefruit](https://github.com/ChiChou/grapefruit) - Runtime Application Instruments for iOS.
- [Passionfruit](https://github.com/chaitin/passionfruit) - Simple iOS app blackbox assessment tool, powered by frida 12.x and vuejs.
- [Runtime Mobile Security (RMS)](https://github.com/m0bilesecurity/RMS-Runtime-Mobile-Security) - Runtime Mobile Security (RMS), powered by FRIDA, is a powerful web interface that helps you to manipulate Android and iOS Apps at Runtime.
- [membuddy](https://zygosec.com/membuddy.html) - Dynamic memory analysis & visualisation tool for security researchers.
- [unidbg](https://github.com/zhkl0228/unidbg) - Allows you to emulate an Android ARM32 and/or ARM64 native library, and an experimental iOS emulation.
- [Qiling](https://github.com/qilingframework/qiling) - An advanced binary emulation framework.
- [fishhook](https://github.com/facebook/fishhook) - A library that enables dynamically rebinding symbols in Mach-O binaries running on iOS.
- [Dwarf](https://github.com/iGio90/Dwarf) - Full featured multi arch/os debugger built on top of PyQt5 and frida.
- [FridaHookSwiftAlamofire](https://github.com/neil-wu/FridaHookSwiftAlamofire) - A frida tool that capture GET/POST HTTP requests of iOS Swift library 'Alamofire' and disable SSL Pinning.
- [ios-deploy](https://github.com/ios-control/ios-deploy) - Install and debug iOS apps from the command line. Designed to work on un-jailbroken devices.
- [aah](https://github.com/zydeco/aah) - Run iOS arm64 binaries on x86_64 macOS, with varying degrees of success.
- [LLDB](https://lldb.llvm.org/) - A next generation, high-performance debugger.
- [mitmproxy](https://mitmproxy.org/) - A free and open source interactive HTTPS proxy.
- [Burp Suite](https://portswigger.net/burp) - An advanced HTTPS proxy software.

## Tweaks

### Reverse Engineering Tweaks
- [FoulDecrypt](https://github.com/NyaMisty/fouldecrypt) - A lightweight and simpling iOS binary decryptor, supports iOS 13.5 and later.
- [iGameGod](https://iosgods.com/repo/) - Cheat Engine, Speed Manager, Auto Touch, Device Spoofer & App Decryptor.
- [CrackerXI](http://cydia.iphonecake.com/) - Tool to Decrypt iOS Apps, based on BFInject, Supports Electra as well as Unc0ver Jailbreaks.
- [flexdecrypt](https://repo.packix.com/) - Command line tool for decrypting Mach-O binaries.
- [Flex 3 Beta](https://getdelta.co/) - Flex gives you the power to modify apps and change their behavior, with no coding experience needed.
- [Frida](https://build.frida.re) - Frida server for iOS.
- [OpenSSH](https://cydia.saurik.com/package/openssh/) - Secure remote access between machines.
- [Apple File Conduit "2"](https://cydia.saurik.com/package/com.saurik.afc2d/) - Unlocks filesystem access over USB on Windows or macOS on jailbroken devices.
- [AppSync Unified](https://cydia.akemi.ai/?page/net.angelxwind.appsyncunified) - Enables the ability to install unsigned/fakesigned iOS applications.
- [NewTerm 2](https://chariz.com/) - A powerful terminal app for iOS.
- [Filza File Manager](http://cydia.saurik.com/package/com.tigisoftware.filza/) - A Powerful File Manager for iOS with IPA Installer, DEB Installer, Web viewer, and Terminal.

### Jailbrek Detection Bypass Tweaks
- [Shadow](https://ios.jjolano.me/depiction/web/me.jjolano.shadow.html) - A lightweight general jailbreak detection bypass tweak.
- [A-Bypass](https://repo.co.kr/package/com.rpgfarm.a-bypass) - A tool that helps block some apps from accessing unauthorized space or calling functions not authorized by Apple due to jailbreak.
- [FlyJB X](https://repo.xsf1re.kr/) - A jailbreak bypass that allows you to bypass the in-app jailbreak detection mechanism.
- [Liberty Lite (Beta)](https://ryleyangus.com/repo/) - A general purpose jailbreak detection bypass patch.
- [vnodebypass](https://cydia.ichitaso.com/) - An expermental tool to hide jailbreak files for bypass detection.
- [KernBypass (Unofficial)](https://cydia.ichitaso.com) - A kernel level jailbreak detection bypass tweak.
- [HideJB](http://cydia.saurik.com/package/com.thuthuatjb.hidejb/) - Bybass jailbreak detection in certain apps.
- [Hestia](https://repo.packix.com/) - A global jailbreak detection bypass tweak.
- [Choicy](http://cydia.saurik.com/package/com.opa334.choicy/) - An advanced tweak configurator.

### SSL Pinning Bypass Tweaks
- [SSL Kill Switch 2](https://github.com/nabla-c0d3/ssl-kill-switch2) - A blackbox tool to disable SSL/TLS certificate validation - including certificate pinning - within iOS and macOS applications.
- [SSLBypass](https://github.com/evilpenguin/SSLBypass) - An iOS SSL Pinning Bypass Tweak (iOS 8 - 14).

## Frida Scripts
- [FridaSwiftDump](https://github.com/neil-wu/FridaSwiftDump/) - A Frida script for retriving the Swift Object info from an running app.
- [iOS 13 SSL Bypass](https://codeshare.frida.re/@federicodotta/ios13-pinning-bypass/) - SSL Pinning Bypass for iOS 13.
- [iOS 12 SSL Bypass](https://codeshare.frida.re/@machoreverser/ios12-ssl-bypass/) - SSL Pinning Bypass for iOS 12.
- [iOS Jailbreak Detection Bypass](https://codeshare.frida.re/@liangxiaoyi1024/ios-jailbreak-detection-bypass/) - A Frida script used for bypass iOS jailbreak detection by hooking some methods and functions.
- [iOS App Static Analysis](https://codeshare.frida.re/@interference-security/ios-app-static-analysis/) - Script for iOS app's static analysis.
- [Touch ID Bypass](https://highaltitudehacks.com/2018/07/26/ios-application-security-part-50-touch-id-bypass-with-frida/) - A Frida script for iOS Touch/Face ID Bypass.

## Courses

- [Pentesting iOS Applications](https://www.pentesteracademy.com/course?id=2) - By PentesterAcademy.
- [iOS Pentesting](https://www.youtube.com/playlist?list=PL5Fxd3nu70eyqiqrVlD9QMoaOARr082TA) - By Mantis.
- [iOS Application Pentesting Series](https://www.youtube.com/playlist?list=PLm_U3e1sSTMvgj1sbZ2Ng6VbxMWw8Wyk9) - By Sateesh Verma.
- [IOS: Penetration Testing](https://www.youtube.com/playlist?list=PLanZMaPa4zzyGJ7IiW2zQNC40pWf2-7uE) - By Noisy Hacker.

## Books

- [iOS Hacking Guide](https://web.securityinnovation.com/hacking-ios-applications) - By Security Innovation.
- [iOS Application Security: The Definitive Guide for Hackers and Developers](https://nostarch.com/iossecurity) - By David Thiel.
- [iOS Penetration Testing: A Definitive Guide to iOS Security](https://link.springer.com/book/10.1007/978-1-4842-2355-0) - By Kunal Relan.
- [Learning iOS Penetration Testing](https://www.packtpub.com/product/learning-ios-penetration-testing/9781785883255) - By Swaroop Yermalkar.
- [Hacking and Securing iOS Applications](https://www.oreilly.com/library/view/hacking-and-securing/9781449325213/) - By Jonathan Zdziarski.
- [iOS Hacker's Handbook](https://www.amazon.com/iOS-Hackers-Handbook-Charlie-Miller/dp/1118204123) - By Charlie Miller.

## Sessions & Workshops

- [iOS + Frida Tutorial](https://youtu.be/h070-YZKOKE) - A 3-parts workshop contains an introduction to Frida and iOS, low-level iOS interfaces (GCD, XPC, IOKit, Mach), and Objective-C instrumentation by @naehrdine.
- [Exploiting Common iOS Apps' Vulnerabilities](https://www.youtube.com/watch?v=RLzbHHoEKo8&t=19s) - A session by @ivRodriguezCA that walks through some of the most common vulnerabilities on iOS apps and shows how to exploit them.
- [iOS Reverse Engineering With Frida](https://www.youtube.com/watch?v=miSg0Km2V-w) - How to get started in iOS RE with any PC/Mac, an iPhone, and Frida by @x71n3.
- [iOS Application Vulnerabilities and how to find them](https://www.youtube.com/watch?v=2CKrw7ErzCY) - How to get started with hacking iOS apps, environment requirement, play ground etc. by @0ctac0der.

## Articles & Tutorials

### Penetration Testing Articles

- [A Comprehensive guide to iOS Penetration Testing](https://www.getastra.com/blog/security-audit/ios-penetration-testing/)
- [Getting Started with iOS Penetration Testing](https://blog.yeswehack.com/yeswerhackers/getting-started-ios-penetration-testing-part-1/)
- [iOS Pentesting 101](https://www.cobalt.io/blog/ios-pentesting-101)
- [Insecure iOS Storage - DVIAv2](https://philkeeble.com/ios/Insecure-iOS-Storage/)

### Reverse Engineering Articles

- [iOS Pentesting Tools Part 1: App Decryption and class-dump](https://www.allysonomalley.com/2018/08/10/ios-pentesting-tools-part-1-app-decryption-and-class-dump/)
- [Anti Anti Hooking/Debugging - DVIAv2](https://philkeeble.com/ios/reverse-engineering/iOS-Anti-Anti-Hooking/)
- [Runtime Manipulation - DVIAv2](https://philkeeble.com/ios/reverse-engineering/iOS-Runtime-Manipulation/)
- [Reverse Engineering iOS Apps - iOS 11 Edition](https://ivrodriguez.com/reverse-engineer-ios-apps-ios-11-edition-part2/)

### Jailbrek Detection Bypass Articles

- [Bypass Jailbreak Detection with Frida in iOS applications](https://blog.attify.com/bypass-jailbreak-detection-frida-ios-applications/)
- [iOS Swift Anti-Jailbreak Bypass with Frida](https://syrion.me/blog/ios-swift-antijailbreak-bypass-frida/)
- [Bypassing JailBreak Detection - DVIAv2](https://philkeeble.com/ios/reverse-engineering/iOS-Bypass-Jailbreak/)
- [Gotta Catch 'Em All: Frida & jailbreak detection](https://www.romainthomas.fr/post/21-07-pokemongo-anti-frida-jailbreak-bypass/)

### SSL Pinning Bypass Articles

- [SSL Pinning bypass in iOS application](https://sudonull.com/post/10665-SSL-Pinning-bypass-in-iOS-application)
- [Bypass Facebook SSL Certificate Pinning for iOS](https://www.cyclon3.com/bypass-facebook-ssl-certificate-pinning-for-ios)
- [Bypass SSL Pinning with LLDB on AppStore iOS apps](https://itnext.io/bypass-ssl-pinning-with-lldb-in-ios-app-b78f9e7cc9cd)

## Checklists & Cheatsheets

- [HackTricks iOS Pentesting Checklist](https://book.hacktricks.xyz/mobile-apps-pentesting/ios-pentesting-checklist)
- [OWASP Mobile Application Security Verification Standard (MASVS)](https://github.com/OWASP/owasp-masvs)
- [iOS CheatSheet](https://github.com/randorisec/MobileHackingCheatSheet/blob/master/LEGACY.md#ios-cheatsheet)
- [iOS Client-Side Attacks and Tests](https://appsec-labs.com/ios-attacks-tests/)

## Labs

- [Damn Vulnerable iOS Application (DVIA)](https://damnvulnerableiosapp.com/)
- [OWASP iGoat](https://igoatapp.com/)
- [WaTF Bank](https://github.com/WaTF-Team/WaTF-Bank)
- [Myriam](https://github.com/GeoSn0w/Myriam)

## CTF
- [OWASP UnCrackable Mobile Apps](https://github.com/OWASP/owasp-mstg/tree/master/Crackmes)
- [r2con Crackmes](https://github.com/hexploitable/r2con2020_r2frida)
- [Headbook-CTF](https://github.com/ivRodriguezCA/Headbook-ctf)
- [iOS CTF](https://www.optiv.com/insights/source-zero/blog/walkthrough-ios-ctf)
- [DFA/CCSC Spring 2020 CTF – Apple iOS Forensics with iLEAPP](https://www.petermstewart.net/dfa-ccsc-spring-2020-ctf-apple-ios-forensics-with-ileapp/)
- [NCC Con 2018 iOS CTF](https://ch1kpee.com/2018/01/08/ncc-con-2018-ios-ctf-solutions/)
- [Cellebrite CTF 2021 - Beth's iPhone](https://www.stark4n6.com/2021/10/cellebrite-ctf-2021-beths-iphone.html)

## Writeups

- [A deep dive into an NSO zero-click iMessage exploit: Remote Code Execution](https://googleprojectzero.blogspot.com/2021/12/a-deep-dive-into-nso-zero-click.html)
- [Airdrop: Symbolic Link Following](https://breakpoint.sh/posts/airdrop-symbolic-link-following)
- [XSS STORED IN FILES.SLACK.COM VIA XML/SVG FILE (IOS)](https://omespino.com/write-up-xss-stored-in-files-slack-com-via-xml-svg-file-ios-1000-usd/)
- [Facebook iOS address bar spoofing](https://servicenger.com/mobile/facebook-ios-address-bar-spoofing/)

## Misc

- [iOS Jailbreak Downloads](https://idevicecentral.com/jailbreak-tools/ios-jailbreak-downloads-download-jailbreak-tools-for-all-ios-versions/) - Download Jailbreak Tools for All iOS Versions.
- [MOBEXLER](https://mobexler.com/) - A customised virtual machine, designed to help in penetration testing of Android & iOS applications.
- [frida Workbench](https://marketplace.visualstudio.com/items?itemName=CodeColorist.vscode-frida) - Unofficial frida workbench for VSCode.
- [Apple Configurator](https://apps.apple.com/app/apple-configurator-2/id1037126344) - Apple Configurator features a flexible, device-centric design that enables you to configure one or dozens of devices quickly and easily.
- [Apple Platform Security](https://support.apple.com/en-gb/guide/security/welcome/web) - Explore Apple Platform Security.
- [IPSW Downloads](https://ipsw.me/) - Download current and previous versions of Apple's iOS, iPadOS, macOS, watchOS, tvOS and audioOS firmware and receive notifications when new firmwares are released.
- [theos](https://github.com/theos/theos) - A cross-platform suite of tools for building and deploying software for iOS and other platforms.

## Contributing

Your contributions are always welcome! Please read the [contribution guidelines](contributing.md) first.


text:
name: Lint
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
jobs:
  Awesome_Lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - run: npx awesome-lint


text:
module github.com/bh90210/models

go 1.17

require (
	gitlab.com/gomidi/midi v1.23.3
	gitlab.com/gomidi/rtmididrv v0.11.0
)

require gitlab.com/gomidi/rtmididrv/imported/rtmidi v0.0.0-20191025100939-514fe0ed97a6 // indirect

repo_language: Go


text:
gitlab.com/gomidi/midi v1.21.0/go.mod h1:3ohtNOhqoSakkuLG/Li1OI6I3J1c2LErnJF5o/VBq1c=
gitlab.com/gomidi/midi v1.23.3 h1://gDrbjiLdDZNW6W60mloIVApZ4PgGNXiGGKgjpJg00=
gitlab.com/gomidi/midi v1.23.3/go.mod h1:3ohtNOhqoSakkuLG/Li1OI6I3J1c2LErnJF5o/VBq1c=
gitlab.com/gomidi/rtmididrv v0.11.0 h1:Bc/zr8agMU2KDcpj932Vy51K4YO5Cp6pPIY6Buf6Q6Y=
gitlab.com/gomidi/rtmididrv v0.11.0/go.mod h1:wjwyt4rCwmvS++uk5mW5S+MnSbUnQUMNVvaEUCunjJE=
gitlab.com/gomidi/rtmididrv/imported/rtmidi v0.0.0-20191025100939-514fe0ed97a6 h1:0XqAH/BAxH5TTBzIWkdlZqpp6VUx6DFcQnMWW6G6hIc=
gitlab.com/gomidi/rtmididrv/imported/rtmidi v0.0.0-20191025100939-514fe0ed97a6/go.mod h1:FYVFN2H23IsX56VntiDF9DgCIekHh359wW+iMl1W8rQ=


text:
[![Go Reference](https://pkg.go.dev/badge/github.com/bh90210/models.svg)](https://pkg.go.dev/github.com/bh90210/models)

# elektron:models

Go package to programmatically control [Elektron's](https://www.elektron.se/) **model:cycles** & **model:samples** via midi.

## Prerequisites

### Go

Install Go https://golang.org/doc/install.

### RtMidi

#### Ubuntu 20.04+

```console
apt install librtmidi4 librtmidi-dev
```
For older versions take a look [here](https://launchpad.net/ubuntu/+source/rtmidi).

#### MacOS

```console
brew install rtmidi
```
For more information see the [formulae page](https://formulae.brew.sh/formula/rtmidi).

#### Windows

`Help needed.`

## Quick Use

_A complete example can be found in the [example](https://github.com/bh90210/elektronmodels/tree/master/example/) folder._

_The relevant cycles/samples manuals' part for this library is the `APPENDIX A: MIDI SPECIFICATIONS`._

Code to get a single kick drum hit at C4 key, with velocity set at `120` and length at 200 milliseconds:
```go
package main

import (
	"time"

	m "github.com/bh90210/models"
)

func main() {
	p, _ := m.NewProject(em.CYCLES)
	defer p.Close()

	p.Note(m.T1, m.C4, 120, 200, m.PT1())
	time.Sleep(200 * time.Millisecond)
}

```
There are four Free methods to use, `Preset` to set preset on the fly, `Note` to fire a note on/off for given duration, `CC` to send a single control change message && `PC` for program changes.