Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discord Linking and update Discord Colors #181

Merged
merged 23 commits into from
Oct 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c199bd2
WIP Add discord account linking
vcfxb Sep 10, 2021
1ff77d8
Merge branch 'master' into account-linking
vcfxb Sep 20, 2021
e06ab48
Merge branch 'master' into account-linking
vcfxb Sep 21, 2021
b9c78a4
Merge branch 'master' into account-linking
vcfxb Sep 27, 2021
bb5560e
Pass discord info to profile template
vcfxb Sep 28, 2021
011d145
Merge remote-tracking branch 'origin/master' into account-linking
vcfxb Sep 28, 2021
bfbf8e0
Fix access glitch by changing back handler arg to identity
vcfxb Sep 28, 2021
e2a0da5
Leave note about rendering discord user info.
vcfxb Sep 28, 2021
ac9148a
Merge branch 'master' into account-linking
vcfxb Sep 29, 2021
14e285b
Merge branch 'master' into account-linking
vcfxb Sep 29, 2021
407d7c4
Merge branch 'master' into account-linking
vcfxb Sep 30, 2021
cfe9149
Merge branch 'master' into account-linking
vcfxb Sep 30, 2021
6300b8d
Merge branch 'master' into account-linking
vcfxb Oct 1, 2021
45ed9ea
Create Discord API module
vcfxb Oct 1, 2021
cba54c1
WIP Discord API
vcfxb Oct 1, 2021
458b48c
lazy static for discord api client
vcfxb Oct 5, 2021
4c94aad
WIP discord on profile page
vcfxb Oct 5, 2021
2f1f40a
Merge branch 'master' into account-linking
vcfxb Oct 8, 2021
082e69d
WIP Discord Linking
vcfxb Oct 8, 2021
1437ded
Update template indexing to not panic on missing key
vcfxb Oct 8, 2021
d7289b3
Make discord profile linking pretty
vcfxb Oct 8, 2021
a9b1d23
Update discord branding on auth template
vcfxb Oct 8, 2021
c46bd83
Update Changelog
vcfxb Oct 8, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ has been made to fill in the gaps. If you find an issue anywhere in this changel
please submit a pull request fixing it.

