Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
cae6ff6271
|
|||
2163ae4dbf
|
|||
083945852b
|
|||
![]() |
e69b1232d7 |
@@ -35,7 +35,7 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
||||||
@@ -45,7 +45,8 @@ dependencies {
|
|||||||
implementation 'com.google.android.material:material:1.0.0'
|
implementation 'com.google.android.material:material:1.0.0'
|
||||||
implementation 'com.google.firebase:firebase-core:16.0.4'
|
implementation 'com.google.firebase:firebase-core:16.0.4'
|
||||||
implementation 'com.google.firebase:firebase-messaging:17.3.4'
|
implementation 'com.google.firebase:firebase-messaging:17.3.4'
|
||||||
implementation 'com.google.android.gms:play-services-ads:17.0.0'
|
implementation 'com.google.android.gms:play-services-ads:17.1.0'
|
||||||
|
implementation 'com.android.billingclient:billing:1.2'
|
||||||
|
|
||||||
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
|
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
|
||||||
implementation 'com.github.kenglxn.QRGen:android:2.5.0'
|
implementation 'com.github.kenglxn.QRGen:android:2.5.0'
|
||||||
|
@@ -4,6 +4,7 @@ import android.app.Application;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.android.billingclient.api.BillingClient;
|
||||||
import com.blackforestbytes.simplecloudnotifier.view.AccountFragment;
|
import com.blackforestbytes.simplecloudnotifier.view.AccountFragment;
|
||||||
import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
|
import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
|
||||||
import com.blackforestbytes.simplecloudnotifier.view.TabAdapter;
|
import com.blackforestbytes.simplecloudnotifier.view.TabAdapter;
|
||||||
@@ -101,29 +102,29 @@ public class SCNApp extends Application implements LifecycleObserver
|
|||||||
/*
|
/*
|
||||||
==TODO==
|
==TODO==
|
||||||
|
|
||||||
- Pro mode
|
[X] - Pro mode
|
||||||
- no ads
|
[X] - no ads
|
||||||
- more quota
|
[X] - more quota
|
||||||
- restore pro mode
|
[X] - restore pro mode
|
||||||
- send pro state to server
|
[X] - send pro state to server
|
||||||
|
|
||||||
- prevent duplicate-send
|
[X] - prevent duplicate-send
|
||||||
- send custom msg-id in API
|
[X] - send custom msg-id in API
|
||||||
- prevent second ack on same msg-id
|
[X] - prevent second ack on same msg-id
|
||||||
|
|
||||||
- more in-depth API doc on website (?)
|
[X] - more in-depth API doc on website (?)
|
||||||
|
|
||||||
- perhaps response codes in api (?)
|
[X] - perhaps response codes in api (?)
|
||||||
|
|
||||||
- test notification channels
|
[ ] - test notification channels
|
||||||
|
|
||||||
- publish (+ HN post ?)
|
[ ] - publish (+ HN post ?)
|
||||||
|
|
||||||
- Use for mscom server errrors
|
[ ] - Use for mscom server errrors
|
||||||
- Use for bfb server errors
|
[ ] - Use for bfb server errors
|
||||||
- Use for transmission state
|
[ ] - Use for transmission state
|
||||||
- Message on connnection lost (seperate process - resend until succ)
|
[ ] - Message on connnection lost (seperate process - resend until succ)
|
||||||
- Message on connnection regained
|
[ ] - Message on connnection regained
|
||||||
- Message on seed-count changed
|
[ ] - Message on seed-count changed
|
||||||
|
|
||||||
*/
|
*/
|
@@ -0,0 +1,23 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.android;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public final class ThreadUtils
|
||||||
|
{
|
||||||
|
public static void safeSleep(int millisMin, int millisMax)
|
||||||
|
{
|
||||||
|
safeSleep(millisMin + (int)(Math.random()*(millisMax-millisMin)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void safeSleep(int millis)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(millis);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
Log.d("ThreadUtils", e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,38 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.collections;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.lambda.Func1to1;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public final class CollectionHelper
|
||||||
|
{
|
||||||
|
public static <T, C> List<T> unique(List<T> input, Func1to1<T, C> mapping)
|
||||||
|
{
|
||||||
|
List<T> output = new ArrayList<>(input.size());
|
||||||
|
|
||||||
|
HashSet<C> seen = new HashSet<>();
|
||||||
|
|
||||||
|
for (T v : input) if (seen.add(mapping.invoke(v))) output.add(v);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> sort(List<T> input, Comparator<T> comparator)
|
||||||
|
{
|
||||||
|
List<T> output = new ArrayList<>(input);
|
||||||
|
Collections.sort(output, comparator);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, U extends Comparable<U>> List<T> sort(List<T> input, Func1to1<T, U> mapper)
|
||||||
|
{
|
||||||
|
return sort(input, mapper, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, U extends Comparable<U>> List<T> sort(List<T> input, Func1to1<T, U> mapper, int sortMod)
|
||||||
|
{
|
||||||
|
List<T> output = new ArrayList<>(input);
|
||||||
|
Collections.sort(output, (o1, o2) -> sortMod * mapper.invoke(o1).compareTo(mapper.invoke(o2)));
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,14 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public class IntRange
|
||||||
|
{
|
||||||
|
private int Start;
|
||||||
|
public int Start() { return Start; }
|
||||||
|
|
||||||
|
private int End;
|
||||||
|
public int End() { return End; }
|
||||||
|
|
||||||
|
public IntRange(int s, int e) { Start = s; End = e; }
|
||||||
|
|
||||||
|
private IntRange() { }
|
||||||
|
}
|
@@ -0,0 +1,10 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public class NInt
|
||||||
|
{
|
||||||
|
public int Value;
|
||||||
|
|
||||||
|
public NInt(int v) { Value = v; }
|
||||||
|
|
||||||
|
private NInt() { }
|
||||||
|
}
|
@@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public final class Nothing
|
||||||
|
{
|
||||||
|
public final static Nothing Inst = new Nothing();
|
||||||
|
}
|
@@ -0,0 +1,11 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public class Tuple1<T1>
|
||||||
|
{
|
||||||
|
public final T1 Item1;
|
||||||
|
|
||||||
|
public Tuple1(T1 i1)
|
||||||
|
{
|
||||||
|
Item1 = i1;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,13 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public class Tuple2<T1, T2>
|
||||||
|
{
|
||||||
|
public final T1 Item1;
|
||||||
|
public final T2 Item2;
|
||||||
|
|
||||||
|
public Tuple2(T1 i1, T2 i2)
|
||||||
|
{
|
||||||
|
Item1 = i1;
|
||||||
|
Item2 = i2;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public class Tuple3<T1, T2, T3>
|
||||||
|
{
|
||||||
|
public final T1 Item1;
|
||||||
|
public final T2 Item2;
|
||||||
|
public final T3 Item3;
|
||||||
|
|
||||||
|
public Tuple3(T1 i1, T2 i2, T3 i3)
|
||||||
|
{
|
||||||
|
Item1 = i1;
|
||||||
|
Item2 = i2;
|
||||||
|
Item3 = i3;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,17 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public class Tuple4<T1, T2, T3, T4>
|
||||||
|
{
|
||||||
|
public final T1 Item1;
|
||||||
|
public final T2 Item2;
|
||||||
|
public final T3 Item3;
|
||||||
|
public final T4 Item4;
|
||||||
|
|
||||||
|
public Tuple4(T1 i1, T2 i2, T3 i3, T4 i4)
|
||||||
|
{
|
||||||
|
Item1 = i1;
|
||||||
|
Item2 = i2;
|
||||||
|
Item3 = i3;
|
||||||
|
Item4 = i4;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func0to0 {
|
||||||
|
|
||||||
|
Func0to0 EMPTY = ()->{};
|
||||||
|
|
||||||
|
void invoke();
|
||||||
|
}
|
@@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func0to1<TResult> {
|
||||||
|
TResult invoke();
|
||||||
|
}
|
@@ -0,0 +1,8 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func0to1WithIOException<TResult> {
|
||||||
|
TResult invoke() throws IOException;
|
||||||
|
}
|
@@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func1to0<TInput1> {
|
||||||
|
void invoke(TInput1 value);
|
||||||
|
}
|
@@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func1to1<TInput1, TResult> {
|
||||||
|
TResult invoke(TInput1 value);
|
||||||
|
}
|
@@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func2to0<TInput1, TInput2> {
|
||||||
|
void invoke(TInput1 value1, TInput2 value2);
|
||||||
|
}
|
@@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func2to1<TInput1, TInput2, TResult> {
|
||||||
|
TResult invoke(TInput1 value1, TInput2 value2);
|
||||||
|
}
|
@@ -0,0 +1,231 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.string;
|
||||||
|
|
||||||
|
// from MonoSAMFramework.Portable.DebugTools.CompactJsonFormatter
|
||||||
|
public class CompactJsonFormatter
|
||||||
|
{
|
||||||
|
private static final String INDENT_STRING = " ";
|
||||||
|
|
||||||
|
public static String formatJSON(String str, int maxIndent)
|
||||||
|
{
|
||||||
|
int indent = 0;
|
||||||
|
boolean quoted = false;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
char last = ' ';
|
||||||
|
for (int i = 0; i < str.length(); i++)
|
||||||
|
{
|
||||||
|
char ch = str.charAt(i);
|
||||||
|
switch (ch)
|
||||||
|
{
|
||||||
|
case '\r':
|
||||||
|
case '\n':
|
||||||
|
break;
|
||||||
|
case '{':
|
||||||
|
case '[':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
indent++;
|
||||||
|
if (indent >= maxIndent) break;
|
||||||
|
sb.append("\n");
|
||||||
|
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
|
||||||
|
last = ' ';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '}':
|
||||||
|
case ']':
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
indent--;
|
||||||
|
if (indent + 1 >= maxIndent) { sb.append(ch); break; }
|
||||||
|
sb.append("\n");
|
||||||
|
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
|
||||||
|
}
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
boolean escaped = false;
|
||||||
|
int index = i;
|
||||||
|
while (index > 0 && str.charAt(--index) == '\\')
|
||||||
|
escaped = !escaped;
|
||||||
|
if (!escaped)
|
||||||
|
quoted = !quoted;
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
if (indent >= maxIndent) { sb.append(' '); last = ' '; break; }
|
||||||
|
sb.append("\n");
|
||||||
|
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ':':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
if (!quoted) { sb.append(" "); last = ' '; }
|
||||||
|
break;
|
||||||
|
case ' ':
|
||||||
|
case '\t':
|
||||||
|
if (quoted)
|
||||||
|
{
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
}
|
||||||
|
else if (last != ' ')
|
||||||
|
{
|
||||||
|
sb.append(' ');
|
||||||
|
last = ' ';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String compressJson(String str, int compressionLevel)
|
||||||
|
{
|
||||||
|
int indent = 0;
|
||||||
|
boolean quoted = false;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
char last = ' ';
|
||||||
|
int compress = 0;
|
||||||
|
for (int i = 0; i < str.length(); i++)
|
||||||
|
{
|
||||||
|
char ch = str.charAt(i);
|
||||||
|
switch (ch)
|
||||||
|
{
|
||||||
|
case '\r':
|
||||||
|
case '\n':
|
||||||
|
break;
|
||||||
|
case '{':
|
||||||
|
case '[':
|
||||||
|
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
if (compress == 0 && getJsonDepth(str, i) <= compressionLevel)
|
||||||
|
compress = 1;
|
||||||
|
else if (compress > 0)
|
||||||
|
compress++;
|
||||||
|
|
||||||
|
indent++;
|
||||||
|
if (compress > 0) break;
|
||||||
|
sb.append("\n");
|
||||||
|
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
|
||||||
|
last = ' ';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '}':
|
||||||
|
case ']':
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
indent--;
|
||||||
|
if (compress > 0) { compress--; sb.append(ch); break; }
|
||||||
|
compress--;
|
||||||
|
sb.append("\n");
|
||||||
|
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
|
||||||
|
}
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
boolean escaped = false;
|
||||||
|
int index = i;
|
||||||
|
while (index > 0 && str.charAt(--index) == '\\')
|
||||||
|
escaped = !escaped;
|
||||||
|
if (!escaped)
|
||||||
|
quoted = !quoted;
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
if (compress > 0) { sb.append(' '); last = ' '; break; }
|
||||||
|
sb.append("\n");
|
||||||
|
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ':':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
if (!quoted) { sb.append(" "); last = ' '; }
|
||||||
|
break;
|
||||||
|
case ' ':
|
||||||
|
case '\t':
|
||||||
|
if (quoted)
|
||||||
|
{
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
}
|
||||||
|
else if (last != ' ')
|
||||||
|
{
|
||||||
|
sb.append(' ');
|
||||||
|
last = ' ';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getJsonDepth(String str, int i)
|
||||||
|
{
|
||||||
|
int maxindent = 0;
|
||||||
|
int indent = 0;
|
||||||
|
boolean quoted = false;
|
||||||
|
for (; i < str.length(); i++)
|
||||||
|
{
|
||||||
|
char ch = str.charAt(i);
|
||||||
|
switch (ch)
|
||||||
|
{
|
||||||
|
case '{':
|
||||||
|
case '[':
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
indent++;
|
||||||
|
maxindent = Math.max(indent, maxindent);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '}':
|
||||||
|
case ']':
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
indent--;
|
||||||
|
if (indent <= 0) return maxindent;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
boolean escaped = false;
|
||||||
|
int index = i;
|
||||||
|
while (index > 0 && str.charAt(--index) == '\\')
|
||||||
|
escaped = !escaped;
|
||||||
|
if (!escaped)
|
||||||
|
quoted = !quoted;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxindent;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,98 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.string;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.lambda.Func1to1;
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Str
|
||||||
|
{
|
||||||
|
public final static String Empty = "";
|
||||||
|
|
||||||
|
public static String format(String fmt, Object... data)
|
||||||
|
{
|
||||||
|
return MessageFormat.format(fmt, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String rformat(int fmtResId, Object... data)
|
||||||
|
{
|
||||||
|
Context inst = SCNApp.getContext();
|
||||||
|
if (inst == null)
|
||||||
|
{
|
||||||
|
Log.e("StringFormat", "rformat::NoInstance --> inst==null for" + fmtResId);
|
||||||
|
return "?ERR?";
|
||||||
|
}
|
||||||
|
|
||||||
|
return MessageFormat.format(inst.getResources().getString(fmtResId), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String firstLine(String content)
|
||||||
|
{
|
||||||
|
int idx = content.indexOf('\n');
|
||||||
|
if (idx == -1) return content;
|
||||||
|
|
||||||
|
if (idx == 0) return Str.Empty;
|
||||||
|
|
||||||
|
if (content.charAt(idx-1) == '\r') return content.substring(0, idx-1);
|
||||||
|
|
||||||
|
return content.substring(0, idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isNullOrWhitespace(String str)
|
||||||
|
{
|
||||||
|
return str == null || str.length() == 0 || str.trim().length() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isNullOrEmpty(String str)
|
||||||
|
{
|
||||||
|
return str == null || str.length() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean equals(String a, String b)
|
||||||
|
{
|
||||||
|
if (a == null) return (b == null);
|
||||||
|
return a.equals(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String join(String sep, List<String> list)
|
||||||
|
{
|
||||||
|
StringBuilder b = new StringBuilder();
|
||||||
|
boolean first = true;
|
||||||
|
for (String v : list)
|
||||||
|
{
|
||||||
|
if (!first) b.append(sep);
|
||||||
|
b.append(v);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> String join(String sep, List<T> list, Func1to1<T, String> map)
|
||||||
|
{
|
||||||
|
StringBuilder b = new StringBuilder();
|
||||||
|
boolean first = true;
|
||||||
|
for (T v : list)
|
||||||
|
{
|
||||||
|
if (!first) b.append(sep);
|
||||||
|
b.append(map.invoke(v));
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Integer tryParseToInt(String s)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Integer.parseInt(s);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -6,7 +6,9 @@ import android.content.SharedPreferences;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.android.billingclient.api.Purchase;
|
||||||
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.service.IABService;
|
||||||
import com.google.firebase.iid.FirebaseInstanceId;
|
import com.google.firebase.iid.FirebaseInstanceId;
|
||||||
|
|
||||||
public class SCNSettings
|
public class SCNSettings
|
||||||
@@ -36,6 +38,10 @@ public class SCNSettings
|
|||||||
public String fcm_token_local;
|
public String fcm_token_local;
|
||||||
public String fcm_token_server;
|
public String fcm_token_server;
|
||||||
|
|
||||||
|
public String promode_token;
|
||||||
|
public boolean promode_local;
|
||||||
|
public boolean promode_server;
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
public boolean Enabled = true;
|
public boolean Enabled = true;
|
||||||
@@ -57,6 +63,9 @@ public class SCNSettings
|
|||||||
user_key = sharedPref.getString("user_key", "");
|
user_key = sharedPref.getString("user_key", "");
|
||||||
fcm_token_local = sharedPref.getString("fcm_token_local", "");
|
fcm_token_local = sharedPref.getString("fcm_token_local", "");
|
||||||
fcm_token_server = sharedPref.getString("fcm_token_server", "");
|
fcm_token_server = sharedPref.getString("fcm_token_server", "");
|
||||||
|
promode_local = sharedPref.getBoolean("promode_local", false);
|
||||||
|
promode_server = sharedPref.getBoolean("promode_server", false);
|
||||||
|
promode_token = sharedPref.getString("promode_token", "");
|
||||||
|
|
||||||
Enabled = sharedPref.getBoolean("app_enabled", Enabled);
|
Enabled = sharedPref.getBoolean("app_enabled", Enabled);
|
||||||
LocalCacheSize = sharedPref.getInt("local_cache_size", LocalCacheSize);
|
LocalCacheSize = sharedPref.getInt("local_cache_size", LocalCacheSize);
|
||||||
@@ -151,10 +160,12 @@ public class SCNSettings
|
|||||||
{
|
{
|
||||||
fcm_token_local = token;
|
fcm_token_local = token;
|
||||||
save();
|
save();
|
||||||
ServerCommunication.register(fcm_token_local, loader);
|
ServerCommunication.register(fcm_token_local, loader, promode_local, promode_token);
|
||||||
|
updateProState(loader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// called at app start
|
||||||
public void work(Activity a)
|
public void work(Activity a)
|
||||||
{
|
{
|
||||||
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult ->
|
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult ->
|
||||||
@@ -166,8 +177,11 @@ public class SCNSettings
|
|||||||
{
|
{
|
||||||
if (isConnected()) ServerCommunication.info(user_id, user_key, null);
|
if (isConnected()) ServerCommunication.info(user_id, user_key, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
updateProState(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset account key
|
||||||
public void reset(View loader)
|
public void reset(View loader)
|
||||||
{
|
{
|
||||||
if (!isConnected()) return;
|
if (!isConnected()) return;
|
||||||
@@ -175,23 +189,50 @@ public class SCNSettings
|
|||||||
ServerCommunication.update(user_id, user_key, loader);
|
ServerCommunication.update(user_id, user_key, loader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// refresh account data
|
||||||
public void refresh(View loader, Activity a)
|
public void refresh(View loader, Activity a)
|
||||||
{
|
{
|
||||||
if (isConnected())
|
if (isConnected())
|
||||||
{
|
{
|
||||||
ServerCommunication.info(user_id, user_key, loader);
|
ServerCommunication.info(user_id, user_key, loader);
|
||||||
|
if (promode_server != promode_local)
|
||||||
|
{
|
||||||
|
updateProState(loader);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// get token then register
|
||||||
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult ->
|
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult ->
|
||||||
{
|
{
|
||||||
String newToken = instanceIdResult.getToken();
|
String newToken = instanceIdResult.getToken();
|
||||||
Log.e("FB::GetInstanceId", newToken);
|
Log.e("FB::GetInstanceId", newToken);
|
||||||
SCNSettings.inst().setServerToken(newToken, loader);
|
SCNSettings.inst().setServerToken(newToken, loader); // does register in here
|
||||||
}).addOnCompleteListener(r ->
|
}).addOnCompleteListener(r ->
|
||||||
{
|
{
|
||||||
if (isConnected()) ServerCommunication.info(user_id, user_key, null);
|
if (isConnected()) ServerCommunication.info(user_id, user_key, null); // info again for safety
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateProState(View loader)
|
||||||
|
{
|
||||||
|
Purchase purch = IABService.inst().getPurchaseCached(IABService.IAB_PRO_MODE);
|
||||||
|
boolean promode_real = (purch != null);
|
||||||
|
|
||||||
|
if (promode_real != promode_local || promode_real != promode_server)
|
||||||
|
{
|
||||||
|
promode_local = promode_real;
|
||||||
|
|
||||||
|
promode_token = promode_real ? purch.getPurchaseToken() : "";
|
||||||
|
updateProStateOnServer(loader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateProStateOnServer(View loader)
|
||||||
|
{
|
||||||
|
if (!isConnected()) return;
|
||||||
|
|
||||||
|
ServerCommunication.upgrade(user_id, user_key, loader, promode_local, promode_token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,7 @@ import org.json.JSONObject;
|
|||||||
import org.json.JSONTokener;
|
import org.json.JSONTokener;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
|
||||||
import okhttp3.Call;
|
import okhttp3.Call;
|
||||||
import okhttp3.Callback;
|
import okhttp3.Callback;
|
||||||
@@ -25,12 +26,12 @@ public class ServerCommunication
|
|||||||
|
|
||||||
private ServerCommunication(){ throw new Error("no."); }
|
private ServerCommunication(){ throw new Error("no."); }
|
||||||
|
|
||||||
public static void register(String token, View loader)
|
public static void register(String token, View loader, boolean pro, String pro_token)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(BASE_URL + "register.php?fcm_token="+token)
|
.url(BASE_URL + "register.php?fcm_token=" + token + "&pro=" + pro + "&pro_token=" + URLEncoder.encode(pro_token, "utf-8"))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
client.newCall(request).enqueue(new Callback()
|
client.newCall(request).enqueue(new Callback()
|
||||||
@@ -67,6 +68,7 @@ public class ServerCommunication
|
|||||||
SCNSettings.inst().fcm_token_server = token;
|
SCNSettings.inst().fcm_token_server = token;
|
||||||
SCNSettings.inst().quota_curr = json.getInt("quota");
|
SCNSettings.inst().quota_curr = json.getInt("quota");
|
||||||
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
||||||
|
SCNSettings.inst().promode_server = json.getBoolean("is_pro");
|
||||||
SCNSettings.inst().save();
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
SCNApp.refreshAccountTab();
|
SCNApp.refreshAccountTab();
|
||||||
@@ -132,6 +134,7 @@ public class ServerCommunication
|
|||||||
SCNSettings.inst().fcm_token_server = token;
|
SCNSettings.inst().fcm_token_server = token;
|
||||||
SCNSettings.inst().quota_curr = json.getInt("quota");
|
SCNSettings.inst().quota_curr = json.getInt("quota");
|
||||||
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
||||||
|
SCNSettings.inst().promode_server = json.getBoolean("is_pro");
|
||||||
SCNSettings.inst().save();
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
SCNApp.refreshAccountTab();
|
SCNApp.refreshAccountTab();
|
||||||
@@ -187,10 +190,11 @@ public class ServerCommunication
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SCNSettings.inst().user_id = json.getInt("user_id");
|
SCNSettings.inst().user_id = json.getInt("user_id");
|
||||||
SCNSettings.inst().user_key = json.getString("user_key");
|
SCNSettings.inst().user_key = json.getString("user_key");
|
||||||
SCNSettings.inst().quota_curr = json.getInt("quota");
|
SCNSettings.inst().quota_curr = json.getInt("quota");
|
||||||
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
||||||
|
SCNSettings.inst().promode_server = json.getBoolean("is_pro");
|
||||||
SCNSettings.inst().save();
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
SCNApp.refreshAccountTab();
|
SCNApp.refreshAccountTab();
|
||||||
@@ -247,9 +251,10 @@ public class ServerCommunication
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SCNSettings.inst().user_id = json.getInt("user_id");
|
SCNSettings.inst().user_id = json.getInt("user_id");
|
||||||
SCNSettings.inst().quota_curr = json.getInt("quota");
|
SCNSettings.inst().quota_curr = json.getInt("quota");
|
||||||
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
||||||
|
SCNSettings.inst().promode_server = json.getBoolean("is_pro");
|
||||||
SCNSettings.inst().save();
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
SCNApp.refreshAccountTab();
|
SCNApp.refreshAccountTab();
|
||||||
@@ -270,4 +275,63 @@ public class ServerCommunication
|
|||||||
SCNApp.showToast("Communication with server failed", 4000);
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void upgrade(int id, String key, View loader, boolean pro, String pro_token)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(BASE_URL + "upgrade.php?user_id=" + id + "&user_key=" + key + "&pro=" + pro + "&pro_token=" + URLEncoder.encode(pro_token, "utf-8"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response) {
|
||||||
|
try (ResponseBody responseBody = response.body()) {
|
||||||
|
if (!response.isSuccessful())
|
||||||
|
throw new IOException("Unexpected code " + response);
|
||||||
|
if (responseBody == null) throw new IOException("No response");
|
||||||
|
|
||||||
|
String r = responseBody.string();
|
||||||
|
Log.d("Server::Response", r);
|
||||||
|
|
||||||
|
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||||
|
|
||||||
|
if (!json.getBoolean("success")) {
|
||||||
|
SCNApp.showToast(json.getString("message"), 4000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SCNSettings.inst().user_id = json.getInt("user_id");
|
||||||
|
SCNSettings.inst().user_key = json.getString("user_key");
|
||||||
|
SCNSettings.inst().quota_curr = json.getInt("quota");
|
||||||
|
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
||||||
|
SCNSettings.inst().promode_server = json.getBoolean("is_pro");
|
||||||
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
|
SCNApp.refreshAccountTab();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
} finally {
|
||||||
|
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,195 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.service;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.android.billingclient.api.BillingClient;
|
||||||
|
import com.android.billingclient.api.BillingClientStateListener;
|
||||||
|
import com.android.billingclient.api.BillingFlowParams;
|
||||||
|
import com.android.billingclient.api.Purchase;
|
||||||
|
import com.android.billingclient.api.PurchasesUpdatedListener;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.lambda.Func0to0;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import static androidx.constraintlayout.widget.Constraints.TAG;
|
||||||
|
|
||||||
|
public class IABService implements PurchasesUpdatedListener
|
||||||
|
{
|
||||||
|
public static final String IAB_PRO_MODE = "scn.pro.tier1";
|
||||||
|
|
||||||
|
private final static Object _lock = new Object();
|
||||||
|
private static IABService _inst = null;
|
||||||
|
public static IABService inst()
|
||||||
|
{
|
||||||
|
synchronized (_lock)
|
||||||
|
{
|
||||||
|
if (_inst != null) return _inst;
|
||||||
|
throw new Error("IABService == null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static void startup(MainActivity a)
|
||||||
|
{
|
||||||
|
synchronized (_lock)
|
||||||
|
{
|
||||||
|
_inst = new IABService(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BillingClient client;
|
||||||
|
private boolean isServiceConnected;
|
||||||
|
private final List<Purchase> purchases = new ArrayList<>();
|
||||||
|
|
||||||
|
public IABService(Context c)
|
||||||
|
{
|
||||||
|
client = BillingClient
|
||||||
|
.newBuilder(c)
|
||||||
|
.setListener(this)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
startServiceConnection(this::queryPurchases, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void queryPurchases()
|
||||||
|
{
|
||||||
|
Func0to0 queryToExecute = () ->
|
||||||
|
{
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
Purchase.PurchasesResult purchasesResult = client.queryPurchases(BillingClient.SkuType.INAPP);
|
||||||
|
Log.i(TAG, "Querying purchases elapsed time: " + (System.currentTimeMillis() - time) + "ms");
|
||||||
|
|
||||||
|
if (purchasesResult.getResponseCode() == BillingClient.BillingResponse.OK)
|
||||||
|
{
|
||||||
|
for (Purchase p : purchasesResult.getPurchasesList())
|
||||||
|
{
|
||||||
|
handlePurchase(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean newProMode = getPurchaseCached(IAB_PRO_MODE) != null;
|
||||||
|
if (newProMode != SCNSettings.inst().promode_local)
|
||||||
|
{
|
||||||
|
refreshProModeListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.w(TAG, "queryPurchases() got an error response code: " + purchasesResult.getResponseCode());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
executeServiceRequest(queryToExecute, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void purchase(Activity a, String id)
|
||||||
|
{
|
||||||
|
executeServiceRequest(() ->
|
||||||
|
{
|
||||||
|
BillingFlowParams flowParams = BillingFlowParams
|
||||||
|
.newBuilder()
|
||||||
|
.setSku(id)
|
||||||
|
.setType(BillingClient.SkuType.INAPP) // SkuType.SUB for subscription
|
||||||
|
.build();
|
||||||
|
client.launchBillingFlow(a, flowParams);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeServiceRequest(Func0to0 runnable, final boolean userRequest) {
|
||||||
|
if (isServiceConnected)
|
||||||
|
{
|
||||||
|
runnable.invoke();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If billing service was disconnected, we try to reconnect 1 time.
|
||||||
|
// (feel free to introduce your retry policy here).
|
||||||
|
startServiceConnection(runnable, userRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void destroy()
|
||||||
|
{
|
||||||
|
if (client != null && client.isReady()) {
|
||||||
|
client.endConnection();
|
||||||
|
client = null;
|
||||||
|
isServiceConnected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPurchasesUpdated(int responseCode, @Nullable List<Purchase> purchases)
|
||||||
|
{
|
||||||
|
if (responseCode == BillingClient.BillingResponse.OK && purchases != null)
|
||||||
|
{
|
||||||
|
for (Purchase purchase : purchases)
|
||||||
|
{
|
||||||
|
handlePurchase(purchase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (responseCode == BillingClient.BillingResponse.ITEM_ALREADY_OWNED && purchases != null)
|
||||||
|
{
|
||||||
|
for (Purchase purchase : purchases)
|
||||||
|
{
|
||||||
|
handlePurchase(purchase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePurchase(Purchase purchase)
|
||||||
|
{
|
||||||
|
Log.d(TAG, "Got a verified purchase: " + purchase);
|
||||||
|
|
||||||
|
purchases.add(purchase);
|
||||||
|
|
||||||
|
refreshProModeListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshProModeListener() {
|
||||||
|
MainActivity ma = SCNApp.getMainActivity();
|
||||||
|
if (ma != null) ma.adpTabs.tab3.updateProState();
|
||||||
|
if (ma != null) ma.adpTabs.tab1.updateProState();
|
||||||
|
SCNSettings.inst().updateProState(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startServiceConnection(final Func0to0 executeOnSuccess, final boolean userRequest)
|
||||||
|
{
|
||||||
|
client.startConnection(new BillingClientStateListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onBillingSetupFinished(@BillingClient.BillingResponse int billingResponseCode)
|
||||||
|
{
|
||||||
|
if (billingResponseCode == BillingClient.BillingResponse.OK)
|
||||||
|
{
|
||||||
|
isServiceConnected = true;
|
||||||
|
if (executeOnSuccess != null) executeOnSuccess.invoke();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (userRequest) SCNApp.showToast("Could not connect to google services", Toast.LENGTH_SHORT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBillingServiceDisconnected() {
|
||||||
|
isServiceConnected = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Purchase getPurchaseCached(String id)
|
||||||
|
{
|
||||||
|
for (Purchase p : purchases)
|
||||||
|
{
|
||||||
|
if (Str.equals(p.getSku(), id)) return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@@ -6,8 +6,6 @@ import android.app.NotificationManager;
|
|||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Color;
|
|
||||||
import android.media.AudioAttributes;
|
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
@@ -1,17 +1,15 @@
|
|||||||
package com.blackforestbytes.simplecloudnotifier.view;
|
package com.blackforestbytes.simplecloudnotifier.view;
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import com.blackforestbytes.simplecloudnotifier.R;
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
|
import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
|
||||||
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.service.IABService;
|
||||||
import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
|
import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
|
||||||
import com.google.android.material.tabs.TabLayout;
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.viewpager.widget.PagerAdapter;
|
import androidx.viewpager.widget.PagerAdapter;
|
||||||
@@ -42,7 +40,7 @@ public class MainActivity extends AppCompatActivity
|
|||||||
tabLayout.setupWithViewPager(viewPager);
|
tabLayout.setupWithViewPager(viewPager);
|
||||||
|
|
||||||
SCNApp.register(this);
|
SCNApp.register(this);
|
||||||
|
IABService.startup(this);
|
||||||
SCNSettings.inst().work(this);
|
SCNSettings.inst().work(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +49,16 @@ public class MainActivity extends AppCompatActivity
|
|||||||
{
|
{
|
||||||
super.onStop();
|
super.onStop();
|
||||||
|
|
||||||
|
SCNSettings.inst().save();
|
||||||
CMessageList.inst().fullSave();
|
CMessageList.inst().fullSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy()
|
||||||
|
{
|
||||||
|
super.onDestroy();
|
||||||
|
|
||||||
|
CMessageList.inst().fullSave();
|
||||||
|
IABService.inst().destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,8 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import com.blackforestbytes.simplecloudnotifier.R;
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.service.IABService;
|
||||||
import com.google.android.gms.ads.doubleclick.PublisherAdRequest;
|
import com.google.android.gms.ads.doubleclick.PublisherAdRequest;
|
||||||
import com.google.android.gms.ads.doubleclick.PublisherAdView;
|
import com.google.android.gms.ads.doubleclick.PublisherAdView;
|
||||||
|
|
||||||
@@ -16,6 +18,8 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
|
|
||||||
public class NotificationsFragment extends Fragment
|
public class NotificationsFragment extends Fragment
|
||||||
{
|
{
|
||||||
|
private PublisherAdView adView;
|
||||||
|
|
||||||
public NotificationsFragment()
|
public NotificationsFragment()
|
||||||
{
|
{
|
||||||
// Required empty public constructor
|
// Required empty public constructor
|
||||||
@@ -30,11 +34,17 @@ public class NotificationsFragment extends Fragment
|
|||||||
rvMessages.setLayoutManager(new LinearLayoutManager(this.getContext(), RecyclerView.VERTICAL, true));
|
rvMessages.setLayoutManager(new LinearLayoutManager(this.getContext(), RecyclerView.VERTICAL, true));
|
||||||
rvMessages.setAdapter(new MessageAdapter(v.findViewById(R.id.tvNoElements)));
|
rvMessages.setAdapter(new MessageAdapter(v.findViewById(R.id.tvNoElements)));
|
||||||
|
|
||||||
PublisherAdView mPublisherAdView = v.findViewById(R.id.adBanner);
|
adView = v.findViewById(R.id.adBanner);
|
||||||
PublisherAdRequest adRequest = new PublisherAdRequest.Builder().build();
|
PublisherAdRequest adRequest = new PublisherAdRequest.Builder().build();
|
||||||
mPublisherAdView.loadAd(adRequest);
|
adView.loadAd(adRequest);
|
||||||
|
|
||||||
|
adView.setVisibility(SCNSettings.inst().promode_local ? View.GONE : View.VISIBLE);
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateProState()
|
||||||
|
{
|
||||||
|
adView.setVisibility(IABService.inst().getPurchaseCached(IABService.IAB_PRO_MODE) != null ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
package com.blackforestbytes.simplecloudnotifier.view;
|
package com.blackforestbytes.simplecloudnotifier.view;
|
||||||
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@@ -16,8 +15,10 @@ import android.widget.Spinner;
|
|||||||
import android.widget.Switch;
|
import android.widget.Switch;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.android.billingclient.api.Purchase;
|
||||||
import com.blackforestbytes.simplecloudnotifier.R;
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.service.IABService;
|
||||||
import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
|
import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -33,6 +34,8 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
|||||||
private Switch prefAppEnabled;
|
private Switch prefAppEnabled;
|
||||||
private Spinner prefLocalCacheSize;
|
private Spinner prefLocalCacheSize;
|
||||||
private Button prefUpgradeAccount;
|
private Button prefUpgradeAccount;
|
||||||
|
private TextView prefUpgradeAccount_msg;
|
||||||
|
private TextView prefUpgradeAccount_info;
|
||||||
|
|
||||||
private Switch prefMsgLowEnableSound;
|
private Switch prefMsgLowEnableSound;
|
||||||
private TextView prefMsgLowRingtone_value;
|
private TextView prefMsgLowRingtone_value;
|
||||||
@@ -85,6 +88,8 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
|||||||
prefAppEnabled = v.findViewById(R.id.prefAppEnabled);
|
prefAppEnabled = v.findViewById(R.id.prefAppEnabled);
|
||||||
prefLocalCacheSize = v.findViewById(R.id.prefLocalCacheSize);
|
prefLocalCacheSize = v.findViewById(R.id.prefLocalCacheSize);
|
||||||
prefUpgradeAccount = v.findViewById(R.id.prefUpgradeAccount);
|
prefUpgradeAccount = v.findViewById(R.id.prefUpgradeAccount);
|
||||||
|
prefUpgradeAccount_msg = v.findViewById(R.id.prefUpgradeAccount2);
|
||||||
|
prefUpgradeAccount_info = v.findViewById(R.id.prefUpgradeAccount_info);
|
||||||
|
|
||||||
prefMsgLowEnableSound = v.findViewById(R.id.prefMsgLowEnableSound);
|
prefMsgLowEnableSound = v.findViewById(R.id.prefMsgLowEnableSound);
|
||||||
prefMsgLowRingtone_value = v.findViewById(R.id.prefMsgLowRingtone_value);
|
prefMsgLowRingtone_value = v.findViewById(R.id.prefMsgLowRingtone_value);
|
||||||
@@ -122,6 +127,10 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
|||||||
|
|
||||||
if (prefAppEnabled.isChecked() != s.Enabled) prefAppEnabled.setChecked(s.Enabled);
|
if (prefAppEnabled.isChecked() != s.Enabled) prefAppEnabled.setChecked(s.Enabled);
|
||||||
|
|
||||||
|
prefUpgradeAccount.setVisibility( SCNSettings.inst().promode_local ? View.GONE : View.VISIBLE);
|
||||||
|
prefUpgradeAccount_info.setVisibility(SCNSettings.inst().promode_local ? View.GONE : View.VISIBLE);
|
||||||
|
prefUpgradeAccount_msg.setVisibility( SCNSettings.inst().promode_local ? View.VISIBLE : View.GONE );
|
||||||
|
|
||||||
ArrayAdapter<Integer> plcsa = new ArrayAdapter<>(c, android.R.layout.simple_spinner_item, SCNSettings.CHOOSABLE_CACHE_SIZES);
|
ArrayAdapter<Integer> plcsa = new ArrayAdapter<>(c, android.R.layout.simple_spinner_item, SCNSettings.CHOOSABLE_CACHE_SIZES);
|
||||||
plcsa.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
plcsa.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
prefLocalCacheSize.setAdapter(plcsa);
|
prefLocalCacheSize.setAdapter(plcsa);
|
||||||
@@ -197,7 +206,16 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
|
|||||||
|
|
||||||
private void onUpgradeAccount()
|
private void onUpgradeAccount()
|
||||||
{
|
{
|
||||||
//TODO
|
IABService.inst().purchase(getActivity(), IABService.IAB_PRO_MODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateProState()
|
||||||
|
{
|
||||||
|
Purchase p = IABService.inst().getPurchaseCached(IABService.IAB_PRO_MODE);
|
||||||
|
|
||||||
|
prefUpgradeAccount.setVisibility( p != null ? View.GONE : View.VISIBLE);
|
||||||
|
prefUpgradeAccount_info.setVisibility(p != null ? View.GONE : View.VISIBLE);
|
||||||
|
prefUpgradeAccount_msg.setVisibility( p != null ? View.VISIBLE : View.GONE );
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getCacheSizeIndex(int value)
|
private int getCacheSizeIndex(int value)
|
||||||
|
@@ -96,7 +96,8 @@
|
|||||||
android:layout_marginTop="2dp"
|
android:layout_marginTop="2dp"
|
||||||
android:background="#c0c0c0"/>
|
android:background="#c0c0c0"/>
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
android:layout_marginStart="4dp"
|
android:layout_marginStart="4dp"
|
||||||
android:layout_marginEnd="4dp"
|
android:layout_marginEnd="4dp"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -107,12 +108,25 @@
|
|||||||
android:id="@+id/prefUpgradeAccount"
|
android:id="@+id/prefUpgradeAccount"
|
||||||
android:text="@string/str_upgrade_account"
|
android:text="@string/str_upgrade_account"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content" />
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
<TextView
|
||||||
|
android:id="@+id/prefUpgradeAccount_info"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:text="@string/str_promode_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/prefUpgradeAccount2"
|
||||||
|
android:textColor="#FF4D00"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:text="@string/str_promode"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
@@ -28,4 +28,6 @@
|
|||||||
<string name="str_ledcolor">Notification light color</string>
|
<string name="str_ledcolor">Notification light color</string>
|
||||||
<string name="str_enable_vibration">Enable notification vibration</string>
|
<string name="str_enable_vibration">Enable notification vibration</string>
|
||||||
<string name="str_upgrade_account">Upgrade account</string>
|
<string name="str_upgrade_account">Upgrade account</string>
|
||||||
|
<string name="str_promode">Thank you for supporting the app and using the pro mode</string>
|
||||||
|
<string name="str_promode_info">Increase your daily quota, remove the ad banner and support the developer (that\'s me)</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
#Mon Oct 22 15:29:01 CEST 2018
|
#Sun Nov 11 19:37:57 CET 2018
|
||||||
VERSION_NAME=0.0.4
|
VERSION_NAME=0.0.5
|
||||||
VERSION_CODE=4
|
VERSION_CODE=5
|
||||||
|
@@ -8,7 +8,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.2.1'
|
classpath 'com.android.tools.build:gradle:3.2.1'
|
||||||
classpath 'com.google.gms:google-services:4.0.1'
|
classpath 'com.google.gms:google-services:4.2.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
3
web/.gitignore
vendored
3
web/.gitignore
vendored
@@ -180,4 +180,5 @@ $RECYCLE.BIN/
|
|||||||
|
|
||||||
#################
|
#################
|
||||||
|
|
||||||
config.php
|
config.php
|
||||||
|
.verify_accesstoken
|
@@ -22,12 +22,24 @@ body
|
|||||||
#mainpnl
|
#mainpnl
|
||||||
{
|
{
|
||||||
box-shadow: 0 0 32px #DDD;
|
box-shadow: 0 0 32px #DDD;
|
||||||
animation:blink-shadow ease-in-out 4s infinite;
|
//animation:blink-shadow ease-in-out 4s infinite;
|
||||||
width: 87%;
|
width: 87%;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: 485px;
|
min-height: 570px;
|
||||||
|
|
||||||
|
background: var(--form-back-color);
|
||||||
|
color: var(--form-fore-color);
|
||||||
|
border: .0625rem solid var(--form-border-color);
|
||||||
|
border-radius: var(--universal-border-radius);
|
||||||
|
margin: 32px .5rem;
|
||||||
|
padding: calc(2 * var(--universal-padding)) var(--universal-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-code
|
||||||
|
{
|
||||||
|
border-left: .25rem solid #E53935;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mainpnl input,
|
#mainpnl input,
|
||||||
@@ -178,6 +190,7 @@ input::-webkit-inner-spin-button {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.card,
|
a.card,
|
||||||
@@ -192,4 +205,24 @@ a.card:hover
|
|||||||
a.card:hover
|
a.card:hover
|
||||||
{
|
{
|
||||||
box-shadow: 0 0 16px #AAA;
|
box-shadow: 0 0 16px #AAA;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.scode_table {
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.scode_table td:nth-child(2) {
|
||||||
|
flex-grow: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.scode_table th:nth-child(2) {
|
||||||
|
flex-grow: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mainpnl h2 {
|
||||||
|
margin-top: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkcaption:hover {
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
@@ -16,7 +16,7 @@
|
|||||||
<a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
|
<a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
|
||||||
<a href="/index_api.php" class="button bordered" id="tr_link">API</a>
|
<a href="/index_api.php" class="button bordered" id="tr_link">API</a>
|
||||||
|
|
||||||
<h1>Simple Cloud Notifier</h1>
|
<a href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a>
|
||||||
|
|
||||||
<div class="row responsive-label">
|
<div class="row responsive-label">
|
||||||
<div class="col-sm-12 col-md-3"><label for="uid" class="doc">UserID</label></div>
|
<div class="col-sm-12 col-md-3"><label for="uid" class="doc">UserID</label></div>
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
<div class="row responsive-label">
|
<div class="row responsive-label">
|
||||||
<div class="col-sm-12 col-md-3"><label for="txt" class="doc">Message Content</label></div>
|
<div class="col-sm-12 col-md-3"><label for="txt" class="doc">Message Content</label></div>
|
||||||
<div class="col-sm-12 col-md"><textarea id="txt" class="doc" <?php echo (isset($_GET['preset_content']) ? (' value="'.$_GET['preset_content'].'" '):(''));?> rows="5"></textarea></div>
|
<div class="col-sm-12 col-md"><textarea id="txt" class="doc" <?php echo (isset($_GET['preset_content']) ? (' value="'.$_GET['preset_content'].'" '):(''));?> rows="8"></textarea></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="/css/mini-default.min.css">
|
<link rel="stylesheet" href="/css/mini-default.min.css"> <!-- https://minicss.org/docs -->
|
||||||
<title>Simple Cloud Notifications - API</title>
|
<title>Simple Cloud Notifications - API</title>
|
||||||
<!--<link rel="stylesheet" href="/css/mini-nord.min.css">-->
|
<!--<link rel="stylesheet" href="/css/mini-nord.min.css">-->
|
||||||
<!--<link rel="stylesheet" href="/css/mini-dark.min.css">-->
|
<!--<link rel="stylesheet" href="/css/mini-dark.min.css">-->
|
||||||
@@ -10,27 +10,31 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<form id="mainpnl">
|
<div id="mainpnl">
|
||||||
<a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
|
<a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
|
||||||
<a href="/index.php" class="button bordered" id="tr_link">Send</a>
|
<a href="/index.php" class="button bordered" id="tr_link">Send</a>
|
||||||
|
|
||||||
<h1>Simple Cloud Notifier</h1>
|
<a href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a>
|
||||||
|
|
||||||
<p>Get your user-id and user-key from the app and send notifications to your phone by performing a POST request against <code>https://simplecloudnotifier.blackforestbytes.com/send.php</code></p>
|
<p>Get your user-id and user-key from the app and send notifications to your phone by performing a POST request against <code>https://simplecloudnotifier.blackforestbytes.com/send.php</code></p>
|
||||||
<pre>curl \
|
<pre>curl \
|
||||||
--data "user_id={userid}" \
|
--data "user_id={userid}" \
|
||||||
--data "user_key={userkey}" \
|
--data "user_key={userkey}" \
|
||||||
--data "priority={0|1|2}" \
|
|
||||||
--data "title={message_title}" \
|
--data "title={message_title}" \
|
||||||
--data "content={message_content}" \
|
--data "content={message_body}" \
|
||||||
|
--data "priority={0|1|2}" \
|
||||||
|
--data "msg_id={unique_message_id}" \
|
||||||
https://scn.blackforestbytes.com/send.php</pre>
|
https://scn.blackforestbytes.com/send.php</pre>
|
||||||
<p>The <code>content</code> and <code>priority</code> parameters are optional, you can also send message with only a title and the default priority</p>
|
<p>The <code>content</code>, <code>priority</code> and <code>msg_id</code> parameters are optional, you can also send message with only a title and the default priority</p>
|
||||||
<pre>curl \
|
<pre>curl \
|
||||||
--data "user_id={userid}" \
|
--data "user_id={userid}" \
|
||||||
--data "user_key={userkey}" \
|
--data "user_key={userkey}" \
|
||||||
--data "title={message_title}" \
|
--data "title={message_title}" \
|
||||||
https://scn.blackforestbytes.com/send.php</pre>
|
https://scn.blackforestbytes.com/send.php</pre>
|
||||||
</form>
|
|
||||||
|
<a href="/index_more.php" class="button bordered tertiary" style="float: right; min-width: 100px; text-align: center">More</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="copyinfo">
|
<div id="copyinfo">
|
||||||
<a href="https://www.blackforestbytes.com">© blackforestbytes</a>
|
<a href="https://www.blackforestbytes.com">© blackforestbytes</a>
|
||||||
|
186
web/index_more.php
Normal file
186
web/index_more.php
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="stylesheet" href="/css/mini-default.min.css"> <!-- https://minicss.org/docs -->
|
||||||
|
<title>Simple Cloud Notifications - API</title>
|
||||||
|
<!--<link rel="stylesheet" href="/css/mini-nord.min.css">-->
|
||||||
|
<!--<link rel="stylesheet" href="/css/mini-dark.min.css">-->
|
||||||
|
<link rel="stylesheet" href="/css/style.css">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="mainpnl">
|
||||||
|
<a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
|
||||||
|
<a href="/index.php" class="button bordered" id="tr_link">Send</a>
|
||||||
|
|
||||||
|
<a href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a>
|
||||||
|
|
||||||
|
<h2>Introduction</h2>
|
||||||
|
<div class="section">
|
||||||
|
<p>
|
||||||
|
With this API you can send push notifications to your phone.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
To recieve them you will need to install the <a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier">SimpleCloudNotifier</a> app from the play store.
|
||||||
|
When you open the app you can click on the account tab to see you unique <code>user_id</code> and <code>user_key</code>.
|
||||||
|
These two values are used to identify and authenticate your device so that send messages can be routed to your phone.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You can at any time generate a new <code>user_key</code> in the app and invalidate the old one.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
There is also a <a href="/index.php">web interface</a> for this API to manually send notifications to your phone or to test your setup.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Quota</h2>
|
||||||
|
<div class="section">
|
||||||
|
<p>
|
||||||
|
By default you can send up to 100 messages per day per device.
|
||||||
|
If you need more you can upgrade your account in the app to get 1000 messages per day, this has the additional benefit of removing ads and supporting the development of the app (and making sure I can pay the server costs).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>API Requests</h2>
|
||||||
|
<div class="section">
|
||||||
|
<p>
|
||||||
|
To send a new notification you send a <code>POST</code> request to the URL <code>https://scn.blackforestbytes.com/send.php</code>.
|
||||||
|
All Parameters can either directly be submitted as URL parameters or they can be put into the POST body.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You <i>need</i> to supply a valid <code>user_id</code> - <code>user_key</code> pair and a <code>title</code> for your message, all other parameter are optional.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>API Response</h2>
|
||||||
|
<div class="section">
|
||||||
|
<p>
|
||||||
|
If the operation was successful the API will respond with an HTTP statuscode 200 and an JSON payload indicating the send message and your remaining quota
|
||||||
|
</p>
|
||||||
|
<pre class="red-code">{
|
||||||
|
"success":true,
|
||||||
|
"message":"Message sent",
|
||||||
|
"response":
|
||||||
|
{
|
||||||
|
"multicast_id":8000000000000000006,
|
||||||
|
"success":1,
|
||||||
|
"failure":0,
|
||||||
|
"canonical_ids":0,
|
||||||
|
"results": [{"message_id":"0:10000000000000000000000000000000d"}]
|
||||||
|
},
|
||||||
|
"messagecount":623,
|
||||||
|
"quota":17,
|
||||||
|
"quota_max":100
|
||||||
|
}</pre>
|
||||||
|
<p>
|
||||||
|
If the operation is <b>not</b> successful the API will respond with an 4xx HTTP statuscode.
|
||||||
|
</p>
|
||||||
|
<table class="scode_table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Statuscode</th>
|
||||||
|
<th>Explanation</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Statuscode">200 (OK)</td>
|
||||||
|
<td data-label="Explanation">Message sent</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Statuscode">400 (Bad Request)</td>
|
||||||
|
<td data-label="Explanation">The request is invalid (missing parameters or wrong values)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Statuscode">401 (Unauthorized)</td>
|
||||||
|
<td data-label="Explanation">The user_id was not found or the user_key is wrong</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Statuscode">403 (Forbidden)</td>
|
||||||
|
<td data-label="Explanation">The user has exceeded its daily quota - wait 24 hours or upgrade your account</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Statuscode">500 (Internal Server Error)</td>
|
||||||
|
<td data-label="Explanation">There was an internal error while sending your data - try again later</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>
|
||||||
|
There is also always a JSON payload with additional information.
|
||||||
|
The <code>success</code> field is always there and in the error state you the <code>message</code> field to get a descritpion of the problem.
|
||||||
|
</p>
|
||||||
|
<pre class="red-code">{
|
||||||
|
"success":false,
|
||||||
|
"error":2101,
|
||||||
|
"errhighlight":-1,
|
||||||
|
"message":"Daily quota reached (100)"
|
||||||
|
}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Message Content</h2>
|
||||||
|
<div class="section">
|
||||||
|
<p>
|
||||||
|
Every message must have a title set.
|
||||||
|
But you also (optionally) add more content, while the title has a max length of 120 characters, the conntent can be up to 10.000 characters.
|
||||||
|
You can see the whole message with title and content in the app or when clicking on the notification.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If needed the content can be supplied in the <code>content</code> parameter.
|
||||||
|
</p>
|
||||||
|
<pre>curl \
|
||||||
|
--data "user_id={userid}" \
|
||||||
|
--data "user_key={userkey}" \
|
||||||
|
--data "title={message_title}" \
|
||||||
|
--data "content={message_content}" \
|
||||||
|
https://scn.blackforestbytes.com/send.php</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Message Priority</h2>
|
||||||
|
<div class="section">
|
||||||
|
<p>
|
||||||
|
Currently you can send a message with three different priorities: 0 (low), 1 (normal) and 2 (high).
|
||||||
|
In the app you can then configure a different behaviour for different priorities, e.g. only playing a sound if the notification is high priority.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Priorites are either 0, 1 or 2 and are supplied in the <code>priority</code> parameter.
|
||||||
|
If no priority is supplied the message will get the default priority of 1.
|
||||||
|
</p>
|
||||||
|
<pre>curl \
|
||||||
|
--data "user_id={userid}" \
|
||||||
|
--data "user_key={userkey}" \
|
||||||
|
--data "title={message_title}" \
|
||||||
|
--data "priority={0|1|2}" \
|
||||||
|
https://scn.blackforestbytes.com/send.php</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Message Uniqueness</h2>
|
||||||
|
<div class="section">
|
||||||
|
<p>
|
||||||
|
Sometimes your script can run in an environment with an unstable connection and you want to implement an automatic re-try mechanism to send a message again if the last try failed due to bad connectivity.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
To ensure that a message is only send once you can generate a unique id for your message (I would recommend a simple <code>uuidgen</code>).
|
||||||
|
If you send a message with an UUID that was already used in the near past the API still returns OK, but no new message is sent.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The message_id is optional - but if you want to use it you need to supply it via the <code>msg_id</code> parameter.
|
||||||
|
</p>
|
||||||
|
<pre>curl \
|
||||||
|
--data "user_id={userid}" \
|
||||||
|
--data "user_key={userkey}" \
|
||||||
|
--data "title={message_title}" \
|
||||||
|
--data "msg_id={message_id}" \
|
||||||
|
https://scn.blackforestbytes.com/send.php</pre>
|
||||||
|
<p>
|
||||||
|
Be aware that the server only saves send messages for a short amount of time. Because of that you can only use this to prevent duplicates in a short time-frame, older messages with the same ID are probably already deleted and the message will be send again.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="copyinfo">
|
||||||
|
<a href="https://www.blackforestbytes.com">© blackforestbytes</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Simple Cloud Notifications</title>
|
<title>Simple Cloud Notifications</title>
|
||||||
<link rel="stylesheet" href="/css/mini-default.min.css">
|
<link rel="stylesheet" href="/css/mini-default.min.css"> <!-- https://minicss.org/docs -->
|
||||||
<!--<link rel="stylesheet" href="/css/mini-nord.min.css">-->
|
<!--<link rel="stylesheet" href="/css/mini-nord.min.css">-->
|
||||||
<!--<link rel="stylesheet" href="/css/mini-dark.min.css">-->
|
<!--<link rel="stylesheet" href="/css/mini-dark.min.css">-->
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
<link rel="stylesheet" href="/css/style.css">
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
||||||
<form id="mainpnl">
|
<div id="mainpnl">
|
||||||
|
|
||||||
<div class="fullcenterflex">
|
<div class="fullcenterflex">
|
||||||
|
|
||||||
@@ -42,9 +42,9 @@
|
|||||||
<a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
|
<a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
|
||||||
<a href="/index.php" class="button bordered" id="tr_link">Send</a>
|
<a href="/index.php" class="button bordered" id="tr_link">Send</a>
|
||||||
|
|
||||||
<h1>Simple Cloud Notifier</h1>
|
<a href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a>
|
||||||
|
|
||||||
</form>
|
</div>
|
||||||
|
|
||||||
<div id="copyinfo">
|
<div id="copyinfo">
|
||||||
<a href="https://www.blackforestbytes.com">© blackforestbytes</a>
|
<a href="https://www.blackforestbytes.com">© blackforestbytes</a>
|
||||||
|
15
web/info.php
15
web/info.php
@@ -15,7 +15,7 @@ $user_key = $INPUT['user_key'];
|
|||||||
|
|
||||||
$pdo = getDatabase();
|
$pdo = getDatabase();
|
||||||
|
|
||||||
$stmt = $pdo->prepare('SELECT user_id, user_key, quota_today, quota_max, quota_day FROM users WHERE user_id = :uid LIMIT 1');
|
$stmt = $pdo->prepare('SELECT user_id, user_key, quota_today, is_pro, quota_day FROM users WHERE user_id = :uid LIMIT 1');
|
||||||
$stmt->execute(['uid' => $user_id]);
|
$stmt->execute(['uid' => $user_id]);
|
||||||
|
|
||||||
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
@@ -27,16 +27,17 @@ if ($data['user_id'] !== (int)$user_id) die(json_encode(['success' => false, 'er
|
|||||||
if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'errid'=>204, 'message' => 'Authentification failed']));
|
if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'errid'=>204, 'message' => 'Authentification failed']));
|
||||||
|
|
||||||
$quota = $data['quota_today'];
|
$quota = $data['quota_today'];
|
||||||
$quota_max = $data['quota_max'];
|
$is_pro = $data['is_pro'];
|
||||||
|
|
||||||
if ($data['quota_day'] === null || $data['quota_day'] !== date("Y-m-d")) $quota=0;
|
if ($data['quota_day'] === null || $data['quota_day'] !== date("Y-m-d")) $quota=0;
|
||||||
|
|
||||||
echo json_encode(
|
echo json_encode(
|
||||||
[
|
[
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'user_id' => $user_id,
|
'user_id' => $user_id,
|
||||||
'quota' => $quota,
|
'quota' => $quota,
|
||||||
'quota_max'=> $quota_max,
|
'quota_max' => Statics::quota_max($is_pro),
|
||||||
'message' => 'ok'
|
'is_pro' => $is_pro,
|
||||||
|
'message' => 'ok'
|
||||||
]);
|
]);
|
||||||
return 0;
|
return 0;
|
144
web/model.php
144
web/model.php
@@ -6,6 +6,8 @@ class Statics
|
|||||||
{
|
{
|
||||||
public static $DB = NULL;
|
public static $DB = NULL;
|
||||||
public static $CFG = NULL;
|
public static $CFG = NULL;
|
||||||
|
|
||||||
|
public static function quota_max($is_pro) { return $is_pro ? 1000 : 100; }
|
||||||
}
|
}
|
||||||
|
|
||||||
function getConfig()
|
function getConfig()
|
||||||
@@ -15,6 +17,50 @@ function getConfig()
|
|||||||
return Statics::$CFG = require "config.php";
|
return Statics::$CFG = require "config.php";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param String $msg
|
||||||
|
* @param Exception $e
|
||||||
|
*/
|
||||||
|
function reportError($msg, $e = null)
|
||||||
|
{
|
||||||
|
if ($e != null) $msg = ($msg."\n\n[[EXCEPTION]]\n" . $e . "\n" . $e->getMessage() . "\n" . $e->getTraceAsString());
|
||||||
|
|
||||||
|
$subject = "SCN_Server has encountered an Error at " . date("Y-m-d H:i:s") . "] ";
|
||||||
|
|
||||||
|
$content = "";
|
||||||
|
|
||||||
|
$content .= 'HTTP_HOST: ' . ParamServerOrUndef('HTTP_HOST') . "\n";
|
||||||
|
$content .= 'REQUEST_URI: ' . ParamServerOrUndef('REQUEST_URI') . "\n";
|
||||||
|
$content .= 'TIME: ' . date('Y-m-d H:i:s') . "\n";
|
||||||
|
$content .= 'REMOTE_ADDR: ' . ParamServerOrUndef('REMOTE_ADDR') . "\n";
|
||||||
|
$content .= 'HTTP_X_FORWARDED_FOR: ' . ParamServerOrUndef('HTTP_X_FORWARDED_FOR') . "\n";
|
||||||
|
$content .= 'HTTP_USER_AGENT: ' . ParamServerOrUndef('HTTP_USER_AGENT') . "\n";
|
||||||
|
$content .= 'MESSAGE:' . "\n" . $msg . "\n";
|
||||||
|
$content .= '$_GET:' . "\n" . print_r($_GET, true) . "\n";
|
||||||
|
$content .= '$_POST:' . "\n" . print_r($_POST, true) . "\n";
|
||||||
|
$content .= '$_FILES:' . "\n" . print_r($_FILES, true) . "\n";
|
||||||
|
|
||||||
|
if (getConfig()['error_reporting']['send-mail']) sendMail($subject, $content, getConfig()['error_reporting']['email-error-target'], getConfig()['error_reporting']['email-error-sender']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $subject
|
||||||
|
* @param string $content
|
||||||
|
* @param string $to
|
||||||
|
* @param string $from
|
||||||
|
*/
|
||||||
|
function sendMail($subject, $content, $to, $from) {
|
||||||
|
mail($to, $subject, $content, 'From: ' . $from);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $idx
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function ParamServerOrUndef($idx) {
|
||||||
|
return isset($_SERVER[$idx]) ? $_SERVER[$idx] : 'NOT_SET';
|
||||||
|
}
|
||||||
|
|
||||||
function getDatabase()
|
function getDatabase()
|
||||||
{
|
{
|
||||||
if (Statics::$DB !== NULL) return Statics::$DB;
|
if (Statics::$DB !== NULL) return Statics::$DB;
|
||||||
@@ -57,6 +103,14 @@ function generateRandomAuthKey()
|
|||||||
return $random;
|
return $random;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $url
|
||||||
|
* @param $body
|
||||||
|
* @param $header
|
||||||
|
* @return array|object|string
|
||||||
|
* @throws \Httpful\Exception\ConnectionErrorException
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
function sendPOST($url, $body, $header)
|
function sendPOST($url, $body, $header)
|
||||||
{
|
{
|
||||||
$builder = \Httpful\Request::post($url);
|
$builder = \Httpful\Request::post($url);
|
||||||
@@ -71,3 +125,93 @@ function sendPOST($url, $body, $header)
|
|||||||
|
|
||||||
return $response->body;
|
return $response->body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function verifyOrderToken($tok)
|
||||||
|
{
|
||||||
|
// https://developers.google.com/android-publisher/api-ref/purchases/products/get
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$package = getConfig()['verify_api']['package_name'];
|
||||||
|
$product = getConfig()['verify_api']['product_id'];
|
||||||
|
$acctoken = getConfig()['verify_api']['accesstoken'];
|
||||||
|
|
||||||
|
if ($acctoken == '') $acctoken = refreshVerifyToken();
|
||||||
|
|
||||||
|
$url = 'https://www.googleapis.com/androidpublisher/v3/applications/'.$package.'/purchases/products/'.$product.'/tokens/'.$tok.'?access_token='.$acctoken;
|
||||||
|
|
||||||
|
$json = sendPOST($url, "", []);
|
||||||
|
$obj = json_decode($json);
|
||||||
|
|
||||||
|
if ($obj === null || $obj === false)
|
||||||
|
{
|
||||||
|
reportError('verify-token returned NULL');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($obj['error']) && isset($obj['error']['code']) && $obj['error']['code'] == 401) // "Invalid Credentials" -- refresh acces_token
|
||||||
|
{
|
||||||
|
$acctoken = refreshVerifyToken();
|
||||||
|
|
||||||
|
$url = 'https://www.googleapis.com/androidpublisher/v3/applications/'.$package.'/purchases/products/'.$product.'/tokens/'.$tok.'?access_token='.$acctoken;
|
||||||
|
$json = sendPOST($url, "", []);
|
||||||
|
$obj = json_decode($json);
|
||||||
|
|
||||||
|
if ($obj === null || $obj === false)
|
||||||
|
{
|
||||||
|
reportError('verify-token returned NULL');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($obj['purchaseState']) && $obj['purchaseState'] === 0) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception $e)
|
||||||
|
{
|
||||||
|
reportError("VerifyOrder token threw exception", $e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @throws Exception */
|
||||||
|
function refreshVerifyToken()
|
||||||
|
{
|
||||||
|
$url = 'https://accounts.google.com/o/oauth2/token'.
|
||||||
|
'?grant_type=refresh_token'.
|
||||||
|
'&refresh_token='.getConfig()['verify_api']['refreshtoken'].
|
||||||
|
'&client_id='.getConfig()['verify_api']['clientid'].
|
||||||
|
'&client_secret='.getConfig()['verify_api']['clientsecret'];
|
||||||
|
|
||||||
|
$json = sendPOST($url, "", []);
|
||||||
|
$obj = json_decode($json);
|
||||||
|
file_put_contents('.verify_accesstoken', $obj['access_token']);
|
||||||
|
|
||||||
|
return $obj->access_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
function api_return($http_code, $message)
|
||||||
|
{
|
||||||
|
http_response_code($http_code);
|
||||||
|
echo $message;
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param String $str
|
||||||
|
* @param String[] $path
|
||||||
|
*/
|
||||||
|
function try_json($str, $path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$o = json_decode($str);
|
||||||
|
foreach ($path as $p) $o = $o[$p];
|
||||||
|
return $o;
|
||||||
|
}
|
||||||
|
catch (Exception $e)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@@ -5,24 +5,34 @@ include_once 'model.php';
|
|||||||
$INPUT = array_merge($_GET, $_POST);
|
$INPUT = array_merge($_GET, $_POST);
|
||||||
|
|
||||||
if (!isset($INPUT['fcm_token'])) die(json_encode(['success' => false, 'message' => 'Missing parameter [[fcm_token]]']));
|
if (!isset($INPUT['fcm_token'])) die(json_encode(['success' => false, 'message' => 'Missing parameter [[fcm_token]]']));
|
||||||
|
if (!isset($INPUT['pro'])) die(json_encode(['success' => false, 'message' => 'Missing parameter [[pro]]']));
|
||||||
|
if (!isset($INPUT['pro_token'])) die(json_encode(['success' => false, 'message' => 'Missing parameter [[pro_token]]']));
|
||||||
|
|
||||||
$fcmtoken = $INPUT['fcm_token'];
|
$fcmtoken = $INPUT['fcm_token'];
|
||||||
|
$ispro = $INPUT['pro'] == 'true';
|
||||||
|
$pro_token = $INPUT['pro_token'];
|
||||||
$user_key = generateRandomAuthKey();
|
$user_key = generateRandomAuthKey();
|
||||||
|
|
||||||
$pdo = getDatabase();
|
$pdo = getDatabase();
|
||||||
|
|
||||||
$stmt = $pdo->prepare('INSERT INTO users (user_key, fcm_token, timestamp_accessed) VALUES (:key, :token, NOW())');
|
if ($ispro)
|
||||||
$stmt->execute(['key' => $user_key, 'token' => $fcmtoken]);
|
{
|
||||||
|
if (!verifyOrderToken($pro_token)) die(json_encode(['success' => false, 'message' => 'Purchase token could not be verified']));
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare('INSERT INTO users (user_key, fcm_token, is_pro, pro_token, timestamp_accessed) VALUES (:key, :token, :bpro, :spro, NOW())');
|
||||||
|
$stmt->execute(['key' => $user_key, 'token' => $fcmtoken, 'bpro' => $ispro, 'spro' => $ispro ? $pro_token : null]);
|
||||||
$user_id = $pdo->lastInsertId('user_id');
|
$user_id = $pdo->lastInsertId('user_id');
|
||||||
|
|
||||||
echo json_encode(
|
echo json_encode(
|
||||||
[
|
[
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'user_id' => $user_id,
|
'user_id' => $user_id,
|
||||||
'user_key' => $user_key,
|
'user_key' => $user_key,
|
||||||
'quota' => 0,
|
'quota' => 0,
|
||||||
'quota_max'=> 100,
|
'quota_max' => Statics::quota_max($ispro),
|
||||||
'message' => 'New user registered'
|
'is_pro' => $ispro,
|
||||||
|
'message' => 'New user registered'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
@@ -1,15 +1,34 @@
|
|||||||
CREATE TABLE `users`
|
CREATE TABLE `users`
|
||||||
(
|
(
|
||||||
`user_id` INT(11) NOT NULL AUTO_INCREMENT,
|
`user_id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
`user_key` VARCHAR(64) NOT NULL,
|
`user_key` VARCHAR(64) NOT NULL,
|
||||||
`fcm_token` VARCHAR(256) NULL DEFAULT NULL,
|
`fcm_token` VARCHAR(256) NULL DEFAULT NULL,
|
||||||
`messages_sent` INT(11) NOT NULL DEFAULT '0',
|
`messages_sent` INT(11) NOT NULL DEFAULT '0',
|
||||||
`timestamp_created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`timestamp_created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`timestamp_accessed` DATETIME NULL DEFAULT NULL,
|
`timestamp_accessed` DATETIME NULL DEFAULT NULL,
|
||||||
|
|
||||||
`quota_today` INT(11) NOT NULL DEFAULT '0',
|
`quota_today` INT(11) NOT NULL DEFAULT '0',
|
||||||
`quota_day` DATE NULL DEFAULT NULL,
|
`quota_day` DATE NULL DEFAULT NULL,
|
||||||
`quota_max` INT(11) NOT NULL DEFAULT '100',
|
|
||||||
|
`is_pro` BIT NOT NULL DEFAULT 0,
|
||||||
|
`pro_token` VARCHAR(256) NULL DEFAULT NULL,
|
||||||
|
|
||||||
PRIMARY KEY (`user_id`)
|
PRIMARY KEY (`user_id`)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE `messages`
|
||||||
|
(
|
||||||
|
`scn_message_id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`sender_user_id` INT(11) NOT NULL,
|
||||||
|
|
||||||
|
`timestamp` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
`title` VARCHAR(256) NOT NULL,
|
||||||
|
`content` VARCHAR(12288) NULL,
|
||||||
|
`priority` INT(11) NOT NULL,
|
||||||
|
|
||||||
|
`fcn_message_id` VARCHAR(256) NOT NULL,
|
||||||
|
`usr_message_id` VARCHAR(256) NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (`scn_message_id`)
|
||||||
|
)
|
235
web/send.php
235
web/send.php
@@ -2,99 +2,152 @@
|
|||||||
|
|
||||||
include_once 'model.php';
|
include_once 'model.php';
|
||||||
|
|
||||||
//------------------------------------------------------------------
|
|
||||||
sleep(1);
|
|
||||||
//------------------------------------------------------------------
|
|
||||||
|
|
||||||
$INPUT = array_merge($_GET, $_POST);
|
|
||||||
|
|
||||||
if (!isset($INPUT['user_id'])) die(json_encode(['success' => false, 'errhighlight' => 101, 'message' => 'Missing parameter [[user_id]]']));
|
|
||||||
if (!isset($INPUT['user_key'])) die(json_encode(['success' => false, 'errhighlight' => 102, 'message' => 'Missing parameter [[user_token]]']));
|
|
||||||
if (!isset($INPUT['title'])) die(json_encode(['success' => false, 'errhighlight' => 103, 'message' => 'Missing parameter [[title]]']));
|
|
||||||
|
|
||||||
//------------------------------------------------------------------
|
|
||||||
|
|
||||||
$user_id = $INPUT['user_id'];
|
|
||||||
$user_key = $INPUT['user_key'];
|
|
||||||
$message = $INPUT['title'];
|
|
||||||
$content = isset($INPUT['content']) ? $INPUT['content'] : '';
|
|
||||||
$priority = isset($INPUT['priority']) ? $INPUT['priority'] : '1';
|
|
||||||
|
|
||||||
//------------------------------------------------------------------
|
|
||||||
|
|
||||||
if ($priority !== '0' && $priority !== '1' && $priority !== '2') die(json_encode(['success' => false, 'errhighlight' => 105, 'message' => 'Invalid priority']));
|
|
||||||
|
|
||||||
if (strlen(trim($message)) == 0) die(json_encode(['success' => false, 'errhighlight' => 103, 'message' => 'No title specified']));
|
|
||||||
if (strlen($message) > 120) die(json_encode(['success' => false, 'errhighlight' => 103, 'message' => 'Title too long (120 characters)']));
|
|
||||||
if (strlen($content) > 10000) die(json_encode(['success' => false, 'errhighlight' => 104, 'message' => 'Content too long (10000 characters)']));
|
|
||||||
|
|
||||||
//------------------------------------------------------------------
|
|
||||||
|
|
||||||
$pdo = getDatabase();
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare('SELECT user_id, user_key, fcm_token, messages_sent, quota_today, quota_max, quota_day FROM users WHERE user_id = :uid LIMIT 1');
|
|
||||||
$stmt->execute(['uid' => $user_id]);
|
|
||||||
|
|
||||||
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
if (count($datas)<=0) die(json_encode(['success' => false, 'errhighlight' => 101, 'message' => 'User not found']));
|
|
||||||
$data = $datas[0];
|
|
||||||
|
|
||||||
if ($data === null) die(json_encode(['success' => false, 'errhighlight' => 101, 'message' => 'User not found']));
|
|
||||||
if ($data['user_id'] !== (int)$user_id) die(json_encode(['success' => false, 'errhighlight' => 101, 'message' => 'UserID not found']));
|
|
||||||
if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'errhighlight' => 102, 'message' => 'Authentification failed']));
|
|
||||||
|
|
||||||
$fcm = $data['fcm_token'];
|
|
||||||
|
|
||||||
$new_quota = $data['quota_today'] + 1;
|
|
||||||
if ($data['quota_day'] === null || $data['quota_day'] !== date("Y-m-d")) $new_quota=1;
|
|
||||||
if ($new_quota > $data['quota_max']) die(json_encode(['success' => false, 'errhighlight' => -1, 'message' => 'Daily quota reached ('.$data['quota_max'].')']));
|
|
||||||
|
|
||||||
//------------------------------------------------------------------
|
|
||||||
|
|
||||||
$url = "https://fcm.googleapis.com/fcm/send";
|
|
||||||
$payload = json_encode(
|
|
||||||
[
|
|
||||||
'to' => $fcm,
|
|
||||||
//'dry_run' => true,
|
|
||||||
'android' => [ 'priority' => 'high' ],
|
|
||||||
//'notification' =>
|
|
||||||
//[
|
|
||||||
// 'title' => $message,
|
|
||||||
// 'body' => $content,
|
|
||||||
//],
|
|
||||||
'data' =>
|
|
||||||
[
|
|
||||||
'title' => $message,
|
|
||||||
'body' => $content,
|
|
||||||
'priority' => $priority,
|
|
||||||
'timestamp' => time(),
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
$header=
|
|
||||||
[
|
|
||||||
'Authorization' => 'key=' . getConfig()['firebase']['server_key'],
|
|
||||||
'Content-Type' => 'application/json',
|
|
||||||
];
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$httpresult = sendPOST($url, $payload, $header);
|
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
//sleep(1);
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
|
||||||
|
$INPUT = array_merge($_GET, $_POST);
|
||||||
|
|
||||||
|
if (!isset($INPUT['user_id'])) api_return(400, json_encode(['success' => false, 'error' => 1101, 'errhighlight' => 101, 'message' => 'Missing parameter [[user_id]]']));
|
||||||
|
if (!isset($INPUT['user_key'])) api_return(400, json_encode(['success' => false, 'error' => 1102, 'errhighlight' => 102, 'message' => 'Missing parameter [[user_token]]']));
|
||||||
|
if (!isset($INPUT['title'])) api_return(400, json_encode(['success' => false, 'error' => 1103, 'errhighlight' => 103, 'message' => 'Missing parameter [[title]]']));
|
||||||
|
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
$user_id = $INPUT['user_id'];
|
||||||
|
$user_key = $INPUT['user_key'];
|
||||||
|
$message = $INPUT['title'];
|
||||||
|
$content = isset($INPUT['content']) ? $INPUT['content'] : '';
|
||||||
|
$priority = isset($INPUT['priority']) ? $INPUT['priority'] : '1';
|
||||||
|
$usrmsgid = isset($INPUT['msg_id']) ? $INPUT['msg_id'] : null;
|
||||||
|
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
|
||||||
|
if ($priority !== '0' && $priority !== '1' && $priority !== '2') api_return(400, json_encode(['success' => false, 'error' => 1104, 'errhighlight' => 105, 'message' => 'Invalid priority']));
|
||||||
|
|
||||||
|
if (strlen(trim($message)) == 0) api_return(400, json_encode(['success' => false, 'error' => 1201, 'errhighlight' => 103, 'message' => 'No title specified']));
|
||||||
|
if (strlen($message) > 120) api_return(400, json_encode(['success' => false, 'error' => 1202, 'errhighlight' => 103, 'message' => 'Title too long (120 characters)']));
|
||||||
|
if (strlen($content) > 10000) api_return(400, json_encode(['success' => false, 'error' => 1203, 'errhighlight' => 104, 'message' => 'Content too long (10000 characters)']));
|
||||||
|
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
|
||||||
|
$pdo = getDatabase();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare('SELECT user_id, user_key, fcm_token, messages_sent, quota_today, is_pro, quota_day FROM users WHERE user_id = :uid LIMIT 1');
|
||||||
|
$stmt->execute(['uid' => $user_id]);
|
||||||
|
|
||||||
|
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
if (count($datas)<=0) die(json_encode(['success' => false, 'error' => 1301, 'errhighlight' => 101, 'message' => 'User not found']));
|
||||||
|
$data = $datas[0];
|
||||||
|
|
||||||
|
if ($data === null) api_return(401, json_encode(['success' => false, 'error' => 1301, 'errhighlight' => 101, 'message' => 'User not found']));
|
||||||
|
if ($data['user_id'] !== (int)$user_id) api_return(401, json_encode(['success' => false, 'error' => 1302, 'errhighlight' => 101, 'message' => 'UserID not found']));
|
||||||
|
if ($data['user_key'] !== $user_key) api_return(401, json_encode(['success' => false, 'error' => 1303, 'errhighlight' => 102, 'message' => 'Authentification failed']));
|
||||||
|
|
||||||
|
$fcm = $data['fcm_token'];
|
||||||
|
|
||||||
|
$new_quota = $data['quota_today'] + 1;
|
||||||
|
if ($data['quota_day'] === null || $data['quota_day'] !== date("Y-m-d")) $new_quota=1;
|
||||||
|
if ($new_quota > Statics::quota_max($data['is_pro'])) api_return(403, json_encode(['success' => false, 'error' => 2101, 'errhighlight' => -1, 'message' => 'Daily quota reached ('.Statics::quota_max($data['is_pro']).')']));
|
||||||
|
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
|
||||||
|
if ($usrmsgid != null)
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare('SELECT scn_message_id FROM messages WHERE sender_user_id=:uid AND usr_message_id IS NOT NULL AND usr_message_id=:umid LIMIT 1');
|
||||||
|
$stmt->execute(['uid' => $user_id, 'umid' => $usrmsgid]);
|
||||||
|
|
||||||
|
if (count($stmt->fetchAll(PDO::FETCH_ASSOC))>0)
|
||||||
|
{
|
||||||
|
api_return(200, json_encode(
|
||||||
|
[
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Message already sent',
|
||||||
|
'suppress_send' => true,
|
||||||
|
'response' => '',
|
||||||
|
'messagecount' => $data['messages_sent']+1,
|
||||||
|
'quota' => $data['quota_today'],
|
||||||
|
'is_pro' => $data['is_pro'],
|
||||||
|
'quota_max' => Statics::quota_max($data['is_pro']),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
|
||||||
|
$url = "https://fcm.googleapis.com/fcm/send";
|
||||||
|
$payload = json_encode(
|
||||||
|
[
|
||||||
|
'to' => $fcm,
|
||||||
|
//'dry_run' => true,
|
||||||
|
'android' => [ 'priority' => 'high' ],
|
||||||
|
//'notification' =>
|
||||||
|
//[
|
||||||
|
// 'title' => $message,
|
||||||
|
// 'body' => $content,
|
||||||
|
//],
|
||||||
|
'data' =>
|
||||||
|
[
|
||||||
|
'title' => $message,
|
||||||
|
'body' => $content,
|
||||||
|
'priority' => $priority,
|
||||||
|
'timestamp' => time(),
|
||||||
|
'usr_msg_id' => $usrmsgid,
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
$header=
|
||||||
|
[
|
||||||
|
'Authorization' => 'key=' . getConfig()['firebase']['server_key'],
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$httpresult = sendPOST($url, $payload, $header);
|
||||||
|
|
||||||
|
if (try_json($httpresult, ['success']) != 1)
|
||||||
|
{
|
||||||
|
reportError("FCM communication failed (success_1 <> true)\n\n".$httpresult);
|
||||||
|
api_return(403, json_encode(['success' => false, 'error' => 9902, 'errhighlight' => -1, 'message' => 'Communication with firebase service failed.']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception $e)
|
||||||
|
{
|
||||||
|
reportError("FCM communication failed", $e);
|
||||||
|
api_return(403, json_encode(['success' => false, 'error' => 9901, 'errhighlight' => -1, 'message' => 'Communication with firebase service failed.'."\n\n".'Exception: ' . $e->getMessage()]));
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare('UPDATE users SET timestamp_accessed=NOW(), messages_sent=messages_sent+1, quota_today=:q, quota_day=NOW() WHERE user_id = :uid');
|
||||||
|
$stmt->execute(['uid' => $user_id, 'q' => $new_quota]);
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare('INSERT INTO messages (sender_user_id, title, content, priority, fcn_message_id, usr_message_id) VALUES (:suid, :t, :c, :p, :fmid, :umid)');
|
||||||
|
$stmt->execute(
|
||||||
|
[
|
||||||
|
'suid' => $user_id,
|
||||||
|
't' => $message,
|
||||||
|
'c' => $content,
|
||||||
|
'p' => $priority,
|
||||||
|
'fmid' => try_json($httpresult, ['results', 'message_id']),
|
||||||
|
'umid' => $usrmsgid,
|
||||||
|
]);
|
||||||
|
|
||||||
|
api_return(200, json_encode(
|
||||||
|
[
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Message sent',
|
||||||
|
'suppress_send' => false,
|
||||||
|
'response' => $httpresult,
|
||||||
|
'messagecount' => $data['messages_sent']+1,
|
||||||
|
'quota' => $new_quota,
|
||||||
|
'is_pro' => $data['is_pro'],
|
||||||
|
'quota_max' => Statics::quota_max($data['is_pro']),
|
||||||
|
]));
|
||||||
}
|
}
|
||||||
catch (Exception $e)
|
catch (Exception $mex)
|
||||||
{
|
{
|
||||||
die(json_encode(['success' => false, 'message' => 'Exception: ' . $e->getMessage()]));
|
reportError("Root try-catch triggered", $mex);
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = $pdo->prepare('UPDATE users SET timestamp_accessed=NOW(), messages_sent=messages_sent+1, quota_today=:q, quota_day=NOW() WHERE user_id = :uid');
|
|
||||||
$stmt->execute(['uid' => $user_id, 'q' => $new_quota]);
|
|
||||||
|
|
||||||
echo (json_encode(
|
|
||||||
[
|
|
||||||
'success' => true,
|
|
||||||
'message' => 'Message sent',
|
|
||||||
'response' => $httpresult,
|
|
||||||
'messagecount' => $data['messages_sent']+1,
|
|
||||||
'quota'=>$new_quota,
|
|
||||||
'quota_max'=>$data['quota_max'],
|
|
||||||
]));
|
|
||||||
return 0;
|
|
@@ -16,7 +16,7 @@ $fcm_token = isset($INPUT['fcm_token']) ? $INPUT['fcm_token'] : null;
|
|||||||
|
|
||||||
$pdo = getDatabase();
|
$pdo = getDatabase();
|
||||||
|
|
||||||
$stmt = $pdo->prepare('SELECT user_id, user_key, quota_today, quota_max, quota_day FROM users WHERE user_id = :uid LIMIT 1');
|
$stmt = $pdo->prepare('SELECT user_id, user_key, quota_today, quota_day, is_pro FROM users WHERE user_id = :uid LIMIT 1');
|
||||||
$stmt->execute(['uid' => $user_id]);
|
$stmt->execute(['uid' => $user_id]);
|
||||||
|
|
||||||
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
@@ -28,7 +28,7 @@ if ($data['user_id'] !== (int)$user_id) die(json_encode(['success' => false, 'me
|
|||||||
if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'message' => 'Authentification failed']));
|
if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'message' => 'Authentification failed']));
|
||||||
|
|
||||||
$quota = $data['quota_today'];
|
$quota = $data['quota_today'];
|
||||||
$quota_max = $data['quota_max'];
|
$is_pro = $data['is_pro'];
|
||||||
|
|
||||||
$new_userkey = generateRandomAuthKey();
|
$new_userkey = generateRandomAuthKey();
|
||||||
|
|
||||||
@@ -39,12 +39,13 @@ if ($fcm_token === null)
|
|||||||
|
|
||||||
echo json_encode(
|
echo json_encode(
|
||||||
[
|
[
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'user_id' => $user_id,
|
'user_id' => $user_id,
|
||||||
'user_key' => $new_userkey,
|
'user_key' => $new_userkey,
|
||||||
'quota' => $quota,
|
'quota' => $quota,
|
||||||
'quota_max'=> $quota_max,
|
'quota_max'=> Statics::quota_max($data['is_pro']),
|
||||||
'message' => 'user updated'
|
'is_pro' => $is_pro,
|
||||||
|
'message' => 'user updated'
|
||||||
]);
|
]);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -59,7 +60,8 @@ else
|
|||||||
'user_id' => $user_id,
|
'user_id' => $user_id,
|
||||||
'user_key' => $new_userkey,
|
'user_key' => $new_userkey,
|
||||||
'quota' => $quota,
|
'quota' => $quota,
|
||||||
'quota_max'=> $quota_max,
|
'quota_max'=> Statics::quota_max($data['is_pro']),
|
||||||
|
'is_pro' => $is_pro,
|
||||||
'message' => 'user updated'
|
'message' => 'user updated'
|
||||||
]);
|
]);
|
||||||
return 0;
|
return 0;
|
||||||
|
78
web/upgrade.php
Normal file
78
web/upgrade.php
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
include_once 'model.php';
|
||||||
|
|
||||||
|
$INPUT = array_merge($_GET, $_POST);
|
||||||
|
|
||||||
|
|
||||||
|
if (!isset($INPUT['user_id'])) die(json_encode(['success' => false, 'message' => 'Missing parameter [[user_id]]']));
|
||||||
|
if (!isset($INPUT['user_key'])) die(json_encode(['success' => false, 'message' => 'Missing parameter [[user_key]]']));
|
||||||
|
if (!isset($INPUT['pro'])) die(json_encode(['success' => false, 'message' => 'Missing parameter [[pro]]']));
|
||||||
|
if (!isset($INPUT['pro_token'])) die(json_encode(['success' => false, 'message' => 'Missing parameter [[pro_token]]']));
|
||||||
|
|
||||||
|
$user_id = $INPUT['user_id'];
|
||||||
|
$user_key = $INPUT['user_key'];
|
||||||
|
$ispro = $INPUT['pro'] == 'true';
|
||||||
|
$pro_token = $INPUT['pro_token'];
|
||||||
|
|
||||||
|
//----------------------
|
||||||
|
|
||||||
|
$pdo = getDatabase();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare('SELECT user_id, user_key, quota_today, quota_day, is_pro, pro_token FROM users WHERE user_id = :uid LIMIT 1');
|
||||||
|
$stmt->execute(['uid' => $user_id]);
|
||||||
|
|
||||||
|
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
if (count($datas)<=0) die(json_encode(['success' => false, 'message' => 'User not found']));
|
||||||
|
$data = $datas[0];
|
||||||
|
|
||||||
|
if ($data === null) die(json_encode(['success' => false, 'message' => 'User not found']));
|
||||||
|
if ($data['user_id'] !== (int)$user_id) die(json_encode(['success' => false, 'message' => 'UserID not found']));
|
||||||
|
if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'message' => 'Authentification failed']));
|
||||||
|
|
||||||
|
if ($ispro)
|
||||||
|
{
|
||||||
|
// set pro=true
|
||||||
|
|
||||||
|
if ($data['pro_token'] != $pro_token)
|
||||||
|
{
|
||||||
|
if (!verifyOrderToken($pro_token)) die(json_encode(['success' => false, 'message' => 'Purchase token could not be verified']));
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare('UPDATE users SET timestamp_accessed=NOW(), is_pro=1, pro_token=:ptk WHERE user_id = :uid');
|
||||||
|
$stmt->execute(['uid' => $user_id, 'ptk' => $pro_token]);
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare('UPDATE users SET is_pro=0, pro_token=NULL WHERE user_id <> :uid AND pro_token = :ptk');
|
||||||
|
$stmt->execute(['uid' => $user_id, 'ptk' => $pro_token]);
|
||||||
|
|
||||||
|
echo json_encode(
|
||||||
|
[
|
||||||
|
'success' => true,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'user_key' => $new_userkey,
|
||||||
|
'quota' => $data['quota'],
|
||||||
|
'quota_max'=> Statics::quota_max(true),
|
||||||
|
'is_pro' => true,
|
||||||
|
'message' => 'user updated'
|
||||||
|
]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// set pro=false
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare('UPDATE users SET timestamp_accessed=NOW(), is_pro=0, pro_token=NULL WHERE user_id = :uid');
|
||||||
|
$stmt->execute(['uid' => $user_id]);
|
||||||
|
|
||||||
|
echo json_encode(
|
||||||
|
[
|
||||||
|
'success' => true,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'user_key' => $new_userkey,
|
||||||
|
'quota' => $data['quota'],
|
||||||
|
'quota_max'=> Statics::quota_max(false),
|
||||||
|
'is_pro' => false,
|
||||||
|
'message' => 'user updated'
|
||||||
|
]);
|
||||||
|
return 0;
|
||||||
|
}
|
Reference in New Issue
Block a user