🆙 Added google ADS

This commit is contained in:
DuckieTM
2026-04-20 21:54:17 +02:00
parent 675b864c51
commit 80033667b8
7 changed files with 194 additions and 5 deletions
+1
View File
@@ -0,0 +1 @@
google.com, ## YOUR pub-XXXXXXXXX, DIRECT, XXXXXXX
+5
View File
@@ -0,0 +1,5 @@
{
"slot": "### SLOT ID FROM GOOGLE - data-ad-slot ###",
"format": "auto",
"fullWidthResponsive": true
}
+1
View File
@@ -27,6 +27,7 @@
"guides.enabled": true, "guides.enabled": true,
"toolbar.hide.quests": true, "toolbar.hide.quests": true,
"catalog.style.new": true, "catalog.style.new": true,
"show.google.ads": false,
"loginview": { "loginview": {
"images": { "images": {
"background": "${asset.url}/c_images/reception/stretch_blue.png", "background": "${asset.url}/c_images/reception/stretch_blue.png",
+2
View File
@@ -24,6 +24,7 @@ import { NavigatorView } from './navigator/NavigatorView';
import { NitrobubbleHiddenView } from './nitrobubblehidden/NitrobubbleHiddenView'; import { NitrobubbleHiddenView } from './nitrobubblehidden/NitrobubbleHiddenView';
import { NitropediaView } from './nitropedia/NitropediaView'; import { NitropediaView } from './nitropedia/NitropediaView';
import { ExternalPluginLoader } from './plugins/ExternalPluginLoader'; import { ExternalPluginLoader } from './plugins/ExternalPluginLoader';
import { GoogleAdsView } from './ads/GoogleAdsView';
import { RightSideView } from './right-side/RightSideView'; import { RightSideView } from './right-side/RightSideView';
import { RoomView } from './room/RoomView'; import { RoomView } from './room/RoomView';
import { ToolbarView } from './toolbar/ToolbarView'; import { ToolbarView } from './toolbar/ToolbarView';
@@ -97,6 +98,7 @@ export const MainView: FC<{}> = props =>
</motion.div> } </motion.div> }
</AnimatePresence> </AnimatePresence>
<ToolbarView isInRoom={ !landingViewVisible } /> <ToolbarView isInRoom={ !landingViewVisible } />
<GoogleAdsView />
<ModToolsView /> <ModToolsView />
<WiredCreatorToolsView /> <WiredCreatorToolsView />
<RoomView /> <RoomView />
+164
View File
@@ -0,0 +1,164 @@
import { FC, useEffect, useRef, useState } from 'react';
import { GetConfigurationValue } from '../../api';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../common';
interface AdsenseConfig {
slot: string;
format?: string;
fullWidthResponsive?: boolean;
}
const ADSENSE_SCRIPT_ID = 'google-adsense-script';
const parsePublisherIdFromAdsTxt = (text: string): string | null => {
for (const rawLine of text.split(/\r?\n/)) {
const line = rawLine.split('#')[0].trim();
if (!line) continue;
const parts = line.split(',').map(part => part.trim());
if (parts.length < 2) continue;
if (parts[0].toLowerCase() !== 'google.com') continue;
const pub = parts[1];
if (/^pub-\d+$/.test(pub)) return pub;
}
return null;
};
const ensureAdsenseScript = (publisherId: string): void => {
if (typeof document === 'undefined') return;
if (document.getElementById(ADSENSE_SCRIPT_ID)) return;
const script = document.createElement('script');
script.id = ADSENSE_SCRIPT_ID;
script.async = true;
script.crossOrigin = 'anonymous';
script.src = `https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-${ publisherId }`;
document.head.appendChild(script);
};
export const GoogleAdsView: FC<{}> = () => {
const adsEnabled = GetConfigurationValue<boolean>('show.google.ads', false);
const [ isOpen, setIsOpen ] = useState(false);
const [ publisherId, setPublisherId ] = useState<string | null>(null);
const [ config, setConfig ] = useState<AdsenseConfig | null>(null);
const [ loadError, setLoadError ] = useState<string | null>(null);
const insRef = useRef<HTMLModElement>(null);
const pushedRef = useRef(false);
const autoOpenedRef = useRef(false);
useEffect(() => {
if (!adsEnabled) return;
const handler = () => setIsOpen(prev => !prev);
window.addEventListener('ads:toggle', handler);
return () => window.removeEventListener('ads:toggle', handler);
}, [ adsEnabled ]);
// Auto-open once on initial mount (the login / landing stage).
// Subsequent toggles are driven by the "ads:toggle" window event
// (e.g. the Show Ad button in NitroSystemAlertView).
useEffect(() => {
if (!adsEnabled) return;
if (autoOpenedRef.current) return;
autoOpenedRef.current = true;
const t = setTimeout(() => setIsOpen(true), 500);
return () => clearTimeout(t);
}, [ adsEnabled ]);
useEffect(() => {
let cancelled = false;
(async () => {
try {
const [ adsTxtRes, configRes ] = await Promise.all([
fetch('/ads.txt', { cache: 'no-cache' }),
fetch('/adsense.json', { cache: 'no-cache' })
]);
if (!adsTxtRes.ok) throw new Error(`ads.txt ${ adsTxtRes.status }`);
const adsTxt = await adsTxtRes.text();
const pubId = parsePublisherIdFromAdsTxt(adsTxt);
if (!pubId) throw new Error('No google.com publisher id in ads.txt');
let cfg: AdsenseConfig = { slot: '', format: 'auto', fullWidthResponsive: true };
if (configRes.ok) cfg = { ...cfg, ...(await configRes.json()) };
if (cancelled) return;
setPublisherId(pubId);
setConfig(cfg);
} catch (err) {
if (!cancelled) setLoadError((err as Error).message);
}
})();
return () => { cancelled = true; };
}, []);
useEffect(() => {
if (!isOpen || !publisherId || !config) return;
ensureAdsenseScript(publisherId);
}, [ isOpen, publisherId, config ]);
useEffect(() => {
if (!isOpen) {
pushedRef.current = false;
return;
}
if (!insRef.current || pushedRef.current) return;
if (!publisherId || !config?.slot) return;
const tryPush = () => {
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const w = window as any;
w.adsbygoogle = w.adsbygoogle || [];
w.adsbygoogle.push({});
pushedRef.current = true;
} catch {
// AdSense script may not be ready yet; retry once
setTimeout(() => {
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const w = window as any;
w.adsbygoogle = w.adsbygoogle || [];
w.adsbygoogle.push({});
pushedRef.current = true;
} catch { /* give up */ }
}, 500);
}
};
const t = setTimeout(tryPush, 50);
return () => clearTimeout(t);
}, [ isOpen, publisherId, config ]);
if (!adsEnabled) return null;
if (!isOpen) return null;
return (
<NitroCardView className="nitro-google-ads" uniqueKey="google-ads" theme="primary">
<NitroCardHeaderView headerText="Sponsored" onCloseClick={ () => setIsOpen(false) } />
<NitroCardContentView>
<div className="flex items-center justify-center w-[300px] h-[250px] bg-white">
{ loadError &&
<div className="text-xs text-red-600 text-center px-2">Ads unavailable: { loadError }</div> }
{ !loadError && (!publisherId || !config) &&
<div className="text-xs text-gray-500">Loading</div> }
{ !loadError && publisherId && config?.slot &&
<ins
ref={ insRef }
key={ `${ publisherId }-${ config.slot }` }
className="adsbygoogle"
style={ { display: 'block', width: '100%', height: '100%' } }
data-ad-client={ `ca-${ publisherId }` }
data-ad-slot={ config.slot }
data-ad-format={ config.format ?? 'auto' }
data-full-width-responsive={ (config.fullWidthResponsive ?? true) ? 'true' : 'false' }
/> }
{ !loadError && publisherId && config && !config.slot &&
<div className="text-xs text-gray-500 text-center px-2">Ad slot not configured in adsense.json</div> }
</div>
</NitroCardContentView>
</NitroCardView>
);
};
@@ -1,5 +1,5 @@
import { FC } from 'react'; import { FC } from 'react';
import { GetRendererVersion, GetUIVersion, NotificationAlertItem } from '../../../../api'; import { GetConfigurationValue, GetRendererVersion, GetUIVersion, NotificationAlertItem } from '../../../../api';
import { Button, Column, Grid, LayoutNotificationAlertView, LayoutNotificationAlertViewProps, Text } from '../../../../common'; import { Button, Column, Grid, LayoutNotificationAlertView, LayoutNotificationAlertViewProps, Text } from '../../../../common';
interface NotificationDefaultAlertViewProps extends LayoutNotificationAlertViewProps interface NotificationDefaultAlertViewProps extends LayoutNotificationAlertViewProps
@@ -9,10 +9,11 @@ interface NotificationDefaultAlertViewProps extends LayoutNotificationAlertViewP
export const NitroSystemAlertView: FC<NotificationDefaultAlertViewProps> = props => export const NitroSystemAlertView: FC<NotificationDefaultAlertViewProps> = props =>
{ {
const { title = 'Nitro', onClose = null, ...rest } = props; const { title = 'Nitro', onClose = null, classNames = [], ...rest } = props;
const adsEnabled = GetConfigurationValue<boolean>('show.google.ads', false);
return ( return (
<LayoutNotificationAlertView title={ title } onClose={ onClose } { ...rest }> <LayoutNotificationAlertView title={ title } onClose={ onClose } classNames={ [ 'nitro-alert-system', ...classNames ] } { ...rest }>
<Grid> <Grid>
<Column size={ 12 }> <Column size={ 12 }>
<Column alignItems="center" gap={ 0 }> <Column alignItems="center" gap={ 0 }>
@@ -23,6 +24,8 @@ export const NitroSystemAlertView: FC<NotificationDefaultAlertViewProps> = props
<Text><b>Renderer:</b> v{ GetRendererVersion() }</Text> <Text><b>Renderer:</b> v{ GetRendererVersion() }</Text>
<Column fullWidth gap={ 1 }> <Column fullWidth gap={ 1 }>
<Button fullWidth variant="success" onClick={ event => window.open('https://discord.nitrodev.co') }>Discord</Button> <Button fullWidth variant="success" onClick={ event => window.open('https://discord.nitrodev.co') }>Discord</Button>
{ adsEnabled &&
<Button fullWidth onClick={ () => window.dispatchEvent(new CustomEvent('ads:toggle')) }>Show Ad</Button> }
</Column> </Column>
</Column> </Column>
<div className="alertView_nitro-coolui-logo"></div> <div className="alertView_nitro-coolui-logo"></div>
@@ -35,7 +38,7 @@ export const NitroSystemAlertView: FC<NotificationDefaultAlertViewProps> = props
</Column> </Column>
</Column> </Column>
</Column> </Column>
</Grid> </Grid>
</LayoutNotificationAlertView> </LayoutNotificationAlertView>
); );
@@ -19,7 +19,7 @@
min-width: auto; min-width: auto;
} }
} }
&.nitro-alert-credits { &.nitro-alert-credits {
width: 370px; width: 370px;
.notification-text { .notification-text {
@@ -34,6 +34,19 @@
min-width: 225px; min-width: 225px;
} }
} }
&.nitro-alert-system {
width: auto;
min-width: 260px;
max-width: 90vw;
min-height: auto;
max-height: none;
height: auto;
.notification-text {
min-width: auto;
}
}
} }
.nitro-notification-bubble { .nitro-notification-bubble {