## Unreleased
- Account linking with Discord ([#5], [#181])
- Updated Discord colors and icons ([#116], [#181])

## 0.7.1 - September 29th, 2021
- Make the `/whois` discord command ephemeral -- only the user who invokes the
Expand Down Expand Up @@ -113,6 +115,8 @@ in the meeting edit form.
[#113]: https://github.com/rcos/Telescope/pull/113
[#114]: https://github.com/rcos/Telescope/issues/114
[#115]: https://github.com/rcos/Telescope/issues/115
[#116]: https://github.com/rcos/Telescope/issues/116
[#142]: https://github.com/rcos/Telescope/issues/142
[#151]: https://github.com/rcos/Telescope/issues/151
[#176]: https://github.com/rcos/Telescope/pull/176
[#181]: https://github.com/rcos/Telescope/pull/181
2 changes: 1 addition & 1 deletion config_example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ telescope_url = "https://rcos.io"
# [REQUIRED]
# Guilds for the discord bot to add commands to. Telescope does not use global
# commands, so each RCOS guild mut be whitelisted.
guild_ids = []
guild_ids = ["xxxxxxxxxxxxxxxxxx"]

# Development Profile
# These options will override the global ones when telescope is run using
Expand Down
14 changes: 14 additions & 0 deletions src/api/discord/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//! Discord API interactions authenticated with the Telescope bot token.

use crate::env::global_config;
use serenity::http::Http;

lazy_static! {
static ref DISCORD_API_CLIENT: Http =
Http::new_with_token(global_config().as_ref().discord_config.bot_token.as_str());
}

/// Get a reference to the global lazily evaluated static discord api client object.
pub fn global_discord_client() -> &'static Http {
DISCORD_API_CLIENT.as_ref()
}
1 change: 1 addition & 0 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use graphql_client::Response;

pub mod github;
pub mod rcos;
pub mod discord;

/// Handle a response from a GraphQL API. Convert any errors as necessary and
/// extract the returned data if possible.
Expand Down
2 changes: 1 addition & 1 deletion src/templates/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub fn login() -> Template {
item(
DiscordOAuth::login_path(),
"btn-discord mb-2",
"Login using Discord",
"Login using",
// This is manually coded for in the template file and is not
// a Feather icon. Do not use it in other places, as it won't work.
Some("discord"),
Expand Down
15 changes: 12 additions & 3 deletions src/templates/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,25 @@ impl Template {
impl<T: Into<String>> Index<T> for Template {
type Output = Value;

/// Returns [`Value::Null`] if the key is not in the template.
fn index(&self, index: T) -> &Self::Output {
// Immutable indexing for fields.
&self.fields[&index.into()]
self.fields
.get(index.into().as_str())
.unwrap_or(&Value::Null)
}
}

impl<T: Into<String>> IndexMut<T> for Template {

/// Returns the existing value or creates a new empty object at the location
/// and returns a reference to that.
fn index_mut(&mut self, index: T) -> &mut Self::Output {
// Mutable indexing for fields.
&mut self.fields[&index.into()]
self.fields
// Get the existing entry if available
.entry(index)
// Or insert an empty object.
.or_insert(json!({}))
}
}

Expand Down
21 changes: 11 additions & 10 deletions src/web/services/auth/oauth2_providers/discord.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
//! Discord OAuth2 flow.

use std::sync::Arc;

use actix_web::http::header::ACCEPT;
use chrono::{DateTime, Duration, Utc};
use futures::future::LocalBoxFuture;
use oauth2::{AccessToken, RefreshToken, Scope, TokenResponse};
use oauth2::{AuthUrl, TokenUrl};
use oauth2::basic::{BasicClient, BasicTokenResponse};
use serenity::model::user::CurrentUser;
use crate::api::rcos::send_query;
use crate::api::rcos::users::accounts::reverse_lookup::ReverseLookup;
use crate::api::rcos::users::UserAccountType;
use crate::env::global_config;
use crate::error::TelescopeError;
use crate::web::services::auth::identity::{AuthenticationCookie, RootIdentity};
use crate::web::services::auth::oauth2_providers::{Oauth2Identity, Oauth2IdentityProvider};
use crate::web::services::auth::IdentityProvider;
use actix_web::http::header::ACCEPT;
use chrono::{DateTime, Duration, Utc};
use futures::future::LocalBoxFuture;
use oauth2::basic::{BasicClient, BasicTokenResponse};
use oauth2::{AccessToken, RefreshToken, Scope, TokenResponse};
use oauth2::{AuthUrl, TokenUrl};
use serenity::model::user::CurrentUser;
use std::sync::Arc;
use crate::web::services::auth::oauth2_providers::{Oauth2Identity, Oauth2IdentityProvider};

/// The Discord API endpoint to query for user data.
const DISCORD_API_ENDPOINT: &'static str = "https://discord.com/api/v8";
pub const DISCORD_API_ENDPOINT: &'static str = "https://discord.com/api/v8";

/// Zero-sized type used to represent Discord based identity verification.
pub struct DiscordOAuth;
Expand Down
69 changes: 62 additions & 7 deletions src/web/services/user/profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use actix_web::web::{Form, Query, ServiceConfig};
use actix_web::{http::header::LOCATION, HttpRequest, HttpResponse};
use chrono::{Datelike, Local};
use std::collections::HashMap;
use serenity::model::user::{User, CurrentUser};
use crate::api::discord::global_discord_client;

/// The path from the template directory to the profile template.
const TEMPLATE_NAME: &'static str = "user/profile";
Expand Down Expand Up @@ -59,19 +61,72 @@ async fn profile(
));
}

// Create the profile template to send back to the viewer.
let mut template: Template = Template::new(TEMPLATE_NAME)
.field("data", &response);

// Get the target user's info.
let target_user: &ProfileTarget = response.target.as_ref().unwrap();
// And use it to make the page title
let page_title = format!("{} {}", target_user.first_name, target_user.last_name);
let page_title: String = format!("{} {}", target_user.first_name, target_user.last_name);

// Get the target user's discord info.
let target_discord_id: Option<&str> = target_user
.discord
.first()
.map(|obj| obj.account_id.as_str());

// If the discord ID exists, and is properly formatted.
if let Some(target_discord_id) = target_discord_id.and_then(|s| s.parse::<u64>().ok()) {
// Get target user info.
let target_user: Result<User, serenity::Error> = global_discord_client()
.get_user(target_discord_id)
.await;

// Check to make sure target user info was available.
match target_user {
// Issue retrieving target user info
Err(e) => {
// Log an error and set a flag for the template.
warn!("Could not get target user account for Discord user ID {}. Account may have been deleted. Internal error: {}", target_discord_id, e);
template["discord"]["target"] = json!({"errored": true});
},

// User returned successfully.
Ok(u) => {
// Add the discord info to the template.
template["discord"]["target"] = json!({
"response": &u,
"resolved": {
"face": u.face(),
"tag": u.tag(),
}
});
}
}

// If we can, resolve the discord tag of the target using the viewer's auth.
// Get the authentication cookie if there is one.
let auth_cookie = identity
.identity()
.await;

// Extract the Discord credentials if available.
let discord_auth = auth_cookie
.as_ref()
.and_then(|auth| auth.get_discord());

// If there are Discord credentials, look up the associated user.
if let Some(discord_auth) = discord_auth {
// Look up the viewer's discord info.
let viewer: CurrentUser = discord_auth.get_authenticated_user().await?;
// Add it to the template.
template["discord"]["viewer"] = json!(viewer);
}
}

// Make a profile template
return Template::new(TEMPLATE_NAME)
.field("data", response)
// Render it inside a page (with the user's name as the title)
.render_into_page(&req, page_title)
.await;
// Render the profile template and send to user.
return template.render_into_page(&req, page_title).await;
}

/// Create a form template for the user settings page.
Expand Down
5 changes: 1 addition & 4 deletions static/styles/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@
--telescope-red: #dd2020;
--rpi-red: #d6001c;
--github-blue: #0366d6;
--discord-blurple: #7289da;
--discord-blurple: #5865F2;
--discord-white: #ffffff;
--discord-greyple: #99aab5;
--discord-dark: #2c2f33;
--discord-darker: #23272a;
--discord-black: #000000;

/* Meeting Colors */
Expand Down
9 changes: 2 additions & 7 deletions templates/auth.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,8 @@
{{message}}
{{#if icon}}
{{#if (eq icon "discord")}}
{{!-- There is no feather icon for discord currently
but I want a discord icon here so we do it manually.
--}}
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 245 240" fill="currentColor">
<path class="st0" d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zM140.9 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/>
<path class="st0" d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/>
</svg>
{{! There is no feather icon for discord currently but I want a discord icon here so we do it manually. }}
<img src="https://discord.com/assets/2f71ab5383293f63985ac8d5c632b3d4.png" alt="Discord Logo" height="22">
{{else}}
<i data-feather="{{icon}}"></i>
{{/if}}
Expand Down
63 changes: 60 additions & 3 deletions templates/user/profile.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@
</div>
{{/if}}
</div>

{{! User Role }}
<span class="badge badge-info">{{format_user_role target.role}}</span>

{{! Cohort info }}
{{#if target.cohort}}
<span class="badge" style="background: var(--rpi-red);">
RPI freshman class of {{target.cohort}}
</span>
{{/if}}

{{! Created at }}
<br>
<span class="text-muted">
Account created {{format_date target.created_at}} {{format_time target.created_at}}
Expand All @@ -30,13 +34,15 @@
<h3 class="mt-2">User Info</h3>

<div class="row row-cols-1 row-cols-md-2 row-cols-xl-4 mt-2">

{{! RPI CAS }}
<div class="col">
<div class="card text-dark text-center">
<h5 class="card-header">RPI E-Mail</h5>
<div class="card-body p-1">
{{! If the user has an email associated }}
{{! If the user has linked RPI CAS }}
{{#if target.rcs_id.[0]}}
{{! Link the email }}
{{! Show the email }}
{{#with target.rcs_id.[0]}}
<a href="mailto:{{account_id}}@rpi.edu">
{{account_id}}@rpi.edu
Expand All @@ -50,7 +56,7 @@
</a>
{{/if}}
{{else}}
{{! Show a message indicating not linked and let the user link if it's their account. }}
{{! Show a message indicating not linked and let the user link if it's their account. }}
RPI e-mail not linked.
{{#if (eq viewer.[0].username target.username)}}
<a href="/link/rpi_cas" class="btn btn-primary w-100">
Expand All @@ -61,6 +67,57 @@
</div>
</div>
</div>

{{! Discord }}
<div class="col">
<div class="card text-dark text-center">
<div class="card-header">
{{! There is no feather icon for discord currently but I want a discord icon here so we do it manually. }}
<img src="https://discord.com/assets/a69bd473fc9eb435cf791b8beaf29e93.png" alt="Discord Logo" height="30">
</div>

<div class="card-body p-1">
{{#if ../discord.target}}
{{! Discord is linked -- check if the discord info was successfully retieved. }}
{{#if ../discord.target.errored}}
Could not get discord info. Refresh the page perhaps?
{{else}}
{{! Display info about target user's Discord }}
<div class="w-50 mx-auto">
<img src="{{../discord.target.resolved.face}}" alt="Discord Profile Picture" class="img-thumbnail">
<p class="font-weight-bold">
{{../discord.target.resolved.tag}}
</p>
</div>
{{/if}}

{{! Check if user owns this account }}
{{#if (eq viewer.[0].username target.username)}}
{{! Allow owner to unlink dead Discord }}
<a href="/unlink/discord" class="btn btn-danger w-100">
Unlink Discord

{{! There is no feather icon for discord currently but I want a discord icon here so we do it manually. }}
<img src="https://discord.com/assets/9f6f9cd156ce35e2d94c0e62e3eff462.png" alt="Discord Logo" height="22">
</a>
{{/if}}
{{else}}
{{! Show message indicating not linked }}
No Discord account linked.

{{! Allow viewer to link their own discord if it's their account }}
{{#if (eq viewer.[0].username target.username)}}
<a href="/link/discord" class="btn btn-discord w-100">
Link Discord

{{! There is no feather icon for discord currently but I want a discord icon here so we do it manually. }}
<img src="https://discord.com/assets/9f6f9cd156ce35e2d94c0e62e3eff462.png" alt="Discord Logo" height="22">
</a>
{{/if}}
{{/if}}
</div>
</div>
</div>
</div>
{{/if}}

Expand Down