├── manifest.json ├── constants.js ├── i18n ├── index.js ├── ru.json ├── pt-BR.json ├── ro.json ├── pl.json ├── it.json ├── tr.json ├── en-US.json └── de.json ├── memberCountsStore ├── actions.js └── store.js ├── styles.scss ├── README.md ├── components ├── GuildProfileIcon.jsx ├── GuildIcon.jsx ├── GuildProfileFeatureList.jsx ├── GuildProfileHeader.jsx ├── GuildRelationships.jsx ├── GuildBanner.jsx ├── GuildProfileModal.jsx ├── GuildInfoBase.jsx └── Icons.jsx ├── index.js └── LICENSE /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Guild Profile", 3 | "version": "2.1.2", 4 | "description": "Allows you to open a guild profile modal that gives you various information about the guild.", 5 | "author": "NurMarvin", 6 | "license": "OSL-3.0" 7 | } 8 | -------------------------------------------------------------------------------- /constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 - 2021 NurMarvin (Marvin Witt) 3 | * Licensed under the Open Software License version 3.0 4 | */ 5 | 6 | module.exports = Object.freeze({ 7 | FluxActions: { 8 | UPDATE_MEMBER_COUNTS: 'GUILD_PROFILE_UPDATE_MEMBER_COUNTS', 9 | }, 10 | }); -------------------------------------------------------------------------------- /i18n/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 NurMarvin (Marvin Witt) 3 | * Licensed under the Open Software License version 3.0 4 | */ 5 | 6 | require('fs') 7 | .readdirSync(__dirname) 8 | .filter(file => file !== 'index.js') 9 | .forEach(filename => { 10 | const moduleName = filename.split('.')[0]; 11 | exports[moduleName] = require(`${__dirname}/${filename}`); 12 | }); 13 | -------------------------------------------------------------------------------- /memberCountsStore/actions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 - 2021 NurMarvin (Marvin Witt) 3 | * Licensed under the Open Software License version 3.0 4 | */ 5 | 6 | const { FluxDispatcher } = require('powercord/webpack'); 7 | const { FluxActions } = require('../constants'); 8 | 9 | module.exports = { 10 | updateMemberCounts: async (memberCountsUpdate) => { 11 | FluxDispatcher.dispatch({ 12 | type: FluxActions.UPDATE_MEMBER_COUNTS, 13 | ...memberCountsUpdate 14 | }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /styles.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 - 2021 NurMarvin (Marvin Witt) 3 | * Licensed under the Open Software License version 3.0 4 | */ 5 | 6 | .guild-info-section { 7 | padding: 5px 10px; 8 | } 9 | 10 | .guild-profile { 11 | padding: 20px 10px; 12 | height: 100%; 13 | } 14 | 15 | .guild-icon-acronym { 16 | font-size: 2em; 17 | text-align: center; 18 | margin: auto; 19 | color: var(--text-normal); 20 | overflow: hidden; 21 | width: 100%; 22 | } 23 | 24 | .guild-profile-feature-badge { 25 | color: var(--interactive-normal); 26 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Guild Profile 2 | 3 | Open a guild profile modal that gives you various information about the guild. 4 | 5 | ## Support Server (NEW) 6 | Need help with the plugin? Feel free to drop into the [support server](https://nurmarv.in/support) and ask for help! 7 | 8 | ## Preview 9 | 10 | ![Preview Image](https://i.imgur.com/jNOxmBU.png) 11 | 12 | ## Installation 13 | 14 | You're probably totally stoked to learn how to install this bad boy, right? \ 15 | It's real simple! Just follow our certified installation guide and you're ready to use it: 16 | 17 | 1. Clone this repository into your Powercord plugins folder using `git clone https://github.com/NurMarvin/guild-profile.git` 18 | 2. Reload Discord using `Ctrl + R` 19 | -------------------------------------------------------------------------------- /components/GuildProfileIcon.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 - 2021 NurMarvin (Marvin Witt) 3 | * Licensed under the Open Software License version 3.0 4 | */ 5 | 6 | const { React, getModule } = require('powercord/webpack'); 7 | const { icon } = getModule(['menu', 'icon'], false); 8 | 9 | module.exports = React.memo((props) => ( 10 | 18 | 24 | 25 | )); 26 | -------------------------------------------------------------------------------- /i18n/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "GUILD_PROFILE": "Профиль Сервера", 3 | "GUILD_INFO": "Информация о Сервере", 4 | "LOADING": "Загрузка", 5 | "FRIENDS_IN_GUILD": "Друзья", 6 | "BLOCKED_USERS_IN_GUILD": "Заблокированные Пользователи", 7 | "NOT_IMPLEMENTED_YET": "Еще Не Реализовано", 8 | "JOINED_AT": "Зашёл", 9 | "CREATED_AT": "Создан", 10 | "COMMUNITY": "Публичный", 11 | "COMMERCE": "Каналы \"Магазины\"", 12 | "ENABLED_DISCOVERABLE_BEFORE": "Enabled Discoverable Before", 13 | "INVITE_SPLASH": "Фон Приглашения На сервер", 14 | "BANNER": "Баннер Сервера", 15 | "ANIMATED_ICON": "Анимированная Иконка Сервера", 16 | "DISCOVERABLE": "Видимый", 17 | "WELCOME_SCREEN_ENABLED": "Экран Приветствия Включен", 18 | "NEWS": "Канал Объявлений", 19 | "FEATURABLE": "Featurable", 20 | "VIP_REGIONS": "VIP Регионы" 21 | } 22 | -------------------------------------------------------------------------------- /i18n/pt-BR.json: -------------------------------------------------------------------------------- 1 | { 2 | "GUILD_PROFILE": "Informações do servidor", 3 | "GUILD_INFO": "Informações do servidor", 4 | "LOADING": "Processando", 5 | "FRIENDS_IN_GUILD": "Amigos", 6 | "BLOCKED_USERS_IN_GUILD": "Usuários bloqueados", 7 | "NOT_IMPLEMENTED_YET": "Ainda não implementado", 8 | "JOINED_AT": "Data de sua entrada", 9 | "CREATED_AT": "Data de criação", 10 | "COMMUNITY": "Comunidade", 11 | "COMMERCE": "Contém canais de loja", 12 | "ENABLED_DISCOVERABLE_BEFORE": "Ativado Servidor público antes", 13 | "INVITE_SPLASH": "Plano de fundo de convite do servidor", 14 | "BANNER": "Banner do servidor", 15 | "ANIMATED_ICON": "Ícone animado do servidor", 16 | "DISCOVERABLE": "Servidor público", 17 | "WELCOME_SCREEN_ENABLED": "Tela de bem-vindo do servidor ativada", 18 | "NEWS": "Contém canais de anúncio", 19 | "FEATURABLE": "Pode ser inserido na home do Discover", 20 | "VIP_REGIONS": "Regiões VIP" 21 | } 22 | -------------------------------------------------------------------------------- /memberCountsStore/store.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 - 2021 NurMarvin (Marvin Witt) 3 | * Licensed under the Open Software License version 3.0 4 | */ 5 | 6 | const { Flux, FluxDispatcher } = require('powercord/webpack'); 7 | const { FluxActions } = require('../constants'); 8 | 9 | const memberCounts = []; 10 | 11 | function handleMemberCountsUpdate(memberCountsUpdate) { 12 | memberCounts.push(memberCountsUpdate); 13 | } 14 | 15 | class MemberCountsStore extends Flux.Store { 16 | getStore() { 17 | return { 18 | memberCounts, 19 | }; 20 | } 21 | 22 | getAllMemberCounts() { 23 | return memberCounts; 24 | } 25 | 26 | getMemberCounts(guildId) { 27 | return memberCounts.find(memberCounts => memberCounts.guildId === guildId); 28 | } 29 | } 30 | 31 | module.exports = new MemberCountsStore(FluxDispatcher, { 32 | [FluxActions.UPDATE_MEMBER_COUNTS]: (guildId, members, membersOnline) => handleMemberCountsUpdate(guildId, members, membersOnline), 33 | }); -------------------------------------------------------------------------------- /i18n/ro.json: -------------------------------------------------------------------------------- 1 | { 2 | "GUILD_PROFILE": "Profilul server-ului", 3 | "GUILD_INFO": "Informații despre server", 4 | "LOADING": "Se încarcă", 5 | "FRIENDS_IN_GUILD": "Prieteni", 6 | "BLOCKED_USERS_IN_GUILD": "Utilizatori blocați", 7 | "NOT_IMPLEMENTED_YET": "În lucru", 8 | "JOINED_AT": "Alaturat la", 9 | "CREATED_AT": "Creat la", 10 | "COMMUNITY": "Comunitate", 11 | "COMMERCE": "Canale de comerț", 12 | "ENABLED_DISCOVERABLE_BEFORE": "Descoperibil înainte", 13 | "INVITE_SPLASH": "Fundal de invite", 14 | "BANNER": "Banner-ul server-ului", 15 | "ANIMATED_ICON": "Imaginea animată a server-ului", 16 | "DISCOVERABLE": "Descoperibilă", 17 | "WELCOME_SCREEN_ENABLED": "Ecran de început", 18 | "NEWS": "Canale de anunțuri", 19 | "FEATURABLE": "Promovabil", 20 | "VIP_REGIONS": "Regiuni VIP", 21 | "MORE_EMOJI": "Mai multe emoji-uri", 22 | "RELAY_ENABLED": "Relay activat", 23 | "CLICK_TO_COPY_SERVER_ICON_URL": "Click pentru a copia imaginea server-ului", 24 | "NO_FRIENDS_IN_THIS_GUILD": "Nu există prieteni în acest server", 25 | "NO_BLOCKED_USERS_IN_THIS_GUILD": "Nu există utilizatori blocați în acest server", 26 | "GUILD_PREMIUM_SUBSCRIBER_COUNT": "Numărul de boosteri", 27 | "GUILD_PREMIUM_TIER": "Nivelul de boost al server-ului" 28 | } -------------------------------------------------------------------------------- /i18n/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "GUILD_PROFILE": "Profil serwera", 3 | "GUILD_INFO": "Informacje o serwerze", 4 | "LOADING": "Ładowanie", 5 | "FRIENDS_IN_GUILD": "Znajomi", 6 | "BLOCKED_USERS_IN_GUILD": "Zablokowani użytkownicy", 7 | "NOT_IMPLEMENTED_YET": "Jeszcze niezaimplementowane", 8 | "JOINED_AT": "Data dołączenia", 9 | "CREATED_AT": "Data utworzenia", 10 | "COMMUNITY": "Społeczność", 11 | "COMMERCE": "Kanały sklepu", 12 | "ENABLED_DISCOVERABLE_BEFORE": "Wcześniej włączone odnajdowanie serwera", 13 | "INVITE_SPLASH": "Tło zaproszenia do serwera", 14 | "BANNER": "Banner serwera", 15 | "ANIMATED_ICON": "Animowana ikona serwera", 16 | "DISCOVERABLE": "Odnajdywalny", 17 | "WELCOME_SCREEN_ENABLED": "Włączony ekran powitalny", 18 | "NEWS": "Kanały ogłoszeń", 19 | "FEATURABLE": "Featurable", 20 | "VIP_REGIONS": "Regiony VIP", 21 | "MORE_EMOJI": "Więcej emoji", 22 | "RELAY_ENABLED": "Włączona retransmisja", 23 | "PREVIEW_ENABLED": "Włączony przegląd", 24 | "MEMBER_VERIFICATION_GATE_ENABLED": "Włączone sprawdzanie członków", 25 | "CLICK_TO_COPY_SERVER_ICON_URL": "Kliknij aby skopiować URL ikony serwera", 26 | "NO_FRIENDS_IN_THIS_GUILD": "Brak znajomych na tym serwerze", 27 | "NO_BLOCKED_USERS_IN_THIS_GUILD": "Brak zablokowanych użytkowników na tym serwerze", 28 | "GUILD_PREMIUM_SUBSCRIBER_COUNT": "Ilość osób ulepszających serwer", 29 | "GUILD_PREMIUM_TIER": "Poziom ulepszenia serwera", 30 | "PREFERRED_LOCALE": "Preferowane umiejscowienie", 31 | "NSFW": "Not Safe For Work (NSFW)/Zbanowane na iOS", 32 | "YES": "Tak", 33 | "NO": "Nie" 34 | } 35 | -------------------------------------------------------------------------------- /i18n/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "GUILD_PROFILE": "Profilo Server", 3 | "GUILD_INFO": "Informazioni Server", 4 | "LOADING": "Caricamento", 5 | "FRIENDS_IN_GUILD": "Amici", 6 | "BLOCKED_USERS_IN_GUILD": "Utenti Bloccati", 7 | "NOT_IMPLEMENTED_YET": "Non ancora implementato", 8 | "JOINED_AT": "Iscritto il", 9 | "CREATED_AT": "Creato il", 10 | "COMMUNITY": "Comunità", 11 | "COMMERCE": "Canali Negozio", 12 | "ENABLED_DISCOVERABLE_BEFORE": "Ha abilitato \"Discovery\" in precedenza", 13 | "INVITE_SPLASH": "Sfondo sulla schermata di invito", 14 | "BANNER": "Banner del Server", 15 | "ANIMATED_ICON": "Icona del server animata", 16 | "DISCOVERABLE": "Disponibile su \"Discovery\"", 17 | "WELCOME_SCREEN_ENABLED": "Schermata di benvenuto abilitata", 18 | "NEWS": "Canale Annunci", 19 | "FEATURABLE": "In Evidenza", 20 | "VIP_REGIONS": "Regioni VIP", 21 | "MORE_EMOJI": "Più Emoji", 22 | "RELAY_ENABLED": "Relay Abilitato", 23 | "PREVIEW_ENABLED": "Anteprima Abilitata", 24 | "MEMBER_VERIFICATION_GATE_ENABLED": "Verifica iniziale dei membri abilitata", 25 | "CLICK_TO_COPY_SERVER_ICON_URL": "Clicca per copiare l'URL dell'icona del server", 26 | "NO_FRIENDS_IN_THIS_GUILD": "Nessuno dei tuoi amici è in questo server", 27 | "NO_BLOCKED_USERS_IN_THIS_GUILD": "Non ci sono utenti bloccati in questo server", 28 | "GUILD_PREMIUM_SUBSCRIBER_COUNT": "Numero Boost nel Server", 29 | "GUILD_PREMIUM_TIER": "Livello Boost del Server", 30 | "PREFERRED_LOCALE": "Lingua Preferita", 31 | "NSFW": "Contenuti Espliciti (NSFW)/Bannato on iOS", 32 | "YES": "Si", 33 | "NO": "No" 34 | } 35 | -------------------------------------------------------------------------------- /components/GuildIcon.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 - 2021 NurMarvin (Marvin Witt) 3 | * Licensed under the Open Software License version 3.0 4 | */ 5 | 6 | const { React, getModule } = require('powercord/webpack'); 7 | 8 | module.exports = class GuildIcon extends React.PureComponent { 9 | constructor(props) { 10 | super(props); 11 | 12 | const { wrapper, avatar, avatarStack } = getModule( 13 | ['avatar', 'wrapper', 'avatarStack'], 14 | false 15 | ); 16 | 17 | this.modules = { 18 | acronym: getModule(['childWrapper', 'acronym'], false).acronym, 19 | wrapper, 20 | avatarStack, 21 | realAvatar: avatar, 22 | ...getModule(['headerTop', 'avatar', 'badgeList'], false), 23 | ...getModule(['getGuildIconURL'], false), 24 | }; 25 | } 26 | 27 | render() { 28 | const { guild } = this.props; 29 | 30 | return ( 31 |
32 |
33 |
34 | {guild.icon ? ( 35 |  41 | ) : ( 42 |
47 | {guild.acronym} 48 |
49 | )} 50 |
51 |
52 |
53 | ); 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /i18n/tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "GUILD_PROFILE": "Sunucu Profili", 3 | "GUILD_INFO": "Sunucu Bilgisi", 4 | "LOADING": "Yükleniyor", 5 | "FRIENDS_IN_GUILD": "Arkadaşlar", 6 | "BLOCKED_USERS_IN_GUILD": "Yasaklanan Kullanıcılar", 7 | "NOT_IMPLEMENTED_YET": "Henüz uygulanmadı", 8 | "JOINED_AT": "Katıldığı Tarih", 9 | "CREATED_AT": "Oluşturulduğu Tarih", 10 | "COMMUNITY": "Topluluk", 11 | "COMMERCE": "Mağaza Kanalları", 12 | "ENABLED_DISCOVERABLE_BEFORE": "Daha Önce Keşfedilebilir Etkinleştirilmiş", 13 | "INVITE_SPLASH": "Sunucu Davet Arkaplanı", 14 | "BANNER": "Sunucu Afişi", 15 | "ANIMATED_ICON": "Hareketli Sunucu Fotoğrafı", 16 | "DISCOVERABLE": "Keşfedilebilir", 17 | "WELCOME_SCREEN_ENABLED": "Hoşgeldin Ekranı Etkin", 18 | "NEWS": "Duyuru Kanalları", 19 | "FEATURABLE": "Öne Çıkan", 20 | "VIP_REGIONS": "VIP Bölgeler", 21 | "MORE_EMOJI": "Daha Fazla Emoji", 22 | "RELAY_ENABLED": "Relay Etkin", 23 | "PREVIEW_ENABLED": "Önizleme Etkin", 24 | "MEMBER_VERIFICATION_GATE_ENABLED": "Üyelik Kapısı Etkin", 25 | "PRIVATE_THREADS": "Özel Konular", 26 | "THREE_DAY_THREAD_ARCHIVE": "Üç Günlük Konu Arşivi", 27 | "SEVEN_DAY_THREAD_ARCHIVE": "Yedi Günlük Konu Arşivi", 28 | "CLICK_TO_COPY_SERVER_ICON_URL": "Sunucu simgesinin URL'sini kopyalamak için tıkla", 29 | "NO_FRIENDS_IN_THIS_GUILD": "Bu sunucuda arkadaşın yok", 30 | "NO_BLOCKED_USERS_IN_THIS_GUILD": "Bu sunucuda engellenen kullanıcı yok", 31 | "NO_EXPERIMENTS_IN_THIS_GUILD": "Bu sunucuda etkin sunucu deneyimleri yok", 32 | "GUILD_PREMIUM_SUBSCRIBER_COUNT": "Sunucu Takviye Sayısı", 33 | "GUILD_PREMIUM_TIER": "Sunucu Takviye Seviyesi", 34 | "PREFERRED_LOCALE": "Tercih Edilen Yer", 35 | "NSFW": "İş İçin Güvenli Değil (NSFW) Düzeyi", 36 | "NSFW_LEVEL_DEFAULT": "Varsayılan", 37 | "NSFW_LEVEL_EXPLICIT": "Müstehcen", 38 | "NSFW_LEVEL_SAFE": "Güvenli", 39 | "NSFW_LEVEL_AGE_RESTRICTED": "Yaş Sınırlı", 40 | "ENABLED_EXPERIMENTS": "Deneyimler Etkin", 41 | "EXCLUSIVE_TO_SERVER_BANNER_FEATURE": "Sunucu afiş özelliğine sahip sunuculara özel", 42 | "NONE": "Yok" 43 | } 44 | -------------------------------------------------------------------------------- /components/GuildProfileFeatureList.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 - 2021 NurMarvin (Marvin Witt) 3 | * Licensed under the Open Software License version 3.0 4 | */ 5 | 6 | const { 7 | React, 8 | getModule, 9 | i18n: { Messages }, 10 | } = require("powercord/webpack"); 11 | 12 | const { Tooltip, Clickable } = require("powercord/components"); 13 | 14 | const Icons = require("./Icons"); 15 | 16 | module.exports = class GuildProfileFeatureList extends React.PureComponent { 17 | constructor(props) { 18 | super(props); 19 | 20 | this.modules = { 21 | ...getModule(["container", "clickable", "profileBadge"], false), 22 | theme: getModule(["theme"], false).theme, 23 | }; 24 | } 25 | 26 | render() { 27 | const { guild, className } = this.props; 28 | 29 | return ( 30 |
35 | {Array.from(guild.features) 36 | .filter( 37 | (feature) => feature !== "VERIFIED" && feature !== "PARTNERED" 38 | ) 39 | .map((feature) => { 40 | const Icon = Icons[feature] ?? Icons.UNKNOWN; 41 | 42 | return ( 43 | 45 | window.open( 46 | `https://github.com/Delitefully/DiscordLists#guild-feature-glossary:~:.-,text=${feature}`, 47 | "_blank" 48 | ) 49 | } 50 | className={this.modules.clickable} 51 | > 52 | 58 | 14 61 | ? this.modules.profileBadge18 62 | : this.modules.profileBadge24, 63 | this.modules.profileBadge, 64 | this.modules.desaturate, 65 | "guild-profile-feature-badge", 66 | ].join(" ")} 67 | /> 68 | 69 | 70 | ); 71 | })} 72 |
73 | ); 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /i18n/en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "GUILD_PROFILE": "Guild Profile", 3 | "GUILD_INFO": "Server Info", 4 | "LOADING": "Loading", 5 | "FRIENDS_IN_GUILD": "Friends", 6 | "BLOCKED_USERS_IN_GUILD": "Blocked Users", 7 | "NOT_IMPLEMENTED_YET": "Not implemented yet", 8 | "JOINED_AT": "Joined at", 9 | "CREATED_AT": "Created at", 10 | "COMMUNITY": "Community", 11 | "COMMERCE": "Store Channels", 12 | "ENABLED_DISCOVERABLE_BEFORE": "Enabled Discoverable Before", 13 | "INVITE_SPLASH": "Server Invite Background", 14 | "BANNER": "Server Banner", 15 | "ANIMATED_ICON": "Animated Server Icon", 16 | "DISCOVERABLE": "Discoverable", 17 | "WELCOME_SCREEN_ENABLED": "Welcome Screen Enabled", 18 | "NEWS": "Announcement Channels", 19 | "FEATURABLE": "Featurable", 20 | "VIP_REGIONS": "VIP Regions", 21 | "MORE_EMOJI": "More Emoji", 22 | "RELAY_ENABLED": "Relay Enabled", 23 | "PREVIEW_ENABLED": "Preview Enabled", 24 | "MEMBER_VERIFICATION_GATE_ENABLED": "Membership Gating Enabled", 25 | "THREADS_ENABLED": "Threads Enabled", 26 | "PRIVATE_THREADS": "Private Threads", 27 | "THREE_DAY_THREAD_ARCHIVE": "Three Day Thread Archive", 28 | "SEVEN_DAY_THREAD_ARCHIVE": "Seven Day Thread Archive", 29 | "ROLE_ICONS": "Role Icons", 30 | "ANIMATED_BANNER": "Animated Server Banner", 31 | "MEMBER_PROFILES": "Member Profiles", 32 | "AUTO_MODERATION": "Auto Moderation", 33 | "TEXT_IN_VOICE_ENABLED": "Text in Voice Enabled", 34 | "ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE": "Role Subscriptions Available for Purchase", 35 | "EXPOSED_TO_ACTIVITIES_WTP_EXPERIMENT": "Exposed to Activities \"Willingness To Pay\" Experiment", 36 | "HAD_EARLY_ACTIVITIES_ACCESS": "Had Early Activities Access", 37 | "INVITES_DISABLED": "Invites Disabled", 38 | "ROLE_SUBSCRIPTIONS_ENABLED": "Role Subscriptions Enabled", 39 | "PREMIUM_TIER_3_OVERRIDE": "Server Boost Tier 3 Override", 40 | "BOT_DEVELOPER_EARLY_ACCESS": "Bot Developer Early Access", 41 | "INTERNAL_EMPLOYEE_ONLY": "Internal Employee Only", 42 | "CREATOR_MONETIZABLE": "Creator Monetizable", 43 | "CLICK_TO_COPY_SERVER_ICON_URL": "Click to copy server icon URL", 44 | "NO_FRIENDS_IN_THIS_GUILD": "No friends in this server", 45 | "NO_BLOCKED_USERS_IN_THIS_GUILD": "No blocked users in this server", 46 | "NO_EXPERIMENTS_IN_THIS_GUILD": "No enabled guild experiments in this server", 47 | "GUILD_PREMIUM_SUBSCRIBER_COUNT": "Server Booster Count", 48 | "GUILD_PREMIUM_TIER": "Server Boost Level", 49 | "PREFERRED_LOCALE": "Preferred Locale", 50 | "NSFW": "Not Safe For Work (NSFW) Level", 51 | "NSFW_LEVEL_DEFAULT": "Default", 52 | "NSFW_LEVEL_EXPLICIT": "Explicit", 53 | "NSFW_LEVEL_SAFE": "Safe", 54 | "NSFW_LEVEL_AGE_RESTRICTED": "Age Restricted", 55 | "ENABLED_EXPERIMENTS": "Enabled Experiments", 56 | "EXCLUSIVE_TO_SERVER_BANNER_FEATURE": "Exclusive to servers with the server banner feature", 57 | "NONE": "None" 58 | } 59 | -------------------------------------------------------------------------------- /i18n/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "GUILD_PROFILE": "Serverprofil", 3 | "GUILD_INFO": "Serverinfo", 4 | "LOADING": "Lade", 5 | "FRIENDS_IN_GUILD": "Freunde", 6 | "BLOCKED_USERS_IN_GUILD": "Blockierte Nutzer", 7 | "NOT_IMPLEMENTED_YET": "Noch nicht implementiert", 8 | "JOINED_AT": "Server betreten", 9 | "CREATED_AT": "Server erstellt", 10 | "COMMUNITY": "Community", 11 | "COMMERCE": "Store-Kanäle", 12 | "ENABLED_DISCOVERABLE_BEFORE": "Hat \"Entdeckbar\" vorher schon mal aktiviert", 13 | "INVITE_SPLASH": "Hintergrund für Servereinladungen", 14 | "BANNER": "Server Banner", 15 | "ANIMATED_ICON": "Animiertes Servericon", 16 | "DISCOVERABLE": "Entdeckbar", 17 | "WELCOME_SCREEN_ENABLED": "Willkommensbildschirm aktiviert", 18 | "NEWS": "Ankündigungskanäle", 19 | "FEATURABLE": "Vorstellbar", 20 | "VIP_REGIONS": "VIP Regionen", 21 | "MORE_EMOJI": "Mehr Emojis", 22 | "RELAY_ENABLED": "Relay aktiviert", 23 | "PREVIEW_ENABLED": "Vorschau aktiviert", 24 | "MEMBER_VERIFICATION_GATE_ENABLED": "Mitgliedschaftsbeschränkungen aktiviert", 25 | "PRIVATE_THREADS": "Private Threads", 26 | "THREE_DAY_THREAD_ARCHIVE": "3-Tage Thread Archivierung", 27 | "SEVEN_DAY_THREAD_ARCHIVE": "7-Tage Thread Archivierung", 28 | "ROLE_ICONS": "Rollenicons", 29 | "ANIMATED_BANNER": "Animierter Serverbanner", 30 | "MEMBER_PROFILES": "Mitgliederprofile", 31 | "AUTO_MODERATION": "Auto-Moderation", 32 | "TEXT_IN_VOICE_ENABLED": "Text in Sprachkanälen aktiviert", 33 | "ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE": "Rollenabonnements zum Kauf verfügbar", 34 | "EXPOSED_TO_ACTIVITIES_WTP_EXPERIMENT": "Hat das \"Bereitschaft Zu Zahlen\" Aktitäts-Experiment", 35 | "HAD_EARLY_ACTIVITIES_ACCESS": "Hatte frühen Zugang zu Aktivitäten", 36 | "INVITES_DISABLED": "Einladungen deaktiviert", 37 | "ROLE_SUBSCRIPTIONS_ENABLED": "Rollenabonnements aktiviert", 38 | "PREMIUM_TIER_3_OVERRIDE": "Server-Boost Level 3 Überschreibung", 39 | "BOT_DEVELOPER_EARLY_ACCESS": "Früher Zugang für Bot-Entwickler", 40 | "INTERNAL_EMPLOYEE_ONLY": "Interner Server, nur für Mitarbeiter", 41 | "CREATOR_MONETIZABLE": "Monetarisierbar für Servereigentümer", 42 | "CLICK_TO_COPY_SERVER_ICON_URL": "Klicke um die Server Icon URL zu kopieren", 43 | "NO_FRIENDS_IN_THIS_GUILD": "Keine Freunde in diesem Server", 44 | "NO_BLOCKED_USERS_IN_THIS_GUILD": "Keine blockierten Nutzer in diesem Server", 45 | "GUILD_PREMIUM_SUBSCRIBER_COUNT": "Anzahl der Server Booster", 46 | "GUILD_PREMIUM_TIER": "Server Boost Level", 47 | "NSFW": "Nicht sicher für die Arbeit (NSFW) Level", 48 | "NSFW_LEVEL_DEFAULT": "Standard", 49 | "NSFW_LEVEL_EXPLICIT": "Explizit", 50 | "NSFW_LEVEL_SAFE": "Sicher", 51 | "NSFW_LEVEL_AGE_RESTRICTED": "Altersbeschränkt", 52 | "ENABLED_EXPERIMENTS": "Aktivierte Experimente", 53 | "EXCLUSIVE_TO_SERVER_BANNER_FEATURE": "Exklusiv für Server mit dem Server Banner Feature", 54 | "NONE": "Keine" 55 | } 56 | -------------------------------------------------------------------------------- /components/GuildProfileHeader.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 - 2021 NurMarvin (Marvin Witt) 3 | * Licensed under the Open Software License version 3.0 4 | */ 5 | 6 | const { 7 | React, 8 | getModule, 9 | i18n: { Messages }, 10 | getModuleByDisplayName, 11 | } = require('powercord/webpack'); 12 | 13 | const { Text } = require('powercord/components'); 14 | const AsyncComponent = require('powercord/components/AsyncComponent'); 15 | 16 | const inviteModule = getModule((m) => m.displayName === 'InviteButton' && m.Header) 17 | const GuildBadge = AsyncComponent.from(getModuleByDisplayName('GuildBadge')); 18 | const InviteButton = AsyncComponent.from(inviteModule); 19 | 20 | inviteModule.then((Button) => { 21 | ['Data'].forEach((prop) => (InviteButton[prop] = Button[prop])); 22 | }); 23 | 24 | const GuildBanner = require('./GuildBanner'); 25 | const GuildIcon = require('./GuildIcon'); 26 | const GuildProfileFeatureList = require('./GuildProfileFeatureList'); 27 | 28 | module.exports = class GuildProfileHeader extends React.PureComponent { 29 | constructor(props) { 30 | super(props); 31 | 32 | this.modules = { 33 | ...getModule(['guildDetail'], false), 34 | ...getModule(['topSection'], false), 35 | ...getModule(['wrapper', 'pointer'], false), 36 | ...getModule(['guildIconContainer', 'guildBadge'], false), 37 | ...getModule(['headerTop', 'avatar', 'badgeList'], false), 38 | }; 39 | } 40 | render() { 41 | const { guild, counts } = this.props; 42 | 43 | return ( 44 |
45 | 46 |
47 | 48 |
49 | 53 |
54 |
55 |
62 | 69 | {guild.name} 70 |
71 | 77 | {counts ? ( 78 | 82 | ) : ( 83 | `${Messages.LOADING}...` 84 | )} 85 | 86 |
87 | ); 88 | } 89 | }; 90 | -------------------------------------------------------------------------------- /components/GuildRelationships.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 - 2021 NurMarvin (Marvin Witt) 3 | * Licensed under the Open Software License version 3.0 4 | */ 5 | 6 | const { 7 | React, 8 | getModule, 9 | i18n: { Messages }, 10 | getModuleByDisplayName, 11 | } = require('powercord/webpack'); 12 | const { AdvancedScrollerThin } = getModule(['AdvancedScrollerThin'], false); 13 | const { Clickable, Spinner } = require('powercord/components'); 14 | const AsyncComponent = require('powercord/components/AsyncComponent'); 15 | const DiscordTag = AsyncComponent.from(getModuleByDisplayName('DiscordTag')); 16 | const { default: Avatar } = getModule(['AnimatedAvatar'], false); 17 | const { close } = require('powercord/modal'); 18 | 19 | const ContextMenu = getModule(['closeContextMenu'], false); 20 | 21 | const UserProfileModalActionCreators = getModule( 22 | ['openUserProfileModal'], 23 | false 24 | ); 25 | 26 | class RelationshipRow extends React.PureComponent { 27 | constructor(props) { 28 | super(props); 29 | 30 | this.modules = { 31 | ...getModule(['listRow'], false), 32 | }; 33 | } 34 | 35 | render() { 36 | const { user, status, onSelect, onContextMenu } = this.props; 37 | 38 | return ( 39 | onSelect(user.id)} 42 | onContextMenu={() => onContextMenu(user.id)} 43 | > 44 | 50 | 55 | 56 | ); 57 | } 58 | } 59 | 60 | module.exports = class Relationships extends React.PureComponent { 61 | constructor(props) { 62 | super(props); 63 | 64 | this.modules = { 65 | nelly: getModule(['flexWrapper', 'image'], false).image, 66 | ...getModule(['emptyIconFriends'], false), 67 | ...getModule(['scrollerBase', 'fade', 'thin'], false), 68 | }; 69 | } 70 | 71 | handleSelect(userId) { 72 | close(); 73 | UserProfileModalActionCreators.openUserProfileModal({ userId }); 74 | } 75 | 76 | render() { 77 | const { relationships, section } = this.props; 78 | 79 | if (!relationships) { 80 | return ( 81 |
82 | 83 |
84 | ); 85 | } else if (relationships.length < 1) { 86 | return ( 87 |
88 |
89 |
90 | {Messages[`NO_${section}_IN_THIS_GUILD`]} 91 |
92 |
93 | ); 94 | } 95 | return ( 96 | 100 | {relationships.map((relationship) => ( 101 | 104 | ContextMenu.openContextMenu(event, () => {}) 105 | } 106 | user={relationship} 107 | /> 108 | ))} 109 | 110 | ); 111 | } 112 | }; 113 | -------------------------------------------------------------------------------- /components/GuildBanner.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 - 2021 NurMarvin (Marvin Witt) 3 | * Licensed under the Open Software License version 3.0 4 | */ 5 | 6 | const { 7 | React, 8 | getModule, 9 | i18n: { Messages }, 10 | } = require("powercord/webpack"); 11 | const { Tooltip } = require("powercord/components"); 12 | 13 | const { TextBadge } = getModule(["TextBadge"], false); 14 | const { Colors } = getModule(["Colors"], false); 15 | 16 | module.exports = class GuildBanner extends React.PureComponent { 17 | state = { 18 | color: null, 19 | hovered: false, 20 | }; 21 | 22 | constructor(props) { 23 | super(props); 24 | 25 | this.modules = { 26 | ...getModule(["profileBannerPremium", "profileBanner"], false), 27 | getPaletteForAvatar: getModule(["getPaletteForAvatar"], false) 28 | .getPaletteForAvatar, 29 | getGuildIconURL: getModule(["getGuildIconURL"], false).getGuildIconURL, 30 | getGuildBannerURL: getModule(["getGuildBannerURL"], false) 31 | .getGuildBannerURL, 32 | ...getModule(["getBestMediaProxySize"], false), 33 | canUseWebp: getModule(["canUseWebp"], false).canUseWebp, 34 | isAnimatedIconHash: getModule(["isAnimatedIconHash"], false) 35 | .isAnimatedIconHash, 36 | Endpoints: getModule(["Endpoints"], false).Endpoints, 37 | }; 38 | } 39 | 40 | async componentDidMount() { 41 | const { guild } = this.props; 42 | 43 | let color = Colors.BRAND; 44 | 45 | if (guild.icon) { 46 | const palette = await this.modules.getPaletteForAvatar( 47 | this.modules.getGuildIconURL(guild) 48 | ); 49 | color = `rgb(${palette[0].join(", ")})`; 50 | } 51 | 52 | this.setState({ color }); 53 | } 54 | 55 | render() { 56 | const { guild } = this.props; 57 | const { color } = this.state; 58 | 59 | if (!color) return null; 60 | 61 | const classes = [this.modules.banner]; 62 | const style = { 63 | backgroundColor: color, 64 | }; 65 | 66 | if (guild.banner) { 67 | classes.push( 68 | this.modules.profileBannerPremium, 69 | this.modules.bannerPremium 70 | ); 71 | 72 | const { id, banner } = guild; 73 | 74 | if (banner == null) return null; 75 | 76 | const cdnHost = window.GLOBAL_ENV.CDN_HOST; 77 | const extension = 78 | this.state.hovered && this.modules.isAnimatedIconHash(banner) 79 | ? "gif" 80 | : this.modules.canUseWebp 81 | ? "webp" 82 | : "jpg"; 83 | const bannerSize = this.modules.getBestMediaProxySize( 84 | 600 * this.modules.getDevicePixelRatio() 85 | ); 86 | let url = 87 | (cdnHost 88 | ? `${location.protocol}//${cdnHost}/banners/${id}/${banner}.${extension}` 89 | : this.modules.Endpoints.GUILD_BANNER(id, banner, extension)) + 90 | `?size=${bannerSize}`; 91 | 92 | if (extension === "jpg") { 93 | url += "&quality=lossless"; 94 | } 95 | 96 | style.backgroundImage = `url(${url})`; 97 | } else { 98 | classes.push(this.modules.profileBanner); 99 | } 100 | 101 | return ( 102 |
this.setState({ hovered: true })} 104 | onMouseLeave={() => this.setState({ hovered: false })} 105 | style={style} 106 | className={classes.join(" ")} 107 | > 108 | 112 | {guild.banner && ( 113 | 123 | 127 | 131 | 132 | } 133 | /> 134 | )} 135 | 136 |
137 | ); 138 | } 139 | }; 140 | -------------------------------------------------------------------------------- /components/GuildProfileModal.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 - 2021 NurMarvin (Marvin Witt) 3 | * Licensed under the Open Software License version 3.0 4 | */ 5 | 6 | const { 7 | React, 8 | i18n: { Messages }, 9 | Flux, 10 | getModule, 11 | } = require('powercord/webpack'); 12 | 13 | const { ModalRoot } = getModule(['ModalRoot'], false); 14 | 15 | const GuildProfileHeader = require('./GuildProfileHeader'); 16 | const GuildRelationships = require('./GuildRelationships'); 17 | const GuildInfoBase = require('./GuildInfoBase'); 18 | 19 | const { TabBar } = require('powercord/components'); 20 | 21 | const GuildProfileSections = { 22 | GUILD_INFO: 'GUILD_INFO', 23 | FRIENDS: 'FRIENDS', 24 | BLOCKED_USERS: 'BLOCKED_USERS', 25 | }; 26 | 27 | class GuildProfileTabBar extends React.PureComponent { 28 | constructor(props) { 29 | super(props); 30 | 31 | this.modules = { 32 | ...getModule(['topSection'], false), 33 | }; 34 | } 35 | render() { 36 | const { setSection, section } = this.props; 37 | 38 | return ( 39 |
40 | 46 | 50 | {Messages.GUILD_INFO} 51 | 52 | 56 | {Messages.FRIENDS_IN_GUILD} 57 | 58 | 62 | {Messages.BLOCKED_USERS_IN_GUILD} 63 | 64 | 65 |
66 | ); 67 | } 68 | } 69 | 70 | class GuildProfileModal extends React.PureComponent { 71 | constructor(props) { 72 | super(props); 73 | 74 | this.modules = { 75 | ...getModule(['topSection'], false), 76 | }; 77 | 78 | this.state = { 79 | section: GuildProfileSections.GUILD_INFO, 80 | }; 81 | } 82 | 83 | async componentDidMount() { 84 | const { guild, getMemberCounts } = this.props; 85 | const memberData = await getMemberCounts(guild.id); 86 | this.setState({ counts: memberData }); 87 | } 88 | 89 | render() { 90 | const { guild } = this.props; 91 | const { counts } = this.state; 92 | 93 | let section; 94 | 95 | switch (this.state.section) { 96 | case GuildProfileSections.GUILD_INFO: 97 | section = ; 98 | break; 99 | case GuildProfileSections.FRIENDS: 100 | section = ( 101 | 105 | ); 106 | break; 107 | case GuildProfileSections.BLOCKED_USERS: 108 | section = ( 109 | 113 | ); 114 | break; 115 | } 116 | 117 | return ( 118 | 119 |
120 | 121 | this.setState({ section })} 123 | section={this.state.section} 124 | guild={guild} 125 | /> 126 |
127 |
{section}
128 |
129 | ); 130 | } 131 | } 132 | 133 | module.exports = Flux.connectStoresAsync( 134 | [ 135 | getModule(['getRelationships']), 136 | getModule(['getCurrentUser', 'getUser']), 137 | getModule(['isMember']), 138 | ], 139 | ([relationshipsStore, userStore, membersStore], compProps) => { 140 | // Its safe to assume if the module aboves were found that this one is also loaded 141 | const userFetcher = getModule(['getUser'], false); 142 | const relationships = relationshipsStore.getRelationships(); 143 | const props = { 144 | friends: [], 145 | blocked: [], 146 | }; 147 | 148 | for (const userId in relationships) { 149 | if (!membersStore.isMember(compProps.guild.id, userId)) { 150 | continue; 151 | } 152 | 153 | const relationshipType = relationships[userId]; 154 | const user = userStore.getUser(userId); 155 | if (!user) { 156 | userFetcher.getUser(userId); 157 | continue; 158 | } 159 | 160 | if (relationshipType === 1) { 161 | props.friends.push(user); 162 | } else if (relationshipType === 2) { 163 | props.blocked.push(user); 164 | } 165 | } 166 | return props; 167 | } 168 | )(GuildProfileModal); 169 | -------------------------------------------------------------------------------- /components/GuildInfoBase.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 - 2021 NurMarvin (Marvin Witt) 3 | * Licensed under the Open Software License version 3.0 4 | */ 5 | 6 | const { 7 | React, 8 | i18n: { Messages }, 9 | getModule, 10 | getModuleByDisplayName, 11 | } = require('powercord/webpack'); 12 | 13 | const { Flex, Text } = require('powercord/components'); 14 | const AsyncComponent = require('powercord/components/AsyncComponent'); 15 | 16 | const FormSection = AsyncComponent.from(getModuleByDisplayName('FormSection')); 17 | const Anchor = AsyncComponent.from(getModuleByDisplayName('Anchor')); 18 | const Mention = AsyncComponent.from(getModuleByDisplayName('Mention')); 19 | 20 | const { AdvancedScrollerThin } = getModule(['AdvancedScrollerThin'], false); 21 | 22 | const ContextMenu = getModule(['closeContextMenu'], false); 23 | const { close } = require('powercord/modal'); 24 | 25 | const UserProfileModalActionCreators = getModule( 26 | ['openUserProfileModal'], 27 | false 28 | ); 29 | 30 | const GuildExplicitContentFilterTypes = [ 31 | 'EXPLICIT_CONTENT_FILTER_DISABLED', 32 | 'EXPLICIT_CONTENT_FILTER_MEDIUM', 33 | 'EXPLICIT_CONTENT_FILTER_HIGH', 34 | ]; 35 | 36 | const GuildVerificationLevels = [ 37 | 'VERIFICATION_LEVEL_NONE', 38 | 'VERIFICATION_LEVEL_LOW', 39 | 'VERIFICATION_LEVEL_MEDIUM', 40 | 'VERIFICATION_LEVEL_HIGH', 41 | 'VERIFICATION_LEVEL_VERY_HIGH', 42 | ]; 43 | 44 | const NsfwLevels = [ 45 | 'NSFW_LEVEL_DEFAULT', 46 | 'NSFW_LEVEL_EXPLICIT', 47 | 'NSFW_LEVEL_SAFE', 48 | 'NSFW_LEVEL_AGE_RESTRICTED', 49 | ]; 50 | 51 | class Section extends React.PureComponent { 52 | constructor(props) { 53 | super(props); 54 | 55 | this.modules = { 56 | marginBottom8: getModule(['marginBottom8'], false).marginBottom8, 57 | }; 58 | } 59 | 60 | render() { 61 | const { children, title } = this.props; 62 | 63 | if (!children) { 64 | return null; 65 | } 66 | 67 | return ( 68 | 73 | {children} 74 | 75 | ); 76 | } 77 | } 78 | 79 | module.exports = class GuildInfoBase extends React.PureComponent { 80 | constructor(props) { 81 | super(props); 82 | 83 | this.modules = { 84 | ...getModule(['topSection'], false), 85 | ...getModule(['infoScroller'], false), 86 | ...getModule(['emptyIconFriends', 'empty'], false), 87 | }; 88 | 89 | this.state = {}; 90 | } 91 | 92 | async componentDidMount() { 93 | const { getUser } = getModule(['getUser'], false); 94 | const { ownerId } = this.props.guild; 95 | 96 | const { getSerializedState } = await getModule(['getSerializedState']); 97 | const { getRegisteredExperiments } = await getModule([ 98 | 'getRegisteredExperiments', 99 | ]); 100 | const { v3: murmurHash } = await getModule(['v3']); 101 | 102 | const { loadedGuildExperiments } = getSerializedState(); 103 | const registeredExperiments = getRegisteredExperiments(); 104 | 105 | const object = {}; 106 | 107 | Object.keys(registeredExperiments).forEach( 108 | (experiment) => (object[murmurHash(experiment)] = experiment) 109 | ); 110 | Object.entries(loadedGuildExperiments).forEach( 111 | ([key, value]) => 112 | (loadedGuildExperiments[object[key]] = { ...value, hashKey: key }) 113 | ); 114 | 115 | const enabledExperiments = Object.keys(loadedGuildExperiments).filter( 116 | (k) => loadedGuildExperiments[k].hashKey != null && k != 'undefined' 117 | ); 118 | const enabledGuildExperiments = {}; 119 | const y = {}; 120 | Object.keys(object).forEach((key) => { 121 | y[object[key]] = key; 122 | }); 123 | enabledExperiments.forEach((k) => { 124 | enabledGuildExperiments[k] = loadedGuildExperiments[y[k]]; 125 | }); 126 | 127 | const { id } = this.props.guild; 128 | const experimentsEnabledForGuild = []; 129 | 130 | enabledExperiments.forEach((k) => { 131 | const d = enabledGuildExperiments[k]; 132 | if (d.overrides[id] != undefined) { 133 | experimentsEnabledForGuild.push(k); 134 | } 135 | }); 136 | 137 | this.setState({ 138 | owner: await getUser(ownerId), 139 | experiments: experimentsEnabledForGuild, 140 | }); 141 | } 142 | 143 | handleContextMenu(event) { 144 | ContextMenu.openContextMenu(event, () => {}); 145 | } 146 | 147 | render() { 148 | const moment = getModule(['momentProperties'], false); 149 | const { extractTimestamp } = getModule(['extractTimestamp'], false); 150 | const { guild } = this.props; 151 | const { 152 | vanityURLCode, 153 | description, 154 | verificationLevel, 155 | explicitContentFilter, 156 | nsfwLevel, 157 | } = guild; 158 | const { owner, experiments } = this.state; 159 | 160 | const streamerMode = getModule( 161 | ['hidePersonalInformation'], 162 | false 163 | ).hidePersonalInformation; 164 | 165 | if (streamerMode) { 166 | return ( 167 |
168 |
169 |
170 | {Messages.STREAMER_MODE_ENABLED} 171 |
172 |
173 | ); 174 | } 175 | 176 | return ( 177 | 181 | 182 |
183 | {owner ? ( 184 | this.handleContextMenu(e)} 187 | onClick={() => { 188 | close(); 189 | UserProfileModalActionCreators.openUserProfileModal({ 190 | userId: owner.id, 191 | }); 192 | }} 193 | > 194 | @{owner.username}#{owner.discriminator} 195 | 196 | ) : ( 197 | `${Messages.LOADING}...` 198 | )} 199 |
200 |
201 | {description} 202 |
203 | {vanityURLCode && ( 204 |
205 | 206 | discord.gg/{vanityURLCode} 207 | 208 |
209 | )} 210 |
211 | {moment(extractTimestamp(guild.id)).format('LLL')} 212 |
213 |
214 | {moment(guild.joinedAt).format('LLL')} 215 |
216 |
217 | {Messages[GuildVerificationLevels[verificationLevel]]} 218 |
219 |
220 | {Messages[GuildExplicitContentFilterTypes[explicitContentFilter]]} 221 |
222 |
223 | {guild.premiumSubscriberCount} 224 |
225 |
226 | {guild.premiumTier} 227 |
228 |
229 | {guild.preferredLocale} 230 |
231 |
232 | {Messages[NsfwLevels[nsfwLevel]]} 233 |
234 |
235 | {experiments && experiments.length > 0 236 | ? experiments.join(', ') 237 | : Messages.NONE} 238 |
239 |
240 |
241 | ); 242 | } 243 | }; 244 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 - 2021 NurMarvin (Marvin Witt) 3 | * Licensed under the Open Software License version 3.0 4 | */ 5 | 6 | const { Plugin } = require("powercord/entities"); 7 | const { inject, uninject } = require("powercord/injector"); 8 | const { 9 | React, 10 | getModule, 11 | getModuleByDisplayName, 12 | FluxDispatcher, 13 | subscribe, 14 | i18n: { Messages }, 15 | } = require("powercord/webpack"); 16 | const { open } = require("powercord/modal"); 17 | const { findInReactTree } = require("powercord/util"); 18 | const i18n = require("./i18n"); 19 | 20 | const GuildProfileModal = require("./components/GuildProfileModal"); 21 | const GuildProfileIcon = require("./components/GuildProfileIcon"); 22 | 23 | const memberCountsStore = require("./memberCountsStore/store"); 24 | const memberCountsActions = require("./memberCountsStore/actions"); 25 | 26 | const { getCurrentUser } = getModule(["getCurrentUser"], false); 27 | 28 | module.exports = class GuildProfile extends Plugin { 29 | async startPlugin() { 30 | powercord.api.i18n.loadAllStrings(i18n); 31 | this.loadStylesheet("styles.scss"); 32 | this._injectContextMenu(); 33 | this._injectMenu(); 34 | 35 | this.handleMemberListUpdate = this.handleMemberListUpdate.bind(this); 36 | 37 | FluxDispatcher.subscribe( 38 | "GUILD_MEMBER_LIST_UPDATE", 39 | this.handleMemberListUpdate 40 | ); 41 | } 42 | 43 | handleMemberListUpdate(memberListUpdate) { 44 | this.updateMemberCounts(memberListUpdate); 45 | } 46 | 47 | getMemberCounts(id) { 48 | return new Promise((resolve) => { 49 | const memberCounts = memberCountsStore.getMemberCounts(id); 50 | 51 | // If the member count is in the Flux store just send that data 52 | if (memberCounts) { 53 | resolve(memberCounts); 54 | return; 55 | } 56 | 57 | const { requestMembers } = getModule(["requestMembers"], false); 58 | requestMembers(id); 59 | 60 | const updateMemberCounts = (memberListUpdate) => { 61 | return this.updateMemberCounts(memberListUpdate); 62 | }; 63 | 64 | function onReceived(memberListUpdate) { 65 | if (memberListUpdate.guildId === id) { 66 | resolve(updateMemberCounts(memberListUpdate)); 67 | } 68 | } 69 | 70 | FluxDispatcher.subscribe("GUILD_MEMBER_LIST_UPDATE", onReceived); 71 | }); 72 | } 73 | 74 | updateMemberCounts(memberListUpdate) { 75 | const { guildId, memberCount, groups } = memberListUpdate; 76 | const onlineCount = groups 77 | .map((group) => (group.id != "offline" ? group.count : 0)) 78 | .reduce((a, b) => { 79 | return a + b; 80 | }, 0); 81 | const memberCounts = { guildId, memberCount, onlineCount }; 82 | 83 | memberCountsActions.updateMemberCounts(memberCounts); 84 | return memberCounts; 85 | } 86 | 87 | async _injectContextMenu() { 88 | const Menu = await getModule(["MenuItem"]); 89 | const getMemberCounts = (guildId) => { 90 | return this.getMemberCounts(guildId); 91 | }; 92 | 93 | subscribe(x => x.default?.displayName === "GuildContextMenuWrapper", GuildContextMenuWrapper => { 94 | inject("guild-profile-context-menu", GuildContextMenuWrapper, "default", ([{ guild }], res) => { 95 | const renderContextMenu = res.props.children.type; 96 | res.props.children.type = (props) => { 97 | const contextMenu = renderContextMenu(props); 98 | contextMenu.props.children.unshift( 99 | React.createElement( 100 | Menu.MenuGroup, 101 | null, 102 | React.createElement(Menu.MenuItem, { 103 | id: "guild-profile", 104 | label: Messages.GUILD_PROFILE, 105 | action: () => 106 | this._openModalHandler(() => 107 | React.createElement(GuildProfileModal, { 108 | guild, 109 | section: "GUILD_INFO", 110 | getMemberCounts, 111 | }) 112 | ), 113 | }) 114 | ) 115 | ); 116 | return contextMenu; 117 | }; 118 | return res; 119 | }) 120 | }); 121 | } 122 | 123 | async _injectMenu() { 124 | const id = "guild-profile"; 125 | const Menu = await getModule(["MenuItem"]); 126 | const { getGuild } = await getModule(["getGuild"]); 127 | const { getGuildId } = await getModule(["getLastSelectedGuildId"]); 128 | 129 | const getMemberCounts = (guildId) => { 130 | return this.getMemberCounts(guildId); 131 | }; 132 | 133 | inject("guild-profile-menu", Menu, "default", ([{ children }], res) => { 134 | const menuId = res.props.children.props.id; 135 | 136 | if (menuId !== "guild-header-popout") 137 | return res; 138 | 139 | if (!findInReactTree(res, (c) => c.props && c.props.id == id)) { 140 | children.unshift( 141 | React.createElement( 142 | Menu.MenuGroup, 143 | null, 144 | React.createElement(Menu.MenuItem, { 145 | id, 146 | label: Messages.GUILD_PROFILE, 147 | icon: () => React.createElement(GuildProfileIcon), 148 | action: () => 149 | this._openModalHandler(() => 150 | React.createElement(GuildProfileModal, { 151 | guild: getGuild(getGuildId()), 152 | section: "GUILD_INFO", 153 | getMemberCounts, 154 | }) 155 | ), 156 | }) 157 | ) 158 | ); 159 | } 160 | return res; 161 | }); 162 | 163 | Menu.default.displayName = "Menu"; 164 | } 165 | 166 | _openModalHandler(element) { 167 | const { openUserProfileModal } = getModule(["openUserProfileModal"], false); 168 | const module = getModule(["openModalLazy"], false); 169 | 170 | if (getModuleByDisplayName("UserProfileModal", false) !== null) { 171 | open(element); 172 | return; 173 | } 174 | 175 | // So, the modules that are needed to render our modal are in another chunk, which is still not loaded. 176 | // We call a function that loads modules for UserProfileModal, 177 | // but the user will not see the final UserProfileModal - 178 | // since it will be replaced at the last stage with our element 179 | 180 | const { ModalRoot } = getModule(["ModalRoot"], false); 181 | const userId = getCurrentUser().id; 182 | 183 | inject( 184 | "guild-profile-open-modal-lazy", 185 | module, 186 | "openModalLazy", 187 | ([initLazyRender]) => { 188 | const warpInitLazyRender = () => { 189 | const lazyRender = initLazyRender(); 190 | 191 | return new Promise(async (resolve) => { 192 | const render = await lazyRender; 193 | 194 | resolve((event) => { 195 | const res = render(event); 196 | 197 | if (res?.type?.displayName === "UserProfileModal") { 198 | const { props } = res; 199 | const { root } = getModule(["root", "body"], false); 200 | 201 | if (props.guildId === "@me" && props.user.id === userId) { 202 | if (props.transitionState === 3) { 203 | // = close modal 204 | uninject("guild-profile-open-modal-lazy"); 205 | } 206 | 207 | return React.createElement(ModalRoot, { 208 | transitionState: props.transitionState, 209 | className: root, 210 | children: element(), 211 | }); 212 | } 213 | } 214 | return res; 215 | }); 216 | }); 217 | }; 218 | 219 | return [warpInitLazyRender]; 220 | }, 221 | true 222 | ); 223 | 224 | openUserProfileModal({ userId }); 225 | } 226 | 227 | pluginWillUnload() { 228 | uninject("guild-profile-context-menu"); 229 | uninject("guild-profile-menu"); 230 | uninject("guild-profile-open-modal-lazy"); 231 | FluxDispatcher.unsubscribe( 232 | "GUILD_MEMBER_LIST_UPDATE", 233 | this.handleMemberListUpdate 234 | ); 235 | } 236 | }; 237 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Open Software License ("OSL") v. 3.0 2 | 3 | This Open Software License (the "License") applies to any original work of 4 | authorship (the "Original Work") whose owner (the "Licensor") has placed the 5 | following licensing notice adjacent to the copyright notice for the Original 6 | Work: 7 | 8 | Licensed under the Open Software License version 3.0 9 | 10 | 1) Grant of Copyright License. Licensor grants You a worldwide, royalty-free, 11 | non-exclusive, sublicensable license, for the duration of the copyright, to do 12 | the following: 13 | 14 | a) to reproduce the Original Work in copies, either alone or as part of a 15 | collective work; 16 | 17 | b) to translate, adapt, alter, transform, modify, or arrange the Original 18 | Work, thereby creating derivative works ("Derivative Works") based upon the 19 | Original Work; 20 | 21 | c) to distribute or communicate copies of the Original Work and Derivative 22 | Works to the public, with the proviso that copies of Original Work or 23 | Derivative Works that You distribute or communicate shall be licensed under 24 | this Open Software License; 25 | 26 | d) to perform the Original Work publicly; and 27 | 28 | e) to display the Original Work publicly. 29 | 30 | 2) Grant of Patent License. Licensor grants You a worldwide, royalty-free, 31 | non-exclusive, sublicensable license, under patent claims owned or controlled 32 | by the Licensor that are embodied in the Original Work as furnished by the 33 | Licensor, for the duration of the patents, to make, use, sell, offer for sale, 34 | have made, and import the Original Work and Derivative Works. 35 | 36 | 3) Grant of Source Code License. The term "Source Code" means the preferred 37 | form of the Original Work for making modifications to it and all available 38 | documentation describing how to modify the Original Work. Licensor agrees to 39 | provide a machine-readable copy of the Source Code of the Original Work along 40 | with each copy of the Original Work that Licensor distributes. Licensor 41 | reserves the right to satisfy this obligation by placing a machine-readable 42 | copy of the Source Code in an information repository reasonably calculated to 43 | permit inexpensive and convenient access by You for as long as Licensor 44 | continues to distribute the Original Work. 45 | 46 | 4) Exclusions From License Grant. Neither the names of Licensor, nor the names 47 | of any contributors to the Original Work, nor any of their trademarks or 48 | service marks, may be used to endorse or promote products derived from this 49 | Original Work without express prior permission of the Licensor. Except as 50 | expressly stated herein, nothing in this License grants any license to 51 | Licensor's trademarks, copyrights, patents, trade secrets or any other 52 | intellectual property. No patent license is granted to make, use, sell, offer 53 | for sale, have made, or import embodiments of any patent claims other than the 54 | licensed claims defined in Section 2. No license is granted to the trademarks 55 | of Licensor even if such marks are included in the Original Work. Nothing in 56 | this License shall be interpreted to prohibit Licensor from licensing under 57 | terms different from this License any Original Work that Licensor otherwise 58 | would have a right to license. 59 | 60 | 5) External Deployment. The term "External Deployment" means the use, 61 | distribution, or communication of the Original Work or Derivative Works in any 62 | way such that the Original Work or Derivative Works may be used by anyone 63 | other than You, whether those works are distributed or communicated to those 64 | persons or made available as an application intended for use over a network. 65 | As an express condition for the grants of license hereunder, You must treat 66 | any External Deployment by You of the Original Work or a Derivative Work as a 67 | distribution under section 1(c). 68 | 69 | 6) Attribution Rights. You must retain, in the Source Code of any Derivative 70 | Works that You create, all copyright, patent, or trademark notices from the 71 | Source Code of the Original Work, as well as any notices of licensing and any 72 | descriptive text identified therein as an "Attribution Notice." You must cause 73 | the Source Code for any Derivative Works that You create to carry a prominent 74 | Attribution Notice reasonably calculated to inform recipients that You have 75 | modified the Original Work. 76 | 77 | 7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that 78 | the copyright in and to the Original Work and the patent rights granted herein 79 | by Licensor are owned by the Licensor or are sublicensed to You under the 80 | terms of this License with the permission of the contributor(s) of those 81 | copyrights and patent rights. Except as expressly stated in the immediately 82 | preceding sentence, the Original Work is provided under this License on an "AS 83 | IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without 84 | limitation, the warranties of non-infringement, merchantability or fitness for 85 | a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK 86 | IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this 87 | License. No license to the Original Work is granted by this License except 88 | under this disclaimer. 89 | 90 | 8) Limitation of Liability. Under no circumstances and under no legal theory, 91 | whether in tort (including negligence), contract, or otherwise, shall the 92 | Licensor be liable to anyone for any indirect, special, incidental, or 93 | consequential damages of any character arising as a result of this License or 94 | the use of the Original Work including, without limitation, damages for loss 95 | of goodwill, work stoppage, computer failure or malfunction, or any and all 96 | other commercial damages or losses. This limitation of liability shall not 97 | apply to the extent applicable law prohibits such limitation. 98 | 99 | 9) Acceptance and Termination. If, at any time, You expressly assented to this 100 | License, that assent indicates your clear and irrevocable acceptance of this 101 | License and all of its terms and conditions. If You distribute or communicate 102 | copies of the Original Work or a Derivative Work, You must make a reasonable 103 | effort under the circumstances to obtain the express assent of recipients to 104 | the terms of this License. This License conditions your rights to undertake 105 | the activities listed in Section 1, including your right to create Derivative 106 | Works based upon the Original Work, and doing so without honoring these terms 107 | and conditions is prohibited by copyright law and international treaty. 108 | Nothing in this License is intended to affect copyright exceptions and 109 | limitations (including "fair use" or "fair dealing"). This License shall 110 | terminate immediately and You may no longer exercise any of the rights granted 111 | to You by this License upon your failure to honor the conditions in Section 112 | 1(c). 113 | 114 | 10) Termination for Patent Action. This License shall terminate automatically 115 | and You may no longer exercise any of the rights granted to You by this 116 | License as of the date You commence an action, including a cross-claim or 117 | counterclaim, against Licensor or any licensee alleging that the Original Work 118 | infringes a patent. This termination provision shall not apply for an action 119 | alleging patent infringement by combinations of the Original Work with other 120 | software or hardware. 121 | 122 | 11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this 123 | License may be brought only in the courts of a jurisdiction wherein the 124 | Licensor resides or in which Licensor conducts its primary business, and under 125 | the laws of that jurisdiction excluding its conflict-of-law provisions. The 126 | application of the United Nations Convention on Contracts for the 127 | International Sale of Goods is expressly excluded. Any use of the Original 128 | Work outside the scope of this License or after its termination shall be 129 | subject to the requirements and penalties of copyright or patent law in the 130 | appropriate jurisdiction. This section shall survive the termination of this 131 | License. 132 | 133 | 12) Attorneys' Fees. In any action to enforce the terms of this License or 134 | seeking damages relating thereto, the prevailing party shall be entitled to 135 | recover its costs and expenses, including, without limitation, reasonable 136 | attorneys' fees and costs incurred in connection with such action, including 137 | any appeal of such action. This section shall survive the termination of this 138 | License. 139 | 140 | 13) Miscellaneous. If any provision of this License is held to be 141 | unenforceable, such provision shall be reformed only to the extent necessary 142 | to make it enforceable. 143 | 144 | 14) Definition of "You" in This License. "You" throughout this License, 145 | whether in upper or lower case, means an individual or a legal entity 146 | exercising rights under, and complying with all of the terms of, this License. 147 | For legal entities, "You" includes any entity that controls, is controlled by, 148 | or is under common control with you. For purposes of this definition, 149 | "control" means (i) the power, direct or indirect, to cause the direction or 150 | management of such entity, whether by contract or otherwise, or (ii) ownership 151 | of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial 152 | ownership of such entity. 153 | 154 | 15) Right to Use. You may use the Original Work in all ways not otherwise 155 | restricted or conditioned by this License or by law, and Licensor promises not 156 | to interfere with or be responsible for such uses by You. 157 | 158 | 16) Modification of This License. This License is Copyright © 2005 Lawrence 159 | Rosen. Permission is granted to copy, distribute, or communicate this License 160 | without modification. Nothing in this License permits You to modify this 161 | License as applied to the Original Work or to Derivative Works. However, You 162 | may modify the text of this License and copy, distribute or communicate your 163 | modified version (the "Modified License") and apply it to other original works 164 | of authorship subject to the following conditions: (i) You may not indicate in 165 | any way that your Modified License is the "Open Software License" or "OSL" and 166 | you may not use those names in the name of your Modified License; (ii) You 167 | must replace the notice specified in the first paragraph above with the notice 168 | "Licensed under " or with a notice of your own 169 | that is not confusingly similar to the notice in this License; and (iii) You 170 | may not claim that your original works are open source software unless your 171 | Modified License has been approved by Open Source Initiative (OSI) and You 172 | comply with its license review and certification process. 173 | -------------------------------------------------------------------------------- /components/Icons.jsx: -------------------------------------------------------------------------------- 1 | const { React } = require("powercord/webpack"); 2 | 3 | module.exports = { 4 | AUTO_MODERATION: React.memo((props) => ( 5 | 6 | 10 | 11 | )), 12 | ANIMATED_BANNER: React.memo((props) => ( 13 | 14 | 18 | 24 | 25 | )), 26 | ANIMATED_ICON: React.memo((props) => ( 27 | 28 | 34 | 35 | )), 36 | BANNER: React.memo((props) => ( 37 | 38 | 42 | 43 | )), 44 | BOT_DEVELOPER_EARLY_ACCESS: React.memo((props) => ( 45 | 46 | 50 | 51 | )), 52 | COMMERCE: React.memo((props) => ( 53 | 54 | 58 | 59 | )), 60 | COMMUNITY: React.memo((props) => ( 61 | 62 | 66 | 67 | )), 68 | CREATOR_MONETIZABLE: React.memo((props) => ( 69 | 70 | 71 | 75 | 76 | )), 77 | DISCOVERABLE: React.memo((props) => ( 78 | 84 | 90 | 91 | )), 92 | ENABLED_DISCOVERABLE_BEFORE: React.memo((props) => ( 93 | 94 | 98 | 102 | 103 | )), 104 | EXPOSED_TO_ACTIVITIES_WTP_EXPERIMENT: React.memo((props) => ( 105 | 106 | 112 | 116 | 120 | 124 | 128 | 129 | )), 130 | FEATURABLE: React.memo((props) => ( 131 | 132 | 136 | 137 | )), 138 | GUILD_HOME_TEST: React.memo((props) => ( 139 | 140 | 144 | 145 | )), 146 | HAD_EARLY_ACTIVITIES_ACCESS: React.memo((props) => ( 147 | 148 | 154 | 158 | 162 | 166 | 167 | )), 168 | HAS_DIRECTORY_ENTRY: React.memo((props) => ( 169 | 170 | 174 | 175 | )), 176 | INVITES_DISABLED: React.memo((props) => ( 177 | 178 | 179 | 183 | 187 | 188 | 189 | 197 | 198 | 199 | )), 200 | INVITE_SPLASH: React.memo((props) => ( 201 | 202 | 206 | 210 | 211 | )), 212 | INTERNAL_EMPLOYEE_ONLY: React.memo((props) => ( 213 | 214 | 218 | 222 | 226 | 227 | )), 228 | MEMBER_PROFILES: React.memo((props) => ( 229 | 230 | 234 | 235 | )), 236 | MEMBER_VERIFICATION_GATE_ENABLED: React.memo((props) => ( 237 | 238 | 242 | 243 | )), 244 | NEWS: React.memo((props) => ( 245 | 246 | 250 | 251 | )), 252 | NEW_THREAD_PERMISSIONS: React.memo((props) => ( 253 | 254 | 258 | 262 | 263 | )), 264 | PREMIUM_TIER_3_OVERRIDE: React.memo((props) => ( 265 | 266 | 270 | 274 | 275 | )), 276 | PREVIEW_ENABLED: React.memo((props) => ( 277 | 278 | 282 | 286 | 287 | )), 288 | PRIVATE_THREADS: React.memo((props) => ( 289 | 290 | 296 | 300 | 304 | 305 | )), 306 | RELAY_ENABLED: React.memo((props) => ( 307 | 308 | 312 | 313 | )), 314 | ROLE_ICONS: React.memo((props) => ( 315 | 316 | 320 | 324 | 325 | )), 326 | ROLE_SUBSCRIPTIONS_ENABLED: React.memo((props) => ( 327 | 328 | 332 | 333 | 337 | 338 | )), 339 | ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE: React.memo((props) => ( 340 | 341 | 345 | 346 | )), 347 | SEVEN_DAY_THREAD_ARCHIVE: React.memo((props) => ( 348 | 349 | 353 | 357 | 361 | 362 | )), 363 | TEXT_IN_VOICE_ENABLED: React.memo((props) => ( 364 | 365 | 371 | 375 | 376 | )), 377 | THREADS_ENABLED: React.memo((props) => ( 378 | 379 | 383 | 387 | 388 | )), 389 | THREADS_ENABLED_TESTING: React.memo((props) => ( 390 | 391 | 395 | 399 | 400 | )), 401 | THREE_DAY_THREAD_ARCHIVE: React.memo((props) => ( 402 | 403 | 407 | 411 | 415 | 416 | )), 417 | VANITY_URL: React.memo((props) => ( 418 | 419 | 420 | 421 | 425 | 429 | 430 | 431 | )), 432 | VIP_REGIONS: React.memo((props) => ( 433 | 434 | 440 | 444 | 445 | )), 446 | WELCOME_SCREEN_ENABLED: React.memo((props) => ( 447 | 448 | 452 | 453 | )), 454 | 455 | // Placeholder for unknown features 456 | UNKNOWN: React.memo((props) => ( 457 | 458 | 462 | 463 | )), 464 | }; 465 | --------------------------------------------------------------------------------